SwiftUI - 用 ForEach 顯示資料的同時取得 index

##  環境

- Xcode 12.0 beta 6 (12A8189n)

雖然在範例用了 Xcode 12 beta ，不過由於沒有用新的 API ，因此在 Xcode 11 也可以順利執行！

## 前言

在 Swift UI 裡面，要做出像是 UITableView 一樣，列出一系列的資料的時候會用像是這樣的用法：

``` swift
ForEach(locations, id: \.id) { location in
    Text(location.name)
}
```

寫出一個簡單的畫面的話就如圖：

![圖片.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41979/1aaf66d2-35c4-dc5f-7595-f200c92492d0.png)

程式碼如下：

```swift
import SwiftUI

struct ContentView: View {
    
    let locations: [Location] = [
        Location(name: "彰化"),
        Location(name: "台中"),
        Location(name: "南投"),
    ]
    
    var body: some View {
        VStack {
            HStack {
                Text("中部")
                    .font(.title)
                    .fontWeight(.heavy)
                Spacer(minLength: 0)
            }
            .padding(.horizontal)
            ScrollView {
                VStack(spacing: 8) {
                    ForEach(locations, id: \.id) { location in
                        HStack {
                            Image(systemName: "mappin")
                                .font(.system(size: 19))
                                .foregroundColor(.red)
                                .padding()
                            VStack(alignment: .leading, spacing: 8) {
                                Text(location.name)
                                Text(location.id)
                                    .font(.caption)
                                    .foregroundColor(Color.gray)
                            }
                            Spacer(minLength: 0)
                        }
                    }
                }
                .padding()
            }
        }
    }
}
```

Location 的定義

``` swift
struct Location: Identifiable {
    let id: String
    let name: String
    
    init(uuid: () -> String = { UUID().uuidString }, name: String) {
        self.id = uuid()
        self.name = name
    }
}
```

## 回到正題

但是 ForEach 沒有像 UITableViewDelegate 的方法 `tableView(_:didSelectRowAt:)` 可以透過傳入的 indexPath 取得點擊到的那一行的索引，而在實務上又經常需要這個索引值，那該怎麼辦呢？

這時候就比較麻煩了，我們就必須透過 [enumerated()](https://developer.apple.com/documentation/swift/array/1687832-enumerated) 這個方法來產生 (index, value) 的陣列。

但是由於 `enumerated()` 本身的回傳型別是 `EnumeratedSequence<Array<Element>>` ，而這個型別無法直接餵給 ForEach ， 編輯器就會開始抱怨：


> Generic struct 'ForEach' requires that 'EnumeratedSequence<[Location]>' conform to 'RandomAccessCollection'

所以在傳入之前就還要再將她生成一個 Array ，傳入 id 的時候也因為結構不同而需要調整，就會變得這樣有點落落長的形式：

``` swift
ForEach(Array(locations.enumerated()), id: \.element.id) { index, location in
    // 省略
}
```

## 最終效果和程式碼

這裡就拿 index 來動態顯示不同的 SF Symbols :

![圖片.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41979/cc86e798-5079-f816-6181-72f6c038bf79.png)

### 程式碼

``` swift
import SwiftUI

struct ContentView: View {
    
    let locations: [Location] = [
        Location(name: "彰化"),
        Location(name: "台中"),
        Location(name: "南投"),
    ]
    
    var body: some View {
        VStack {
            HStack {
                Text("中部")
                    .font(.title)
                    .fontWeight(.heavy)
                Spacer(minLength: 0)
            }
            .padding(.horizontal)
            ScrollView {
                VStack(spacing: 8) {
                    ForEach(Array(locations.enumerated()), id: \.element.id) { index, location in
                        HStack {
                            Image(systemName: "mappin")
                                .font(.system(size: 19))
                                .foregroundColor(.red)
                                .padding()
                            VStack(alignment: .leading, spacing: 8) {
                                HStack {
                                    // 為了顯示在這邊用 index
                                    Image(systemName: "\(index).circle")
                                    Text(location.name)
                                }
                                Text(location.id)
                                    .font(.caption)
                                    .foregroundColor(Color.gray)
                            }
                            Spacer(minLength: 0)
                        }
                    }
                }
                .padding()
            }
        }
    }
}

struct Location: Identifiable {
    let id: String
    let name: String
    
    init(uuid: () -> String = { UUID().uuidString }, name: String) {
        self.id = uuid()
        self.name = name
    }
}

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

以上。