一般来说,我们给 UIView 添加边框时都会添加内边框。然而有时也有添加外边框的需求,如裁剪框,给裁剪的view 添加外边框才能精确裁剪。
内外边框说明.png
可以看到使用外边框时更符合人的思维习惯:截取边框内部。
思路是为 view 添加一个 layer,将 layer 路径设在 view 外,这个 layer 就是外边框。
注意:
- layer.masksToBounds 必须为 false,否则画的外边框会被截掉
- 如果 view 的宽高会改变,那么 这个 layer 必须在 view 大小改变时更新路径。
override var frame: CGRect {
didSet {
refreshSubLayers() // 更新路径
}
}
- 将外边框的 zPosition 设高防止遮挡
下面是完整代码
class BorderCutterView: UIView {
var cutterBorderWidth: CGFloat = 1
var cutterBorderColor: UIColor = .white
var borderLayer = CALayer()
override var frame: CGRect {
didSet {
refreshSubLayers()
}
}
func refreshSubLayers() {
CATransaction.begin()
CATransaction.setDisableActions(true)
let borderWidth = layer.borderWidth
borderLayer.frame = CGRect(x: -borderWidth, y: -borderWidth, width: frame.size.width + borderWidth * 2, height: frame.size.height + borderWidth * 2)
CATransaction.commit()
layer.displayIfNeeded()
}
func addOutSideBorder(color: UIColor, borderWidth: CGFloat) {
if layer.masksToBounds == true {
fatalError("masksToBounds 必须为 false,否则外边框会被截掉")
}
borderLayer = CALayer()
borderLayer.borderColor = color.cgColor
borderLayer.borderWidth = borderWidth
layer.addSublayer(borderLayer)
}
// MARK: - Lifecycle
private func commonLoad() {
layer.masksToBounds = false
layer.contentsGravity = .resizeAspect
layer.zPosition = 100
addOutSideBorder(color: cutterBorderColor, borderWidth: cutterBorderWidth)
}
override init(frame: CGRect) {
super.init(frame: frame)
commonLoad()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
沿着这个思路,增加 subLayer,就可以直接画出带四个角的裁剪框
image-20220222210126075.png
class BorderCutterView: UIView {
var cutterBorderWidth: CGFloat = 1
var cutterBorderColor: UIColor = .white
var cornerLineWidth: CGFloat = 2.5
var cornerLineLength: CGFloat = 20
var borderLayer = CALayer()
var cornerLayer = CAShapeLayer()
override var frame: CGRect {
didSet {
refreshSubLayers()
}
}
func refreshSubLayers() {
CATransaction.begin()
CATransaction.setDisableActions(true)
let borderWidth = layer.borderWidth
borderLayer.frame = CGRect(x: -borderWidth, y: -borderWidth, width: frame.size.width + borderWidth * 2, height: frame.size.height + borderWidth * 2)
let cgPath = cornerPath(with: cornerLineWidth, length: cornerLineLength)
cornerLayer.path = cgPath
CATransaction.commit()
layer.displayIfNeeded()
}
// MARK: - Lifecycle
private func commonLoad() {
layer.masksToBounds = false
layer.contentsGravity = .resizeAspect
layer.zPosition = 100
addOutSideBorder(color: cutterBorderColor, borderWidth: cutterBorderWidth)
addFourCornerBorder(color: cutterBorderColor, lineWidth: cornerLineWidth)
}
override init(frame: CGRect) {
super.init(frame: frame)
commonLoad()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
/// 外边框
func addOutSideBorder(color: UIColor, borderWidth: CGFloat) {
if layer.masksToBounds == true {
fatalError("masksToBounds 必须为 false,否则外边框会被截掉")
}
borderLayer = CALayer()
borderLayer.borderColor = color.cgColor
borderLayer.borderWidth = borderWidth
layer.addSublayer(borderLayer)
}
/// 裁剪框的四个角
/// - Parameters:
/// - lineWidth: 线宽
/// - length: 画线的长度
func addFourCornerBorder(color: UIColor, lineWidth: CGFloat) {
if layer.masksToBounds == true {
fatalError("masksToBounds 必须为 false,否则外边框会被截掉")
}
cornerLayer.lineWidth = lineWidth
cornerLayer.strokeColor = color.cgColor
cornerLayer.fillColor = nil
layer.addSublayer(cornerLayer)
}
func cornerPath(with lineWidth: CGFloat, length: CGFloat) -> CGPath {
let path = UIBezierPath()
let x: CGFloat = -lineWidth / 2
let y: CGFloat = -lineWidth / 2
let maxX = frame.size.width + lineWidth / 2
let maxY = frame.size.height + lineWidth / 2
let rightBeginX = maxX - length
let bottomBeginY = maxY - length
// 左上
path.move(to: CGPoint(x: x, y: y))
path.addLine(to: CGPoint(x: x + length , y: y))
path.move(to: CGPoint(x: x, y: -lineWidth))
path.addLine(to: CGPoint(x: x, y: -lineWidth / 2 + length))
// 左下
path.move(to: CGPoint(x: x, y: maxY))
path.addLine(to: CGPoint(x: x + length , y: maxY))
path.move(to: CGPoint(x: x, y: bottomBeginY))
path.addLine(to: CGPoint(x: x, y: lineWidth / 2 + maxY))
// 右上
path.move(to: CGPoint(x: rightBeginX, y: y))
path.addLine(to: CGPoint(x: rightBeginX + length , y: y))
path.move(to: CGPoint(x: maxX, y: -lineWidth))
path.addLine(to: CGPoint(x: maxX, y: -lineWidth / 2 + length))
// 右下
path.move(to: CGPoint(x: rightBeginX, y: maxY))
path.addLine(to: CGPoint(x: rightBeginX + length , y: maxY))
path.move(to: CGPoint(x: maxX, y: bottomBeginY))
path.addLine(to: CGPoint(x: maxX, y: lineWidth / 2 + maxY))
return path.cgPath
}
}