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

UIBezierPath圆角设置异常:超阈值变圆形及进度起点调整需求

问题解决指南

一、圆角突变问题的原因与修复

原因

UIBezierPath的roundedRect(_:cornerRadius:)有内置限制:当圆角半径超过矩形最小边长的一半时,系统会自动将其调整为最大可能的圆角(近似圆形)。你遇到的0.328突变,是因为此时计算出的cornerRadius刚好触及这个临界值。此外代码还有几个加重问题的点:

  • 混用layer.cornerRadius和CAShapeLayer边框,导致渲染逻辑冲突
  • 在draw(_:)中重复添加子图层,造成性能浪费和布局混乱
  • 使用frame而非bounds计算尺寸,AutoLayout场景下易获取错误值

修复步骤

  1. 限制圆角半径最大值:计算允许的最大圆角半径为min(bounds.width, bounds.height)/2,确保百分比设置不会触发临界值
  2. 移除layer.cornerRadius设置:完全用CAShapeLayer实现圆角边框和进度条,避免裁剪冲突
  3. 图层初始化移至commonInit:避免在draw中重复添加子图层,改用layoutSubviews更新路径(布局变化时自动触发)
  4. 统一用bounds计算尺寸:bounds是视图自身坐标系尺寸,比frame更可靠

二、进度从顶部中心开始的实现

默认圆角矩形路径起点为左上角,顺时针绘制。要让进度从顶部中心开始,有两种可行方案:

方案1:自定义构建路径(精准控制)

手动绘制从顶部中心出发的圆角矩形路径,确保strokeStart=0时起点刚好在顶部中心。

方案2:路径旋转变换(快速实现)

对CAShapeLayer应用旋转变换,将默认路径的左上角起点转到顶部中心,适配进度方向。

三、修改后的完整代码

class MyView: UIView {
    var fraction: CGFloat = 0.5 {
        didSet {
            progressBorder.strokeEnd = fraction
        }
    }
    
    var strokeWidth: CGFloat = 4.0 {
        didSet {
            border.lineWidth = strokeWidth
            progressBorder.lineWidth = strokeWidth
            updatePaths()
        }
    }
    
    @IBInspectable var cornerPercentage: CGFloat = 0.327 {
        didSet {
            updatePaths()
        }
    }
    
    private let border = CAShapeLayer()
    private let progressBorder = CAShapeLayer()
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        commonInit()
    }
    
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        commonInit()
    }
    
    private func commonInit() {
        backgroundColor = .white
        
        // 配置基础边框图层
        border.strokeColor = AppColor.fieldsBackground().cgColor
        border.fillColor = nil
        border.strokeStart = 0
        border.strokeEnd = 1
        layer.addSublayer(border)
        
        // 配置进度边框图层
        progressBorder.strokeColor = AppColor.buttonsPrimary().cgColor
        progressBorder.fillColor = nil
        progressBorder.lineCap = .round
        progressBorder.strokeStart = 0
        progressBorder.strokeEnd = fraction
        layer.addSublayer(progressBorder)
    }
    
    override func layoutSubviews() {
        super.layoutSubviews()
        updatePaths()
    }
    
    private func updatePaths() {
        let minSide = min(bounds.width, bounds.height)
        let maxAllowedCornerRadius = minSide / 2
        // 确保圆角半径不超过最大值
        let cornerRad = min(bounds.height * cornerPercentage, maxAllowedCornerRadius)
        
        // 自定义路径:从顶部中心开始绘制圆角矩形
        let path = UIBezierPath()
        let topCenter = CGPoint(x: bounds.midX, y: bounds.minY)
        
        path.move(to: topCenter)
        
        // 右上角直线+圆弧
        let topRightStart = CGPoint(x: bounds.maxX - cornerRad, y: bounds.minY)
        path.addLine(to: topRightStart)
        let topRightCenter = CGPoint(x: bounds.maxX - cornerRad, y: bounds.minY + cornerRad)
        path.addArc(withCenter: topRightCenter, radius: cornerRad, startAngle: 3 * .pi / 2, endAngle: 0, clockwise: true)
        
        // 右下角直线+圆弧
        let bottomRightStart = CGPoint(x: bounds.maxX, y: bounds.maxY - cornerRad)
        path.addLine(to: bottomRightStart)
        let bottomRightCenter = CGPoint(x: bounds.maxX - cornerRad, y: bounds.maxY - cornerRad)
        path.addArc(withCenter: bottomRightCenter, radius: cornerRad, startAngle: 0, endAngle: .pi / 2, clockwise: true)
        
        // 左下角直线+圆弧
        let bottomLeftStart = CGPoint(x: bounds.minX + cornerRad, y: bounds.maxY)
        path.addLine(to: bottomLeftStart)
        let bottomLeftCenter = CGPoint(x: bounds.minX + cornerRad, y: bounds.maxY - cornerRad)
        path.addArc(withCenter: bottomLeftCenter, radius: cornerRad, startAngle: .pi / 2, endAngle: .pi, clockwise: true)
        
        // 左上角直线+圆弧+回到顶部中心
        let topLeftStart = CGPoint(x: bounds.minX, y: bounds.minY + cornerRad)
        path.addLine(to: topLeftStart)
        let topLeftCenter = CGPoint(x: bounds.minX + cornerRad, y: bounds.minY + cornerRad)
        path.addArc(withCenter: topLeftCenter, radius: cornerRad, startAngle: .pi, endAngle: 3 * .pi / 2, clockwise: true)
        
        path.addLine(to: topCenter)
        
        // 给图层设置路径
        border.path = path.cgPath
        progressBorder.path = path.cgPath
        
        // 更新线宽
        border.lineWidth = strokeWidth
        progressBorder.lineWidth = strokeWidth
    }
}

代码说明

  • 图层初始化移至commonInit,避免重复添加子图层
  • 用layoutSubviews触发路径更新,适配布局变化
  • 增加最大圆角半径限制,防止突变问题
  • 自定义路径从顶部中心开始,完美匹配进度起点需求
  • 移除冗余的layer.cornerRadius设置,消除渲染冲突

如果觉得自定义路径繁琐,可改用旋转变换方案,替换updatePaths中的路径构建部分:

private func updatePaths() {
    let minSide = min(bounds.width, bounds.height)
    let maxAllowedCornerRadius = minSide / 2
    let cornerRad = min(bounds.height * cornerPercentage, maxAllowedCornerRadius)
    
    // 使用默认圆角矩形路径
    let path = UIBezierPath(roundedRect: bounds, cornerRadius: cornerRad)
    
    // 逆时针旋转90度,将起点从左上角转到顶部中心
    let rotationTransform = CGAffineTransform(rotationAngle: -(.pi / 2))
    path.apply(rotationTransform)
    
    // 平移到视图中心修正偏移
    let centerOffset = CGPoint(x: (bounds.width - bounds.height)/2, y: (bounds.height - bounds.width)/2)
    let translationTransform = CGAffineTransform(translationX: centerOffset.x, y: centerOffset.y)
    path.apply(translationTransform)
    
    border.path = path.cgPath
    progressBorder.path = path.cgPath
    
    border.lineWidth = strokeWidth
    progressBorder.lineWidth = strokeWidth
}

内容的提问来源于stack exchange,提问作者Danyal Naveed

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.10 14:55:19