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

Swift自定义ExpandedCustomLoopedPageControl选中圆点遮挡问题求助

问题分析

你遇到的遮挡问题核心是视图层级顺序错误:当currentPage为0时,选中的大尺寸圆点层级高于第二个圆点,导致后者被遮挡。因为动画过程中调用了bringSubviewToFront(currentView)会把选中圆点带到上层,后续切换回第0页时未调整层级,最终引发遮挡。

解决方案

要确保选中的圆点始终处于视图层级的最底层,让未选中的小圆点在它上方显示,这样即使选中圆点宽度更大,也不会遮挡其他圆点。需要在两个关键位置调整层级:

  • 初始化布局完成后,将当前选中的圆点发送到最底层
  • 页面切换动画完成后,将新选中的圆点发送到最底层
修改后的完整代码
import UIKit

class ExpandedCustomLoopedPageControl: UIView {

// MARK: - start flow in pagenumber  set
    public var numberOfPages: Int = 0 {
        didSet {

            dotViewArray.forEach { $0.removeFromSuperview() }
            dotViewArray.removeAll()

            for _ in 0..<numberOfPages {
                let dotView = UIView()
                dotView.layer.cornerRadius = cornerRadius
                addSubview(dotView)
                dotViewArray.append(dotView)
            }

            isInitialize = true
            setNeedsLayout()
        }
    }

    public var currentPage: Int {
        set {
            if newValue < 0 ||
                newValue >= dotViewArray.count ||
                dotViewArray.count == 0 ||
                newValue == currentPage ||
                inAnimating {
                return
            }

            var animationDuration = 0.1
            if ((  currentPage == (numberOfPages - 1) ) && ( newValue == 0))
                || (currentPage == 0 && (newValue == (numberOfPages - 1))) {
                animationDuration = 0
            }
// MARK: - Shift to the right
            if newValue > currentPage {
                let currentView = dotViewArray[currentPage]
                bringSubviewToFront(currentView)
                inAnimating = true
                UIView.animate(withDuration: animationDuration, animations: {
                    let x = currentView.frame.origin.x
                    let y = currentView.frame.origin.y
                    let w = self.currentDotWidth + (self.dotSpace + self.otherDotWidth) * CGFloat(newValue - self.currentPage)
                    let h = currentView.frame.size.height
                    currentView.frame = CGRect(x: x, y: y, width: w, height: h)
                }) { (_) in
                    let endView = self.dotViewArray[newValue]
                    endView.backgroundColor = currentView.backgroundColor
                    endView.frame = currentView.frame
                    self.bringSubviewToFront(endView)

                    currentView.backgroundColor = self.otherDotColor

                    let start_X = currentView.frame.origin.x
                    for i in 0..<(newValue - self.currentPage) {
                        let dotView = self.dotViewArray[self.currentPage + i]
                        let x = start_X + (self.otherDotWidth + self.dotSpace) * CGFloat(i)
                        let y = dotView.frame.origin.y
                        let w = self.otherDotWidth
                        let h = self.dotHeight
                        dotView.frame = CGRect(x: x, y: y, width: w, height: h)
                    }
                    UIView.animate(withDuration: 0.1, animations: {
                        let w = self.currentDotWidth
                        let x = endView.frame.maxX - self.currentDotWidth
                        let y = endView.frame.origin.y
                        let h = endView.frame.size.height
                        endView.frame = CGRect(x: x, y: y, width: w, height: h)
                    }) { (_) in
                        self.currentPageInner = newValue
                        self.inAnimating = false
                        // 将新选中的dot发送到最底层,避免遮挡其他dot
                        self.sendSubviewToBack(endView)
                    }
                }
            }
 // MARK: - Shift to the Left
            else {
                let currentView = self.dotViewArray[self.currentPage]
                bringSubviewToFront(currentView)
                inAnimating = true

                UIView.animate(withDuration: animationDuration, animations: {
                    let x = currentView.frame.origin.x - (self.dotSpace + self.otherDotWidth) * CGFloat(self.currentPage - newValue)
                    let y = currentView.frame.origin.y
                    let w = self.currentDotWidth + (self.dotSpace + self.otherDotWidth) * CGFloat(self.currentPage - newValue)
                    let h = currentView.frame.size.height
                    currentView.frame = CGRect(x: x, y: y, width: w, height: h)
                }) { (_) in
                    let endView = self.dotViewArray[newValue]
                    endView.backgroundColor = currentView.backgroundColor
                    endView.frame = currentView.frame
                    self.bringSubviewToFront(endView)

                    currentView.backgroundColor = self.otherDotColor

                    let start_X = currentView.frame.maxX
                    for i in 0..<(self.currentPage - newValue) {
                        let dotView = self.dotViewArray[self.currentPage - i]
                        let tempFrame = dotView.frame
                        let x = start_X - self.otherDotWidth - (self.otherDotWidth + self.dotSpace) * CGFloat(i)
                        let y = tempFrame.origin.y
                        let w = self.otherDotWidth
                        let h = tempFrame.size.height
                        dotView.frame = CGRect(x: x, y: y, width: w, height: h)
                    }

                    UIView.animate(withDuration: 0.1, animations: {
                        let x = endView.frame.origin.x
                        let y = endView.frame.origin.y
                        let w = self.currentDotWidth
                        let h = endView.frame.size.height
                        endView.frame = CGRect(x: x, y: y, width: w, height: h)
                    }) { (_) in
                        self.currentPageInner = newValue
                        self.inAnimating = false
                        // 将新选中的dot发送到最底层,避免遮挡其他dot
                        self.sendSubviewToBack(endView)
                    }
                }
            }
        }
        get {
            currentPageInner
        }
    }

// MARK: - properties of pageControl

    public var currentDotWidth: CGFloat = 20
    public var otherDotWidth: CGFloat = 5
    public var dotHeight: CGFloat = 5
    public var dotSpace: CGFloat = 7
    public var cornerRadius: CGFloat = 2.5
    public var currentDotColor: UIColor = Color.lines.color
    public var otherDotColor: UIColor = Color.slider_indicator_bubble.color

    private var currentPageInner: Int = 0
    private var dotViewArray = [UIView]()
    private var isInitialize: Bool = false
    private var inAnimating: Bool = false

// MARK: - init()
    override init(frame: CGRect) {
        super.init(frame: frame)
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func layoutSubviews() {
        super.layoutSubviews()
        setupUI()
    }
// MARK: - Setup UI
    private func setupUI() {
        if dotViewArray.isEmpty || isInitialize == false {
            return
        }
        self.isInitialize = false

        let totalWidth = CGFloat(numberOfPages) * (currentDotWidth + dotSpace) - dotSpace
        var currentX = (frame.size.width - totalWidth) / 2

        for (index, dotView) in dotViewArray.enumerated() {
            let width = (index == currentPage ? currentDotWidth : otherDotWidth)
            let height = dotHeight
            let y = (frame.size.height - height) / 2
            dotView.frame = CGRect(x: currentX, y: y, width: width, height: height)

            dotView.backgroundColor = (index == currentPage) ? currentDotColor : otherDotColor

            currentX += width + dotSpace
        }
        
        // 初始化时将选中的dot发送到最底层,避免遮挡其他dot
        sendSubviewToBack(dotViewArray[currentPage])
    }
}
关键修改点
  1. 在setupUI方法末尾添加sendSubviewToBack(dotViewArray[currentPage]),确保初始化时选中的圆点在最底层
  2. 在向右滑动和向左滑动的动画完成回调中,分别添加self.sendSubviewToBack(endView),确保切换页面后新选中的圆点始终在最底层

调整后,选中的大圆点会被其他小圆点覆盖在下方,不会出现遮挡问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.07 10:34:54