UIProgressView调整高度并设置圆角后进度动画异常问题求助
我之前也踩过UIProgressView自定义样式的坑,来给你拆解一下问题根源和可行的解决办法:
问题1:进度跳停的原因及解决方法
为什么会出现跳停?
UIProgressView内部是由轨道图层和进度图层两个独立部分组成的。当你直接通过Interface Builder给它加高度约束强制修改高度时,系统的内部布局逻辑并没有适配这个自定义高度——它依然按照默认的intrinsicContentSize来计算进度值对应的绘制位置,导致进度的数值映射和视觉显示区域错位,就出现了你看到的“直接跳至1/3位置”的异常。
简单说:你改了外层View的高度,但内部进度的绘制区域没跟着同步调整,进度值的计算逻辑乱了。
解决方法
方案1:用可拉伸图片适配自定义高度(推荐)
不要直接改高度约束,而是通过设置可拉伸的轨道图和进度图来实现自定义高度,同时配合圆角设置:
// 准备1px高的纯色图片(可根据需求调整样式),设置为可垂直拉伸 let trackImage = UIImage(named: "track_bg")?.resizableImage(withCapInsets: .zero, resizingMode: .stretch) let progressImage = UIImage(named: "progress_fill")?.resizableImage(withCapInsets: .zero, resizingMode: .stretch) // 给进度条设置图片 audioProgress.trackImage = trackImage audioProgress.progressImage = progressImage // 最后设置圆角和裁剪 audioProgress.layer.cornerRadius = audioProgress.frame.height / 3 audioProgress.clipsToBounds = true
这种方式下,系统会根据你设置的高度约束正确拉伸图片,内部进度的计算逻辑也会基于正确的绘制区域,不会再出现跳停问题。
方案2:自定义UIProgressView子类,手动调整内部图层
如果不想用图片,可以自定义子类,在layoutSubviews里手动修正内部图层的frame:
class CustomProgressView: UIProgressView { override func layoutSubviews() { super.layoutSubviews() // 调整轨道图层的frame和圆角 subviews.first?.frame = bounds subviews.first?.layer.cornerRadius = bounds.height / 3 subviews.first?.clipsToBounds = true // 调整进度图层的frame和圆角 subviews.last?.frame = bounds subviews.last?.layer.cornerRadius = bounds.height / 3 subviews.last?.clipsToBounds = true } }
使用这个子类后,你可以安全地给它加高度约束,进度显示会完全正常。
问题2:CGAffineTransform缩放时如何实现正常圆角
为什么圆角会变形?
当你先设置cornerRadius再做缩放时,圆角半径是基于原始未缩放的frame计算的,缩放后圆角的视觉大小会跟着被拉伸(比如y轴缩放3倍,圆角半径也会被拉长3倍),所以看起来边角变形。反过来先缩放再设置圆角也不行——因为UIProgressView的内部图层已经被缩放,系统的绘制逻辑依然会出现偏差。
解决方法
方案1:缩放后重新计算圆角半径
在设置transform之后,根据缩放后的实际高度重新设置cornerRadius:
let transformFactor: CGFloat = 3.0 // 你的缩放倍数 audioProgress.transform = CGAffineTransform(scaleX: 1.0, y: transformFactor) // 基于缩放后的实际高度重新计算圆角 let scaledHeight = audioProgress.bounds.height * transformFactor audioProgress.layer.cornerRadius = scaledHeight / 3 audioProgress.clipsToBounds = true
这个方法能缓解变形,但因为整个视图layer被缩放,可能还是会有细微的视觉偏差。
方案2:用CAShapeLayer自定义进度条(最可靠)
直接放弃系统的UIProgressView,用两个CAShapeLayer实现完全可控的进度条,彻底避免系统布局的坑:
class CustomProgressBar: UIView { private let trackLayer = CAShapeLayer() private let progressLayer = CAShapeLayer() var progress: Float = 0.0 { didSet { updateProgress() } } override init(frame: CGRect) { super.init(frame: frame) setupLayers() } required init?(coder: NSCoder) { super.init(coder: coder) setupLayers() } override func layoutSubviews() { super.layoutSubviews() updateLayerFrames() } private func setupLayers() { // 配置轨道图层 trackLayer.fillColor = UIColor.lightGray.cgColor layer.addSublayer(trackLayer) // 配置进度图层 progressLayer.fillColor = UIColor.blue.cgColor layer.addSublayer(progressLayer) } private func updateLayerFrames() { trackLayer.frame = bounds trackLayer.cornerRadius = bounds.height / 3 // 更新进度图层的宽度 let progressWidth = bounds.width * CGFloat(progress) progressLayer.frame = CGRect(x: 0, y: 0, width: progressWidth, height: bounds.height) progressLayer.cornerRadius = bounds.height / 3 // 进度未满时隐藏右侧圆角(可选优化) progressLayer.maskedCorners = progress < 1.0 ? [.layerMinXMinYCorner, .layerMinXMaxYCorner] : [.layerMinXMinYCorner, .layerMinXMaxYCorner, .layerMaxXMinYCorner, .layerMaxXMaxYCorner] } private func updateProgress() { updateLayerFrames() } }
使用这个自定义View,你可以直接设置高度约束,圆角完全符合预期,进度更新也会无比平滑。
内容的提问来源于stack exchange,提问作者rumoldfashioned

