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

如何根据API返回的数组数量展示UIPageViewController的子控制器

实现UIPageViewController根据API返回数组动态加载子控制器

没问题,我来帮你一步步实现这个需求——核心思路就是先做好单个页面的子控制器模板,再通过API拿到数据后动态生成对应数量的子控制器,最后交给UIPageViewController来管理滑动逻辑和页面指示器。

1. 先做单个页面的子控制器模板

我们先创建一个专门用来展示「图片+下方标题」的子控制器,叫PageContentViewController,给它预留好接收外部数据的接口:

import UIKit

class PageContentViewController: UIViewController {
    // 页面核心UI组件
    private let contentImageView: UIImageView = {
        let iv = UIImageView()
        iv.contentMode = .scaleAspectFit
        iv.clipsToBounds = true
        iv.translatesAutoresizingMaskIntoConstraints = false
        return iv
    }()
    
    private let titleLabel: UILabel = {
        let label = UILabel()
        label.textAlignment = .center
        label.font = UIFont.systemFont(ofSize: 16, weight: .medium)
        label.translatesAutoresizingMaskIntoConstraints = false
        return label
    }()
    
    // 接收外部传入的展示数据
    var imageURL: URL?
    var pageTitle: String?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        setupUI()
        loadContentToUI()
    }
    
    // 布局UI组件
    private func setupUI() {
        view.backgroundColor = .white
        
        view.addSubview(contentImageView)
        view.addSubview(titleLabel)
        
        NSLayoutConstraint.activate([
            contentImageView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            contentImageView.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: -40),
            contentImageView.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.8),
            contentImageView.heightAnchor.constraint(equalTo: contentImageView.widthAnchor),
            
            titleLabel.topAnchor.constraint(equalTo: contentImageView.bottomAnchor, constant: 20),
            titleLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
            titleLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20)
        ])
    }
    
    // 将数据加载到UI上
    private func loadContentToUI() {
        titleLabel.text = pageTitle
        
        // 加载图片(示例用系统URLSession,实际项目推荐用SDWebImage/Kingfisher带缓存的库)
        guard let url = imageURL else { return }
        URLSession.shared.dataTask(with: url) { [weak self] data, _, error in
            guard let data = data, error == nil else { return }
            DispatchQueue.main.async {
                self?.contentImageView.image = UIImage(data: data)
            }
        }.resume()
    }
}

2. 在UIPageViewController中处理API与控制器生成

接下来是核心的UIPageViewController类,我们要完成「API请求→数据解析→生成子控制器数组→交给UIPageViewController管理」的流程:

首先定义和API返回匹配的数据模型(根据你的实际API结构调整):

struct PageItem: Codable {
    let imageUrl: String
    let title: String
}

然后是UIPageViewController的完整实现:

import UIKit

class CustomPageViewController: UIPageViewController {
    // 存储API返回的原始数据
    private var pageItems: [PageItem] = []
    // 存储生成的所有子控制器
    private var viewControllersArray: [UIViewController] = []
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // 设置数据源和代理,让UIPageViewController知道如何切换页面
        dataSource = self
        delegate = self
        
        // 发起API请求获取页面数据
        fetchPageDataFromAPI()
    }
    
    // 发起API请求
    private func fetchPageDataFromAPI() {
        // 替换成你的实际API地址
        guard let apiURL = URL(string: "https://your-api-endpoint.com/page-content") else { return }
        
        URLSession.shared.dataTask(with: apiURL) { [weak self] data, _, error in
            guard let data = data, error == nil else {
                print("API请求失败:\(error?.localizedDescription ?? "未知错误")")
                // 实际项目这里可以给用户显示错误提示,比如Toast/Alert
                return
            }
            
            do {
                // 解析JSON数据到模型数组
                self?.pageItems = try JSONDecoder().decode([PageItem].self, from: data)
                DispatchQueue.main.async {
                    // 生成对应数量的子控制器
                    self?.generateViewControllers()
                    // 设置初始显示的第一个页面
                    if let firstVC = self?.viewControllersArray.first {
                        self?.setViewControllers([firstVC], direction: .forward, animated: true, completion: nil)
                    }
                }
            } catch {
                print("数据解析失败:\(error.localizedDescription)")
            }
        }.resume()
    }
    
    // 根据API数据生成子控制器数组
    private func generateViewControllers() {
        viewControllersArray = pageItems.map { item in
            let contentVC = PageContentViewController()
            contentVC.imageURL = URL(string: item.imageUrl)
            contentVC.pageTitle = item.title
            return contentVC
        }
    }
}

// 实现UIPageViewController数据源协议,处理页面切换逻辑
extension CustomPageViewController: UIPageViewControllerDataSource {
    // 获取当前页面的前一个页面
    func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
        guard let currentIndex = viewControllersArray.firstIndex(of: viewController), currentIndex > 0 else {
            return nil // 已经是第一个页面,没有前一页
        }
        return viewControllersArray[currentIndex - 1]
    }
    
    // 获取当前页面的后一个页面
    func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
        guard let currentIndex = viewControllersArray.firstIndex(of: viewController), currentIndex < viewControllersArray.count - 1 else {
            return nil // 已经是最后一个页面,没有后一页
        }
        return viewControllersArray[currentIndex + 1]
    }
    
    // 设置底部页面指示器的总数量
    func presentationCount(for pageViewController: UIPageViewController) -> Int {
        return viewControllersArray.count
    }
    
    // 设置底部页面指示器当前选中的索引
    func presentationIndex(for pageViewController: UIPageViewController) -> Int {
        guard let currentVC = pageViewController.viewControllers?.first,
              let currentIndex = viewControllersArray.firstIndex(of: currentVC) else {
            return 0
        }
        return currentIndex
    }
}

// 可选:实现代理方法监听页面切换完成事件
extension CustomPageViewController: UIPageViewControllerDelegate {
    func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
        if completed, let currentVC = pageViewController.viewControllers?.first, let index = viewControllersArray.firstIndex(of: currentVC) {
            print("当前切换到第 \(index + 1) 页")
            // 这里可以做一些页面切换后的逻辑,比如更新顶部标题栏等
        }
    }
}

几个实用提示

  • 图片加载:示例用了系统URLSession,实际项目推荐用SDWebImage或Kingfisher,它们自带缓存、占位图、错误图等功能,体验更优。
  • 错误处理:示例只做了简单打印,实际项目中应该给用户展示友好的错误提示(比如弹窗或顶部提示条)。
  • 数据映射:如果API返回的字段名和你的模型属性名不一致,可以用CodingKeys枚举来做字段映射。
  • 布局调整:子控制器的UI布局可以根据你的设计需求,修改AutoLayout约束来调整图片和标题的位置、大小。

内容的提问来源于stack exchange,提问作者roshan.k

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.28 09:52:24