5

I'm trying to make a drawing app. I have a single custom UIView:

class DrawView: UIView {

var touch : UITouch!
var lastPoint : CGPoint!
var currentPoint : CGPoint!

override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
    touch = touches.first as! UITouch
    lastPoint = touch.locationInView(self)
    println(lastPoint)
}

override func touchesMoved(touches: Set<NSObject>, withEvent event: UIEvent) {
    touch = touches.first as! UITouch
    currentPoint = touch.locationInView(self)

    self.setNeedsDisplay()

    lastPoint = currentPoint
}

override func drawRect(rect: CGRect) {
    var context = UIGraphicsGetCurrentContext()
    CGContextSetLineWidth(context, 5)
    CGContextSetStrokeColorWithColor(context, UIColor.blueColor().CGColor)
    CGContextSetLineCap(context, kCGLineCapRound)

    CGContextBeginPath(context)

    if lastPoint != nil {
        CGContextMoveToPoint(context, lastPoint.x, lastPoint.y)
        CGContextAddLineToPoint(context, currentPoint.x, currentPoint.y)
    }

    CGContextStrokePath(context)
}

}

When I run it, however, all I get is a blue dot that follows my finger, but no lines?

What am I doing wrong?

Wain
  • 118,658
  • 15
  • 128
  • 151
user2397282
  • 3,798
  • 15
  • 48
  • 94
  • Because you're only drawing the lastPoint to currentPoint. When you say "set needs display" you "invalidate" the whole view, meaning each draw call is blanking the view and only drawing that single segment. – Ben Zotto May 13 '15 at 18:03

3 Answers3

4

Hi i make some simple changes and fixed your code, hope it helps someone in the future (code it's updated for Swift 3) :

class DrawView: UIView {

    var touch : UITouch!
    var lineArray : [[CGPoint]] = [[CGPoint]()]
    var index = -1

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        touch = touches.first! as UITouch
        let lastPoint = touch.location(in: self)

        index += 1
        lineArray.append([CGPoint]())
        lineArray[index].append(lastPoint)
    }

    override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
        touch = touches.first! as UITouch
        let currentPoint = touch.location(in: self)

        self.setNeedsDisplay()

        lineArray[index].append(currentPoint)
    }

    override func draw(_ rect: CGRect) {

        if(index >= 0){
            let context = UIGraphicsGetCurrentContext()
            context!.setLineWidth(5)
            context!.setStrokeColor((UIColor(red:0.00, green:0.38, blue:0.83, alpha:1.0)).cgColor)
            context!.setLineCap(.round)

            var j = 0
            while( j <= index ){
                context!.beginPath()
                var i = 0
                context?.move(to: lineArray[j][0])
                while(i < lineArray[j].count){
                    context?.addLine(to: lineArray[j][i])
                    i += 1
                }
                context!.strokePath()
                j += 1
            }
        }
    }
}
Keuha
  • 295
  • 3
  • 18
marcomoreira92
  • 369
  • 3
  • 9
1

Two things:

  1. Calling self.setNeedsDisplay doesn't immediately call drawRect. It just sets a flag so that drawRect will be called in the near future. Since you set lastPoint to currentPoint right after that, when drawRect is called lastPoint is always equal to currentPoint.

  2. drawRect redraws the entire view every time it is called, so at most you'd only ever see the most recent line. If you fixed problem 1, you'd have a short line following your finger instead of a dot. If you want to see the whole trail, you'll need to store the points in an array that is a property of your view, and then draw lines to connect all of the points in drawRect.

vacawama
  • 150,663
  • 30
  • 266
  • 294
  • Thanks, this works now! However, it slows down a lot as the number of lines increases. What can be done about this? – user2397282 May 13 '15 at 18:21
  • 1
    You can set up an offscreen context to draw into, and then just update the view with that in `drawRect`. Also, if you call `setNeedsDisplayInRect(rect)` only part of your view will need to be redrawn (ie. the part inside of `rect`). By combining these two methods, `drawRect` will only need to do a little work each call. – vacawama May 13 '15 at 18:29
  • Check out this tutorial. They are using a `UIImageView`: http://www.raywenderlich.com/18840/how-to-make-a-simple-drawing-app-with-uikit – vacawama May 13 '15 at 18:39
0

marcomoreira92 and Keuha's version worked for me, but I don't like to use indices that much. Thus here is an alternative version, which was tested in Swift 4.2:

class DrawView: UIView {

    var lineArray: [[CGPoint]] = [[CGPoint]]()

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        guard let touch = touches.first else { return }
        let firstPoint = touch.location(in: self)
        lineArray.append([CGPoint]())
        lineArray[lineArray.count - 1].append(firstPoint)
    }

    override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
        guard let touch = touches.first else { return }
        let currentPoint = touch.location(in: self)
        lineArray[lineArray.count - 1].append(currentPoint)
        setNeedsDisplay()
    }

    override func draw(_ rect: CGRect) {
        let context = UIGraphicsGetCurrentContext()
        context?.setLineWidth(5)
        context?.setStrokeColor(UIColor.black.cgColor)
        context?.setLineCap(.round)

        for line in lineArray {
            guard let firstPoint = line.first else { continue }
            context?.beginPath()
            context?.move(to: firstPoint)
            for point in line.dropFirst() {
                context?.addLine(to: point)
            }
            context?.strokePath()
        }
    }
}