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

SwiftUI中不使用PencilKit实现铅笔绘图效果的技术求助

实现SwiftUI铅笔绘图效果(无需PencilKit)

要模拟铅笔的自然纹理与笔触,我们可以从线条粗细随机变化、颗粒纹理叠加、颜色深浅波动三个维度入手,以下是基于你现有代码的针对性修改方案:

1. 扩展Line结构体,添加工具类型标记

首先修改Line.swift,新增工具类型枚举,让每条线记录自身的绘制工具,方便后续差异化渲染:

import SwiftUI

enum DrawingTool {
    case pen, marker, pencil
}

struct Line {
    var points: [CGPoint]
    var color: Color
    var lineWidth: CGFloat
    var tool: DrawingTool // 新增:标记线条对应的绘制工具
}

2. 优化ContentView的工具选择逻辑

替换原来的三个独立Bool变量,改用统一的工具状态管理,简化逻辑:

替换工具状态变量

删除isPen、isMarker、isPencil,新增统一的选中工具状态:

@State private var selectedTool: DrawingTool = .pen

更新工具切换函数

修改三个工具按钮的触发逻辑,直接设置选中工具:

func penAction() {
    selectedTool = .pen
}
func pencilAction() {
    selectedTool = .pencil
}
func markerAction() {
    selectedTool = .marker
}

更新按钮UI判断逻辑

基于selectedTool判断按钮高亮状态:

func pen() -> some View {
    Button(action: penAction) {
        Image(systemName: "paintbrush.pointed.fill")
            .font(.title)
            .foregroundColor(selectedTool == .pen ? selectedColor : .gray)
    }
}

func pencil() -> some View {
    Button(action: pencilAction) {
        Image(systemName: "pencil")
            .font(.title)
            .foregroundColor(selectedTool == .pencil ? selectedColor : .gray)
    }
}

func marker() -> some View {
    Button(action: markerAction) {
        Image(systemName: "paintbrush.fill")
            .font(.title)
            .foregroundColor(selectedTool == .marker ? selectedColor : .gray)
    }
}

拖动创建线条时传入工具类型

修改DragGesture的onChanged逻辑,创建Line时传入当前选中的工具:

.gesture(
    DragGesture(minimumDistance: 0, coordinateSpace: .local)
        .onChanged({value in
            let position = value.location
            
            if value.translation == .zero {
                lines.append(Line(
                    points: [position],
                    color: selectedColor,
                    lineWidth: selectedLineWidth,
                    tool: selectedTool // 传入当前工具类型
                ))
            } else {
                guard let lastIdx = lines.indices.last else {
                    return
                }
                lines[lastIdx].points.append(position)
            }
        })
)

3. 实现铅笔的纹理绘制逻辑

在Canvas的渲染代码中,针对不同工具做差异化处理:

Canvas {ctx, size in
    for line in lines {
        var path = Path()
        path.addLines(line.points)
        
        switch line.tool {
        case .pen:
            // 钢笔:保持原有圆润笔触逻辑
            ctx.stroke(path, with: .color(line.color),
                       style: StrokeStyle(lineWidth: line.lineWidth, lineCap: .round, lineJoin: .round))
        case .marker:
            // 马克笔:方头笔触
            ctx.stroke(path, with: .color(line.color),
                       style: StrokeStyle(lineWidth: line.lineWidth, lineCap: .square, lineJoin: .miter))
        case .pencil:
            // 铅笔:模拟纹理+粗细随机变化
            // 1. 生成铅笔颗粒纹理
            let pencilTexture = Image(uiImage: generatePencilTexture(size: CGSize(width: 200, height: 200)))
            let texturePaint = ImagePaint(image: pencilTexture, scale: 0.1)
            
            // 2. 叠加多层随机偏移的细线条,模拟铅笔不均匀笔触
            for offset in stride(from: -1.0, through: 1.0, by: 0.5) {
                let offsetPath = path.offsetBy(dx: CGFloat.random(in: -offset...offset), dy: CGFloat.random(in: -offset...offset))
                let randomWidth = line.lineWidth * CGFloat.random(in: 0.7...1.2)
                let alpha = CGFloat.random(in: 0.6...0.9) // 颜色深浅波动
                
                ctx.stroke(offsetPath, with: .color(line.color.opacity(alpha)),
                           style: StrokeStyle(lineWidth: randomWidth, lineCap: .round, lineJoin: .round))
            }
            
            // 3. 叠加纹理层,增强铅笔质感
            ctx.stroke(path, with: texturePaint,
                       style: StrokeStyle(lineWidth: line.lineWidth, lineCap: .round, lineJoin: .round))
        }
    }
}

新增铅笔纹理生成函数

在ContentView中添加辅助函数,生成模拟铅笔颗粒的噪声纹理:

// 生成铅笔纹理(基于CoreGraphics随机噪点)
private func generatePencilTexture(size: CGSize) -> UIImage {
    UIGraphicsBeginImageContextWithOptions(size, false, 0)
    guard let context = UIGraphicsGetCurrentContext() else { return UIImage() }
    
    // 填充白色底色
    context.setFillColor(UIColor.white.cgColor)
    context.fill(CGRect(origin: .zero, size: size))
    
    // 绘制灰色噪点,模拟铅笔石墨质感
    for _ in 0..<Int(size.width * size.height * 0.05) {
        let x = CGFloat.random(in: 0...size.width)
        let y = CGFloat.random(in: 0...size.height)
        let gray = CGFloat.random(in: 0.2...0.6)
        context.setFillColor(UIColor(red: gray, green: gray, blue: gray, alpha: 0.3).cgColor)
        context.fillEllipse(in: CGRect(x: x, y: y, width: 1, height: 1))
    }
    
    let image = UIGraphicsGetImageFromCurrentImageContext() ?? UIImage()
    UIGraphicsEndImageContext()
    return image
}

4. 清理冗余代码

删除原来的isPen、isMarker、isPencil三个Bool变量,以及lineCapIs()函数,所有工具逻辑已整合到selectedTool的switch判断中。

效果说明

  • 铅笔线条具备随机粗细波动和颜色深浅变化,叠加的噪点纹理会模拟出铅笔在纸上的颗粒感
  • 保留了钢笔的圆润笔触和马克笔的方头笔触,工具切换逻辑更简洁清晰

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.09 23:38:14