6

I'am trying to draw on a UIImageView. With Swift 1.2 I was able to get it to work, but I had to convert it to swift 3.0 and I just can't get it to work.

What it needs to do is draw exactly what you draw on the screen with your finger.

The codes gives no errors, but just does not display anything.

Variables;

var lastPoint = CGPoint.zero
var red: CGFloat = 0.0
var green: CGFloat = 0.0
var blue: CGFloat = 0.0
var brushWidth: CGFloat = 10.0
var opacity: CGFloat = 1.0
var swiped = false

The code;

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
    swiped = false
    if let touch = touches.first {
        lastPoint = touch.location(in: self.view)
    }
}

func drawLineFrom(fromPoint: CGPoint, toPoint: CGPoint) {


    imageView.image?.draw(in: CGRect(x: 0, y: 0, width: view.frame.size.width, height: view.frame.size.height))
    UIGraphicsBeginImageContext(self.imageView.bounds.size);
    let context = UIGraphicsGetCurrentContext()

        context?.move(to: fromPoint)
        context?.addLine(to: toPoint)

        context?.setLineCap(CGLineCap.round)
        context?.setLineWidth(brushWidth)
        context?.setStrokeColor(red: red, green: green, blue: blue, alpha: 1.0)
        context?.setBlendMode(CGBlendMode.normal)

        imageView.image = UIGraphicsGetImageFromCurrentImageContext()
        imageView.alpha = opacity
        UIGraphicsEndImageContext()

}

override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
    swiped = true
    if let touch = touches.first {
        let currentPoint = touch.location(in: view)
        drawLineFrom(fromPoint: lastPoint, toPoint: currentPoint)

        lastPoint = currentPoint
    }
}

override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
    if !swiped {
                    // draw a single point
        self.drawLineFrom(fromPoint: lastPoint, toPoint: lastPoint)
    }
Daan
  • 179
  • 1
  • 4
  • 14
  • All the functions are called correctly, and the imageView frames are also set correctly, the imageView on startup is set with a actual image. The imageView becomes white when drawing starts. – Daan Sep 21 '16 at 15:28
  • 1
    from what I am seeing, you are only drawing tiny pixels at each iteration, erasing the old. You commented out drawing the old image in the context – Knight0fDragon Sep 21 '16 at 15:28
  • 1
    is there code missing? I do not see `UIGraphicsBeginImageContext(...)` – Knight0fDragon Sep 21 '16 at 15:33
  • @Knight0fDragon I have edited the post! – Daan Sep 21 '16 at 15:40
  • you still dont draw inside the context – Knight0fDragon Sep 21 '16 at 15:41
  • As an unrelated aside, this process of creating a new snapshot for every `touchedMoved` is a bit inefficient (and the curve isn't going to be very smooth). I'd generally use a `CAShapeLayer` and update the `path` (or implement a custom `UIView` subclass with `drawRect` that strokes the path), and only make a new snapshot every 50-100 points or so. You can also [smooth the curve](http://stackoverflow.com/a/34583708/1271826) and/or use coalesced touches to get more data points. – Rob Sep 21 '16 at 16:53
  • how you are clearing then sketched line from `imageView` ? – vaibhav Jun 15 '17 at 12:24

1 Answers1

11

You have to begin the image context:

UIGraphicsBeginImageContextWithOptions(view.bounds.size, false, 0)

You also have to stroke the path:

context?.strokePath()

You also are not drawing the previous image:

imageView.image?.draw(in: view.bounds)

Thus:

func drawLine(from fromPoint: CGPoint, to toPoint: CGPoint) {
    UIGraphicsBeginImageContextWithOptions(view.bounds.size, false, 0)

    imageView.image?.draw(in: view.bounds)

    let context = UIGraphicsGetCurrentContext()

    context?.move(to: fromPoint)
    context?.addLine(to: toPoint)

    context?.setLineCap(CGLineCap.round)
    context?.setLineWidth(brushWidth)
    context?.setStrokeColor(red: red, green: green, blue: blue, alpha: 1.0)
    context?.setBlendMode(CGBlendMode.normal)
    context?.strokePath()

    imageView.image = UIGraphicsGetImageFromCurrentImageContext()
    imageView.alpha = opacity
    UIGraphicsEndImageContext()
}
Rob
  • 415,655
  • 72
  • 787
  • 1,044
  • Hello, how do we can clear that sketched line from `imageView`? – vaibhav Jun 15 '17 at 12:25
  • You could just set the `imageView.image = nil` to erase the whole thing. If you want to remove just the last line, you'd have to save the old image somewhere before you started drawing the path and then restore it when your wanted to undo the stroke. But be careful, as images are resource hogs, so don't try to hold a bunch of old images in memory at the same time! – Rob Jun 15 '17 at 16:03
  • @Rob I'm trying to implement something similar but on my case I just want to draw in the view. How can give the context to self.view (UIGraphics) ? – user2924482 Aug 31 '17 at 20:05
  • @user2924482 - There are 2 approaches: First, you could subclass `UIView` and put your drawing of the view at a point in time in `draw(rect:)`. You can just `stroke` your `UIBezierPath`, safely knowing that you’re already in a graphic context inside `draw(rect:)`. Second (and even easier IMHO), you can use a `CAShapeLayer`, adding it as a sublayer of your view’s `layer`, set the shape layer’s `path`, and the shape layer takes care of all that stroking and graphic context stuff for you. See https://stackoverflow.com/a/16846770/1271826. – Rob Aug 31 '17 at 20:56