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

如何用自定义图片替换UIPageControl的指示点?

如何将UIPageControl的指示点替换为自定义图片?

我来帮你搞定UIPageControl自定义指示点的问题!看你已经在写PageViewController的代码了,这里给你两种实用的实现方案,分版本兼容和原生API两种情况:

一、iOS 14+ 直接用系统原生API(最推荐)

苹果在iOS14之后给UIPageControl新增了直接设置自定义图片的API,不用绕弯子,代码简洁还稳定:

// 假设你已经有了UIPageControl实例,比如叫pageControl
pageControl.preferredIndicatorImage = UIImage(named: "你的选中状态图片")
// 给每个未选中的page设置默认图片
for index in 0..<pageControl.numberOfPages {
    pageControl.setIndicatorImage(UIImage(named: "未选中状态图片"), forPage: index)
}

这种方法最省心,系统会自动帮你管理选中和未选中状态的切换,不需要额外处理代理或者绘制逻辑,直接用就完事儿了。

二、兼容iOS 14以下版本的方案

如果你的项目需要支持更低版本的iOS,那可以用下面两种方法:

方法1:通过KVC替换私有视图

这种方法是通过访问UIPageControl的私有子视图来替换成自定义图片,目前大部分版本都能兼容,不过要注意苹果后续可能修改私有属性:

extension UIPageControl {
    func setCustomIndicatorImages(selectedImage: UIImage, unselectedImage: UIImage) {
        guard numberOfPages > 0 else { return }
        
        // 获取系统自带的指示器视图数组
        if let indicators = self.value(forKey: "_indicators") as? [UIView] {
            for (index, indicator) in indicators.enumerated() {
                let imageView = UIImageView(image: index == currentPage ? selectedImage : unselectedImage)
                // 清空原有视图,添加自定义图片
                indicator.subviews.forEach { $0.removeFromSuperview() }
                indicator.addSubview(imageView)
                imageView.frame = indicator.bounds
            }
        }
    }
    
    // 监听currentPage变化,自动更新图片状态
    override open var currentPage: Int {
        didSet {
            setCustomIndicatorImages(selectedImage: UIImage(named: "选中图")!, unselectedImage: UIImage(named: "未选中图")!)
        }
    }
}

使用的时候,在你的PageViewController里初始化UIPageControl后,调用setCustomIndicatorImages方法即可。

方法2:完全自定义PageControl控件

如果担心KVC的风险,或者需要更灵活的样式控制,完全可以自己写一个自定义的PageControl,用UIStackView来管理指示器:

class CustomPageControl: UIView {
    // 总页数
    var numberOfPages = 0 {
        didSet {
            setupIndicators()
        }
    }
    // 当前选中页
    var currentPage = 0 {
        didSet {
            updateIndicatorStates()
        }
    }
    // 自定义图片(根据你的需求替换)
    private let selectedImage = UIImage(named: "选中状态图片")!
    private let unselectedImage = UIImage(named: "未选中状态图片")!
    private var indicatorViews = [UIImageView]()
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        setupStackView()
    }
    
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        setupStackView()
    }
    
    // 创建栈视图来管理指示器
    private func setupStackView() {
        let stackView = UIStackView()
        stackView.axis = .horizontal
        stackView.spacing = 8 // 指示器之间的间距,可自定义
        stackView.alignment = .center
        stackView.translatesAutoresizingMaskIntoConstraints = false
        addSubview(stackView)
        
        NSLayoutConstraint.activate([
            stackView.centerXAnchor.constraint(equalTo: centerXAnchor),
            stackView.centerYAnchor.constraint(equalTo: centerYAnchor),
            stackView.heightAnchor.constraint(equalToConstant: selectedImage.size.height)
        ])
    }
    
    // 生成所有指示器视图
    private func setupIndicators() {
        guard let stackView = subviews.first as? UIStackView else { return }
        // 先清除旧的指示器
        indicatorViews.forEach { $0.removeFromSuperview() }
        indicatorViews.removeAll()
        
        for _ in 0..<numberOfPages {
            let imageView = UIImageView(image: unselectedImage)
            imageView.contentMode = .scaleAspectFit
            indicatorViews.append(imageView)
            stackView.addArrangedSubview(imageView)
        }
        
        updateIndicatorStates()
    }
    
    // 更新选中/未选中状态
    private func updateIndicatorStates() {
        guard currentPage < indicatorViews.count else { return }
        for (index, imageView) in indicatorViews.enumerated() {
            imageView.image = index == currentPage ? selectedImage : unselectedImage
        }
    }
}

然后在你的PageViewController里使用这个自定义控件:

class PageViewController: UIPageViewController, UIPageViewControllerDelegate, UIPageViewControllerDataSource {
    // 自定义分页控件
    let customPageControl = CustomPageControl()
    // 你的视图控制器数组
    var orderedViewControllers: [UIViewController] = []

    override func viewDidLoad() {
        super.viewDidLoad()
        delegate = self
        dataSource = self
        
        // 设置自定义分页控件的位置和页数
        customPageControl.numberOfPages = orderedViewControllers.count
        customPageControl.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(customPageControl)
        
        NSLayoutConstraint.activate([
            customPageControl.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -20),
            customPageControl.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            customPageControl.heightAnchor.constraint(equalToConstant: 20)
        ])
    }
    
    // 监听页面切换,更新当前选中页
    func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
        if completed, let currentVC = pageViewController.viewControllers?.first, let index = orderedViewControllers.index(of: currentVC) {
            customPageControl.currentPage = index
        }
    }
    
    // 你的数据源方法(补充完整你的代码)
    func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
        guard let viewControllerIndex = orderedViewControllers.index(of: viewController) else { return nil }
        let previousIndex = viewControllerIndex - 1
        guard previousIndex >= 0 else { return orderedViewControllers.last }
        guard orderedViewControllers.count > previousIndex else { return nil }
        return orderedViewControllers[previousIndex]
    }
    
    func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
        guard let viewControllerIndex = orderedViewControllers.index(of: viewController) else { return nil }
        let nextIndex = viewControllerIndex + 1
        guard orderedViewControllers.count != nextIndex else { return orderedViewControllers.first }
        guard orderedViewControllers.count > nextIndex else { return nil }
        return orderedViewControllers[nextIndex]
    }
}

完全自定义的方式最稳定,不会受系统版本更新的影响,适合需要长期维护的项目。

内容的提问来源于stack exchange,提问作者Lama

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.22 09:56:03