[SwiftUI] Buttonの中にButtonを置いたときのハイライト問題について

この記事は[and factory.inc Advent Calendar 2023](https://qiita.com/advent-calendar/2023/andfactory)の16日目の記事です。

# はじめに
SwiftUIでButtonの中に別のButtonを配置したいときがありました。
例えば、Cellのように全体がタップ可能なViewの中にタップ可能なアイコンを配置するようなときです。
この場合、以下のようにアイコンをタップするとCell全体ハイライトが効いてしまい、Cell自体をタップしたかのような状態になってしまいます。

![gif1.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/520642/fa044c72-b151-9386-b4a3-b4f0c03dbe28.gif)



アイコンをタップしたときは、Cell全体にはハイライトが効かないようにしたかったのですが、容易には実装できなかったので色々と試行錯誤した結果をお伝えしようと思います。

結論からいうと、理想的な解決法は見つけることがまだできていません。もしいい案があればコメントしていただけると幸いです。

# 試したこと
## 1. ボタンアイコンをCellの上に配置する
アイコンのButtonがCell全体を覆っているButtonの中にあることで、アイコンをタップしたときにCell全体を覆っているButtonもタップイベントを受け取ってしまいCell全体がハイライトされてしまっていると考えました。
そこで、ZStackを使ってアイコンをCellを覆っているButtonの中ではなく、Z軸上で上に配置することで解決を図りました。
~~~ swift
ZStack(alignment: .trailing) {
            Button {
                print("$$$ アイテム２がタップされました")
            } label: {
                HStack(spacing: 50) {
                    VStack(alignment: .leading, spacing: 8) {
                        Text("アイテム２")
                            .foregroundColor(.primary)
                            .font(.headline)
                            .frame(maxWidth: .infinity, alignment: .leading)

                        Text("ボタンの上にボタン")
                            .foregroundColor(.primary)
                            .font(.caption)
                    }


                    Spacer()
                }
                .padding(.horizontal, 24)
                .frame(width: 300, height: 80)
                .background(Color.blue.opacity(0.6))
            }

            Button {
                print("アイテム２のアイコンがタップされました")
            } label: {
                Image(systemName: "trash.fill")
                    .resizable()
                    .tint(.red)
                    .frame(width: 30, height: 30)
            }
            .padding(.trailing, 24)
        }
~~~

この実装の場合、アイコンをタップしたときはアイコンのみがハイライトされCell全体がハイライトされなくなりました。
しかし、アイコンがCellを覆っているButtonの上に配置されていることでCell自体をタップしたときにアイコンはハイライトされません。

![gif2.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/520642/d28c415f-b3d3-b06c-f777-b703d67c717d.gif)


## 2. アイコンのみCellの中に残してButtonをCellの上に配置する
1の場合のCell自体をタップしたときにアイコンがハイライトされないことに対処するために、アイコンのImageとButtonを分割した実装が以下になります。
~~~ swift
ZStack(alignment: .trailing) {
            Button {
                print("$$$ アイテム３がタップされました")
            } label: {
                HStack(spacing: 50) {
                    VStack(alignment: .leading, spacing: 8) {
                        Text("アイテム３")
                            .foregroundColor(.primary)
                            .font(.headline)
                            .frame(maxWidth: .infinity, alignment: .leading)

                        Text("ボタンの中にアイコン")
                            .foregroundColor(.primary)
                            .font(.caption)
                    }

                    Image(systemName: "trash.fill")
                        .resizable()
                        .tint(.red)
                        .frame(width: 30, height: 30)
                }
                .padding(.horizontal, 24)
                .frame(width: 300, height: 80)
                .background(Color.blue.opacity(0.6))
            }

            Button {
                print("アイテム３のアイコンがタップされました")
            } label: {
                Rectangle()
                    .foregroundColor(.clear)
                    .frame(width: 40, height: 40)
                    .border(Color.black)
                    .contentShape(Rectangle())

            }
            .padding(.trailing, 19)
        }
~~~
アイコンのImage自体はCellを覆っているButtonの中に配置し、アイコンのButtonはZStackでアイコンImageのちょうど真上に来るようにCellを覆っているButtonの上に配置するような形です。

![gif3.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/520642/5da794e3-5f7b-fc16-fe9e-a00c9a25870b.gif)

このようにすることで、Cell自体をタップしたときはアイコンも一緒にハイライトされますが、逆にアイコンをタップしたときにアイコン自体がハイライトされない問題に直面してしまいました。

## 3. onTapGestureやonLongPressGestureを使った方法
Buttonを使わずにタップイベントをonTapGestureやonLongPressGestureを使って回避する方法も模索しましたが、Buttonをタップしたときの挙動やアニメーションを再現するのが難しく、よい解決方法が見つけれれていません。

# まとめ
以上が、SwiftUIでButtonの中にButtonを配置したときのハイライト問題を回避しようと模索した結果になります。
今後も引き続き、良い解決方法を模索していきたいと思いますが、もし良い案があればコメントいただけると幸いです。

最後まで読んでいただきありがとうございました。

明日の[Advent Calender](https://qiita.com/advent-calendar/2023/andfactory)もお楽しみに！