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

iOS开发:如何复用按钮与操作并让动画作用于对应图层而非仅最新添加图层

问题分析与解决方案

你遇到的问题核心很明确:你复用了全局的shapeLayer和percentageLabel变量,导致所有圆形都共享同一组图层/标签。每次调用createCircle时,你都会覆盖这个全局shapeLayer的位置属性,最终它只会绑定到最后一个创建的圆形位置。所以不管点击哪个按钮,修改的都是这个唯一的全局图层,自然只会在最后一个圆形上看到动画效果。

下面是最优雅的解决方式——把每个圆形的所有关联元素(进度轨道、进度图层、百分比标签、点击事件)封装到一个自定义UIView子类中,让每个圆形成为独立的实例,各自管理自己的状态:

步骤1:创建自定义进度圆形视图

这个视图会封装所有需要的元素和逻辑,确保每个圆形都是独立的个体:

import UIKit

class ProgressCircleView: UIView {
    // 每个视图都有自己独立的图层和标签
    private let trackLayer = CAShapeLayer()
    private let shapeLayer = CAShapeLayer()
    private let percentageLabel: UILabel = {
        let label = UILabel()
        label.text = "0%"
        label.textAlignment = .center
        label.font = UIFont.boldSystemFont(ofSize: 28)
        label.textColor = UIColor(red: 0.59, green: 0.42, blue: 0.23, alpha: 1.00)
        return label
    }()
    
    // 每个视图独立维护进度状态
    private var done = 0
    private let toDo = 5
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        setupUI()
        setupLayers()
        setupTapHandling()
    }
    
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        setupUI()
        setupLayers()
        setupTapHandling()
    }
    
    private func setupUI() {
        addSubview(percentageLabel)
        percentageLabel.frame = bounds // 让标签铺满整个视图,居中显示
    }
    
    private func setupLayers() {
        // 基于视图自身的 bounds 创建圆形路径,不用再手动传位置
        let center = CGPoint(x: bounds.midX, y: bounds.midY)
        let radius = bounds.width / 2 - 5 // 减去线宽的一半,避免图层超出视图范围
        let circularPath = UIBezierPath(arcCenter: center, 
                                        radius: radius, 
                                        startAngle: 0, 
                                        endAngle: 2 * CGFloat.pi, 
                                        clockwise: true)
        
        // 背景轨道层
        trackLayer.path = circularPath.cgPath
        trackLayer.strokeColor = UIColor(red: 0.82, green: 0.69, blue: 0.52, alpha: 1.00).cgColor
        trackLayer.fillColor = UIColor.clear.cgColor
        trackLayer.lineWidth = 10
        layer.addSublayer(trackLayer)
        
        // 进度图层
        shapeLayer.path = circularPath.cgPath
        shapeLayer.strokeColor = UIColor(red: 0.59, green: 0.42, blue: 0.23, alpha: 1.00).cgColor
        shapeLayer.fillColor = UIColor.clear.cgColor
        shapeLayer.lineWidth = 10
        shapeLayer.lineCap = .round
        shapeLayer.transform = CATransform3DMakeRotation(-CGFloat.pi / 2, 0, 0, 1)
        shapeLayer.strokeEnd = 0 // 初始进度为0
        layer.addSublayer(shapeLayer)
    }
    
    private func setupTapHandling() {
        // 用手势识别器处理点击,也可以换成UIButton,逻辑一样
        let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTap))
        addGestureRecognizer(tapGesture)
        isUserInteractionEnabled = true
    }
    
    @objc private func handleTap() {
        // 更新进度状态
        if done < toDo {
            done += 1
        } else {
            done = 0 // 重置进度
        }
        
        // 计算百分比并更新UI
        let percentage = CGFloat(done) / CGFloat(toDo)
        percentageLabel.text = "\(Int(percentage * 100))%"
        
        // 动画更新进度条
        UIView.animate(withDuration: 0.3) {
            self.shapeLayer.strokeEnd = percentage
        }
    }
}

步骤2:修改ViewController代码

现在只需要创建这个自定义视图的实例,设置好位置添加到界面即可:

import UIKit

class SecondViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .white // 建议设置背景色,避免和进度条颜色冲突
        
        // 定义所有圆形的位置
        let circlePositions = [
            CGPoint(x: 100, y: 200),
            CGPoint(x: 100, y: 375),
            CGPoint(x: 100, y: 550),
            CGPoint(x: 100, y: 725),
            CGPoint(x: 300, y: 200),
            CGPoint(x: 300, y: 375),
            CGPoint(x: 300, y: 550),
            CGPoint(x: 300, y: 725)
        ]
        
        // 创建并添加每个进度圆形
        for position in circlePositions {
            let circleView = ProgressCircleView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
            circleView.center = position
            view.addSubview(circleView)
        }
    }
}

为什么这个方案有效?

  • 独立性:每个ProgressCircleView都拥有自己的trackLayer、shapeLayer和percentageLabel,彼此完全独立,不会互相覆盖。
  • 内聚性:点击逻辑和进度管理都封装在视图内部,代码更清晰,后续维护(比如修改进度颜色、调整动画时长)也更方便。
  • 灵活性:如果后续需要给圆形添加更多功能(比如长按重置、拖拽移动),直接在自定义视图里扩展即可,不用修改ViewController的代码。

如果你暂时不想用自定义视图,也可以用UIButton的tag或者关联对象把每个按钮对应的shapeLayer和percentageLabel绑定起来,但这种方式代码会更零散,不如自定义视图的方案优雅。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.29 14:47:38