Last active
July 22, 2022 13:46
-
-
Save almaleh/4dd0c90260f30a7b0216d7f1cebc70ef to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// 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) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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