2

The below code draws straight lines between points onto a UIImageView. Drawings render fast and look good but could be much better, so I want to modify the code to create smooth curved lines. I’ve investigated a number of examples in Swift over the past six weeks with zero success.

What sections of the below code must I modify (and how) to achieve this please? I’ve been told calling the below snippet instead of CGContextAddLineToPoint() (at // 2 below) should do the trick, but I’m not clear on how to call it and how it relates to the current code entirely.

SNIPPET:

func drawQuadLineWithPoints(firstPoint: CGPoint, secondPoint: CGPoint, thirdPoint: CGPoint, inContext: CGContext) {
    CGContextMoveToPoint(context, 0.5 * (firstPoint.x + secondPoint.x), 0.5 * (firstPoint.y + secondPoint.y));
    CGContextAddQuadCurveToPoint(context, secondPoint.x, secondPoint.y, 0.5 * (secondPoint.x + thirdPoint.x), 0.5 * (secondPoint.y + thirdPoint.y));
}

CODE:

  override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
    swiped = false
    if let touch = touches.anyObject() as? UITouch {
      lastPoint = touch.locationInView(self.view)
    }
  }

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

    // 1
    UIGraphicsBeginImageContext(view.frame.size)
    let context = UIGraphicsGetCurrentContext()
    tempImageView.image?.drawInRect(CGRect(x: 0, y: 0, width: view.frame.size.width, height: view.frame.size.height))

    // 2
    CGContextMoveToPoint(context, fromPoint.x, fromPoint.y)
    CGContextAddLineToPoint(context, toPoint.x, toPoint.y)

    // 3
    CGContextSetLineCap(context, kCGLineCapRound)
    CGContextSetLineWidth(context, brushWidth)
    CGContextSetRGBStrokeColor(context, red, green, blue, 1.0)
    CGContextSetBlendMode(context, kCGBlendModeNormal)

    // 4
    CGContextStrokePath(context)

    // 5
    tempImageView.image = UIGraphicsGetImageFromCurrentImageContext()
    tempImageView.alpha = opacity
    UIGraphicsEndImageContext()

  }

  override func touchesMoved(touches: NSSet, withEvent event: UIEvent) {
    // 6
    swiped = true
    if let touch = touches.anyObject() as? UITouch {
      let currentPoint = touch.locationInView(view)
      drawLineFrom(lastPoint, toPoint: currentPoint)

      // 7
      lastPoint = currentPoint
    }
  }

  override func touchesEnded(touches: NSSet, withEvent event: UIEvent) {

    if !swiped {
      // draw a single point
      drawLineFrom(lastPoint, toPoint: lastPoint)
    }

    // Merge tempImageView into mainImageView
    UIGraphicsBeginImageContext(mainImageView.frame.size)
    mainImageView.image?.drawInRect(CGRect(x: 0, y: 0, width: view.frame.size.width, height: view.frame.size.height), blendMode: kCGBlendModeNormal, alpha: 1.0)
    tempImageView.image?.drawInRect(CGRect(x: 0, y: 0, width: view.frame.size.width, height: view.frame.size.height), blendMode: kCGBlendModeNormal, alpha: opacity)
    mainImageView.image = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext()

    tempImageView.image = nil
  }
user4806509
  • 2,925
  • 5
  • 37
  • 72

4 Answers4

5

Look at the answer for THIS

This is a rough translation to Swift I used:

override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {


    if let touch = touches.first as? UITouch{

        prevPoint1 = touch.previousLocationInView(self.view)

        prevPoint2 = touch.previousLocationInView(self.view)

        lastPoint = touch.locationInView(self.view)

    }

}



override func touchesMoved(touches: Set<NSObject>, withEvent event: UIEvent) {


    if let touch = touches.first as? UITouch{

        let currentPoint = touch.locationInView(view)



        prevPoint2 = prevPoint1

        prevPoint1 = touch.previousLocationInView(self.view)





        UIGraphicsBeginImageContext(view.frame.size)

        let context = UIGraphicsGetCurrentContext()

        TempImage.image?.drawInRect(CGRect(x: 0, y: 0, width: view.frame.size.width, height: view.frame.size.height))



        var mid1 = CGPointMake((prevPoint1.x + prevPoint2.x)*0.5, (prevPoint1.y + prevPoint2.y)*0.5)

        var mid2 = CGPointMake((currentPoint.x + prevPoint1.x)*0.5, (currentPoint.y + prevPoint1.y)*0.5)



        CGContextMoveToPoint(context, mid1.x, mid1.y)

        CGContextAddQuadCurveToPoint(context, prevPoint1.x, prevPoint1.y, mid2.x, mid2.y)

        //CGContextAddLineToPoint(context, toPoint.x, toPoint.y)



        CGContextSetLineCap(context, kCGLineCapRound)

        CGContextSetLineWidth(context, brushWidth)

        CGContextSetRGBStrokeColor(context, red, green,blue, 1.0)

        CGContextSetBlendMode(context, kCGBlendModeNormal)



        CGContextStrokePath(context)



        TempImage.image = UIGraphicsGetImageFromCurrentImageContext()

        TempImage.alpha = opacity

        UIGraphicsEndImageContext()



        lastPoint = currentPoint

    }

}



override func touchesEnded(touches: Set<NSObject>, withEvent event: UIEvent) {



    UIGraphicsBeginImageContext(MainImage.frame.size)

    MainImage.image?.drawInRect(CGRect(x: 0, y: 0, width: view.frame.size.width, height: view.frame.size.height), blendMode: kCGBlendModeNormal, alpha: 1.0)

    TempImage.image?.drawInRect(CGRect(x: 0, y: 0, width: view.frame.size.width, height: view.frame.size.height), blendMode: kCGBlendModeNormal, alpha: opacity)

    MainImage.image = UIGraphicsGetImageFromCurrentImageContext()

    UIGraphicsEndImageContext()



    TempImage.image = nil

}
Community
  • 1
  • 1
Bjqn
  • 146
  • 2
  • 19
  • Wonderful! Thank you very much for your help. Your answer helped put in context where the changes to the previous code needed to be made. As an on/off programmer, Swift is new to me (early 2015), and I’m still learning. It wasn’t a simple copy paste as I seem to be using Swift 1.1 (I will upgrade very shortly!), but line by line I could see the changes required and implemented them. Best of all, the solution draws smooth and fast. Thanks again, really appreciate it! Up vote! – user4806509 Aug 04 '15 at 11:48
  • I'm glad i cloud help :) – Bjqn Aug 04 '15 at 11:55
  • sir, could you please help me on this one ? https://stackoverflow.com/questions/49919853/how-to-draw-doodle-line-on-uiimage-in-swift – sarah Apr 19 '18 at 11:24
2

Update for Swift 4

import UIKit
class ViewController: UIViewController {
    @IBOutlet weak var imageView: UIImageView!
    var prevPoint1: CGPoint!
    var prevPoint2: CGPoint!
    var lastPoint:CGPoint!

    var width: CGFloat!
    var red:CGFloat!
    var green:CGFloat!
    var blue:CGFloat!
    var alpha: CGFloat!

    override func viewDidLoad() {
        super.viewDidLoad()
        width = 3.0
        red = (0.0/255.0)
        green = (0.0/255.0)
        blue = (0.0/255.0)
        alpha = 1.0
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }

    override func touchesBegan(_ touches: Set, with event: UIEvent?) {
        if let touch = touches.first {
            prevPoint1 = touch.previousLocation(in:self.view)
            prevPoint2 = touch.previousLocation(in:self.view)
            lastPoint = touch.location(in:self.view)
        }
    }

    override func touchesMoved(_ touches: Set, with event: UIEvent?) {
        if let touch = touches.first {
            let currentPoint = touch.location(in: view)
            prevPoint2 = prevPoint1
            prevPoint1 = touch.previousLocation(in: self.view)

            UIGraphicsBeginImageContext(imageView.frame.size)
            guard let context = UIGraphicsGetCurrentContext() else {
                return
            }

            context.move(to:prevPoint2)
            context.addQuadCurve(to: prevPoint1, control: prevPoint2)
            context.setLineCap(.butt)
            context.setLineWidth(width)
            context.setStrokeColor(red: red, green: green, blue: blue, alpha: 1.0)
            context.setBlendMode(.normal)
            context.strokePath()

            imageView.image?.draw(in: CGRect(x: 0, y: 0, width: imageView.frame.size.width, height: imageView.frame.size.height), blendMode: .overlay, alpha: 1.0)
            imageView.image = UIGraphicsGetImageFromCurrentImageContext()

            UIGraphicsEndImageContext()
            lastPoint = currentPoint
        }
    }
}
  • sir, could you please help me on this one ? https://stackoverflow.com/questions/49919853/how-to-draw-doodle-line-on-uiimage-in-swift – sarah Apr 19 '18 at 11:24
2

Here is my adaptation to SwiftUI. I hold on to all the points so that I can undo/redo and so that I can have the original touch points as this is for a signature. I don't need/want an actual image...

Other descriptions are found in-code

import SwiftUI

struct DrawPad: View {
    @State private var path = Path()
    @State private var allStrokes: [[DragGesture.Value]] = []
    @State private var currentStroke: [DragGesture.Value] = []
    
    var body: some View {
        path
            .stroke(Color.blue, lineWidth: 1.0)
            .background(Color(white: 0.95))
            .gesture(
                DragGesture(minimumDistance: 0.1)
                    .onChanged { value in
                        addNextPath(from: value)
                    }
                    .onEnded { value in
                        addNextPath(from: value)
                        
                        // We finished the current stroke. Remove it and start a new one
                        allStrokes.append(currentStroke)
                        currentStroke = []
                    }
            )
    }
    
    private func addNextPath(from value: DragGesture.Value) {
        currentStroke.append(value)
        // We need at least 3 points to draw a nice line
        guard currentStroke.count >= 3 else { return }
        
        // Get the last 3 points
        let first = currentStroke[currentStroke.count - 3]
        let second = currentStroke[currentStroke.count - 2]
        let third = currentStroke[currentStroke.count - 1]
        
        // Get two mid points. Mid points are just the average of the two points
        let firstMid = [first.location, second.location].average
        let secondMid = [second.location, third.location].average
        
        // Create the curve from the mid point between first and second to the mid point between second and third
        // and the second point is control point. If you draw this out on a piece of paper you will see that
        // the second point is the tangent for the first mid point.
        path.move(to: firstMid)
        path.addQuadCurve(to: secondMid, control: second.location)
    }
}

struct DrawPad_Previews: PreviewProvider {
    static var previews: some View {
        DrawPad()
    }
}

extension Array where Element == CGPoint {
    var average: CGPoint {
        let count = CGFloat(self.count)
        let sumX = reduce(0, { $0 + $1.x })
        let sumY = reduce(0, { $0 + $1.y })
        return CGPoint(x: sumX/count, y: sumY/count)
    }
}
Jacob
  • 1,052
  • 8
  • 10
1

For Swift 3.0

func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
    swiped = false
    if let touch = touches.first as? UITouch {
        lastPoint = touch.location(in: self.view)
    }
}
func drawLineFrom(fromPoint: CGPoint, toPoint: CGPoint) {

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


    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()


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

}

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

        lastPoint = currentPoint
    }
}
Dhruv Khatri
  • 803
  • 6
  • 15
  • sir, could you please help me on this one ? https://stackoverflow.com/questions/49919853/how-to-draw-doodle-line-on-uiimage-in-swift – sarah Apr 19 '18 at 11:25