Reputation: 495
I'm encountering an issue with my drawing functionality in Swift, specifically when using CALayer instead of UIBezierPath. Here's the problem I'm facing:
I am creating a texture from an image and using the texture to create a drawing line through finger stroke. In this process, the line is being created by joining the texture one by one, side by side. Initially, I used UIBezierPath for drawing, and everything worked fine. However, when I switched to using CALayer for drawing, for using textures to draw lines, I noticed that the touch points are not sequentially joining or increasing if I move my finger fast.
When I move my finger slowly, the drawing works fine, and the touch points are joining sequentially. However, when I move my finger fast, the touch points seem to skip, resulting in disjointed lines and multiple blank spaces within a line.
class CustomStrokeDrawingView: UIView {
var path = UIBezierPath()
var startPoint = CGPoint()
var touchPoint = CGPoint()
var shape = UIImage(named:"square-outline")
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = touches.first
if let point = touch?.location(in: self) {
startPoint = point
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = touches.first
if let point = touch?.location(in: self) {
touchPoint = point
}
let imageLayer = CALayer()
imageLayer.backgroundColor = UIColor.clear.cgColor
imageLayer.bounds = CGRect(x: startPoint.x, y: startPoint.y , width: 20, height: 20)
imageLayer.position = CGPoint(x:touchPoint.x ,y:touchPoint.y)
imageLayer.contents = shape?.cgImage
self.layer.addSublayer(imageLayer)
}
override func draw(_ rect: CGRect) {
super.draw(rect)
}
}
Upvotes: 0
Views: 70
Reputation: 77672
It is very easy to move your finger faster than iOS can generate touchesMoved
events ... if you log the points with print()
statements, you can easily see this in the debug console. It's possible to "swipe" fast enough across the device screen and only generate 2 or 3 points.
One approach is to interpolate n-number of points on the lines between the touchesMoved
points, and then draw your shape image at each of those points.
Here's some quick example code...
UIView subclass
class CustomStrokeDrawingView: UIView {
var shape: UIImage!
var pts: [CGPoint] = []
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() {
// crash if we can't load the "brush" image
guard let img = UIImage(named:"square-outline") else {
fatalError("Could not load shape image!")
}
shape = img
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = touches.first
if let startPoint = touch?.location(in: self) {
// reset points array to only the starting point
//print("Start:", startPoint)
pts = [startPoint]
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = touches.first
if let touchPoint = touch?.location(in: self) {
//print("Moved:", touchPoint)
pts.append(touchPoint)
// trigger draw()
setNeedsDisplay()
}
}
override func draw(_ rect: CGRect) {
super.draw(rect)
guard pts.count > 1 else { return }
let shapeSize: CGSize = shape.size
// adjust as desired
let pixelsPerIteration: CGFloat = 5.0
for idx in 0..<(pts.count - 1) {
let startx: CGFloat = pts[idx].x
let starty: CGFloat = pts[idx].y
let dx: CGFloat = pts[idx+1].x - startx
let dy: CGFloat = pts[idx+1].y - starty
let distance: CGFloat = sqrt(dx * dx + dy * dy)
let iterations = distance / pixelsPerIteration + 1
let dxIncrement = dx / iterations
let dyIncrement = dy / iterations
var dstRect: CGRect = .init(x: 0.0, y: 0.0, width: shapeSize.width, height: shapeSize.height)
var x: CGFloat = startx - shapeSize.width / 2.0
var y: CGFloat = starty - shapeSize.height / 2.0
// draw a series of shape images to form the line
for _ in 0..<Int(iterations) {
dstRect.origin.x = x
dstRect.origin.y = y
x += dxIncrement
y += dyIncrement
shape.draw(in: dstRect)
}
}
}
}
Example controller class
class DrawTestVC: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
navigationController?.setNavigationBarHidden(true, animated: false)
view.backgroundColor = .systemYellow
let drawView = CustomStrokeDrawingView()
drawView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(drawView)
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
drawView.topAnchor.constraint(equalTo: g.topAnchor, constant: 20.0),
drawView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
drawView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
drawView.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -20.0),
])
drawView.backgroundColor = UIColor(white: 0.95, alpha: 1.0)
}
}
Looks about like this:
Upvotes: 1