SwiftUI自定义Circle+trim进度条:0.98进度显示为100%的问题
问题分析
你遇到的问题根源在于方形线帽(.square)与较粗线宽(15)在小尺寸Frame下的重叠冲突:
- 200x200的Frame中,实际绘制的圆半径为
(200-15)/2=92.5,当completed=0.98时,剩余缺口的周长约为2π×92.5×0.02≈11.6。 - 而
.square线帽的长度等于线宽(15),这个长度大于缺口周长,导致进度条终点的线帽直接覆盖起点位置,视觉上呈现完全填满的效果。
解决方案
基于必须保留200x200尺寸的要求,提供三种可行方案:
方案1:替换为圆角线帽
将stroke的lineCap从.square改为.round,圆角线帽的延伸长度是线宽的一半(7.5),小于缺口周长,不会覆盖缺口:
struct ContentView: View { let remainingTimeStrokeColor: Color = .secondary let completedTimeStrokeColor: Color = .blue @State var completed: Double = 0.98 // 测试值 var body: some View { ZStack { Circle() .stroke(remainingTimeStrokeColor, style: StrokeStyle(lineWidth: 15)) .frame(width: 200, height: 200) Circle() .trim(from: 0, to: completed) .stroke(completedTimeStrokeColor, style: StrokeStyle(lineWidth: 15, lineCap: .round)) // 修改为圆角线帽 .rotationEffect(.degrees(-90)) .frame(width: 200, height: 200) } } }
方案2:动态调整Trim终点(保留方形线帽)
如果必须保留.square线帽,可计算线帽对应的周长比例,当进度接近1时自动减小trim的终点值,确保缺口长度大于线帽长度:
struct ContentView: View { let remainingTimeStrokeColor: Color = .secondary let completedTimeStrokeColor: Color = .blue @State var completed: Double = 0.98 // 测试值 private let lineWidth: CGFloat = 15 private let frameSize: CGFloat = 200 // 计算最大可显示进度,避免线帽覆盖缺口 private var adjustedCompleted: Double { let radius = (frameSize - lineWidth) / 2 let lineCapRatio = Double(lineWidth) / (2 * .pi * radius) return min(completed, 1 - lineCapRatio) } var body: some View { ZStack { Circle() .stroke(remainingTimeStrokeColor, style: StrokeStyle(lineWidth: lineWidth)) .frame(width: frameSize, height: frameSize) Circle() .trim(from: 0, to: adjustedCompleted) // 使用调整后的进度值 .stroke(completedTimeStrokeColor, style: StrokeStyle(lineWidth: lineWidth, lineCap: .square)) .rotationEffect(.degrees(-90)) .frame(width: frameSize, height: frameSize) } } }
该方案中,adjustedCompleted会自动限制最大可显示进度(约0.974),确保缺口不会被线帽覆盖;若completed小于该阈值,则正常显示原进度。
方案3:缩小绘制路径(进阶)
通过inset(by:)向内收缩Circle的绘制路径,在保持Frame尺寸不变的前提下,增加圆的周长,让缺口长度大于线帽:
struct ContentView: View { let remainingTimeStrokeColor: Color = .secondary let completedTimeStrokeColor: Color = .blue @State var completed: Double = 0.98 // 测试值 private let lineWidth: CGFloat = 15 var body: some View { ZStack { Circle() .inset(by: lineWidth/2) // 向内收缩线宽的一半 .stroke(remainingTimeStrokeColor, style: StrokeStyle(lineWidth: lineWidth)) .frame(width: 200, height: 200) Circle() .inset(by: lineWidth/2) .trim(from: 0, to: completed) .stroke(completedTimeStrokeColor, style: StrokeStyle(lineWidth: lineWidth, lineCap: .square)) .rotationEffect(.degrees(-90)) .frame(width: 200, height: 200) } } }
内容的提问来源于stack exchange,提问作者Mahi Al Jawad
相关产品推荐
相关产品推荐

