.onReceive 触发两次 | SwiftUI

问题描述 投票:0回答:2

我有一个包含选择器的 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
            }
        }
    }
}

任何帮助将不胜感激。

ios swift xcode swiftui
2个回答
5
投票

很可能每当重新创建您的

ObservedObject
时,您都会重新创建您的
FilteredMoviesGridView
。每当 SwiftUI 的运行时认为需要重新创建它时,就会发生这种情况。因此,您的视图创建应该很便宜,并且您应该确保不会意外地重新创建您需要的资源。幸运的是,iOS 14 等中的 SwiftUI 使解决这个问题变得更加容易。不要使用
@ObservedObject
,而是使用
@StateObject
,这将在重新创建视图时使同一实例保持活动状态。


0
投票

您可以使用

dropFirst()
忽略每次计算发布者时发送的第一个值,例如

.onReceive(pickerModel.$filter.dropFirst())

但是,标准代码会有一个

@State
作为过滤器,然后使用
onChange
来更新数据模型。

© www.soinside.com 2019 - 2024. All rights reserved.