我有一个包含选择器的 SwiftUI 视图。我在 Picker 的 .onReceive 中使用 Switch 语句来调用函数。该函数调用外部 API。
问题是每当视图初始化时,该函数就会被调用两次,即复制数据。我不明白为什么 .onReceive 被调用两次。
我认为这可能与我初始化选取器模型时调用的函数有关,然后从选取器本身收到另一个通知,但我不确定如何解决它。
这是我的代码:
选择器模型
import Foundation
class PickerModel: ObservableObject {
@Published var filter = 0
let pickerOptions = ["Popular", "Top Rated"]
}
包含选择器的视图:
import SwiftUI
struct FilteredMoviesGridView: View {
@ObservedObject private var filteredMovieVM = FilteredMovieGridViewModel()
@ObservedObject private var pickerModel = PickerModel()
private var twoColumnGrid = [GridItem(.flexible()), GridItem(.flexible())]
var body: some View {
NavigationView {
VStack {
Picker(selection: $pickerModel.filter, label: Text("Select")) {
ForEach(0 ..< pickerModel.pickerOptions.count) {
Text(pickerModel.pickerOptions[$0])
}
}.onReceive(pickerModel.$filter) { (value) in
switch value {
case 0:
filteredMovieVM.movies.removeAll()
filteredMovieVM.currentPage = 1
filteredMovieVM.fetchMovies(filter: "popularity")
case 1:
filteredMovieVM.movies.removeAll()
filteredMovieVM.currentPage = 1
filteredMovieVM.fetchMovies(filter: "vote_average")
default:
filteredMovieVM.movies.removeAll()
filteredMovieVM.currentPage = 1
filteredMovieVM.fetchMovies(filter: "popularity")
}
}.pickerStyle(SegmentedPickerStyle())
ScrollView {
LazyVGrid(columns: twoColumnGrid, spacing: 10) {
ForEach(filteredMovieVM.movies, id:\.id) { movie in
NavigationLink(destination: MovieDetailView(movie: movie)) {
MovieGridItemView(movies: movie)
}.buttonStyle(PlainButtonStyle())
.onAppear(perform: {
if movie == self.filteredMovieVM.movies.last {
switch pickerModel.filter {
case 0:
self.filteredMovieVM.checkTotalMovies(filter: "popularity")
case 1:
self.filteredMovieVM.checkTotalMovies(filter: "vote_average")
default:
self.filteredMovieVM.checkTotalMovies(filter: "popularity")
}
}
})
}
}
}
.navigationBarTitle("Movies")
}
}.accentColor(.white)
}
}
包含函数的视图模型:
import Foundation
class FilteredMovieGridViewModel: ObservableObject {
@Published var movies = [Movie]()
private var filteredMovies = [MovieList]()
var currentPage = 1
func checkTotalMovies(filter: String) {
if filteredMovies.count < 20 {
fetchMovies(filter: filter)
}
}
func fetchMovies(filter: String) {
WebService().getMoviesByFilter(filter: filter, page: currentPage) { movie in
if let movie = movie {
self.filteredMovies.append(movie)
for movie in movie.movies {
self.movies.append(movie)
print(self.movies.count)
}
}
}
if let totalPages = filteredMovies.first?.totalPages {
if currentPage <= totalPages {
currentPage += 1
}
}
}
}
任何帮助将不胜感激。
很可能每当重新创建您的
ObservedObject
时,您都会重新创建您的 FilteredMoviesGridView
。每当 SwiftUI 的运行时认为需要重新创建它时,就会发生这种情况。因此,您的视图创建应该很便宜,并且您应该确保不会意外地重新创建您需要的资源。幸运的是,iOS 14 等中的 SwiftUI 使解决这个问题变得更加容易。不要使用 @ObservedObject
,而是使用 @StateObject
,这将在重新创建视图时使同一实例保持活动状态。
您可以使用
dropFirst()
忽略每次计算发布者时发送的第一个值,例如
.onReceive(pickerModel.$filter.dropFirst())
但是,标准代码会有一个
@State
作为过滤器,然后使用 onChange
来更新数据模型。