3
votes

I have a simple application with TabView and 4 different tabs in it. In my second view I want to make make API Call to get bigger type of data.

The problem I have. To prevent multiple backend requests, my idea was to make initial call onAppear() life cycle instead of adding it to ViewModel init(), because this part is called on every change of tabView.

However the code in view is not updated if I call network request from onAppear.

If I call mockApiCall from viewModel init(). then data is updated and view is updated as well. However, as I mentioned before, I don't want to do it, because on every tab change there will be lots of requests


import SwiftUI

class TabsViewModel: ObservableObject {
    var shopView: some View {
        ViewsBuilder.makeShopView()
    }
    
    struct ViewsBuilder {
        static func makeShopView() -> some View {
            ShopView(viewModel: .init())
        }
    }
}

//To reproduce go to second tab, view is loaded, go to first tab and then again go to second one
struct TabsView: View {
    
    @ObservedObject
    var viewModel: TabsViewModel
    
    @State
    private var selection = 1
    
    var body: some View {
        TabView(
            selection: $selection) {
                Text("Tab Content 1")
                    .tabItem {
                        if selection == 1 {
                            Image(systemName: "star.fill")
                        } else {
                            Image(systemName: "star")
                        }
                        //it works if I set Image(systemName: "star.fill") instead of if statement
                    }
                    .tag(1)
                viewModel
                    .shopView
                    .tabItem {
                        if selection == 2 {
                            Image(systemName: "star.fill")
                        } else {
                            Image(systemName: "star")
                        }
                        //it works if I set Image(systemName: "star.fill") instead of if statement
                    }
                    .tag(2)
            }
    }
}


struct ShopView: View {
    
    @ObservedObject
    var viewModel: ShopViewModel
    
    var body: some View {
        VStack {
            ForEach(viewModel.mockedUser) { user in
                Text(user.name)
            }
        }
        .onAppear() {
            viewModel.mockApiCall()
        }
        
    }
}

class ShopViewModel: ObservableObject {
    
    @Published
    var mockedUser: [TestUser] = []
    
    func mockApiCall() {
        print("API CALLED")
        DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
            self.mockedUser = [
                TestUser(id: 1, name: "Test"),
                TestUser(id: 2, name: "User2")
            ]
        }
    }
    
    struct TestUser: Identifiable {
        let id: Int
        let name: String
    }
}


1
It should still work by calling the api from onAppear. I have ran your code on iOS 14.2 and it works. - AAV
Works fine with Xcode 12.1 / iOS 14.1. I assume the problem is some other code. Would you elaborate more? - Asperi

1 Answers

0
votes

Couldn't find a valid solution, so decided to go ahead with StateObject annotation for view models