如何根据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
相关产品推荐
相关产品推荐

