はじめに
前回の投稿で縦横それぞれで拡大縮小できる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 件のコメント:
コメントを投稿