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

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

<img width="457" alt="00_DefaultTapArea.png" src="https://qiita-image-store.s3.amazonaws.com/0/88266/b9d8c56d-857e-395e-8545-5a74aea1a2dd.png">


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

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

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

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

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

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

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

<img width="788" alt="implementationIamge.png" src="https://qiita-image-store.s3.amazonaws.com/0/88266/90cbc8ac-9a9e-16c4-d82b-b8e74d136a9c.png">



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

[GitHub Objective-C 版](https://github.com/MilanistaDev/OCNavigationBarButtonAdjustTapArea)
[GitHub Swift 3.0 版](https://github.com/MilanistaDev/NavigationBarButtonAdjustTapArea)
[Blog(Objective-C でほぼ同じ内容)](http://milanista224.blogspot.com/2016/10/OCNavigationBarButtonAdjustTapArea.html)

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

<img width="730" alt="01_AddSubClass.png" src="https://qiita-image-store.s3.amazonaws.com/0/88266/cb37ab82-415a-554a-d336-8dcac5de70f1.png">


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

[API Reference hitTest(_:with:)](https://developer.apple.com/reference/uikit/uiview/1622469-hittest)

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

```Swift: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)
訳ありでもう一個伸びていますが，気にしないでください。

<img width="832" alt="02_Storyboard.png" src="https://qiita-image-store.s3.amazonaws.com/0/88266/3f001afe-e68d-e258-d170-4639e650c4c9.png">


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

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

<img width="1021" alt="03_ApplyNewClass.png" src="https://qiita-image-store.s3.amazonaws.com/0/88266/debf2cd9-9c57-4d95-734c-7d5202a9252a.png">


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

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

```Swift: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``` メソッドは汎用性効きそうで，
覚えておくと引き出しが広がりそうです。

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

## 参考
* [API Reference hitTest(_:with:)](https://developer.apple.com/reference/uikit/uiview/1622469-hittest)

## サンプルコード
* [GitHub Objective-C 版](https://github.com/MilanistaDev/OCNavigationBarButtonAdjustTapArea)
* [GitHub Swift 3.0 版](https://github.com/MilanistaDev/NavigationBarButtonAdjustTapArea)