1
1

More than 3 years have passed since last update.

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

Posted at

1. やりたいこと

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

2. 具体例

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

// 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))
               }
           }
        }
   }

}

3. 課題

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

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

4. 解決策

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

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

(参考)Getting the index in ForEach

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

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1