1

I have a list of users in a navigation stack, each item wrapped by a navigation link.

struct UserListView: View {
    
    @ObservedObject private var viewModel = ViewModel()

    var body: some View {
        NavigationStack {
            List {
                ForEach(viewModel.users) { user in
                    NavigationLink(value: user.id) {
                        Text(user.givenName)
                    }
                }
            }
            .navigationTitle("UserList")
            .navigationDestination(for: Int.self, destination: { id in
                FormView(id: id) // gets called multiple times, 2 times in my experience
            })
            .onAppear {
                viewModel.findAll()
            }
        }
    }
}

The problem is in the FormView that gets init multiple times, and as a result, the same for it's associated viewModel, and as I call fetch department list from the network layer, by the time the result comes back, the caller (viewModel) is already gone (deinit).

struct UserFormView: View {
    
    @ObservedObject private var viewModel = FormViewModel()
    
    @State var first: String = ""
    @State var last: String = ""
    @State var department: Department? = nil
    
    var id: Int?
    
    var body: some View {
        VStack {
            HStack {
                TextField("First", text: $first)
                    .textFieldStyle(RoundedBorderTextFieldStyle())
                    .font(.system(size: 16))
                              
                TextField("Last", text: $last)
                    .textFieldStyle(RoundedBorderTextFieldStyle())
                    .font(.system(size: 16))
            }

            HStack {
                Picker(selection: $department, label: Text("")) {
                    ForEach(viewModel.departments, id: \.self) { department in
                        Text(department.name ?? "").tag(department)
                    }
                }
                .pickerStyle(WheelPickerStyle())
                .padding()
            }
        }
        .padding()
        .navigationTitle("User Form")
        .onAppear {
            viewModel.findAllDepartments()
        }
    }
}

Here's my ObservableObject that gets no chance to update the departments, because this object gets deinit before that.

I could tell from the print statement in the init and deinit as they come and go multiple times.

class FormViewModel: ObservableObject {
 
    @Published var user: User? = nil
    
    @Published var departments: [Department] = []
    
    private var cancellable = Set<AnyCancellable>()
    
    private var rand = Int(arc4random_uniform(10)) + 1
    
    init() {
        print("init \(rand)") // gets called multiple times
    }

    
    func findAllDepartments() {
        delegate?.findAllDepartments()
            .receive(on: DispatchQueue.main)
            .sink(receiveCompletion: {[weak self] completion in
                switch completion{
                case .finished:
                    print("finished")
                case .failure(let error):
                    print(error)
                }
            }, receiveValue: { [weak self] departments in
                self?.departments.append(contentsOf: departments) // no chance, it's deallocated already
                self?.objectWillChange.send()
            })
            .store(in: &cancellable)
    }

    deinit {
        print("deinit \(rand)") // multiple times
    }
}

Question: I understand it's perhaps the nature of the SwiftUI to create and re-recreate Views as state changes, but how can we be consistent then?

9
  • 1
    You shouldn't ever be using @ObservedObject where you own the object. It should be @StateObject. @StateObject prevents the object from getting recreated on each render. Commented Nov 27, 2023 at 17:04
  • 1
    developer.apple.com/documentation/swiftui/… Commented Nov 27, 2023 at 17:06
  • @jnpdx I see, so I just remove ObservedObject from @ObservedObject private var viewModel = FormViewModel(), ? and use @@StateObject private var viewModel = FormViewModel() Commented Nov 27, 2023 at 17:07
  • 1
    Replace @ObservedObject with @StateObject Commented Nov 27, 2023 at 17:08
  • thanks, I guess i was following some old references. @loremipsum Commented Nov 27, 2023 at 17:09

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.