Swift: 使 UIView 的轮廓像草图一样

Swift: Make the outline of a UIView sketch-like

我想让 UIView 的轮廓看起来像有人画的那样“波浪形”。

我有这个来自 PowerPoint 的例子,它允许这样做(应该适用于任何尺寸和角半径):

目前我拥有的是:

myView.layer.borderWidth = 10
myView.layer.borderColor = UIColor.blue.cgColor
myView.layer.cornerRadius = 5 // Optional

感谢

使用此扩展程序解决问题

导入基金会 导入 UIKit

extension UIView {
    func dropShadow(scale: Bool = true) {
        layer.masksToBounds = false
        layer.shadowColor = UIColor.black.cgColor
        layer.shadowOpacity = 0.2
        layer.shadowOffset = .zero
        layer.shadowRadius = 5
        layer.shouldRasterize = true
        layer.rasterizationScale = scale ? UIScreen.main.scale : 1
    }
   
    @IBInspectable
    var cornerRadius: CGFloat {
        get {
            return layer.cornerRadius
        }
        set {
            layer.cornerRadius = newValue
            layer.masksToBounds = newValue > 0
        }
    }
    
    @IBInspectable
    var borderWidth: CGFloat {
        get {
            return layer.borderWidth
        }
        set {
            layer.borderWidth = newValue
        }
    }
    
    @IBInspectable
    var borderColor: UIColor? {
        get {
            let color = UIColor.init(cgColor: layer.borderColor!) //UIColor.init(CGColor: layer.borderColor!)
            return color
        }
        set {
            layer.borderColor = newValue?.cgColor
        }
    }
    
    @IBInspectable
    var shadowRadius: CGFloat {
        get {
            return layer.shadowRadius
        }
        set {
            layer.shadowRadius = newValue
        }
    }
    
    @IBInspectable
    var shadowOpacity: Float {
        get {
            return layer.shadowOpacity
        }
        set {
            layer.shadowOpacity = newValue
        }
    }
    
    @IBInspectable
    var shadowOffset: CGSize {
        get {
            return layer.shadowOffset
        }
        set {
            layer.shadowOffset = newValue
        }
    }
    
    @IBInspectable
    var shadowColor: UIColor? {
        get {
            if let color = layer.shadowColor {
                return UIColor(cgColor: color)
            }
            return nil
        }
        set {
            if let color = newValue {
                layer.shadowColor = color.cgColor
            } else {
                layer.shadowColor = nil
            }
        }
    }
}

看起来像这样

您可以使用 UIBezierPath 结合四边形曲线、直线、圆弧等来创建“波浪”线

我们将从一条简单的线开始,它是视图宽度的四分之一:

我们的路径包括:

  • 移动到 0,0
  • 将行添加到 80,0

如果我们将其更改为四曲线:

现在我们正在做:

  • 移动到 0,0
  • 使用控制点 40,40
  • 将四边形曲线添加到 80,0

如果我们添加另一个方向相反的四边形曲线:

现在我们正在做:

  • 移动到 0,0
  • 使用控制点 40,40
  • 将四边形曲线添加到 80,0
  • 使用控制点 120,-40
  • 将四边形曲线添加到 160,0

我们可以扩展视图的宽度:

当然,这看起来不像您的“草图”目标,所以让我们将控制点偏移量从 40 更改为 2:

现在看起来更像是手绘的“素描”线。

它太统一了,但是,它部分超出了视图的范围,所以让我们将它插入 8 磅,而不是四个 25% 的片段,我们将使用(例如)其中的五个片段宽度:

0.15, 0.2, 0.2, 0.27, 0.18

如果我们采用相同的方法从右侧向下,返回底部,然后从左侧向上,我们可以得到:

下面是生成该视图的一些示例代码:

class SketchBorderView: UIView {
    
    let borderLayer: CAShapeLayer = CAShapeLayer()
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        commonInit()
    }
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        commonInit()
    }
    func commonInit() -> Void {
        borderLayer.fillColor = UIColor.clear.cgColor
        borderLayer.strokeColor = UIColor.blue.cgColor
        layer.addSublayer(borderLayer)
        backgroundColor = .yellow
    }
    override func layoutSubviews() {
        
        let incrementVals: [CGFloat] = [
            0.15, 0.2, 0.2, 0.27, 0.18,
        ]
        let lineOffsets: [[CGFloat]] = [
            [ 1.0, -2.0],
            [-1.0,  2.0],
            [-1.0, -2.0],
            [ 1.0,  2.0],
            [ 0.0, -2.0],
        ]

        let pth: UIBezierPath = UIBezierPath()
        
        // inset bounds by 8-pts so we can draw the "wavy border"
        //  inside our bounds
        let r: CGRect = bounds.insetBy(dx: 8.0, dy: 8.0)
        
        var ptDest: CGPoint = .zero
        var ptControl: CGPoint = .zero

        // start at top-left
        ptDest = r.origin
        pth.move(to: ptDest)

        // we're at top-left
        for i in 0..<incrementVals.count {

            ptDest.x += r.width * incrementVals[i]
            ptDest.y = r.minY + lineOffsets[i][0]

            ptControl.x = pth.currentPoint.x + ((ptDest.x - pth.currentPoint.x) * 0.5)
            ptControl.y = r.minY + lineOffsets[i][1]

            pth.addQuadCurve(to: ptDest, controlPoint: ptControl)

        }
        
        // now we're at top-right
        for i in 0..<incrementVals.count {
            
            ptDest.y += r.height * incrementVals[i]
            ptDest.x = r.maxX + lineOffsets[i][0]
            
            ptControl.y = pth.currentPoint.y + ((ptDest.y - pth.currentPoint.y) * 0.5)
            ptControl.x = r.maxX + lineOffsets[i][1]
            
            pth.addQuadCurve(to: ptDest, controlPoint: ptControl)
            
        }
        
        // now we're at bottom-right
        for i in 0..<incrementVals.count {
            
            ptDest.x -= r.width * incrementVals[i]
            ptDest.y = r.maxY + lineOffsets[i][0]
            
            ptControl.x = pth.currentPoint.x - ((pth.currentPoint.x - ptDest.x) * 0.5)
            ptControl.y = r.maxY + lineOffsets[i][1]
            
            pth.addQuadCurve(to: ptDest, controlPoint: ptControl)
            
        }
        
        // now we're at bottom-left
        for i in 0..<incrementVals.count {
            
            ptDest.y -= r.height * incrementVals[i]
            ptDest.x = r.minX + lineOffsets[i][0]
            
            ptControl.y = pth.currentPoint.y - ((pth.currentPoint.y - ptDest.y) * 0.5)
            ptControl.x = r.minX + lineOffsets[i][1]
            
            pth.addQuadCurve(to: ptDest, controlPoint: ptControl)
            
        }

        borderLayer.path = pth.cgPath
    }
    
}

和一个示例控制器:

class SketchTestVC: UIViewController {
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let v = SketchBorderView()
        v.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(v)
        let g = view.safeAreaLayoutGuide
        NSLayoutConstraint.activate([
            v.centerXAnchor.constraint(equalTo: g.centerXAnchor),
            v.centerYAnchor.constraint(equalTo: g.centerYAnchor),
            v.widthAnchor.constraint(equalToConstant: 320.0),
            v.heightAnchor.constraint(equalTo: v.widthAnchor),
        ])
    }
    
}

虽然使用该代码,我们仍然有太多的统一性,因此在实际使用中我们希望随机化段数、段的宽度和控制点偏移。

当然,要获得“圆角矩形”,您需要在角处添加圆弧。

我希望这能让你顺利上路。