SwiftUI中Movie Database API点击加载更多无新请求的问题修复
问题:点击“Load More”按钮无法触发Movie Database API新请求
我了解到Movie Database API无法批量获取多页数据(如1到50页),只能单独请求单页数据。我尝试实现加载更多功能,但点击“Load More”按钮时并未触发新的API请求。
相关代码
API调用代码
func fetchMovies(from endpoint: MovieListEndpoint, page: Int, completion: @escaping (Result<MovieResponse, MovieError>) -> ()) { guard let url = URL(string: "\(baseAPIURL)/movie/\(endpoint.rawValue)?api_key=\(apiKey)&page=\(page)") else { completion(.failure(.invalidEndpoint)) return } self.loadURLAndDecode(url: url) { [weak self] (result: Result<MovieResponse, MovieError>) in switch result { case .success(let movieResponse): self?.movies.append(contentsOf: movieResponse.results) // Increment the current page for the next fetch self?.currentPage += 1 completion(.success(movieResponse)) case .failure(let error): completion(.failure(error)) } } }
加载逻辑代码
class MovieListState: ObservableObject { @Published var movies: [Movie] = [] @Published var isLoading: Bool = false @Published var error: MovieError? private let movieService: MovieService private var currentPage: Int = 1 init(movieService: MovieService = MovieStore.shared) { self.movieService = movieService } func fetchNextPage(with endpoint: MovieListEndpoint) { guard !isLoading else { return } currentPage += 1 loadMovies(with: endpoint, page: currentPage) } func loadMovies(with endpoint: MovieListEndpoint, page: Int) { isLoading = true movieService.fetchMovies(from: endpoint, page: page) { [weak self] result in guard let self = self else { return } self.isLoading = false switch result { case .success(let response): self.movies.append(contentsOf: response.results) case .failure(let error): self.error = error } } }
视图代码
struct MovieFullListView: View { @ObservedObject private var movieListState: MovieListState let title: String let endpoint: MovieListEndpoint @State var selection = 0 @State private var isScrolledToEnd = false var items: [GridItem] = [GridItem(.flexible(), spacing: 20), GridItem(.flexible(), spacing: 20)] init(title: String, endpoint: MovieListEndpoint) { self.title = title self.endpoint = endpoint self.movieListState = MovieListState() } var body: some View { VStack(alignment: .leading, spacing: 16) { Text(title) .font(.title) .fontWeight(.bold) .padding(.horizontal) ScrollView(.vertical, showsIndicators: false) { LazyVGrid(columns: items, spacing: 30) { ForEach(movieListState.movies) { movie in VStack { NavigationLink(destination: MovieDetailView(movieId: movie.id)) { MoviePosterCard(movie: movie) .aspectRatio(contentMode: .fit) } .buttonStyle(.plain) HStack { Spacer() Button(action: { // Action code here }) { VStack { if selection == 0 { Image("Watched") .resizable() .frame(width: 28, height: 28) } else { Image("watch") .resizable() .frame(width: 28, height: 28) } } } Spacer() Button(action: { // Action code here }) { VStack { if selection == 1 { Image("Saved") .resizable() .frame(width: 25, height: 25) } else { Image("Save") .resizable() .frame(width: 25, height: 25) } } } Spacer() } .padding(.bottom, 6) } .background(Color.gray.opacity(0.1)) .cornerRadius(10) } } Button(action: { movieListState.fetchNextPage(with: endpoint) }) { Text("Load More") .font(.headline) .foregroundColor(.blue) .padding() .background(Color.gray.opacity(0.1)) .cornerRadius(10) } .padding() } } .onAppear { movieListState.loadMovies(with: endpoint, page: 1) } }
修复方案
问题根源在于状态管理混乱:API服务层和视图状态层同时维护了movies数组和currentPage页码,导致页码递增逻辑冲突,无法正确发起新请求。
步骤1:剥离服务层的状态维护逻辑
API服务层只负责纯数据请求,不应该持有视图相关状态。修改fetchMovies方法,移除对movies和currentPage的操作,直接返回请求结果:
func fetchMovies(from endpoint: MovieListEndpoint, page: Int, completion: @escaping (Result<MovieResponse, MovieError>) -> ()) { guard let url = URL(string: "\(baseAPIURL)/movie/\(endpoint.rawValue)?api_key=\(apiKey)&page=\(page)") else { completion(.failure(.invalidEndpoint)) return } self.loadURLAndDecode(url: url) { (result: Result<MovieResponse, MovieError>) in completion(result) // 直接返回请求结果,不处理状态 } }
步骤2:统一视图状态层的页码管理
在MovieListState中,确保页码递增逻辑仅在请求成功后更新,避免提前递增导致的页码错误:
class MovieListState: ObservableObject { @Published var movies: [Movie] = [] @Published var isLoading: Bool = false @Published var error: MovieError? private let movieService: MovieService private var currentPage: Int = 1 init(movieService: MovieService = MovieStore.shared) { self.movieService = movieService } func fetchNextPage(with endpoint: MovieListEndpoint) { guard !isLoading else { return } let nextPage = currentPage + 1 // 计算下一页,不提前修改currentPage loadMovies(with: endpoint, page: nextPage) } func loadMovies(with endpoint: MovieListEndpoint, page: Int) { isLoading = true movieService.fetchMovies(from: endpoint, page: page) { [weak self] result in guard let self = self else { return } self.isLoading = false switch result { case .success(let response): self.movies.append(contentsOf: response.results) self.currentPage = page // 请求成功后,将currentPage更新为已加载的页码 case .failure(let error): self.error = error } } } }
逻辑说明
- 初始加载时请求page1,成功后
currentPage设为1; - 点击“Load More”时,计算
nextPage = 1 + 1 = 2,发起page2的请求; - 请求成功后
currentPage更新为2,下次点击则请求page3,以此类推; - 服务层不再干扰状态,所有视图相关状态由
MovieListState统一管理,避免冲突。
内容的提问来源于stack exchange,提问作者Joost van Grieken
相关产品推荐
相关产品推荐

