0

I have 2 tabs and the associated views are tabAView and tabBView. On tabAView, 1 API call is there and got user object which is Published object in its ViewModel. ViewModel name is UserViewModel. UserViewModel is being observed by tabAView. On tabBView, I have to use that user object. Because on some actions, user object value is changed, those changes should be reflected on subsequent views.

I am confused about the environment object usage here. Please suggest what will be the best approach.

Here is the code to understand better my problem.

struct ContentView: View {
    enum AppPage: Int {
        case TabA=0, TabB=1
    }
    
    @StateObject var settings = Settings()
    var viewModel: UserViewModel
    
    var body: some View {
        NavigationView {
            TabView(selection: $settings.tabItem) {
                TabAView(viewModel: viewModel)
                    .tabItem {
                        Text("TabA")
                    }
                    .tag(AppPage.TabA)
                
                AppsView()
                    .tabItem {
                        Text("Apps")
                    }
                    .tag(AppPage.TabB)
            }
            .accentColor(.white)
            .edgesIgnoringSafeArea(.top)
            .onAppear(perform: {
                settings.tabItem = .TabA
            })
            .navigationBarTitleDisplayMode(.inline)
        }
        .environmentObject(settings)
    }
}

This is TabAView:

struct TabAView: View {

    @ObservedObject var viewModel: UserViewModel
    @EnvironmentObject var settings: Settings

    init(viewModel: UserViewModel) {
        self.viewModel = viewModel
    }

    var body: some View {
        Vstack {
        /// code
        }
        .onAppear(perform: {
            /// code
        })
        .environmentObject(settings)
    }
}

This is the UserViewModel where API is hit and user object comes:

class UserViewModel: ObservableObject {
    
    private var apiService = APIService.shared
    @Published var user: EndUserData?

    init () {
        getUserProfile()
    }
    
    func getUserProfile() {
        apiService.getUserAccount() { user in
            DispatchQueue.main.async {
                self.user = user
            }
        }
    }
}

Below is the APIService function, where the user object is saved into UserDefaults for use. Which I know is incorrect.(That is why I am looking for another solution). Hiding the URL, because of its confidential.

func getUserAccount(completion: @escaping (EndUserData?) -> Void) {
    self.apiManager.makeRequest(toURL: url, withHttpMethod: .get) { results in
        guard let response = results.response else { return completion(nil) }
        if response.httpStatusCode == 200 {
            guard let data = results.data else { return completion(nil) }
            do {
                let str = String(decoding: data, as: UTF8.self)
                print(str)
                let decoder = JSONDecoder()
                let responseData = try decoder.decode(ResponseData<EndUserData>.self, from: data)
                UserDefaults.standard.set(data, forKey: "Account")
                completion(responseData.data)
            } catch let jsonError as NSError {
                print(jsonError.localizedDescription)
                return completion(nil)
            }
        }
    }
}

This is another TabBView:

struct TabBView: View {
    
    var user: EndUserData?
    
    init() {
        do {
            guard let data = UserDefaults.standard.data(forKey: "Account") else {
                return
            }
            let decoder = JSONDecoder()
            let responseData = try decoder.decode(ResponseData<EndUserData>.self, from: data)
            user = responseData.data
        } catch let jsonError as NSError {
            print(jsonError.localizedDescription)
        }
    }
    var body: some View {
        VStack (spacing: 10) {
            UserSearch()
        }
    }
}

This is another view in TabBView, where the User object is used. Changes are not reflecting here.

struct UserSearch: View {

    private var user: EndUserData?
    
    init(comingFromAppsSection: Bool) {
        do {
            guard let data = UserDefaults.standard.data(forKey: "Account") else {
                return
            }
            let decoder = JSONDecoder()
            let responseData = try decoder.decode(ResponseData<EndUserData>.self, from: data)
            user = responseData.data
        } catch let jsonError as NSError {
            print(jsonError.localizedDescription)
        }
    }
    
    var body: some View {
        Vstack {
            Text(user.status)
        }
    }
}

I have removed most of the code from a confidential point of view but this code will explain the reason and error. Please look into the code and help me.

user1960279
  • 494
  • 1
  • 8
  • 24
  • In `ContentView` you should declare your `viewModel` as `@ObservedObject var viewModel: UserViewModel`, so that in `TabAView` any change to `viewModel` is propagated. Similarly in `TabBView` and `UserSearch`, you have to use the same approach as in `ContentView` and `TabAView` as shown above. – workingdog support Ukraine Oct 14 '21 at 07:48
  • should I use UserViewModel as @ObservedObject/@EnvironmentObject or @StateObject ? – user1960279 Oct 14 '21 at 10:05
  • I used @ObservedObject var viewModel: UserViewModel, but because this view model is used inside many view, so i changed at every place. After that when i am running the code, after 2 views i am getting crash: Thread 30: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0) – user1960279 Oct 14 '21 at 11:06
  • How are you passing the data to each of these views? You need to be careful that you are using a "Single Source of Truth" if you are sharing data. Most times, these types of crashes indicate that you are not. – Yrb Oct 14 '21 at 12:04
  • can you edit the code in your question, to show what you are doing now with `@ObservedObject`. – workingdog support Ukraine Oct 14 '21 at 12:25
  • It is working now. That crash was resolved after deleting the app multiple times. Might be due to something else. – user1960279 Oct 15 '21 at 09:36

0 Answers0