Skip to content

Instantly share code, notes, and snippets.

@almaleh
Last active July 22, 2022 13:46
Show Gist options
  • Star 11 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save almaleh/4dd0c90260f30a7b0216d7f1cebc70ef to your computer and use it in GitHub Desktop.
Save almaleh/4dd0c90260f30a7b0216d7f1cebc70ef to your computer and use it in GitHub Desktop.
//
// FreeDrawingImageView.swift
//
// Created by Besher on 2018-12-30.
// Copyright © 2018 Besher Al Maleh. All rights reserved.
//
import UIKit
class FreeDrawingImageViewDrawLayer: UIView {
var drawingLayer: CAShapeLayer?
var line = [CGPoint]() {
didSet { checkIfTooManyPoints() }
}
var sublayers: [CALayer] {
return self.layer.sublayers ?? [CALayer]()
}
let lineWidth: CGFloat = 5
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let newTouchPoint = touches.first?.location(in: self) else { return }
line.append(newTouchPoint)
let lastTouchPoint: CGPoint = line.last ?? .zero
let rect = calculateRectBetween(lastPoint: lastTouchPoint, newPoint: newTouchPoint)
layer.setNeedsDisplay(rect)
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesBegan(touches, with: event)
flattenImage()
}
override func draw(_ layer: CALayer, in ctx: CGContext) {
let drawingLayer = self.drawingLayer ?? CAShapeLayer()
let linePath = UIBezierPath()
drawingLayer.contentsScale = UIScreen.main.scale
for (index, point) in line.enumerated() {
if index == 0 {
linePath.move(to: point)
} else {
linePath.addLine(to: point)
}
}
drawingLayer.path = linePath.cgPath
drawingLayer.opacity = 1
drawingLayer.lineWidth = lineWidth
drawingLayer.lineCap = .round
drawingLayer.fillColor = UIColor.clear.cgColor
drawingLayer.strokeColor = UIColor.red.cgColor
if self.drawingLayer == nil {
self.drawingLayer = drawingLayer
layer.addSublayer(drawingLayer)
}
}
func checkIfTooManyPoints() {
let maxPoints = 25
if line.count > maxPoints {
updateFlattenedLayer()
// we leave two points to ensure no gaps or sharp angles
_ = line.removeFirst(maxPoints - 2)
}
}
func flattenImage() {
updateFlattenedLayer()
line.removeAll()
}
func updateFlattenedLayer() {
// 1
guard let drawingLayer = drawingLayer,
// 2
let optionalDrawing = try? NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(
NSKeyedArchiver.archivedData(withRootObject: drawingLayer, requiringSecureCoding: false))
as? CAShapeLayer,
// 3
let newDrawing = optionalDrawing else { return }
// 4
layer.addSublayer(newDrawing)
}
func clear() {
emptyFlattenedLayers()
drawingLayer?.removeFromSuperlayer()
drawingLayer = nil
line.removeAll()
layer.setNeedsDisplay()
}
func emptyFlattenedLayers() {
for case let layer as CAShapeLayer in sublayers {
layer.removeFromSuperlayer()
}
}
func calculateRectBetween(lastPoint: CGPoint, newPoint: CGPoint) -> CGRect {
let originX = min(lastPoint.x, newPoint.x) - (lineWidth / 2)
let originY = min(lastPoint.y, newPoint.y) - (lineWidth / 2)
let maxX = max(lastPoint.x, newPoint.x) + (lineWidth / 2)
let maxY = max(lastPoint.y, newPoint.y) + (lineWidth / 2)
let width = maxX - originX
let height = maxY - originY
return CGRect(x: originX, y: originY, width: width, height: height)
}
}
@KPDeng
Copy link

KPDeng commented Oct 13, 2021

thanks for good code!

a minor question in :

what is the work of this method "func updateFlattenedLayer"

because i just Foolishly think

they have a same result in:

"self.layer.addSublayer(newDrawing)" and "layer.addSublayer(drawingLayer) "

So they may be also performance problems after writing a lot

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment