UIBezierPath在部分Frame下失效问题求助
问题排查与解决方案
你猜的没错!问题核心就是固定半径导致图形超出View边界:
- 当View尺寸是375x667时,它的短边是375,一半是187.5,比你设置的固定半径100大,所以环形能完整显示在View内部;
- 但换成120x120或74x74时,短边的一半分别是60和37,都小于100,这时候环形的直径(200)远大于View尺寸,整个图形都跑到View可视区域外面了,自然看不到。
下面是修复后的完整代码,已经适配任意尺寸的View,同时做了细节优化:
import UIKit // 补全你代码中用到的扩展 extension Int { var degreesToRadians: CGFloat { return CGFloat(self) * .pi / 180 } } extension TimeInterval { var time: String { let minutes = Int(self) / 60 % 60 let seconds = Int(self) % 60 return String(format: "%02d:%02d", minutes, seconds) } } class CountdownView: UIView { let timeLeftShapeLayer = CAShapeLayer() let bgShapeLayer = CAShapeLayer() var timeLeft: TimeInterval = 3783 var endTime: Date? var timeLabel = UILabel() var timer = Timer() let strokeIt = CABasicAnimation(keyPath: "strokeEnd") func drawBgShape() { // 核心修改:用View短边的一半减去线宽的一半作为半径,确保环形完全在View内部 let radius = min(frame.width, frame.height) / 2 - bgShapeLayer.lineWidth / 2 bgShapeLayer.path = UIBezierPath(arcCenter: CGPoint(x: bounds.midX, y: bounds.midY), radius: radius, startAngle: -90.degreesToRadians, endAngle: 270.degreesToRadians, clockwise: true).cgPath bgShapeLayer.strokeColor = UIColor.white.cgColor bgShapeLayer.fillColor = UIColor.clear.cgColor bgShapeLayer.lineWidth = 15 layer.addSublayer(bgShapeLayer) } func drawTimeLeftShape() { let radius = min(frame.width, frame.height) / 2 - timeLeftShapeLayer.lineWidth / 2 timeLeftShapeLayer.path = UIBezierPath(arcCenter: CGPoint(x: bounds.midX, y: bounds.midY), radius: radius, startAngle: -90.degreesToRadians, endAngle: 270.degreesToRadians, clockwise: true).cgPath timeLeftShapeLayer.strokeColor = UIColor.red.cgColor timeLeftShapeLayer.fillColor = UIColor.clear.cgColor timeLeftShapeLayer.lineWidth = 15 // 初始化stroke状态,避免动画异常 timeLeftShapeLayer.strokeStart = 0 timeLeftShapeLayer.strokeEnd = 0 layer.addSublayer(timeLeftShapeLayer) } func addTimeLabel() { // 用bounds代替frame计算位置,适配View尺寸变化 timeLabel.frame = CGRect(x: bounds.midX - 50, y: bounds.midY - 25, width: 100, height: 50) timeLabel.textAlignment = .center timeLabel.text = timeLeft.time timeLabel.textColor = .black addSubview(timeLabel) } override func draw(_ rect: CGRect) { super.draw(rect) backgroundColor = UIColor(white: 0.94, alpha: 1.0) drawBgShape() drawTimeLeftShape() addTimeLabel() strokeIt.fromValue = 0 strokeIt.toValue = 1 strokeIt.duration = 60 timeLeftShapeLayer.add(strokeIt, forKey: nil) endTime = Date().addingTimeInterval(timeLeft) timer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(updateTime), userInfo: nil, repeats: true) } @objc func updateTime() { if timeLeft > 0 { timeLeft = endTime?.timeIntervalSinceNow ?? 0 timeLabel.text = timeLeft.time // 同步更新进度条,让进度和剩余时间对应(可选优化) timeLeftShapeLayer.strokeEnd = CGFloat(timeLeft / 3783) } else { timeLabel.text = "00:00" timer.invalidate() } } // 额外优化:View尺寸变化时重新绘制,适配布局调整 override func layoutSubviews() { super.layoutSubviews() bgShapeLayer.removeFromSuperlayer() timeLeftShapeLayer.removeFromSuperlayer() timeLabel.removeFromSuperview() drawBgShape() drawTimeLeftShape() addTimeLabel() } }
关键修改说明:
- 自适应半径:替换固定半径为
min(frame.width, frame.height) / 2 - lineWidth / 2,确保环形始终在View内部,还预留了线宽空间避免线条溢出; - 使用bounds坐标系:
bounds是View自身的内部坐标系,比依赖父视图的frame更可靠,适配View位置变化; - 添加布局更新逻辑:重写
layoutSubviews,在View尺寸变化时重新绘制内容,适配旋转屏幕、父视图布局调整等场景; - 初始化动画状态:给进度层设置初始的
strokeStart和strokeEnd,避免动画启动异常。
这样修改后,不管你的View是多大尺寸,都能正常显示环形进度条啦!
内容的提问来源于stack exchange,提问作者FS.O6
相关产品推荐
相关产品推荐

