"Learn how to create a simple app that handles coalesced touches."
통합된 터치를 처리하는 간단한 앱 생성 방법을 알아봅니다.
Figure 1은 터치를 캡처하고 결과 경로를 화면에 렌더링하는 간단한 드로잉 앱을 보여줍니다. 앱은 UIKit
이 알려주는 모든 터치를 추적합니다. 통합된 터치도 포함합니다. 앱은 하나의 터치 지점에서 다음까지 이어지는 라인 세그먼트들을 그리는 것을 통해 경로를 빌드합니다.
Figure 1 Using coalesced touches for drawing
앱의 메인 뷰는 Stroke
객체의 집합을 빌드하기 위한 들어오는 터치 이벤트들을 사용합니다. Listing 1은 Stroke
클래스의 정의를 나타내고 있고, 각 터치 이벤트에 대한 정보를 저장하는 StrokeSample
클래스 또한 보여주고 있습니다.
Listing 1 Storing touch information in the Stroke class
class Stroke {
var samples = [StrokeSample]()
func add(sample: StrokeSample) {
samples.append(sample)
}
}
struct StrokeSample {
let location: CGPoint
let coalescedSample: Bool
init(point: CGPoint, coalesced : Bool = false) {
location = point
coalescedSample = coalesced
}
}
메인 뷰는 StrokeCollection
클래스를 사용해 생성되었던 Stroke
객체의 컬렉션을 유지합니다. 이 클래스의 strokes
속성은 완성된 strokes
를 저장하고, activeStroke
속성은 현재 수정되고 있는 stroke
객체를 포함시킵니다. acceptActiveStroke
메소드를 호출하는 것은 활성화된 stroke
를 완성된 strokes
의 집합으로 이동시킵니다.
Listing 2 Storing user-generated strokes
class StrokeCollection {
var strokes = [Stroke]()
var activeStroke: Stroke? = nil
func acceptActiveStroke() {
if let stroke = activeStroke {
strokes.append(stroke)
activeStroke = nil
}
}
}
Listing 3은 새 Stroke
객체를 생성하는 메인 드로잉 뷰의 일부분을 보여주고 있습니다. 뷰는 다중 터치를 지원하지 않기 때문에 첫 번째 터치 이벤트만 추적될 필요가 있습니다. touchesBegan(_:with:)
메소드는 새 stroke
객체를 생성하고, 이를 활성화된 stroke
로 표시합니다. 새 터치 데이터는 활성화된 stroke
에 추가됩니다. 이는 stroke
가 stroke
컬렉션에 허용되는 시점인 touchesEnded(_:with:)
메소드가 호출되는 시점까지 계속됩니다. 만약 터치 연쇄가 어떠한 이유로 방해받을 경우 touchesCancelled(_:with:)
메소드는 현재 활성화된 stroke
를 포기합니다.
Listing 3 Receiving touches in the main view
class DrawingView : UIView {
var strokeCollection: StrokeCollection? {
didSet {
// If the strokes change, redraw the view's content.
if oldValue !== strokeCollection {
setNeedsDisplay()
}
}
}
// Initialization methods...
// Touch Handling methods
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
// Create a new stroke and make it the active stroke.
let newStroke = Stroke()
strokeCollection?.activeStroke = newStroke
// The view does not support multitouch, so get the samples
// for only the first touch in the event.
if let coalesced = event?.coalescedTouches(for: touches.first!) {
addSamples(for: coalesced)
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
if let coalesced = event?.coalescedTouches(for: touches.first!) {
addSamples(for: coalesced)
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
// Accept the current stroke and add it to the stroke collection.
if let coalesced = event?.coalescedTouches(for: touches.first!) {
addSamples(for: coalesced)
}
// Accept the active stroke.
strokeCollection?.acceptActiveStroke()
}
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
// Clear the last stroke.
strokeCollection?.activeStroke = nil
}
// More methods...
}
DrawingView
의 터치 입력 메소드는 새 터치들을 활성화된 stroke
에 통합시키기 위해 addSamples
메소드(Listing 4에서 볼 수 있는)를 사용합니다. 이 메소드는 각각의 터치 지점마다 새 StrokeSample
을 생성하고, 해당 샘플을 활성화된 stroke
에 추가시킵니다. 예제는 플래그는 내부적으로 터치를 통합시킵니다. 하지만 터치는 시스템에 의해 알려진 regular 터치와 다르지 않습니다.
Listing 4 Adding touches to the active stroke
func addSamples(for touches: [UITouch]) {
if let stroke = strokeCollection?.activeStroke {
// Add all of the touches to the active stroke.
for touch in touches {
if touch == touches.last {
let sample = StrokeSample(point: touch.preciseLocation(in: self))
stroke.add(sample: sample)
} else {
// If the touch is not the last one in the array,
// it was a coalesced touch.
let sample = StrokeSample(point: touch.preciseLocation(in: self),
coalesced: true)
stroke.add(sample: sample)
}
}
// Update the view.
self.setNeedsDisplay()
}
}
Note
애플 펜슬로부터 오는 드로잉 입력을 캡처하려는 경우 더 정확한 터치 정보를 얻기 위해location(in:)
메소드 대신preciseLocation(in:)
메소드를 사용할 수 있습니다.preciseLocation(in:)
메소드는 오직 드로잉 관련 입력을 캡처하기 위해서만 사용되어야 합니다. 인터페이스에 대한 일반적인 상호작용은location(in:)
메소드를 사용해서 터치 위치를 가져올 수 있도록 해야 합니다.
DrawingView
클래스의 남아있는 메소드는 터치 샘플들을 가져오고 이들을 렌더링된 출력으로 바꿔줍니다. 앱의 Clear
버튼은 뷰의 현재 StrokeCollection
객체를 해제하고 새로운 하나를 생성합니다.