【SwiftUI】ForEachで追加したviewを識別する方法

・やりたいこと
1つの画面にForEachで複数のviewを追加する。この時、複数のviewを識別し、それぞれの状態に対応した処理を実行する。

・具体例
SNSのタイムラインの画面で、複数の投稿をForEachでlistに追加(リスト上で1行となる)することを想定する。
ここで、返信(吹き出し記号)をタップすると、その色が赤に反転するようにしたい。

画像1

// TimelineCell.swift

import SwiftUI

struct TimeLineCell: View {

    var replies = [TimelinePost]()
    var elementCount: Int

    init(replies: [TimelinePost]) {
           self.replies = replies
           elementCount = replies.count
           prepFlags()
    }

    @State var isReplied: [Bool] = [Bool]()
   
    mutating func prepFlags(){
        var initIsReplied: [Bool] = [Bool]()
        for _ in 0..<elementCount {
            initIsReplied.append(false)
        }
        _isReplied = State(initialValue: initIsReplied)
    }
    
    func symbolColor(_ isActive: Bool) -> Color {
       if isActive {
           return Color.red
       } else {
           return Color.gray
       }
    }

    var body: some View {
       return VStack(alignment: .leading){
           ForEach(replies, id: \.self) {reply in
           
               HStack{
                   if self.replies.firstIndex(of: reply) != nil {
                       Image(systemName: "bubble.right")
                           .foregroundColor(self.symbolColor(self.isReplied[self.replies.firstIndex(of: reply)!]))
                           .onTapGesture {
                               if let index = self.replies.firstIndex(of: reply) {
                                   self.isReplied[index].toggle()
                               }
                            }
                   }
                   Text(String(reply.replyCount))
               }
           }
        }
   }

}

・課題
(1)返信記号を行ごと(ForEachのインデックス毎)に識別する必要がある(datasourceであるrepliesに固定値としてこの記号を与えると、ForEachの中でletになるため、タップジェスチャーで色などを変更できない)
→ 「ForEach<Data, ID, Content> * Data : コレクション」を採用した場合に、どのようにインデックスを取得するか?

(2)返信記号のタップを受け付けるために、インデックス毎にフラグ(isReplied: [Bool])を用意し、Stateプロトコルに準拠する。
→ Stateプロトコルは、Computed Propertyにできないし、初期化もできないが、初期値として、どのように"false"をappendするか?
(参考)
@Stateを付けたプロパティは、イニシャライザの中で値を変更しても必ず無視される

・解決策
(1)今回は、以下のパターンを採用。前提として、リストの並び替えや削除を実装しないので、リスト表示後にインデックスが変わることがない。

Foreach(array) { item in
    let index = array.firstIndex(of: item)
}

(参考)Getting the index in ForEach

(2)上述の参考リンクにあるように、State.init(initialValue:)と言うイニシャライザで初期化する

この記事が気に入ったらサポートをしてみませんか?