LoginSignup
4
4

More than 5 years have passed since last update.

【Swift 3】NavigationBar のボタンのタップ可能領域を NavigationBar内のみに限定する

Posted at

はじめに

iOS 端末のユーザだとよく知っていることですが,
通常普通に実装すると NavigationBar のボタンの
押せる領域はボタンだけでなくボタンの周りも反応します。
試した感じだと下記の領域でした。

00_DefaultTapArea.png

NavigationBar から下も少しではありますがタップ可能な領域に入ります。
今回,この NavigationBar からはみ出した部分が
タップできることで端末のサイズによっては
コンテンツに不具合が起こることがあったので
不具合が起きないように対処することになりました。

実装方針

考えた実装方針は下記になります。

1. コンテンツを配置する位置を中央側にずらす
2. UINavigationBarItem のタップ領域を制限する
3. タップ領域を NavigationBar 内のみに限定する

最初の方法,Storyboard で配置する部品の位置ををずらせばいいじゃん。
というのがまずは思いつきましたが,画面サイズによっては
バランスが悪くなってしまい,端末サイズによる
場合分けはちょっと・・・ということでサブ案に。

二番目の方法はじゃぁ具体的にどのくらいの領域にするの?
ということで具体的な数値の指示が来なかったのでこちらもサブ案に。

三番目がデザインも壊さないし,タップできる領域を大幅に
いじるのもなんかなぁということで UX も考えたら,これが
一番現実的ではないかということで今回はこれを採用しました。

実装イメージは下図の通りです。

implementationIamge.png

実装

実装自体は難しくないです。
UINavigationController のサブクラスを新規追加して,
そのクラス内でタップされた座標が NavigationBar の
内側なら採用,外側ならタップアクションを捨てるって感じです。
最後に Storyboard の NavigationController に割り当てる。
GitHub にサンプルを用意しましたので気になる方は Clone してみてください。

GitHub Objective-C 版
GitHub Swift 3.0 版
Blog(Objective-C でほぼ同じ内容)

1. UINavigationController のサブクラスを追加

File -> New -> File… でサブクラス UINavigationController を追加。
名前は MainNavigationController にしました。

01_AddSubClass.png

2. MainNavigationController に実装

hitTest というメソッド(iOS 8 以上)を Override して使います。
Apple の Developer サイトの API レファレンスをご覧ください。

API Reference hitTest(_:with:)

タップされた座標が NavigationBar の内側なら
ユーザインタラクションを有効,外側なら無効にします。
無効だと実質ボタンは押せないので押したことにならないはず。

MainNavigationController.swift
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {

    if (self.point(inside: point, with: event)) {
        self.isUserInteractionEnabled = true
    } else {
        self.isUserInteractionEnabled = false
    }
    return super.hitTest(point, with: event)
}

他に色系の設定などもこのクラスでやってもいいです。

3. クラスを割り当てる

今回は Storyboard を使います。
NavigationController を追加して,ViewController につなげる。
NavigationBarItem も追加しておきます。(もちろんコードでもOK)
訳ありでもう一個伸びていますが,気にしないでください。

02_Storyboard.png

↑この段階でビルドすると,デフォルトのタップ領域がわかります。

NavigationController の方のクラスに
MainNavigationController を割り当てます。

03_ApplyNewClass.png

これで実行してみると,NavigationBar の下部分の
タップができなくなっていると思います。

問題点

トップの NavigationController に割り当てるので,
この配下にある ViewController 全てに影響を与えます。
この画面だけーということであれば,該当の画面の
ViewController に Extension とかで拡張してあげれば良いと思います。
こんな感じですかね。

ViewController.swift
extension UINavigationBar {
    open override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {

        if (self.point(inside: point, with: event)) {
            self.isUserInteractionEnabled = true
        } else {
            self.isUserInteractionEnabled = false
        }
        return super.hitTest(point, with: event)
    }
}

おわりに

今回は,NavigationBar のボタンが押せる領域を
NavigationBar の内側だけにする実装について書きました。
そもそも論ではありますが,こういうことが起きないような
画面設計にするべきではあります。
今回 Override した hitTest メソッドは汎用性効きそうで,
覚えておくと引き出しが広がりそうです。

ここまでご覧いただき,ありがとうございました。
もっとこうしたら楽だよ〜とかこういうやり方もあるよ〜
ということがありましたらコメントでご教示いただければ嬉しいです。

参考

サンプルコード

4
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
4