bludginozzie
bludginozzie

Reputation: 119

Why does the @Published value not get triggered in SwiftUI ObservableObject?

I have create the below contrived example to demonstrate my problem. In this code I am expecting the TextField to be initialised with "Test Company X" where X is the view depth based on the ObservableObject ViewModel. This is true and works fine for the first view only. Subsequent views do not get the published value on initial appearance. However as you back through the views and the onAppear triggers it does get initialised. Why is the TextField not correctly initialised on the second and subsequent views?

class TestViewModel: ObservableObject {
    @Published var companyName: String = ""
    
    func load(_ viewDepth: Int) {
        debugPrint("load: \(viewDepth)")
        
        // Mimic a network request to get data
        DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
            debugPrint("publish: \(viewDepth)")
            self.companyName = "Test Company \(viewDepth)"
        }
    }
}

struct TestFormView: View {
    
    var viewDepth: Int
    @Binding var companyName: String
    @State private var navigateToNextView: Bool = false
    
    var body: some View {
        VStack(alignment: .leading, spacing: 3) {
            
            TextField("Company name", text: $companyName)
                .padding()
                .background(
                    RoundedRectangle(cornerRadius: 5)
                        .strokeBorder(Color.primary.opacity(0.5), lineWidth: 3)
                )
            
            Button.init("NEXT") {
                self.navigateToNextView = true
            }
            .frame(maxWidth: .infinity)
            .foregroundColor(Color.primary)
            .padding(10)
            .font(Font.system(size: 18,
                        weight: .semibold,
                        design: .default))
                .background(Color.secondary)
            .cornerRadius(Sizes.cornerRadius)
            
            NavigationLink(destination: TestView(viewDepth: viewDepth + 1), isActive: $navigateToNextView) {
                EmptyView()
            }
            .isDetailLink(false)
            
            Spacer()
        }
    }
}

struct TestView: View {
    @ObservedObject var viewModel = TestViewModel()
    var viewDepth: Int
    
    var body: some View {
        VStack {
            TestFormView(viewDepth: viewDepth, companyName: $viewModel.companyName)
        }
        .onAppear(perform: {
            self.viewModel.load(self.viewDepth)
        })
        .navigationBarTitle("View Depth \(viewDepth)")
        .padding()
    }
}

struct TestNavigationView: View {
    
    @State private var begin: Bool = false
    
    var body: some View {
        NavigationView {
            VStack {
                if begin {
                    TestView(viewDepth: 1)
                } else {
                    Button.init("BEGIN") {
                        self.begin = true
                    }
                    .frame(maxWidth: .infinity)
                    .foregroundColor(Color.primary)
                    .padding(10)
                    .font(Font.system(size: 18,
                                weight: .semibold,
                                design: .default))
                        .background(Color.secondary)
                    .cornerRadius(Sizes.cornerRadius)
                }
            }
        }
    }
}

Upvotes: 2

Views: 695

Answers (1)

Asperi
Asperi

Reputation: 258413

Your model is recreated (due to current nature of NavigationLink)

SwiftUI 2.0

Fix is simple - use specially intended for such purpose StateObject

demo

struct TestView: View {
    @StateObject var viewModel = TestViewModel()

   // .. other code

Upvotes: 1

Related Questions