UIButtonのタップ領域のみを拡大する (Interface Builderで領域のプレビューもする)

# はじめに

### なぜやるのか
iOSアプリのボタン、`デザインは良いけど、タップ領域が小さすぎて押しづらい`という事、ありませんか?
例えば、下記のサンプルのボタン。ボタンのデフォルトサイズで、System Imageを設定したよくある情報ボタンです。
しっかしこれがくせ者。サイズは`20x20px`とデザイン上はよくあるのですが、タップするには小さく認識に苦労します。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136288/26f40d7e-1d0e-9f57-a00d-8bdbec356ce1.png" width="300">

確かに、Appleの公開している、[Apple Interface Guideline](https://developer.apple.com/jp/design/tips/) によると、ボタンサイズは少なくとも `44 x 44 px`以上とする事が推奨されています。

しかし`44 x 44 px`は案外大きく、ボタンのサイズを物理的に大きくするだけでは、不都合が生じる事があります。
そこで今回は、ボタンの大きさはそのままに、タップ範囲のみを拡張して、かつ Interface Builder上でプレビューしながら作れる様にUIButtonをカスタムしていきたいと思います。

# UIButtonをカスタムする
ボタンの大きさはそのままに、タップ範囲のみを拡張して、かつ Interface Builder上でプレビュー可能なカスタムボタン`TapAreaExpandableButton`を作成します。

```Swift
import UIKit

// @IBDesignableを付与する事で、Storyboard上に設定した値が反映される
@IBDesignable 
class TapAreaExpandableButton: UIButton {
    private var _minTapArea = CGSize(width: 0, height: 0)
    private var _previewArea = false

    // @IBInspectableを付与した変数は、Interface Builder側で編集できる
    @IBInspectable var minTapArea: CGSize {
        get { return _minTapArea }
        set { _minTapArea = newValue }
    }
    
    @IBInspectable var previewArea: Bool {
        get { return _previewArea }
        set { _previewArea = newValue }
    }
    
    open override func point(inside point: CGPoint, with _: UIEvent?) -> Bool {
        if isHidden || !isUserInteractionEnabled { return false }
        return expandedArea.contains(point)
    }
    
    private var expandedArea: CGRect {
        let buttonSize = self.bounds.size
        let widthToAdd = max(_minTapArea.width - buttonSize.width, 0)
        let heightToAdd = max(_minTapArea.height - buttonSize.height, 0)
        return self.bounds.insetBy(dx: -widthToAdd / 2, dy: -heightToAdd / 2)
    }
    
    override func prepareForInterfaceBuilder() {
        super.prepareForInterfaceBuilder()
        if previewArea { setupPreviewArea() }
    }
    
    private func setupPreviewArea() {
        let tapGuideView = UIView(frame: expandedArea)
        let previewLineColor = Asset.Color.themeMidGray.color.cgColor
        let lineLayer = CAShapeLayer()
        lineLayer.frame = tapGuideView.bounds
        lineLayer.strokeColor = previewLineColor
        lineLayer.lineWidth = 1
        lineLayer.lineDashPattern = [2, 2]
        lineLayer.fillColor = nil
        lineLayer.path = UIBezierPath(rect: lineLayer.frame).cgPath
        tapGuideView.isUserInteractionEnabled = false
        tapGuideView.backgroundColor = UIColor.clear
        tapGuideView.isHidden = false
        tapGuideView.layer.addSublayer(lineLayer)
        addSubview(tapGuideView)
    }
}

```

# 使い方
先に出てきた、情報ボタンが1個だけ存在するViewを例とします。このボタンは`20 x 20px`と小さくタップが難しいです。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136288/aab65242-ce81-8504-efc4-f3b3ebf09787.png" width="300">

そこで `TapAreaExpandableButton` をカスタムクラスとして指定します。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136288/22a8fd79-e247-73f2-a42a-3191e2111a33.png" width="500">

するとプロパティ設定に `TapAreaExpandableButton` の項目が出現しますので、
Min Tap Areaの項目に`44 x 44px`と設定して、Preview Areaの項目を`ON`にします。
すると、下図の様に設定したMin Tap AreaがStoryBoard上でプレビューされます。
※もしプレビューが表示されない場合は、Xcodeの`Editor -> Automatically Refresh Designable View`のチェックをONにしてお試しください。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136288/680f159b-1b99-21ba-40a3-51b07b068fc2.png" width="500">

# おわりに
タップ可能領域をプレビューしながら設定できるため、ボタンの大きさを微調整する手間を大きく減らす事ができると思います。本記事が少しでも参考になったのであれば幸いです。

# 参考
https://qiita.com/KokiEnomoto/items/264f26bfa92d06b1996e

https://qiita.com/KikurageChan/items/9d0e3ba82e6492e02d12