【SwiftUI】Listの超基本的なこと

# はじめに
SwiftUIのListについて公式ドキュメントの内容をまとめてみました

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

# 環境
Xcode 13.4.1

# 内容

`List`とは、1つの列に並んだデータの行を表示するコンテナで、オプションで1つ以上のメンバーを選択する機能を提供する

```swift
struct List<SelectionValue, Content> where SelectionValue : Hashable, Content : View
```

```swift
var body: some View {
    List {
        Text("A List Item")
        Text("A Second List Item")
        Text("A Third List Item")
    }
}
```
<img width="300" alt="" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/631585/da349a6d-8403-652c-9636-82164c74b214.png">

一般的には、`Identifiable`に準拠したデータから動的にリストを作成する

```swift
struct Ocean: Identifiable {

    let name: String
    let id = UUID()
}

private var oceans = [
    Ocean(name: "Pacific"),
    Ocean(name: "Atlantic"),
    Ocean(name: "Indian"),
    Ocean(name: "Southern"),
    Ocean(name: "Arctic")
]

var body: some View {
    List(oceans) {
        Text($0.name)
    }
}

```

<img width="300" alt="" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/631585/d3b4b6fb-a84c-3760-14c3-79720ad99530.png">


### 選択可能なリスト

リストのメンバーを選択可能にするには、選択変数へのバインディングを提供する。リストデータのIdentifiable.ID型の単一のインスタンスにバインドすると、単一選択のリストが、セットへのバインディングは、複数の選択をサポートするリストを作成する

```swift
struct Ocean: Identifiable, Hashable {
    let name: String
    let id = UUID()
}

private var oceans = [
    Ocean(name: "Pacific"),
    Ocean(name: "Atlantic"),
    Ocean(name: "Indian"),
    Ocean(name: "Southern"),
    Ocean(name: "Arctic")
]

@State private var multiSelection = Set<UUID>()

var body: some View {
    NavigationView {
        List(oceans, selection: $multiSelection) {
            Text($0.name)
        }
        .navigationTitle("Oceans")
        .toolbar { EditButton() }
    }
    Text("\(multiSelection.count) selections")
}

```

<img width="300" alt="" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/631585/9d28e700-bdd0-ab4a-0bf6-f525f24d5389.png">
<img width="300" alt="" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/631585/0c6c516f-4121-e87e-5830-3674bda7e7ab.png">

タップやクリックで1つ選択すると、選択されたセルの外観が変わり、選択されたことが示される。タップ操作で複数選択できるようにするには、editModeの値を変更するか、アプリのインターフェースにEditButtonを追加して、リストを編集モードにする。リストを編集モードにすると、各リスト項目の横に丸印が表示され、ユーザーが関連する項目を選択すると、円にはチェックマークが入る

### リストの更新

標準のrefreshコントロールを使ってリストの内容を更新可能にするには、 `refreshable(action:)`を使用する
ユーザーがリストのトップを下にドラッグすると、リフレッシュコントロールを表示し、指定されたアクションを実行する。データをリフレッシュするためにアクションクロージャの内部でawait式を使用、リフレッシュインジケータは待機している操作の間、表示されたままとなる

```swift

struct Ocean: Identifiable, Hashable {
     let name: String
     let id = UUID()
     let stats: [String: String]
 }

 class OceanStore: ObservableObject {
     @Published var oceans = [Ocean]()
     func loadStats() async {
         oceans .append(Ocean(name: "hoge", stats: ["hoge":"hoge"]))
     }
 }

struct ContentView: View {
     @EnvironmentObject var store: OceanStore

     var body: some View {
         NavigationView {
             List(store.oceans) { ocean in
                 HStack {
                     Text(ocean.name)
                     StatsSummary(stats: ocean.stats)
                 }
             }
             .refreshable {
                 await store.loadStats()
             }
             .navigationTitle("Oceans")
         }
     }
}

struct StatsSummary: View {
    @State var stats: [String: String]
    var body: some View {
        Text(stats.description)
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
            .environmentObject(OceanStore())
    }
}
```

<img width="200" alt="" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/631585/c7548693-7be0-5167-193b-7f9e8185f436.png">
<img width="200" alt="" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/631585/bc497919-78b1-0222-1411-3f25aa6fbf7c.png">
<img width="200" alt="" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/631585/736580cf-2b9f-e056-7cb3-ab2550317635.png">


### リストのセクション分け

2 次元リストを作成するには、セクションインスタンス内の項目をグループ化する

```swift

struct ContentView: View {
    struct Sea: Hashable, Identifiable {
        let name: String
        let id = UUID()
    }

    struct OceanRegion: Identifiable {
        let name: String
        let seas: [Sea]
        let id = UUID()
    }

    private let oceanRegions: [OceanRegion] = [
        OceanRegion(name: "Pacific",
                    seas: [Sea(name: "Australasian Mediterranean"),
                           Sea(name: "Philippine"),
                           Sea(name: "Coral"),
                           Sea(name: "South China")]),
        OceanRegion(name: "Atlantic",
                    seas: [Sea(name: "American Mediterranean"),
                           Sea(name: "Sargasso"),
                           Sea(name: "Caribbean")]),
        OceanRegion(name: "Indian",
                    seas: [Sea(name: "Bay of Bengal")]),
        OceanRegion(name: "Southern",
                    seas: [Sea(name: "Weddell")]),
        OceanRegion(name: "Arctic",
                    seas: [Sea(name: "Greenland")])
    ]

    @State private var singleSelection: UUID?

    var body: some View {
        NavigationView {
            List(selection: $singleSelection) {
                ForEach(oceanRegions) { region in
                    Section(header: Text("Major \(region.name) Ocean Seas")) {
                        ForEach(region.seas) { sea in
                            Text(sea.name)
                        }
                    }
                }
            }
            .navigationTitle("Oceans and Seas")
        }
    }
}

```

<img width="300" alt="" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/631585/cdcb0a53-49e7-fde5-5ad5-c47eb3f65286.png">


### アコーディオン（階層のある）リスト

ツリー構造のデータと、任意のレベルの子ノードを取得するためのキーパスを提供するchildrenパラメータを提供することにより、任意の深さの階層的リストを作成することができる。折りたたみ式のセルを使用して、ユーザーがツリー構造をナビゲートできるようになっている


```swift
struct ContentView: View {
    struct FileItem: Hashable, Identifiable, CustomStringConvertible {
        var id: Self { self }
        var name: String
        var children: [FileItem]? = nil
        var description: String {
            switch children {
            case nil:
                return "📄 \(name)"
            case .some(let children):
                return children.isEmpty ? "📂 \(name)" : "📁 \(name)"
            }
        }
    }
    let fileHierarchyData: [FileItem] = [
      FileItem(name: "users", children:
        [FileItem(name: "user1234", children:
          [FileItem(name: "Photos", children:
            [FileItem(name: "photo001.jpg"),
             FileItem(name: "photo002.jpg")]),
           FileItem(name: "Movies", children:
             [FileItem(name: "movie001.mp4")]),
              FileItem(name: "Documents", children: [])
          ]),
         FileItem(name: "newuser", children:
           [FileItem(name: "Documents", children: [])
           ])
        ]),
        FileItem(name: "private", children: nil)
    ]
    var body: some View {
        List(fileHierarchyData, children: \.children) { item in
            Text(item.description)
        }
    }
}

```


<img width="200" alt="" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/631585/5059dc38-6bac-01d0-3a0f-ae54f78752ad.png">
<img width="200" alt="" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/631585/f64c3c8a-a139-675e-0e82-adc1fa8fd3ac.png">
<img width="200" alt="" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/631585/ff691ed7-b642-d414-cdf3-a522e9c77fc3.png">
<img width="200" alt="" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/631585/f8c96f9e-2f71-7caa-86aa-975845417c15.png">


### リストのスタイル
SwiftUI はプラットフォームとリストが表示されるビュータイプに基づいてリストの表示スタイルを選ぶ。ビュー内のすべてのリストに異なる ListStyle を適用するには、`listStyle(_:)`修飾子を使用する

##### `static var automatic: DefaultListStyle`
プラットフォームのデフォルトの動作とリストの外観を記述するリストスタイル
<img width="200" alt="" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/631585/183d1bf9-2bea-5338-2d90-d5d2a18d3e66.png">

##### `static var grouped: GroupedListStyle`
グループ化されたリストの動作と外観を記述するリストスタイル
<img width="200" alt="" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/631585/928c62f7-974d-87eb-b8fa-63dfcd7de6e9.png">

##### `static var inset: InsetListStyle`
インセットリストの動作と外観を記述するリストスタイル
<img width="200" alt="" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/631585/7582479f-600a-92c3-4043-9d1961d01875.png">

##### `static var insetGrouped: InsetGroupedListStyle`
グループ化されたインセットリストの動作と外観を記述するリストスタイル
<img width="200" alt="" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/631585/d66a5041-1746-6b9f-d5e5-0ffede4430ca.png">

##### `static var plain: PlainListStyle`
プレーンリストの動作と外観を記述するリストスタイル
<img width="200" alt="" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/631585/21aaedf3-4498-83a5-3859-a6fd93435284.png">

##### `static var sidebar: SidebarListStyle`
サイドバーリストの動作と外観を記述するリストスタイル
<img width="200" alt="" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/631585/6f951962-b62e-18e8-6b3b-8f3de14eaf2b.png">

# おわりに
この記事を見る限り`List`はすでに`Lazy`であるようなので、カスタマイズに問題がなければ`List`をどんどん使っていこうと思いました

https://developer.apple.com/forums/thread/651256


# 参考

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

https://angelostavrow.com/post/fix-environmentobject-errors-in-swiftui-previews/