SwiftUIでListの上にButtonを置く場合List自体にイベントを奪われないようにする

# はじめに結論

SwiftUIでListの上にButtonを置くと、タップイベントがListのほうで実行されてしまう。そのため、Buttonのスタイルを`BorderlessButtonStyle`か`PlainButtonStyle`にする必要がある。

（この記事はXcode 12.4でのSwiftUIでの話です）

# 本題
## 解決方法

Buttonに`BorderlessButtonStyle`か`PlainButtonStyle`のスタイルを設定する

```swift
//: A UIKit based Playground for presenting user interface
  
import UIKit
import SwiftUI
import PlaygroundSupport

struct ContentView: View {
    var body: some View {
        List {
            HStack {
                Text("Cell 1")

                Spacer()

                Button(action: {
                    print("B_1押したね")
                }, label: {
                    Text("Border")
                })
                .buttonStyle(BorderlessButtonStyle())

                Button(action: {
                    print("P_1押したね")
                }, label: {
                    Text("Plain")
                })
                .buttonStyle(PlainButtonStyle())
            }
        }
    }
}

PlaygroundPage.current.liveView = UIHostingController(rootView: ContentView())
```

これで解決。タップイベントはListではなくボタンで反応するようになった。

デザイン的なものはButtonでなく内部のTextに適用していけばいい。

# 蛇足: なぜButtonのスタイルで判断するのかを考える

- 予想
    - AppleはListをUITableViewのように動作させたい場合をデフォルトと考えているのではないかと思う。
- 実験してなんとなくわかること
    - SwiftUIはListの要素のモディファイアではonTapGestureでタップしたときにUITableViewのようにList自体を変更することはやりたくない
    - SwiftUIのListのなかにButtonがあるときはそれが要素なのでUITableViewのように振る舞わうことを制御できる
        - もしその挙動を変更したければButton側で変更できるようにしている

以下は実験

## 全てのスタイルを適用してみる

他にも`DefaultButtonStyle`というのがあるのでそれも適用してみる。結果はListがタップできるようになってしまう。つまりこれがデフォルトか。

```swift
//: A UIKit based Playground for presenting user interface
  
import UIKit
import SwiftUI
import PlaygroundSupport

struct ContentView: View {
    var body: some View {
        List {
            HStack {
                Text("Cell 1")

                Spacer()

                Button(action: {
                    print("B_1押したね")
                }, label: {
                    Text("Border")
                })
                .buttonStyle(BorderlessButtonStyle())

                Button(action: {
                    print("P_1押したね")
                }, label: {
                    Text("Plain")
                })
                .buttonStyle(PlainButtonStyle())

                Button(action: {
                    print("D_1押したね")
                }, label: {
                    Text("Default")
                })
                .buttonStyle(DefaultButtonStyle())
            }
        }
    }
}

PlaygroundPage.current.liveView = UIHostingController(rootView: ContentView())
```

## なぜButtonのスタイルで判断するのか

- Listには複数のSectionやView（Cell/Item）が必要なためListのスタイルで統一的に指定しづらい
  - 1行目とか指定する形にしてしまえばできるとは思うがそれも複雑でどうかと思う
      - 内部のViewの状態で判断しようとしている 

具体的にはListが2行になっている場合、1行目はCellにはタップできないが、2行目はCellにもタップできる。

```swift
//: A UIKit based Playground for presenting user interface
  
import UIKit
import SwiftUI
import PlaygroundSupport

struct ContentView: View {
    var body: some View {
        List {
            HStack {
                Text("Cell 1")

                Spacer()

                Button(action: {
                    print("B_1押したね")
                }, label: {
                    Text("Border")
                })
                .buttonStyle(BorderlessButtonStyle())

                Button(action: {
                    print("P_1押したね")
                }, label: {
                    Text("Plain")
                })
                .buttonStyle(PlainButtonStyle())
            }

            HStack {
                Text("Cell 2")

                Spacer()

                Button(action: {
                    print("D_2押したね")
                }, label: {
                    Text("Default")
                })
                .buttonStyle(DefaultButtonStyle())
            }
        }
    }
}

PlaygroundPage.current.liveView = UIHostingController(rootView: ContentView())
```

![スクリーンショット 2021-04-10 16.16.30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/16400/d2cd8d6c-0bea-b591-a12f-8bc031ad38a0.png)


上記の予想はあくまで身勝手な予想なので、何かコメントあればコメント欄にお願いします。