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.
答案 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()
}