.on接收两次发射SwiftUI

问题描述

我有一个包含Picker的SwiftUI视图。我在Picker的.onReceive内使用Switch语句来调用函数。该函数调用外部API。

问题在于,无论何时初始化视图(即复制数据),该函数都会被调用两次。我不知道为什么两次调用.onReceive。

我认为这可能与在初始化Picker Model并从Picker本身收到另一个通知调用函数有关,但我不确定如何解决

这是我的代码

选择器模型

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,它会在重新创建视图时使同一实例保持活动状态。