はじめに
前回の投稿で縦横それぞれで拡大縮小できるUIPinchGestureRecognizerを作ったが、次の様な問題点があった。
- たまにXとYのスケールが無限大になってしまう
- 拡大縮小→指を離す→拡大縮小を繰り返していると、急にXとYのスケールが変わってしまう
原因
調査してみたところそれぞれ次の様なことが原因であった。
たまにXとYのスケールが無限大になってしまう
これはtouchesBegan()が必ず、touchesMoved()よりも前に認識されるだろうという前提のコードになっており、XとYのスケールの計算時にinitPinchWidthとinitPinchHeightの初期値である0で値を割ってしまっていることが原因であった。touchesBegan()のタイミングでユーザが初めに置いた指の間隔からinitPinchWidthとinitPinchHeightを計算するため、touchesMoved()が先に呼ばれるとこの様な状態に陥ってしまう。
尚、Appleの公式のドキュメントにも下記の様に記載があるが、
When the value of this property is false (the default), views analyze touch events in UITouch.Phase.began and UITouch.Phase.moved in parallel with the receiver.
デフォルトの状態ではUIGestureRecognizerはbegan状態とmoved状態を並行して検知してしまうらしく、touchesBegan()がtouchesMoved()よりも先に呼ばれることを前提としてはいけなかった。
拡大縮小→指を離す→拡大縮小を繰り返していると、急にXとYのスケールが変わってしまう
これはオーバライドしたtouchesBegan()とtouchesMoved()の頭でスーパークラスのメソッドを呼んでしまっていることが原因であった。オーバライドしたメソッドの先頭で呼んでしまうとその時点でtouchイベントが発火して次のイベントが検知されてしまうことになる。そうなると、touchesMoved ()のなかでXとYの間隔を計算しているタイミングで次のtouchesBegan()イベントが発生してしまい、initPinchWidthとinitPinchHeightの値が書き換わり、スケールが狂ってしまっていた。
修正コード
上記2点の不具合を修正したコードは次の通り
class TwoDimentionsPinchGestureRecognizer : UIPinchGestureRecognizer { private var initPinchWidth : CGFloat = 0 private var initPinchHeight : CGFloat = 0 private var _scaleX : CGFloat = 0 private var _scaleY : CGFloat = 0 var scaleX : CGFloat { get { return _scaleX } } var scaleY : CGFloat { get { return _scaleY } } override func touchesBegan(_ touches: Set, with event: UIEvent) { guard touches.count == 2 else { return } let locations = touches.compactMap { touch in return touch.location(in: self.view) } initPinchWidth = abs(locations[0].x - locations[1].x) initPinchHeight = abs(locations[0].y - locations[1].y) super.touchesBegan(touches, with: event) } override func touchesMoved(_ touches: Set , with event: UIEvent) { guard touches.count == 2 else { return } guard initPinchWidth != 0 else { return } guard initPinchHeight != 0 else { return } let locations = touches.compactMap { touch in return touch.location(in: self.view) } let newPinchWidth = abs(locations[0].x - locations[1].x) let newPinchHeight = abs(locations[0].y - locations[1].y) _scaleX = newPinchWidth / initPinchWidth _scaleY = newPinchHeight / initPinchHeight super.touchesMoved(touches, with: event) } override func touchesEnded(_ touches: Set , with event: UIEvent) { initPinchWidth = 0 initPinchHeight = 0 super.touchesEnded(touches, with: event) } }
0 件のコメント:
コメントを投稿