1

I'm having my View's reload when my TabView tab changes. I'm trying to show a ProgressView while fetching data from an API on initial data loading, but it's being reloaded every tab change, even after navigating to another view. If I remove my ProgressView it works, it just doesn't look great when loading data.

I assumed this would have been a simple task, but I haven't found a great solution. I found StatefulTabView but it seems broken on iOS 15. I'm not sure if I'm just doing something incorrectly with my view model. Any help would be appreciated.

enter image description here

view model

class RickAndMortyViewModel: ObservableObject {
    private var cancellables = Set<AnyCancellable>()
    @Published var dataone = RickandMorty(results: [])
    @Published var datatwo = RickandMorty(results: [])
    @Published var loadingone: Bool = false
    @Published var loadingtwo: Bool = false
    
    func getpageone() {
        self.loadingone = true
        URLSession.shared.dataTaskPublisher(for:URLRequest(url: URL(string: "https://rickandmortyapi.com/api/character/?page=1")!))
            .map{ $0.data }
            .decode(type: RickandMorty.self, decoder: JSONDecoder())
            .receive(on: DispatchQueue.main)
            .sink(receiveCompletion: { results in
                switch results {
                case .finished:
                    self.loadingone = false
                case .failure(let error):
                    print(error)
                }
            },receiveValue: { data in
                self.dataone = data
            })
            .store(in: &cancellables)
    }
    
    func getpagetwo() {
        self.loadingtwo = true
        URLSession.shared.dataTaskPublisher(for:URLRequest(url: URL(string: "https://rickandmortyapi.com/api/character/?page=2")!))
            .map{ $0.data }
            .decode(type: RickandMorty.self, decoder: JSONDecoder())
            .receive(on: DispatchQueue.main)
            .sink(receiveCompletion: { results in
                switch results {
                case .finished:
                    self.loadingtwo = false
                case .failure(let error):
                    print(error)
                }
            },receiveValue: { data in
                self.datatwo = data
            })
            .store(in: &cancellables)
    }
    
}

view

struct ContentView: View {
    @StateObject var viewmodel = RickAndMortyViewModel()
    var body: some View {
        TabView {
            NavigationView {
                if viewmodel.loadingone {
                    ProgressView("Loading Page 1")
                        .navigationTitle("Page 1")
                } else {
                    List {
                        ForEach(viewmodel.dataone.results, id: \.id) { character in
                            NavigationLink(destination: EmptyView()) {
                                Text(character.name)
                            }
                        }
                    }
                    .navigationTitle("Page 1")
                }
            }
            .onAppear {
                viewmodel.getpageone()
            }
            .tabItem {
                Label("Page 1", systemImage: "person")
            }
            .tag(1)
            
            NavigationView {
                if viewmodel.loadingtwo {
                    ProgressView("Loading Page 2")
                        .navigationTitle("Page 2")
                } else {
                    List {
                        ForEach(viewmodel.datatwo.results, id: \.id) { character in
                            NavigationLink(destination: EmptyView()) {
                                Text(character.name)
                            }
                        }
                    }
                    .navigationTitle("Page 2")
                }
            }
            .onAppear {
                viewmodel.getpagetwo()
            }
            .tabItem {
                Label("Page 2", systemImage: "person")
            }
            .tag(2)
        }
    }
}
cole
  • 1,039
  • 1
  • 8
  • 35
  • Could not replicate your issue, works well for me, on macos 12.3, using xcode 13.3, targets ios 15 and macCatalyst 12. I changed the `NavigationLink(destination: EmptyView())` to `NavigationLink(destination: Text(character.name))` to shows something on the screen. You could try adding: `.navigationViewStyle(.stack)` to your `NavigationViews`. – workingdog support Ukraine Mar 17 '22 at 01:36
  • The issue doesn’t arise in NavigationLink just trying not to get the TabView to reload the data if it doesn’t have to. It triggers my progressview every tab change. I’m trying to avoid that. – cole Mar 17 '22 at 01:43

2 Answers2

0

Break them into Views with independent @StateObjects unless you want to share data across them

class RickAndMortyViewModel: ObservableObject {
    private var cancellables = Set<AnyCancellable>()
    @Published var dataone = RickandMorty(results: [])
    @Published var datatwo = RickandMorty(results: [])
    @Published var loadingone: Bool = false
    @Published var loadingtwo: Bool = false
    
    func getpageone() {
        self.loadingone = true
        URLSession.shared.dataTaskPublisher(for:URLRequest(url: URL(string: "https://rickandmortyapi.com/api/character/?page=1")!))
            .map{ $0.data }
            .decode(type: RickandMorty.self, decoder: JSONDecoder())
            .receive(on: DispatchQueue.main)
            .sink(receiveCompletion: { results in
                switch results {
                case .finished:
                    self.loadingone = false
                case .failure(let error):
                    print(error)
                }
            },receiveValue: { data in
                self.dataone = data
            })
            .store(in: &cancellables)
    }
    
    func getpagetwo() {
        self.loadingtwo = true
        URLSession.shared.dataTaskPublisher(for:URLRequest(url: URL(string: "https://rickandmortyapi.com/api/character/?page=2")!))
            .map{ $0.data }
            .decode(type: RickandMorty.self, decoder: JSONDecoder())
            .receive(on: DispatchQueue.main)
            .sink(receiveCompletion: { results in
                switch results {
                case .finished:
                    self.loadingtwo = false
                case .failure(let error):
                    print(error)
                }
            },receiveValue: { data in
                self.datatwo = data
            })
            .store(in: &cancellables)
    }
    
}
struct ContentView: View {
    @StateObject var viewmodel = RickAndMortyViewModel()
    var body: some View {
        TabView {
            TabPageOne()
            .tabItem {
                Label("Page 1", systemImage: "person")
            }
            .tag(1)
            
           TabPageTwo()
            .tabItem {
                Label("Page 2", systemImage: "person")
            }
            .tag(2)
        }
    }
}

struct TabPageOne: View {
    
    @StateObject var viewmodel = RickAndMortyViewModel()
    
    var body: some View {
        NavigationView {
            if viewmodel.loadingone {
                ProgressView("Loading Page 1")
                    .navigationTitle("Page 1")
            } else {
                List {
                    ForEach(viewmodel.dataone.results, id: \.id) { character in
                        NavigationLink(destination: EmptyView()) {
                            Text(character.name)
                        }
                    }
                }
                .navigationTitle("Page 1")
            }
        }
        .onAppear {
            viewmodel.getpageone()
        }
        
    }
    
}

struct TabPageTwo: View {
    
    @StateObject var viewmodel = RickAndMortyViewModel()
    
    var body: some View {
        NavigationView {
            if viewmodel.loadingtwo {
                ProgressView("Loading Page 2")
                    .navigationTitle("Page 2")
            } else {
                List {
                    ForEach(viewmodel.datatwo.results, id: \.id) { character in
                        NavigationLink(destination: EmptyView()) {
                            Text(character.name)
                        }
                    }
                }
                .navigationTitle("Page 2")
            }
        }
        .onAppear {
            viewmodel.getpagetwo()
        }
        
    }
    
}
Denzel
  • 309
  • 1
  • 8
  • this does the same thing, I still have the issue separating them into their own `View`'s. – cole Mar 17 '22 at 02:38
0

I see what you mean now (I think). My internet is very fast and so I could not see the ProgressView going on.

You could try this in RickAndMortyViewModel:

@Published var loadingone: Bool = true
@Published var loadingtwo: Bool = true

    func getpageone() {
    if !loadingone {return}
    ...
    }
    
    func getpagetwo() {
if !loadingtwo {return}
...
}
  • Thanks, I'm doing to mark as answered, I guess my real question is a bit more advanced and will have to ask another question. – cole Mar 17 '22 at 21:35