UIView の Swipe イベントは実行して Tap は後ろの View に流したいけど出来なさそう

とある View の上に透明な UIView を載せて、 Swipe だったら透明な View でイベントをハンドリングして Tap だったら後ろの View にスルーしようとしたけど出来なさそうだった。

どういうことか

f:id:star__hoshi:20171114210748p:plain

こういう View があって、右にスワイプしたら数値を200にして、左にスワイプしたら数値を100にしたい。でもボタンタップや TextFeild の入力はできるようにしたい。
この View の上に透明な View を被せて Swipe イベントを取って、 Tap イベントだったら後ろの TextField やボタンをタップできるようにすれば良さそうじゃんって最初は思った。

でも出来なさそう。

View のイベントを後ろにある View に流す方法

1. isUserInteractionEnabled = false

isUserInteractionEnabled = false にすると透明な View のタップイベントがスルーされ後ろにある View をタップできる。
しかしこれだと Swipe もスルーされてしまうのでだめ。

2. hitTest:withEvent:

hitTest を override して、 return nil しても後ろの View にイベントを流せる。しかし Swipe も反応しなくなってしまうのでダメ。

if event?.type == .swipe { } ができればよかったが、UIEventType は touches, motion, remoteControl, presses の4種類しかなく、 Tap もスワイプも touches になってしまいダメだった。

3. point(inside:with:)

hitTest と似てる point というものあって、これはタップの判定位置を広くしたり狭くしたりできるっぽい。
ここで false を返すと後ろの View にイベントを流せる。
(しかしそういう用途には hitTest を使うべきで point は純粋にタップ範囲の変更に使った方が良さそう。)

結局どうしたか

透明な UIView を上に貼って、 Tap だったら後ろに流すというのは難しそう。
なので、透明な UIView の中にボタンや TextView を addSubview した。

class SwipableView: UIView {
    let leftSwipeGesture = UISwipeGestureRecognizer()
    let rightSwipeGesture = UISwipeGestureRecognizer()

    override init(frame: CGRect) {
        super.init(frame: frame)

        leftSwipeGesture.direction = .left
        rightSwipeGesture.direction = .right
        addGestureRecognizer(leftSwipeGesture)
        addGestureRecognizer(rightSwipeGesture)
    }

    required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") }
}
        let swipableView = SwipableView()
        contentView.addSubview(swipableView)

        swipableView.leftSwipeGesture.rx.event.subscribe { event in
            // left event handler ...
        }
        aIVSwipeView.rightSwipeGesture.rx.event.subscribe { event in
            // right event handler ...
        }

        let textField = UITextField()
        swipableView.addSubView(textField)
        let button = UIButton()
        swipableView.addSubView(button)

これで Swipe した時はそれぞれハンドリング出来て、 textField や button のイベントはそれぞれちゃんと扱える。

最初からこうしろよ、という話なのだがこれをすると既存の View 構造を変えないといけないので気が重かった、透明な View を上に貼り付けるだけなら既存の View 構造は変えなくて済むので...。

遠回りだったかもしれないけど hitTest や pointinside の知識が深まってよかった。