1

I am trying to navigate to a subview and initialize it from a property passed from the parent view WITHOUT using onAppear.

I am able to make this work with onAppear but don't like how it calls the function everytime the view shows up and there has to be a cleaner way??

I want to initialize PopupCollectorView by calling loadCollectors in its init() function but I keep getting errors:

"Variable 'self.profileData' used before being initialized"

"Variable 'self.profileData' used before being initialized"

"Return from initializer without initializing all stored properties"

How can I make the subview view call loadCollectors() with the profileData I am passing to it??

THANK YOU

PARENT VIEW - all inside a navigation view hierarchy...

struct PopupProfileFull: View {
    @ObservedObject var popupVM : PopupProfileViewModel
    
    @State var presentCollectors: Bool = false

    var body: some View {
        VStack {
            if let profileData = popupVM.profileData {
                // tapping on this triggers navigation to popupCollectorsView
                PopupAccountStats(popupVM: popupVM, presentCollectors: $presentCollectors, presentCollecting: $presentCollecting)
                .background (
                    NavigationLink("", destination: PopupCollectorsView(profileData: $popupVM.profileData), isActive: $presentCollectors)
                )
            }
        }
    }
}

CHILD VIEW NAVIGATED TO FROM PARENT

struct PopupCollectorsView: View {
    @StateObject var popupCollectorVM = PopupCollectorsViewModel()
    
    @Binding var profileData: ProfileData?
    
     // ERRORS OCCUR HERE
     init(profileData: ProfileData) {
        self.profileData = profileData
        loadCollectors()
    }
    
    var body: some View {
        VStack {
            List(0..<popupCollectorVM.collectors.count, id: \.self) { i in
                Text(popupCollectorVM.collectors[i].collectedUsername)
            }
        }
    }
    
    func loadCollectors() {
        if let profileData = profileData {
            popupCollectorVM.loadCollectors(userId: profileData.userId)
        }
    }
}

Jake Smith
  • 580
  • 3
  • 11

1 Answers1

1

You can pass the profileData directly to the loadCollectors function:

struct PopupCollectorsView: View {
    @StateObject var popupCollectorVM = PopupCollectorsViewModel()
    
    @Binding var profileData: ProfileData?
    
     init(profileData: ProfileData) {
         self.profileData = profileData
         loadCollectors(data: profileData)
    }
    
    var body: some View {
        VStack {
            List(0..<popupCollectorVM.collectors.count, id: \.self) { i in
                Text(popupCollectorVM.collectors[i].collectedUsername)
            }
        }
    }
    
    func loadCollectors(data: ProfileData) {
        popupCollectorVM.loadCollectors(userId: data.userId)
    }
}

You could simplify even further by getting rid of the loadCollectors function (however, see my note at the end about using init for tasks like this):

init(profileData: ProfileData) {
  self.profileData = profileData
  popupCollectorVM.loadCollectors(userId: profileData.userId)
}

You may (hard to tell without being able to run your example) also have to use something like NavigationLazyView so that your View doesn't reach init before the correct data is passed to it.

It's also worth noting that generally, you want to avoid doing heavy lifting in init since SwiftUI views are transient and can be recreated often. The above solution will work if you have a high degree of confidence that you're view will only reach init once, but in general, it's better to rely on something like onAppear or even the init method of a @StateObject that only gets run once.

jnpdx
  • 45,847
  • 6
  • 64
  • 94