何事か
あるViewがタップされたときにそれをハイライトさせたいけど、ハイライトの演出をカスタムしたいとき。
私の観測上では touchesbegan 系メソッドを利用してハイライト演出をするというコードが書かれやすいように感じます。ググり方によってはこれを使う方法がトップヒットするのでしょうか。
私はこの手法は良くないと思っているため、その問題点と改善案を紹介します。
よくない例
例えば以下のようなコードです。
ハイライト時に表示される用のViewを用意し、 touchesBegan
系メソッドをオーバーライドしてそのViewの isHidden
を書き換えるパターン。
class BadHighlightView: UIView {
var highlightView: UIView!
init() { ... } // highlightView を生成して配置
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
highlightView.isHidden = false
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
highlightView.isHidden = true
}
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
highlightView.isHidden = true
}
}
なにがいけないのか
touchesEnded
及び touchesCancelled
は呼ばれないことがあるため、容易にハイライト状態が維持されて表示がグリッチします。
言い直すと、 touchesBegan
が呼び出されたあとに touchesEnded
または touchesCancelled
が呼ばれる保証がないため ハイライトされっぱなしになる ケースがあります
呼ばれないケースの例
- 1本の指でタップしながらもう1本の指で他のボタンを操作するなどして画面遷移したとき
- タップしながらホームボタンを押したとき(OSバージョンによって挙動が違うかも)
- 複数タップでごちゃごちゃやったとき。条件不明
改善案
経験上、 UIView
で作ってる画面構成パーツのどれかを UIButton
か UIControl
にするだけで解決することが多いように思います。
-
UIButton
を配置し setBackgroundImage:forState: で背景色を指定した画像をセットする - カスタムView自身の親クラスを
UIControl
にしてisHighlighted
をオーバーライドする
UIControl
のハイライト状態の管理はUIKitがよしなにやってくれるため上記のようなケースにおいてもハイライト状態が維持されるようなことはありません。
既存構成要素だけではうまくいかない場合は以下のような簡単なハンドル用Viewを差し込むのもありだと思います。
class HighlightHandleView: UIControl {
var highlightChanged: ((HighlightHandleView, Bool) -> ())?
override var isHighlighted: Bool {
didSet {
highlightChanged?(self, isHighlighted)
}
}
}
まとめ
画面要件によって最適な実装の形は変化しますが、原則としてUIKitが標準で提供しているメソッドをそのまま使ったほうが良いです。