Swift中如何在UIImage上绘制涂鸦线条并保存为新图片?
我完全懂你的需求——很多教程都是教在UIView上涂鸦,但你要的是在UIImage上画完直接保存成带线条的新图对吧?其实核心就是在UIImageView的基础上叠加绘制线条,最后把原图和所有涂鸦合成一张新的UIImage。下面给你一套完整的Swift实现方案,亲测可用:
核心思路
- 自定义继承自
UIImageView的子类,开启用户交互来响应触摸事件 - 用数组记录所有绘制的线条信息(起点、终点、颜色、宽度),保证重绘时线条不会消失
- 在
draw(_:)方法中遍历线条数组绘制所有涂鸦 - 合成图片时,先绘制原始图片,再叠加所有线条,生成最终的带涂鸦图片
1. 定义线条数据结构
首先我们需要一个结构体来存储单条线条的关键信息:
struct Stroke { let startPoint: CGPoint let endPoint: CGPoint let color: UIColor let lineWidth: CGFloat }
2. 自定义涂鸦UIImageView子类
这个类会处理触摸事件、绘制线条,以及合成保存图片的逻辑:
class DoodleImageView: UIImageView { // 保存所有已绘制的线条 private var strokes: [Stroke] = [] // 记录上一个触摸点,用于绘制连续线条 private var lastTouchPoint: CGPoint? // 可自定义的涂鸦样式 var doodleColor: UIColor = .systemRed var doodleLineWidth: CGFloat = 3.0 override init(image: UIImage?) { super.init(image: image) setupBasicConfig() } required init?(coder: NSCoder) { super.init(coder: coder) setupBasicConfig() } private func setupBasicConfig() { // UIImageView默认关闭交互,必须开启才能响应触摸 isUserInteractionEnabled = true // 设置背景色避免透明背景绘制异常 backgroundColor = .white } // MARK: - 触摸事件处理 override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) { guard let touch = touches.first else { return } lastTouchPoint = touch.location(in: self) } override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) { guard let touch = touches.first, let lastPoint = lastTouchPoint else { return } let currentPoint = touch.location(in: self) // 添加一段新的线条到数组 strokes.append(Stroke( startPoint: lastPoint, endPoint: currentPoint, color: doodleColor, lineWidth: doodleLineWidth )) // 更新上一个触摸点 lastTouchPoint = currentPoint // 触发视图重绘 setNeedsDisplay() } override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) { // 结束绘制,清空临时触摸点 lastTouchPoint = nil } // MARK: - 绘制所有线条 override func draw(_ rect: CGRect) { super.draw(rect) guard let context = UIGraphicsGetCurrentContext() else { return } for stroke in strokes { stroke.color.setStroke() context.setLineWidth(stroke.lineWidth) // 设置圆角端点和拐角,让涂鸦更自然 context.setLineCap(.round) context.setLineJoin(.round) context.move(to: stroke.startPoint) context.addLine(to: stroke.endPoint) context.strokePath() } } // MARK: - 合成并保存图片 func saveDoodledImage() -> UIImage? { // 开启图片绘制上下文,适配屏幕分辨率 UIGraphicsBeginImageContextWithOptions(bounds.size, false, UIScreen.main.scale) // 先绘制原始图片 image?.draw(in: bounds) // 再叠加绘制所有涂鸦线条 guard let context = UIGraphicsGetCurrentContext() else { UIGraphicsEndImageContext() return nil } for stroke in strokes { stroke.color.setStroke() context.setLineWidth(stroke.lineWidth) context.setLineCap(.round) context.setLineJoin(.round) context.move(to: stroke.startPoint) context.addLine(to: stroke.endPoint) context.strokePath() } // 获取合成后的图片 let finalImage = UIGraphicsGetImageFromCurrentImageContext() UIGraphicsEndImageContext() // 可选:保存到系统相册 if let image = finalImage { UIImageWriteToSavedPhotosAlbum(image, self, #selector(imageSavedCallback(_:didFinishSavingWithError:contextInfo:)), nil) } return finalImage } // 相册保存回调 @objc private func imageSavedCallback(_ image: UIImage, didFinishSavingWithError error: Error?, contextInfo: UnsafeRawPointer) { if let error = error { print("图片保存失败:\(error.localizedDescription)") } else { print("图片已成功保存到相册!") } } // 清空所有涂鸦 func clearAllDoodles() { strokes.removeAll() setNeedsDisplay() } }
3. 使用示例
在ViewController中快速集成这个涂鸦视图:
class ViewController: UIViewController { private var doodleView: DoodleImageView! override func viewDidLoad() { super.viewDidLoad() // 创建涂鸦视图并设置原图 doodleView = DoodleImageView(image: UIImage(named: "your-image-name")) doodleView.frame = view.bounds doodleView.contentMode = .scaleAspectFit view.addSubview(doodleView) // 添加保存按钮 let saveBtn = UIButton(type: .system) saveBtn.setTitle("保存涂鸦图片", for: .normal) saveBtn.titleLabel?.font = .systemFont(ofSize: 16, weight: .medium) saveBtn.frame = CGRect(x: 20, y: view.safeAreaInsets.top + 20, width: view.bounds.width - 40, height: 50) saveBtn.addTarget(self, action: #selector(saveButtonTapped), for: .touchUpInside) view.addSubview(saveBtn) } @objc private func saveButtonTapped() { _ = doodleView.saveDoodledImage() } }
关键注意点
- 必须开启
isUserInteractionEnabled = true,否则UIImageView无法响应触摸事件 - 保存线条数组是为了在视图重绘时(比如屏幕旋转)重新绘制所有线条,避免涂鸦消失
- 合成图片时要先画原图再画线条,这样得到的是真正合并后的UIImage,而非视图截图
内容的提问来源于stack exchange,提问作者Agung
相关产品推荐
相关产品推荐

