

**質問内容**  
SwiftUIでForEach構文を用いて並べたビューを、タップしたら、タップされたビューだけが色が変わるように動かしたかったため、まずは以下のテストコードを作成しました。

質問ですが、  
このテストコードで、とりあえずは期待する結果を得られたのですが、ビューの数が以下コードのように３つだけでなくもっと多いとき（テストコードは1行３列ですが、20行30列の場合など）でも、同じような実装方法で良いでしょうか。

私のやり方のままでは、ビューの数と同じ数だけ@State変数を用意しなければならず、もっと良いやり方があるように思えてならないため、質問させていただきました。

何かお気づきの方、ご指摘よろしくお願いいたします。

**サンプルコード**

[code]

    //Xcodeのバージョン　Version 12.3 (12C33)
    //Swiftのバージョン　Apple Swift version 5.3.2
    
    import SwiftUI
    
    struct ContentView: View {
        
        @State var tapped = [false, false, false]
        
        var body: some View {
            HStack(spacing: 20) {
                ForEach(0 ..< 3){ i in
                    Text("\(i)")
                        .frame(width: 50, height: 50)
                        .font(.custom("default", size: 30))
                        .background(self.tapped[i] ? Color.blue : Color.red)
                        .onTapGesture {
                            tapped[i].toggle()
                        }
                }
            }
        }
    }
    
    struct ContentView_Previews: PreviewProvider {
        static var previews: some View {
            ContentView()
        }
    }
    
[/code]


CC BY-SA 4.0
2021-05-20T04:23:06.823
75969
2023-09-11T09:07:40.910
45415
question
swift swiftui
SwiftUIでForEach構文を用いて並べたビューを、タップジェスチャーに個別に反応するようにする方法について


> ビューの数と同じ数だけ@State変数を用意しなければならず、もっと良いやり方があるように思えてならない

「状態を管理しなければいけないセルの個数だけ状態変数を用意する」と言うのは、SwiftUIで複数のセルを管理する場合の基本です。「一つのセルごとに複数の別々の状態がある」と言うのであれば、複数個の配列を管理することは避けて、一つのセルの状態を表すstructを定義するなどした方が良いですが、単に配列の要素が複数あるのを、「もっと良いやり方があるように思えてならない」と言うのは、SwiftUI的発想に慣れていないように思われます。

私は以前どこかのQ&Aサイトで`@State var tapped1: Bool = false`, `@State var tapped2: Bool =
false`, `@State var tapped3: Bool = false`...
のように、本当に`@State`をセルの数だけ宣言しているのを見かけたことがありますが、ちゃんと配列を使用しているあなたのコードはむしろまともだと言えるでしょう。

書き方を少し工夫すれば、要素数を変えてもほとんどコードはいじらなくてよくなります。

[code]

    struct ContentView: View {
        
        @State var tapped = Array(repeating: false, count: 5)
        
        var body: some View {
            HStack(spacing: 20) {
                ForEach(tapped.indices) { i in
                    Text("\(i)")
                        .frame(width: 50, height: 50)
                        .font(.custom("default", size: 30))
                        .background(self.tapped[i] ? Color.blue : Color.red)
                        .onTapGesture {
                            tapped[i].toggle()
                        }
                }
            }
        }
    }
    
[/code]

* * *

「すべてのセルについて、true/falseの二つの状態だけを管理できれば良い」と言うのであれば、`Set`を使う手もありますが、この方が使える状況は少ないので、ぜひ覚えて置いた方が良いテクニックとは言えないですね。

[code]

    struct ContentView: View {
        
        @State var tappedIndices: Set<Int> = []
        
        var body: some View {
            HStack(spacing: 20) {
                ForEach(0..<3) { i in
                    Text("\(i)")
                        .frame(width: 50, height: 50)
                        .font(.custom("default", size: 30))
                        .background(self.tappedIndices.contains(i) ? Color.blue : Color.red)
                        .onTapGesture {
                            if tappedIndices.contains(i) {
                                tappedIndices.remove(i)
                            } else {
                                tappedIndices.insert(i)
                            }
                        }
                }
            }
        }
    }
    
[/code]


CC BY-SA 4.0
2021-05-20T05:19:36.270
75971
2021-05-20T05:19:36.270
13972
75969
answer


別Viewに変数を持たせるという形でも実装できます。

[code]

    struct ContentView: View {
      var body: some View {
        HStack(spacing: 20) {
          ForEach(0..<3) { i in
            TappableText(text: String(i))
              .frame(width: 50, height: 50)
              .font(.custom("default", size: 30))
          }
        }
      }
    }
    
    struct TappableText: View {
      let text: String
    
      @State var tapped = false
    
      var body: some View {
        Text(text)
          .background(tapped ? Color.blue : Color.red)
          .onTapGesture {
            tapped.toggle()
          }
      }
    }
    
[/code]


CC BY-SA 4.0
2022-09-01T14:39:53.707
90883
2022-09-01T14:39:53.707
40856
75969
answer