【iOS】ビューを凹んだように見せて「押した」感を演出する

iOS アプリで、ボタン以外にタップ可能なビューを置きたいケースがあります。例えば、[マンガ向け・ストーリー構成力養成ドリル drill](https://drill.5thfloor.co.jp/) というアプリで以下のようなカードっぽいビューを作る機会がありました。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/65644/0d51a77b-a529-9ca7-c44e-862f9faf630b.png" width="320">


このカードはタップすると次の画面に遷移しますが、そのままだとフィードバックが何もなく、「ちゃんとタップされた」かどうか不安になりそうでした。

そこで、ビューを視覚的に凹んだように見せることで、「押した」感のあるフィードバックを実現しました。意外と情報がなかったのでメモしておきます。

# ガイドラインを調べる

早速実装…といきたいことろですが、もし「押した」感の演出で公式で推されている表現があるならそれに従っておきたかったので、最初に簡単にガイドラインを調べました。

## Human Interface Guidelineでは？

Apple 公式の UI デザインのガイドラインである [Human Interface Guidelines](https://developer.apple.com/design/human-interface-guidelines/) をざっと眺めてみましたが、タップに対する視覚的なフィードバックの記述が見つかりません…　ありそうなものですが。見逃しているだけ？:thinking:


## Material Designでは？

Android アプリの UI ガイドラインである Material Design も調べてみました。Material Design のコンポーネントは、押した部分から波紋が広がるようなエフェクトがついています。このエフェクトには [Ripple](https://material.io/develop/ios/components/ripple/) という名前がついているようです。

~~ただ、なぜか Material Design のページからは Ripple の記述が削除されてしまっていました。obsolete になった…？:thinking:~~
~~https://material.io/go/design-ripple/ [^1]~~

2020/02/10追記: @mono0926 さんに現在の記載箇所を教えていただきました:raised_hands:
https://material.io/design/interaction/states.html#pressed


実は、Ripple も試してみたのですが、非常に「Android 感」が出てしまったので、iOS 版では違う表現を使うことにしました。

# 公式アプリを参考にする

ガイドラインにはあまり情報が無さそうだったので、実際のアプリを見て参考にしてみます。

Apple 公式の「App Store」アプリなどでは、カードっぽいものを押すとわずかにサイズが小さくなって、「凹んだ」ような感じになって押したことが視覚的にわかりやすくなっています。

![7edc9634-c6db-1b9e-a734-f5865f71b8ad.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/65644/97ddc7cd-76fb-ba51-c358-1d5e792e71ea.gif)

確かにこんな感じにすれば、押した感を出せそうです。今回はこの表現を真似します。


# 実装

まさにな記事があったのでほぼこちらを参考にしました:pray:

[SwiftでUIButtonのタップ時に押された感のアニメーション - Qiita](https://qiita.com/osamu1203/items/cc22cdf750dde3782448)

ただ、書き方が古くてそのままではコンパイルできなかったので、最新の UIKit のメソッドやプロパティに合わせて書き換えました。Xcode 10.2.1、Swift 5 で試しています。

以下のようなクラスを作って、これを継承すると、押した感のあるフィードバックがあるビューが作れます。

```swift
import UIKit

class TouchFeedbackView: UIView {
    // タップ開始時の処理
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        super.touchesBegan(touches, with: event)
        self.touchStartAnimation()
    }

    // タップキャンセル時の処理
    override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
        super.touchesCancelled(touches, with: event)
        self.touchEndAnimation()
    }

    // タップ終了時の処理
    override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
        super.touchesEnded(touches, with: event)
        self.touchEndAnimation()
    }

    // ビューを凹んだように見せるアニメーション
    private func touchStartAnimation() {
        UIView.animate(withDuration: 0.1,
                       delay: 0.0,
                       options: UIView.AnimationOptions.curveEaseIn,
                       animations: {
                        // 少しだけビューを小さく縮めて、奥に行ったような「凹み」を演出する
                        self.transform = CGAffineTransform(scaleX: 0.95, y: 0.95)
        },
                       completion: nil
        )
    }

    // 凹みを元に戻すアニメーション
    private func touchEndAnimation() {
        UIView.animate(withDuration: 0.1,
                       delay: 0.0,
                       options: UIView.AnimationOptions.curveEaseIn,
                       animations: {
                        // 元の倍率に戻す
                        self.transform = CGAffineTransform(scaleX: 1.0, y: 1.0)
        },
                       completion: nil
        )
    }
}

// 使用例
class LessonCardView: TouchFeedbackView {
    // ...
}
```

課題: `UIButton` など `UIView` 以外の親クラスを持ちたいビューではこれを使えない

こんな感じの表示になります:raised_hands:[^2]

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/65644/90da5c75-ddaa-ca17-b3b2-85d5aaed2a87.gif" width="320">


# まとめ

ビューを視覚的に凹んだように見せることで、「押した」感を演出する表現を紹介しました。実際に触ってみるとわかるのですが、これがあるのとないのとでは大きく体験が違います。ちょっとした工夫でユーザー体験が向上するので、ぜひ参考にしてみてください:pray:


[^1]: Material Design の404ページはメタくて面白い。
[^2]: この例は効果をわかりやすくするために、やや大げさに縮小率を設定しています。実際のアプリはもう少し控えめです。