How to enable multi touches and draw two lines at the same time?

时间:2019-03-19 14:40:20

标签: ios swift

I don't understand why my program isn't behaving the way I want it. I enabled multi touches through the storyboard for the view class. But I'm still not able to draw two lines at the same time (multi touches).

Code:

import Foundation
import UIKit

class DrawView: UIView {
    var currentLine: Line?
    var finishedLines = [Line]();
    //for debug
    let line1 = Line(begin: CGPoint(x:50,y:50), end: CGPoint(x:100,y:100));
    let line2 = Line(begin: CGPoint(x:50,y:100), end: CGPoint(x:100,y:300));

    func strokeLine(line: Line){
        //Use BezierPath to draw lines
        let path = UIBezierPath();
        path.lineWidth = 5;
        path.lineCapStyle = CGLineCap.round;

        path.move(to: line.begin);
        path.addLine(to: line.end);
        path.stroke(); //actually draw the path
    }

    override func draw(_ rect: CGRect) {
        //draw the finished lines
        UIColor.green.setStroke() //finished lines in black
        for line in finishedLines{
            strokeLine(line: line);
        }

        //for debug
        strokeLine(line: line1);
        strokeLine(line: line2);

        //draw current line if it exists
        if let line = currentLine {
            UIColor.purple.setStroke(); //current line in red
            strokeLine(line: line);
        }
    }

    //Override Touch Functions
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        print(#function) //for debugging
        let touch = touches.first!; //get first touch event and unwrap optional
        let location = touch.location(in: self); //get location in view co-ordinate
        currentLine = Line(begin: location, end: location);
        setNeedsDisplay(); //this view needs to be updated
    }

    override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
        print(#function)
        let touch = touches.first!
        let location = touch.location(in: self);
        currentLine?.end = location;

        setNeedsDisplay()
    }

    override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
        print(#function) //for debugging
        if var line = currentLine {
            let touch = touches.first!;
            let location = touch.location(in: self);
            line.end = location;
            finishedLines.append(line);
        }
        currentLine = nil;

        setNeedsDisplay()
    }

    override func touchesCancelled(_ touches: Set<UITouch>?, with event: UIEvent?) {
        print(#function) //for debugging
        currentLine = nil;
        setNeedsDisplay();

    }

    @IBInspectable var finishedLineColor: UIColor = UIColor.green {
        didSet {
            setNeedsDisplay()
        }
    }

    @IBInspectable var currentLineColor: UIColor = UIColor.purple {
        didSet {
            setNeedsDisplay()
        }
    }

    @IBInspectable var lineThickness: CGFloat = 10 {
        didSet {
            setNeedsDisplay()
        }
    }
}

Example of what I'm trying to do.

image

2 个答案:

答案 0 :(得分:0)

What you have done is directly for drawing a single a line. You use the first touch in your touch methods as well as you only have a single "current line".

I would go with something like the following. I hope the code speaks for itself:

class Line {
    var start: CGPoint = .zero
    var end: CGPoint = .zero
}

private var finishedLines: [Line] = [Line]()
private var currentLines: [UITouch: Line] = [UITouch: Line]()

private func insertNewPoint(_ point: CGPoint, forTouch touch: UITouch) {
    if let line = currentLines[touch] {
        line.end = point
    } else {
        currentLines[touch] = {
            let newLine = Line()
            newLine.start = point
            return newLine
        }()
    }
}

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
    super.touchesBegan(touches, with: event)
    touches.forEach { touch in
        insertNewPoint(touch.location(in: self.view), forTouch: touch)
    }
}

override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
    super.touchesMoved(touches, with: event)
    touches.forEach { touch in
        insertNewPoint(touch.location(in: self.view), forTouch: touch)
    }
}

private func finishLine(at point: CGPoint, forTouch touch: UITouch) {
    guard let line = currentLines[touch] else { return }
    currentLines[touch] = nil
    line.end = point
    self.finishedLines.append(line)
}

override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
    super.touchesEnded(touches, with: event)
    touches.forEach { touch in
        finishLine(at: touch.location(in: self.view), forTouch: touch)
    }
}

override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
    super.touchesCancelled(touches, with: event)
    touches.forEach { touch in
        finishLine(at: touch.location(in: self.view), forTouch: touch)
    }
}

So where you handle touches it is most important to iterate through all of them touches.forEach { touch in. And you need a collection of lines which in your case should be assigned to touches. It should work using a dictionary [UITouch: Line].

Full example:

import UIKit

class ViewController: UIViewController {

    private var finishedLines: [Line] = [Line]()
    private var currentLines: [UITouch: Line] = [UITouch: Line]()

    private var lineView: LineView?

    override func viewDidLoad() {
        super.viewDidLoad()
        self.view.isMultipleTouchEnabled = true
        self.lineView = {
            let view = LineView(frame: self.view.bounds)
            view.isUserInteractionEnabled = false
            self.view.addSubview(view)
            return view
        }()
    }

    private func updateDrawing() {
        lineView?.lines = finishedLines + currentLines.map { $0.value }
        lineView?.setNeedsDisplay()
    }

    private func insertNewPoint(_ point: CGPoint, forTouch touch: UITouch) {
        if let line = currentLines[touch] {
            line.end = point
        } else {
            currentLines[touch] = {
                let newLine = Line()
                newLine.start = point
                return newLine
            }()
        }
        updateDrawing()
    }

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        super.touchesBegan(touches, with: event)
        touches.forEach { touch in
            insertNewPoint(touch.location(in: self.view), forTouch: touch)
        }
    }

    override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
        super.touchesMoved(touches, with: event)
        touches.forEach { touch in
            insertNewPoint(touch.location(in: self.view), forTouch: touch)
        }
    }

    private func finishLine(at point: CGPoint, forTouch touch: UITouch) {
        guard let line = currentLines[touch] else { return }
        currentLines[touch] = nil
        line.end = point
        self.finishedLines.append(line)
        updateDrawing()
    }

    override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
        super.touchesEnded(touches, with: event)
        touches.forEach { touch in
            finishLine(at: touch.location(in: self.view), forTouch: touch)
        }
    }

    override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
        super.touchesCancelled(touches, with: event)
        touches.forEach { touch in
            finishLine(at: touch.location(in: self.view), forTouch: touch)
        }
    }


}

// MARK: - Line

extension ViewController {

    class Line {
        var start: CGPoint?
        var end: CGPoint?
    }

}

// MARK: - LineView

extension ViewController {

    class LineView: UIView {

        var lines: [Line]?

        override func draw(_ rect: CGRect) {
            super.draw(rect)

            // Fill white background
            UIColor.white.setFill()
            UIBezierPath(rect: rect).fill()

            // Draw all lines
            UIColor.red.setStroke()
            lines?.forEach { line in
                if let start = line.start, let end = line.end {
                    let path = UIBezierPath()
                    path.move(to: start)
                    path.addLine(to: end)
                    path.stroke()
                }
            }
        }

    }

}

You can paste this code into a newly created Xcode iOS project into your ViewController and it should work.

答案 1 :(得分:0)

Each of touchesBegan..., touchesMoved..., touchesEnded..., touchesCancelled... has set of touches as input parameter. It's guaranteed by system that each finger has same UITouch from began to ended/cancelled.
Right now you process only one finger by using touches.first. Instead you need draw separate line for each touch. Something like:

  override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
        touches.forEach { touch in
            line[touch]?.end = touch.location(in: self)
        }
        setNeedsDisplay()
    }