You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.20 21:14:54