TabView 在选项卡上保持状态更改 SwiftUI

TabView keeping state on tab changes SwiftUI

当我的 TabView 选项卡更改时,我正在重新加载 View。我试图在初始数据加载时从 API 获取数据时显示 ProgressView,但每次更改选项卡时都会重新加载它,即使在导航到另一个视图之后也是如此。如果我删除我的 ProgressView 它可以工作,它只是在加载数据时看起来不太好。

我原以为这会是一项简单的任务,但我还没有找到很好的解决方案。我找到了 StatefulTabView 但它似乎在 iOS 15 上坏了。我不确定我是否只是用我的视图模型做了一些不正确的事情。任何帮助将不胜感激。

查看模型

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{ [=10=].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{ [=10=].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 {
            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)
        }
    }
}

将它们分解为具有独立@StateObject 的视图,除非您想在它们之间共享数据

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{ [=10=].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{ [=10=].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()
        }
        
    }
    
}

我明白你的意思了(我想)。 我的网速很快,所以我看不到 ProgressView 的进展。

你可以试试这个in RickAndMortyViewModel:

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

    func getpageone() {
    if !loadingone {return}
    ...
    }
    
    func getpagetwo() {
if !loadingtwo {return}
...
}