I want my swift code to draw a straight horiziontal line. Right now it selects a point and the user extends the line anchored in the first point. I just want the user to be able to draw the line left or right. I tried alternating bezier.addLine(to: CGPoint(x:startTouch!.x, y:startTouch!.y)) but that does not seem to have any effect.
import UIKit
class ViewController2: UIViewController {
@IBOutlet weak var drawingPlace: UIImageView!
var startTouch : CGPoint?
var secondTouch : CGPoint?
var currentContext : CGContext?
var prevImage : UIImage?
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemOrange
drawingPlace.backgroundColor = .gray
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = touches.first
startTouch = touch?.location(in: drawingPlace)
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches{
secondTouch = touch.location(in: drawingPlace)
if(self.currentContext == nil){
UIGraphicsBeginImageContext(drawingPlace.frame.size)
self.currentContext = UIGraphicsGetCurrentContext()
}else{
self.currentContext?.clear(CGRect(x: 0, y: 0, width: drawingPlace.frame.width, height: drawingPlace.frame.height))
}
self.prevImage?.draw(in: self.drawingPlace.bounds)
let bezier = UIBezierPath()
bezier.move(to: startTouch!)
bezier.addLine(to: secondTouch!)
bezier.addLine(to: CGPoint(x:startTouch!.x, y:startTouch!.y))
bezier.close()
UIColor.blue.set()
self.currentContext?.setLineWidth(4)
self.currentContext?.addPath(bezier.cgPath)
self.currentContext?.strokePath()
let img2 = self.currentContext?.makeImage()
drawingPlace.image = UIImage.init(cgImage: img2!)
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
self.currentContext = nil
self.prevImage = self.drawingPlace.image
}
}
If you want to draw a horizontal line, create a CGPoint
whose x
is the location of the touch and whose y
is that of the starting point. That will result in a horizontal line.
That having been said, here are a few other of observations:
If you call UIGraphicsBeginImageContext
, you must call UIGraphicsEndImageContext
. You should do this within touchesMoved
, not trying to hang on to the context beyond this call.
If you were to do this, we would generally use UIGraphicsImageRenderer
nowadays.
Personally, I wouldn't try rendering an image for every touch. That is a pretty expensive operation. I would just add a CAShapeLayer
and then update the path
for that layer. Let CAShapeLayer
take care of the rendering of the path.
I'm not quite sure why you are iterating through the array of touches. I would just grab one and use that.
I might suggest using predictive touches to minimize perceived lagginess.
The startTouch
is actually a CGPoint
, not a UITouch
, so I might call it startPoint
instead.
If you want to make a snapshot image, I'd do that in touchesEnded
, not in touchesMoved
.
For example:
class ViewController: UIViewController {
@IBOutlet weak var imageView: UIImageView!
var startPoint: CGPoint?
let shapeLayer: CAShapeLayer = {
let shapeLayer = CAShapeLayer()
shapeLayer.lineWidth = 4
shapeLayer.fillColor = UIColor.clear.cgColor
shapeLayer.strokeColor = UIColor.blue.cgColor
return shapeLayer
}()
override func viewDidLoad() {
super.viewDidLoad()
imageView.backgroundColor = .systemOrange
imageView.layer.addSublayer(shapeLayer)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = touches.first
startPoint = touch?.location(in: imageView)
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
guard var touch = touches.first else { return }
if let predicted = event?.predictedTouches(for: touch)?.last {
touch = predicted
}
updatePath(in: imageView, to: touch)
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else { return }
updatePath(in: imageView, to: touch)
let image = UIGraphicsImageRenderer(bounds: imageView.bounds).image { _ in
imageView.drawHierarchy(in: imageView.bounds, afterScreenUpdates: true)
}
shapeLayer.path = nil
imageView.image = image
}
}
private extension ViewController {
func updatePath(in view: UIView, to touch: UITouch) {
let point = touch.location(in: view)
guard view.bounds.contains(point) else { return }
let bezier = UIBezierPath()
bezier.move(to: startPoint!)
bezier.addLine(to: CGPoint(x: point.x, y: startPoint!.y))
shapeLayer.path = bezier.cgPath
}
}
This just renders the current line as a CAShapeLayer
, but when the stroke is done, it creates a snapshot of the image view (permanently capturing the stroke in an image), removes the shape layer’s path, and updates the image view’s image
.
But hopefully this answers the question on how to make the line horizontal.