如何使用UIBezierPath绘制带锐利切角的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
相关产品推荐
相关产品推荐

