前置き
UIButtonなどの同時タップにより起きる不具合は多い。
例えば、UINavigationControllerなどを使っている画面で、pushするボタンを同時タップすれば同時に2つのViewControllerがpushされ、意図した画面階層にならなくなる。
(私はこれを”ViewController階層が破壊された”と表現している)
同時タップを禁止する
それを防ぐには簡単で、UIViewで定義されているexclusiveTouchをtrueにしてあげれば良い。
button1.exclusiveTouch = true
button2.exclusiveTouch = true
これでbutton1
とbutton2
の同時タップは禁止される。
再帰的に適用する
こんな感じで簡単にできるものなのだが、いかんせん 面倒な上に設定漏れがあったらアウト である。
そもそも 同時タップを有効にしたい方が稀 なので、デフォルトですべての同時タップを禁止したいくらいである。
というわけで以下のように再帰的に適用するコードを書くと便利な気がした。
import UIKit
extension UIViewController {
func exclusiveAllTouches() {
self.exclusiveTouches(self.view)
}
private func exclusiveTouches(view: UIView) { // 再帰関数
for view in view.subviews {
let aView = view as UIView
aView.exclusiveTouch = true
self.exclusiveTouches(aView)
}
}
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.exclusiveAllTouches() // これを呼ぶだけで同時タップが防げる
// TODO: 同時タップを許したいViewだけ個別にexclusiveTouchをfalseに
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
おまけ
Swift使いだったら以下のように書くのがスマート(たぶん)
extension UIViewController {
func exclusiveAllTouches() {
self.applyAllViews { $0.exclusiveTouch = true }
}
func applyAllViews(apply: (UIView) -> ()) {
apply(self.view)
self.applyAllSubviews(self.view, apply: apply)
}
private func applyAllSubviews(view: UIView, apply: (UIView) -> ()) {
let subviews = view.subviews as [UIView]
subviews.map { (view: UIView) -> () in // Swiftのmapって戻り値なくても大丈夫なのね(ちょっと不気味
apply(view)
self.applyAllSubviews(view, apply: apply)
}
}
}