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

如何在Swift中创建铅笔手绘风格的视图边框?

嘿,要实现铅笔手绘风格的视图边框,我有几个实用的Swift方案分享给你,亲测能做出那种随性的手绘感~

方法一:用CAShapeLayer生成随机抖动边框

这种方式通过创建带随机偏移的路径,模拟手绘时的手部抖动,再用CAShapeLayer把路径作为边框添加到视图上,灵活度很高。

实现代码

import UIKit

extension UIView {
    func addPencilBorder(lineWidth: CGFloat = 2.0, color: UIColor = .black) {
        let shapeLayer = CAShapeLayer()
        shapeLayer.lineWidth = lineWidth
        shapeLayer.strokeColor = color.cgColor
        shapeLayer.fillColor = UIColor.clear.cgColor
        shapeLayer.lineCap = .round  // 模拟铅笔笔触的圆润感
        shapeLayer.lineJoin = .round
        
        // 生成带抖动效果的手绘路径
        let path = UIBezierPath()
        let drawBounds = self.bounds.insetBy(dx: lineWidth/2, dy: lineWidth/2)
        let cornerRadius = self.layer.cornerRadius
        
        // 上边:从左到右添加随机偏移的点
        var prevPoint = CGPoint(x: drawBounds.minX, y: drawBounds.minY + cornerRadius)
        path.move(to: prevPoint)
        for x in stride(from: drawBounds.minX, to: drawBounds.maxX, by: 2.0) {
            let randomY = drawBounds.minY + cornerRadius + CGFloat.random(in: -1.0...1.0)
            path.addLine(to: CGPoint(x: x, y: randomY))
            prevPoint = CGPoint(x: x, y: randomY)
        }
        path.addLine(to: CGPoint(x: drawBounds.maxX, y: drawBounds.minY + cornerRadius))
        
        // 右上角圆角
        path.addArc(withCenter: CGPoint(x: drawBounds.maxX - cornerRadius, y: drawBounds.minY + cornerRadius),
                    radius: cornerRadius,
                    startAngle: .pi * 1.5,
                    endAngle: 0,
                    clockwise: true)
        
        // 右边:从上到下添加随机偏移的点
        prevPoint = CGPoint(x: drawBounds.maxX - cornerRadius, y: drawBounds.minY)
        path.move(to: prevPoint)
        for y in stride(from: drawBounds.minY, to: drawBounds.maxY, by: 2.0) {
            let randomX = drawBounds.maxX - cornerRadius + CGFloat.random(in: -1.0...1.0)
            path.addLine(to: CGPoint(x: randomX, y: y))
            prevPoint = CGPoint(x: randomX, y: y)
        }
        path.addLine(to: CGPoint(x: drawBounds.maxX - cornerRadius, y: drawBounds.maxY))
        
        // 右下角圆角
        path.addArc(withCenter: CGPoint(x: drawBounds.maxX - cornerRadius, y: drawBounds.maxY - cornerRadius),
                    radius: cornerRadius,
                    startAngle: 0,
                    endAngle: .pi * 0.5,
                    clockwise: true)
        
        // 下边:从右到左添加随机偏移的点
        prevPoint = CGPoint(x: drawBounds.maxX, y: drawBounds.maxY - cornerRadius)
        path.move(to: prevPoint)
        for x in stride(from: drawBounds.maxX, to: drawBounds.minX, by: -2.0) {
            let randomY = drawBounds.maxY - cornerRadius + CGFloat.random(in: -1.0...1.0)
            path.addLine(to: CGPoint(x: x, y: randomY))
            prevPoint = CGPoint(x: x, y: randomY)
        }
        path.addLine(to: CGPoint(x: drawBounds.minX, y: drawBounds.maxY - cornerRadius))
        
        // 左下角圆角
        path.addArc(withCenter: CGPoint(x: drawBounds.minX + cornerRadius, y: drawBounds.maxY - cornerRadius),
                    radius: cornerRadius,
                    startAngle: .pi * 0.5,
                    endAngle: .pi,
                    clockwise: true)
        
        // 左边:从下到上添加随机偏移的点
        prevPoint = CGPoint(x: drawBounds.minX + cornerRadius, y: drawBounds.maxY)
        path.move(to: prevPoint)
        for y in stride(from: drawBounds.maxY, to: drawBounds.minY, by: -2.0) {
            let randomX = drawBounds.minX + cornerRadius + CGFloat.random(in: -1.0...1.0)
            path.addLine(to: CGPoint(x: randomX, y: y))
            prevPoint = CGPoint(x: randomX, y: y)
        }
        
        // 左上角圆角闭合路径
        path.addArc(withCenter: CGPoint(x: drawBounds.minX + cornerRadius, y: drawBounds.minY + cornerRadius),
                    radius: cornerRadius,
                    startAngle: .pi,
                    endAngle: .pi * 1.5,
                    clockwise: true)
        
        shapeLayer.path = path.cgPath
        self.layer.addSublayer(shapeLayer)
    }
}

使用方式

直接给目标视图调用方法即可:

// 假设你有一个名为myView的视图
myView.layer.cornerRadius = 12  // 可选:如果需要圆角边框
myView.addPencilBorder(lineWidth: 2.5, color: .darkGray)

方法二:自定义UIView重写draw方法

如果你的视图需要动态调整(比如随布局变化),可以自定义一个UIView子类,在draw(_:)方法里直接绘制手绘边框,适配性更好。

实现代码

class PencilBorderView: UIView {
    // 可配置的边框属性
    var lineWidth: CGFloat = 2.0 {
        didSet { setNeedsDisplay() }
    }
    var borderColor: UIColor = .black {
        didSet { setNeedsDisplay() }
    }
    
    override func draw(_ rect: CGRect) {
        super.draw(rect)
        guard let context = UIGraphicsGetCurrentContext() else { return }
        
        context.setStrokeColor(borderColor.cgColor)
        context.setLineWidth(lineWidth)
        context.setLineCap(.round)
        context.setLineJoin(.round)
        
        let drawRect = rect.insetBy(dx: lineWidth/2, dy: lineWidth/2)
        let cornerRadius = self.layer.cornerRadius
        
        // 绘制上边
        var startPoint = CGPoint(x: drawRect.minX, y: drawRect.minY + cornerRadius)
        context.move(to: startPoint)
        for x in stride(from: drawRect.minX, to: drawRect.maxX, by: 1.5) {
            let randomY = drawRect.minY + cornerRadius + CGFloat.random(in: -0.8...0.8)
            context.addLine(to: CGPoint(x: x, y: randomY))
        }
        context.addLine(to: CGPoint(x: drawRect.maxX, y: drawRect.minY + cornerRadius))
        
        // 右上角圆角
        context.addArc(center: CGPoint(x: drawRect.maxX - cornerRadius, y: drawRect.minY + cornerRadius),
                       radius: cornerRadius,
                       startAngle: .pi * 1.5,
                       endAngle: 0,
                       clockwise: true)
        
        // 绘制右边
        startPoint = CGPoint(x: drawRect.maxX - cornerRadius, y: drawRect.minY)
        context.move(to: startPoint)
        for y in stride(from: drawRect.minY, to: drawRect.maxY, by: 1.5) {
            let randomX = drawRect.maxX - cornerRadius + CGFloat.random(in: -0.8...0.8)
            context.addLine(to: CGPoint(x: randomX, y: y))
        }
        context.addLine(to: CGPoint(x: drawRect.maxX - cornerRadius, y: drawRect.maxY))
        
        // 右下角圆角
        context.addArc(center: CGPoint(x: drawRect.maxX - cornerRadius, y: drawRect.maxY - cornerRadius),
                       radius: cornerRadius,
                       startAngle: 0,
                       endAngle: .pi * 0.5,
                       clockwise: true)
        
        // 绘制下边
        startPoint = CGPoint(x: drawRect.maxX, y: drawRect.maxY - cornerRadius)
        context.move(to: startPoint)
        for x in stride(from: drawRect.maxX, to: drawRect.minX, by: -1.5) {
            let randomY = drawRect.maxY - cornerRadius + CGFloat.random(in: -0.8...0.8)
            context.addLine(to: CGPoint(x: x, y: randomY))
        }
        context.addLine(to: CGPoint(x: drawRect.minX, y: drawRect.maxY - cornerRadius))
        
        // 左下角圆角
        context.addArc(center: CGPoint(x: drawRect.minX + cornerRadius, y: drawRect.maxY - cornerRadius),
                       radius: cornerRadius,
                       startAngle: .pi * 0.5,
                       endAngle: .pi,
                       clockwise: true)
        
        // 绘制左边
        startPoint = CGPoint(x: drawRect.minX + cornerRadius, y: drawRect.maxY)
        context.move(to: startPoint)
        for y in stride(from: drawRect.maxY, to: drawRect.minY, by: -1.5) {
            let randomX = drawRect.minX + cornerRadius + CGFloat.random(in: -0.8...0.8)
            context.addLine(to: CGPoint(x: randomX, y: y))
        }
        
        // 左上角圆角闭合路径
        context.addArc(center: CGPoint(x: drawRect.minX + cornerRadius, y: drawRect.minY + cornerRadius),
                       radius: cornerRadius,
                       startAngle: .pi,
                       endAngle: .pi * 1.5,
                       clockwise: true)
        
        context.strokePath()
    }
}

使用方式

可以直接在Storyboard里把视图类改成PencilBorderView,或者代码创建:

let pencilView = PencilBorderView(frame: CGRect(x: 50, y: 50, width: 200, height: 150))
pencilView.lineWidth = 2.0
pencilView.borderColor = .darkGray
pencilView.layer.cornerRadius = 10
view.addSubview(pencilView)

优化小技巧

  • 调整随机偏移的范围(比如-1.0...1.0):数值越大,抖动越明显,手绘感越强;数值越小,边框越规整。
  • 用纹理颜色增强真实感:可以把borderColor换成带铅笔纹理的图案颜色,比如UIColor(patternImage: UIImage(named: "pencilTexture")!),记得纹理图要支持平铺。
  • 适配圆角:如果视图有圆角,一定要在路径生成时同步处理圆角部分,不然边框会和视图圆角错位。

内容的提问来源于stack exchange,提问作者Kani Lana

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.27 04:24:58