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

如何使用UIBezierPath绘制带锐利切角的UIView/UIButton

Swift实现可复用锐利斜切角UIView/UIButton

系统提供的UIBezierPath(roundedRect:byRoundingCorners:cornerRadii:)方法仅支持绘制圆弧过渡的圆角,无法生成锐利直线斜切的倒角效果,需要手动逐点定义多边形路径实现。

实现思路

  • 封装独立可继承的基类,支持自定义切角位置、切角缩进尺寸
  • 重写layoutSubviews方法在视图布局完成后更新遮罩路径,避免约束更新、横竖屏切换时出现遮罩错位
  • 路径绘制逻辑添加尺寸容错,避免切角大小超过视图尺寸时出现绘制异常
  • 兼容Storyboard实时预览,可直接在属性面板调整参数

完整封装代码

import UIKit

// 斜切角位置配置,用法与系统UIRectCorner一致
struct ChamferCorner: OptionSet {
    let rawValue: Int
    static let topLeft = ChamferCorner(rawValue: 1 << 0)
    static let topRight = ChamferCorner(rawValue: 1 << 1)
    static let bottomLeft = ChamferCorner(rawValue: 1 << 2)
    static let bottomRight = ChamferCorner(rawValue: 1 << 3)
    static let allCorners: ChamferCorner = [.topLeft, .topRight, .bottomLeft, .bottomRight]
}

// 斜切角基类视图,可直接继承复用
@IBDesignable
class ChamferCornerView: UIView {
    /// 斜切角的直角边缩进长度
    @IBInspectable var cornerInset: CGFloat = 12 {
        didSet { setNeedsLayout() }
    }
    /// 需要应用斜切效果的角
    var chamferCorners: ChamferCorner = .allCorners {
        didSet { setNeedsLayout() }
    }
    
    override func layoutSubviews() {
        super.layoutSubviews()
        applyChamferMask()
    }
    
    fileprivate func applyChamferMask() {
        let path = UIBezierPath()
        let viewWidth = bounds.width
        let viewHeight = bounds.height
        // 容错:切角最大不超过视图宽高的一半,避免路径错乱
        let validInset = min(cornerInset, min(viewWidth, viewHeight) / 2)
        
        // 路径起点:左上角位置
        let topLeftStart = chamferCorners.contains(.topLeft) 
        ? CGPoint(x: validInset, y: 0) 
        : CGPoint(x: 0, y: 0)
        path.move(to: topLeftStart)
        
        // 绘制上边缘+右上角切角
        if chamferCorners.contains(.topRight) {
            path.addLine(to: CGPoint(x: viewWidth - validInset, y: 0))
            path.addLine(to: CGPoint(x: viewWidth, y: validInset))
        } else {
            path.addLine(to: CGPoint(x: viewWidth, y: 0))
        }
        
        // 绘制右边缘+右下角切角
        if chamferCorners.contains(.bottomRight) {
            path.addLine(to: CGPoint(x: viewWidth, y: viewHeight - validInset))
            path.addLine(to: CGPoint(x: viewWidth - validInset, y: viewHeight))
        } else {
            path.addLine(to: CGPoint(x: viewWidth, y: viewHeight))
        }
        
        // 绘制下边缘+左下角切角
        if chamferCorners.contains(.bottomLeft) {
            path.addLine(to: CGPoint(x: validInset, y: viewHeight))
            path.addLine(to: CGPoint(x: 0, y: viewHeight - validInset))
        } else {
            path.addLine(to: CGPoint(x: 0, y: viewHeight))
        }
        
        // 绘制左边缘+闭合路径
        if chamferCorners.contains(.topLeft) {
            path.addLine(to: CGPoint(x: 0, y: validInset))
        } else {
            path.addLine(to: CGPoint(x: 0, y: 0))
        }
        path.close()
        
        let maskLayer = CAShapeLayer()
        maskLayer.path = path.cgPath
        layer.mask = maskLayer
    }
}

// 斜切角按钮,直接继承即可复用所有切角能力
@IBDesignable
class ChamferCornerButton: UIButton {
    @IBInspectable var cornerInset: CGFloat = 12 {
        didSet { setNeedsLayout() }
    }
    var chamferCorners: ChamferCorner = .allCorners {
        didSet { setNeedsLayout() }
    }
    
    override func layoutSubviews() {
        super.layoutSubviews()
        // 复用基类的切角绘制逻辑
        let tempView = ChamferCornerView(frame: bounds)
        tempView.cornerInset = cornerInset
        tempView.chamferCorners = chamferCorners
        tempView.applyChamferMask()
        layer.mask = tempView.layer.mask
    }
}

使用方式

  • 直接继承使用:将需要斜切效果的UIView改为继承ChamferCornerView,UIButton改为继承ChamferCornerButton即可
  • 自定义参数示例:
// 初始化斜切按钮,仅左上角、右下角应用8px斜切
let submitBtn = ChamferCornerButton(type: .custom)
submitBtn.cornerInset = 8
submitBtn.chamferCorners = [.topLeft, .bottomRight]
submitBtn.backgroundColor = .systemBlue
submitBtn.setTitle("提交", for: .normal)
  • 扩展斜角边框:如果需要添加和切角贴合的描边,只需新建一个CAShapeLayer,使用上述绘制的同一份path作为路径,设置strokeColor、lineWidth后添加到视图layer即可。

注意:不要在视图初始化方法(如init(frame:)、init(coder:))中绘制切角路径,此时视图尚未完成布局,获取到的bounds尺寸不准确,会导致遮罩位置错乱。

内容的提问来源于stack exchange,提问作者Kumar Lav

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.27 21:09:17