【Vision OS】視線に合わせてエフェクトを追加したい！

# 概要

本記事では、視覚的な変化を与えるためのエフェクト、[HoverEffect](https://developer.apple.com/documentation/swiftui/hovereffect)についてご紹介します。


Apple Vision Proでは、スマホよりも操作感を意識する必要があります。
例えば、スマホで言えば、タップをするだけで良いですが、
Apple Vision Proでは、[ジェスチャーでの操作](https://support.apple.com/ja-jp/guide/apple-vision-pro/tan1e2a29e00/visionos)が一般的になります。
こうした違いから、Apple Vision Proでも操作しやすいように視覚的な違いを設けるべきかと思います。

※[WWDC2024](https://developer.apple.com/jp/videos/play/wwdc2024/10152)で紹介されたものベースです。

# 開発環境

Xcode:  16.3
Vision OS: 2.4

# HoverEffectとは

公式から引用すると、
> An effect applied when the pointer hovers over a view.

とあります。
Vision OSでは、視線がポインタとなります。
つまり、HoverEffectを与えたViewに対して、視線が向くとエフェクトが発生します。

# 最終的な動作

視線が向くと、ボタンが拡大します。
その後、横に拡張し、Addという文字が表示されます。

![output.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/669204/cbbf8b6b-05f0-4f86-a057-621bfd4d86f3.gif)

※マウスカーソルが視線の代用です

# 実際に作ってみる

拡大と文字のフェードエフェクトを作成します。
それぞれをカスタムエフェクトとして定義します。

## 拡大エフェクト

まずは、視線が向いた時に拡大されるようなものを作ってみましょう。

HoverEffectをカスタムするには、「[hoverEffect(in:isEnabled:body:)](https://developer.apple.com/documentation/swiftui/view/hovereffect(in:isenabled:body:))」を利用します。
| 引数 | 説明 |
|---|---|
| effect | 与えたいエフェクトをかけることでエフェクト効果を追加できる |
|isActive | 視線が向いたか |
| proxy | View情報 |
```swift
.hoverEffect{ effect, isActive, proxy in
}
```

例えば、
```swift
.hoverEffect{ effect, isActive, proxy in
    effect.scaleEffect(isActive ? 1.05, 1.0)
}
```
とすると、Viewに対して視線が向くと拡大するようなエフェクトを追加できます。

今回の場合、円から楕円に大きくなるアニメーションも必要なので、clipShapeを利用してアニメーションを追加します。
```swift
struct ExpandEffect: CustomHoverEffect {
    func body(content: Content) -> some CustomHoverEffect {
        content.hoverEffect{ effect, isActive, proxy in
            effect
                .animation(.default.delay(isActive ? 0.8 : 0.2)) {
                    $0.clipShape(
                        .capsule.size(
                            width: isActive ? proxy.size.width : proxy.size.height,
                            height: proxy.size.height,
                            anchor: .trailing
                        )
                    )
                }
                .scaleEffect(isActive ? 1.05 : 1)
        }
    }
}
```

おおよそこれで見た目は完成します。
アニメーションに遅延をかけているのは、視線を向けた瞬間に大きく変化してしまうとユーザー体験を損なってしまうためです。まず、視線が向いたことを円の拡大表示で示した後に、テキストまで表示するよう楕円に拡大します。

## フェードエフェクト

細かいですが、描画後に文字をフェードインするようにしてみます。
拡大エフェクトと同様に、「hoverEffect(in:isEnabled:body:)」を利用します。

```swift
struct FadeEffect: CustomHoverEffect {
    func body(content: Content) -> some CustomHoverEffect {
        content.hoverEffect { effect, isActive, _ in
            effect.animation(.default.delay(isActive ? 1.0 : 0.0)) {
                $0.opacity(isActive ? 1.0 : 0.0)
            }
        }
    }
}
```
拡大エフェクトを考慮して、こちらにも遅延をかけます。

## エフェクトをつける

WWDC2024のサンプルでは、拡大エフェクトは、ButtonStyleとして定義していましたので、本記事でもButtonStyleを定義してみます。
```swift
struct CustomButtonStyle: ButtonStyle {
    func makeBody(configuration: Configuration) -> some View {
        configuration.label
            .background(.thinMaterial)
            .hoverEffect(.highlight)
            .hoverEffect(ExpandEffect())
    }
}
```
ここで、視線が向いた時に拡大エフェクトとハイライトをかけます。

最後に、Viewに対して、作成してButtonStyleとエフェクトをかけていきます。
フェードエフェクトは、文字だけにかかってほしいので、Textに対してかけます。
```swift
struct AddButton: View {
    /// アクション
    var action: () -> Void

    // MARK: - body
    
    var body: some View {
        Button(action: action) {
            HStack(spacing: 12) {
                Text("Add")
                    .font(.title)
                    .foregroundColor(.white)
                    .hoverEffect(FadeEffect())

                Image(systemName: "plus")
                    .font(.largeTitle)
            }
            .padding()
        }
        .buttonStyle(CustomButtonStyle())
        .hoverEffectGroup()
    }
}
```

ここで大事になってくるのが、「[.hoverEffectGroup()](https://developer.apple.com/documentation/swiftui/view/hovereffectgroup(_:))」です。
このモディファイアをつけることで、このView全体を暗黙的に同じEffectGroupにしてくれます。同じEffectGroupにしなかった場合、ボタンが楕円に拡張した後、文字がある部分に視線を向けるまで、文字がフェードインしてくれません。

明示的にも指定はできますが、複雑なことをしない限りは暗黙的なグループの定義で問題ないです。

これで完成です。

# さいごに

HoverEffectを利用することで、簡単にUXを向上させることができます。
特に、利用者が慣れていないデバイスであればあるほど、このような細かな意識は大切です。
より良いアプリ体験になるように開発者自身が意識して取り組んでいきましょう。

# 参考

https://developer.apple.com/jp/videos/play/wwdc2024/10152

https://developer.apple.com/documentation/swiftui/hovereffect

https://developer.apple.com/documentation/swiftui/view/hovereffectgroup(_:)