Incorporating Predicted Touches into an App

17831 ワード

https://developer.apple.com/documentation/uikit/touches_presses_and_gestures/handling_touches_in_your_view/minimizing_latency_with_predicted_touches/incorporating_predicted_touches_into_an_app
"Learn how to create a simple app that incorporates predicted touches into its drawing code."
予測されたコンタクトを図面コードにマージするための簡単なアプリケーションの作成方法について説明します.

Overview


例示的なアプリケーションSpeed Sketch(「平均接触図形描画」アプリケーションを参照)は、図面描画時に予測接触を使用して遅延を最小限に抑える.アップルのペンや指を使うときは、すべての内容が含まれています.コンタクト収集のキークラスはStrokeGestureRecognizerクラスである.各タッチイベントの新しい連鎖により、アプリケーションの描画キャンバスにStrokeオブジェクトが作成されます.Strokeオブジェクトは、線図のスタイルを適用するために必要なタッチデータを格納し、カレンダペン、通常ペン、および各分割タッチイベントに線分を描画する特殊なデバッグモードを使用してレンダリングできます.
Figure 1 Speed Sketch drawing modes

Collect the Touch Input

StrokeGestureRecognizerクラスは、図面に関連するタッチ入力を収集し、Strokeオブジェクトを作成し、レンダリングされたパスを表す.実際に発生したタッチに加えて、このコースでは予測されたタッチもすべて収集します.Listing 1は,ジェスチリコ・グナゼ予測のタッチを収集するappend法の一部を示している.このコードで呼び出されたコレクタブロックは、各トリガイベントを処理します.このブロック内のパラメータは、タッチが実際のタッチであるか予測タッチであるかを示す.
Listing 1 Collecting predicted touches in Speed Sketch
// Collect predicted touches only while the gesture is ongoing. 
if (usesPredictedSamples && stroke.state == .active) {
   if let predictedTouches = event?.predictedTouches(for: touchToAppend) {
      for touch in predictedTouches {
         collector(stroke, touch, view, false, true)
      }
   }
}
タッチ入力のセットによって、StrokeSampleオブジェクトが作成され、現在Strokeオブジェクトに追加されます.Strokeオブジェクトは、予測されたタッチポイントを別のタッチポイントから切断することによって記憶する.取り外すと、後で削除しやすくなり、実際のタッチ入力と混同されません.アプリケーションが実際のタッチセットを追加するたびに、予測サンプルのセットが破棄されます.
Listing 2はStrokeクラスの一部を示している.このセクションには、直線に関連するタッチが表示されます.タッチの各セットについて、クラスは実際のタッチをサンプルのメインリストに追加します.すべての予測タッチはpredictedSamples属性に格納される.StrokeGestureRecognizerStrokeメソッドの追加を呼び出すたびに、メソッドは最後の予測されたタッチのセットをpreviousPredictedSamplesに移動し、最終的に破棄する.したがって、Strokeは、最後の予測タッチセットのみを保持する.
Listing 2 Managing predicted samples in the Stroke
class
class Stroke {
    static let calligraphyFallbackAzimuthUnitVector = CGVector(dx: 1.0, dy:1.0).normalize! 
    var samples: [StrokeSample] = []
    var predictedSamples: [StrokeSample] = []
    var previousPredictedSamples: [StrokeSample]?
    var state: StrokeState = .active
    var sampleIndicesExpectingUpdates = Set<Int>()
    var expectsAltitudeAzimuthBackfill = false
    var hasUpdatesFromStartTo: Int?
    var hasUpdatesAtEndFrom: Int? 
    var receivedAllNeededUpdatesBlock: (() -> ())?
 
    func add(sample: StrokeSample) -> Int {
        let resultIndex = samples.count
        if hasUpdatesAtEndFrom == nil {
            hasUpdatesAtEndFrom = resultIndex
        }
 
        samples.append(sample)
        if previousPredictedSamples == nil {
            previousPredictedSamples = predictedSamples
        }
 
        if sample.estimatedPropertiesExpectingUpdates != [] {
            sampleIndicesExpectingUpdates.insert(resultIndex)
        }
 
        predictedSamples.removeAll()
        return resultIndex
    } 
 
    func addPredicted(sample: StrokeSample) {
        predictedSamples.append(sample)
    } 
 
    func clearUpdateInfo() {
        hasUpdatesFromStartTo = nil
        hasUpdatesAtEndFrom = nil
        previousPredictedSamples = nil
    } 
 
    // Other methods...
}

Render the Predicted Touches


レンダリング中、アプリケーションは予測されたタッチを実際のタッチと見なします.各Strokeオブジェクトの内容は、1つまたは複数のStrokeSegmentオブジェクトに分割される.StrokeSegmentオブジェクトは、図形描画コードがStrokeSegmentIteratorオブジェクトを使用してインポートされたオブジェクトです.Listing 3は、このクラスの実施を示す.図面コードがstroke例繰り返されると、sampleAt方法は実際のタッチ例を返す.メソッドが実際のイテレーションを返すと、イテレーションはすべての予想されるイテレーションを返します.したがって、予測される接触は常にスクライブの末端にあるべきである.
Listing 3 Fetching predicted touches during drawing
class StrokeSegmentIterator: IteratorProtocol {
    private let stroke: Stroke
    private var nextIndex: Int
    private let sampleCount: Int
    private let predictedSampleCount: Int
    private var segment: StrokeSegment!
 
    init(stroke: Stroke) {
        self.stroke = stroke
        nextIndex = 1
        sampleCount = stroke.samples.count
        predictedSampleCount = stroke.predictedSamples.count
        if (predictedSampleCount + sampleCount > 1) {
            segment = StrokeSegment(sample: sampleAt(0)!)
            segment.advanceWithSample(incomingSample: sampleAt(1))
        }
    } 
 
    func sampleAt(_ index: Int) -> StrokeSample? {
        if (index < sampleCount) {
            return stroke.samples[index]
        }
        let predictedIndex = index - sampleCount
        if predictedIndex < predictedSampleCount {
            return stroke.predictedSamples[predictedIndex]
        } else {
            return nil
        }
    }
 
    func next() -> StrokeSegment? {
        nextIndex += 1
        if let segment = self.segment {
            if segment.advanceWithSample(incomingSample: sampleAt(nextIndex)) {
                return segment
            }
        }
        return nil
    }
}