Accessing Stateobject's Object Without Being Installed on a View. This Will Create a New Instance Each Time - Swiftui

Accessing StateObject's object without being installed on a View. This will create a new instance each time - SwiftUI

You cannot access any value before they get initialized, use onAppear():

import SwiftUI

@main
struct YourApp: App {

@StateObject private var amplifyConfig: AmplifyConfig = AmplifyConfig()

var body: some Scene {

WindowGroup {
ContentView()
.onAppear() {
if (!amplifyConfig.isAmplifyConfigured) {
amplifyConfig.dataStoreHubEventSubscriber()
amplifyConfig.configureAmplify()
}
}
}
}
}


Update: An actual use case

import SwiftUI

@main
struct YourApp: App {

@StateObject private var amplifyConfig: AmplifyConfig = AmplifyConfig()

@State private var isLoaded: Bool = Bool()

var body: some Scene {

WindowGroup {

VStack {
if (isLoaded) { ContentView() }
else { Text("Loading . . .") }
}
.onAppear() {
if (!amplifyConfig.isAmplifyConfigured) {
amplifyConfig.dataStoreHubEventSubscriber()
amplifyConfig.configureAmplify()
completionHandler { value in isLoaded = value }
}
else {
isLoaded = true
}
}
}
}
}

func completionHandler(value: @escaping (Bool) -> Void) {

// Some heavy work here, I am using DispatchQueue.main.asyncAfter for replicating that heavy work is happening! But you use your own code here.
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + DispatchTimeInterval.milliseconds(3000)) { value(true) }

}

Use of @StateObject in Document-based SwiftUI application: getting instances instead of references

@StateObject is just for Views, try ReferenceFileDocument but before you resort to using classes I highly recommend figuring out if you can stick to structs because Swift and SwiftUI work best with value types. See https://developer.apple.com/documentation/swift/choosing_between_structures_and_classes

Form reverts and reloads data on unwind

As I´ve allready mentioned in the comments you are using multiple sources of truth. This is in general a bad idea as you would need to syncronize this changes all over your app.

You mentioned:

I have trimmed down my actual code to replicate the my actual environment, but may have made this example complicated but in the actual app is needed.

So please take this as a more general example:

If you want to reuse your ContentView pass the data into it. Capsulate this data in the Viewmodel and pass that into the Environment.

The View shouldn´t care if the data given is new or should be edited. It has a certain job: presenting what it has been given.

struct ContentView: View {

@EnvironmentObject private var vm: ViewModel

var body: some View {
Form {
TextField("Name", text: $vm.model.name)
Section {
NavigationLink(vm.model.numbers.isEmpty ? "Choose items" : vm.getNames()) {
ListItemsView()
.environmentObject(vm)
}
} footer: { Text("\(vm.model.numbers.description)") }
}
.navigationTitle("Add new")
}
}

struct ListItemsView: View {
@EnvironmentObject private var vm: ViewModel
var body: some View {
Form {
List(0..<10) { index in
Text("\(index)")
.frame(maxWidth: .infinity)
.contentShape(Rectangle())
.onTapGesture {
vm.model.numbers.append(index)
}
}
}
}
}

class ViewModel: ObservableObject {

@Published var model: Model = Model(name: "", numbers: [])

func getNames() -> String{
getNames(from: model.numbers)
}

func getNames(from items: [Int]) -> String {
var output: [String] = []
for item in items {
output.append("\(item)")
}
return output.joined(separator: ", ")
}
}

struct Model {
let id = UUID().uuidString
//use vars here
var name: String
var numbers: [Int]
}

Edit:

This is still possible. With the now changed design I would recommend the following solution:

NavigationView {
Section {
VStack {
List($viewmodel.models) { $model in
NavigationLink("Go to form - \(model.name.isEmpty ? "new user" : model.name)") {
ContentView(model: $model, title: "edit")
}
}
Button("Add new"){
// use this button to add a new model an trigger navigation
viewmodel.models.append(Model(name: "", numbers: []))
showNew = true
}
// create this invisible navigation link to shwo the newly added model
NavigationLink("", isActive: $showNew){
ContentView(model: $viewmodel.models.last!, title: "add new")
}
}
}
.navigationTitle("Main screen")
}

your Model would need to be Identifiable and changes those let´s to var´s:

struct Model: Identifiable {
let id = UUID().uuidString
var name: String
var numbers: [Int]
}

class ViewModel: ObservableObject {

@Published var models: [Model]

init(){
models = [
Model(name: "Michael", numbers: [1, 5, 7]),
Model(name: "Jan", numbers: [1]),
Model(name: "Liam", numbers: [3, 6]),
Model(name: "Rav", numbers: [7, 8]),
Model(name: "Paul", numbers: [2, 4])
]

}
}

struct ContentView: View {
@Binding var model: Model
let title: String
var body: some View {
Form {
TextField("Name", text: $model.name)
Section {
NavigationLink(model.numbers.isEmpty ? "Choose items" : getNames()) {
ListItemsView(model: $model)

}
} footer: { Text("\(model.numbers.description)") }
}
.navigationTitle(title)
}

func getNames() -> String{
var output: [String] = []
for item in model.numbers {
output.append("\(item)")
}
return output.joined(separator: ", ")
}
}

struct ListItemsView: View {
@Binding var model: Model
var body: some View {
Form {
List(0..<10) { index in
Text("\(index)")
.frame(maxWidth: .infinity)
.contentShape(Rectangle())
.onTapGesture {
model.numbers.append(index)
}
}
}
}
}


Related Topics



Leave a reply



Submit