SwiftUI:如何使用 ForEach select 多个项目(图像)?

SwiftUI: How to select multi items(image) with ForEach?

我正在开发具有 select 多个缩略图块功能的项目。只有 selected 缩略图/图像将突出显示。 对于 ChildView,如果使用点击图像,绑定 activeBlock 应该被打开 true/false。 但是,当我 select 一个缩略图时,所有的缩略图都会 highlighted.I 想出一些想法 like

@State var selectedBlocks:[Bool] 
// which should contain wether or not a certain block is selected.

但是不知道怎么实现。

这是我的代码:

子视图

@Binding var activeBlock:Bool
var thumbnail: String
var body: some View {
    VStack {
        ZStack {
            Image(thumbnail)
               .resizable()
               .frame(width: 80, height: 80)
                    .background(Color.black)
                    .cornerRadius(10)
            if activeBlock {
               RoundedRectangle(cornerRadius: 10)
                    .stroke(style: StrokeStyle(lineWidth: 2))
                    .frame(width: 80, height: 80)
                    .foregroundColor(Color("orange"))           
            }
        }
}

BlockBView

struct VideoData: Identifiable{
    var id = UUID()
    var thumbnails: String
}

struct BlockView: View {
        var videos:[VideoData] = [
        VideoData(thumbnails: "test"), VideoData(thumbnails: "test2"), VideoData(thumbnails: "test1")
    ]
    
    @State var activeBlock = false

    var body: some View {
        ScrollView(.horizontal){
                HStack {
                    ForEach(0..<videos.count) { _ in
                        Button(action: {
                            self.activeBlock.toggle()
                        }, label: {
                            
                            ChildView(activeBlock: $activeBlock, thumbnail: "test")  
                        })           
                    }
                }                
            }
}

感谢您的帮助!

这是一个可能的方法演示 - 我们通过视频计数初始化 Bool 数组,并通过索引将激活标志传递到子视图中。

使用 Xcode 12.1 / iOS 14.1 测试(带有一些复制代码)

struct BlockView: View {
    var videos:[VideoData] = [
        VideoData(thumbnails: "flag-1"), VideoData(thumbnails: "flag-2"), VideoData(thumbnails: "flag-3")
    ]
    
    @State private var activeBlocks: [Bool]    // << declare
    
    init() {
        // initialize state with needed count of bools
        self._activeBlocks = State(initialValue: Array(repeating: false, count: videos.count))
    }
    
    var body: some View {
        ScrollView(.horizontal){
            HStack {
                ForEach(videos.indices, id: \.self) { i in
                    Button(action: {
                        self.activeBlocks[i].toggle()       // << here !!
                    }, label: {
                        ChildView(activeBlock: activeBlocks[i],       // << here !!
                                  thumbnail: videos[i].thumbnails)
                    })
                }
            }
        }
    }
}

struct ChildView: View {
    var activeBlock:Bool       // << value, no binding needed
    var thumbnail: String
    
    var body: some View {
        VStack {
            ZStack {
                Image(thumbnail)
                    .resizable()
                    .frame(width: 80, height: 80)
                    .background(Color.black)
                    .cornerRadius(10)
                if activeBlock {
                    RoundedRectangle(cornerRadius: 10)
                        .stroke(style: StrokeStyle(lineWidth: 2))
                        .frame(width: 80, height: 80)
                        .foregroundColor(Color.orange)
                }
            }
        }
    }
}

最终结果

  1. 首先构建您的元素和模型。我正在使用 MVVM,

     class RowModel : ObservableObject, Identifiable {
         @Published var isSelected = false
         @Published var thumnailIcon: String
         @Published var name: String
    
         var id : String
    
         var cancellables = Set<AnyCancellable>()
    
         init(id: String, name: String, icon: String) {
             self.id = id
             self.name = name
             self.thumnailIcon = icon
         }
     }
    
     //Equivalent to your BlockView
     struct Row : View {
         @ObservedObject var model: RowModel
    
         var body: some View {
             GroupBox(label:
                 Label(model.name, systemImage: model.thumnailIcon)
                     .foregroundColor(model.isSelected ? Color.orange : .gray)
             ) {
                 HStack {
                     Capsule()
                         .fill(model.isSelected ? Color.orange : .gray)
                         .onTapGesture {
                             model.isSelected = !model.isSelected
                         }
    
                     //Two way binding
                     Toggle("", isOn: $model.isSelected)
                 }
    
             }.animation(.spring())
         }
     }
    
  2. 在您的父视图中准备数据和处理操作

     struct ContentView: View {
         private let layout = [GridItem(.flexible()),GridItem(.flexible())]
    
         @ObservedObject var model = ContentModel()
    
         var body: some View {
             VStack {
                 ScrollView {
                     LazyVGrid(columns: layout) {
                         ForEach(model.rowModels) { model in
                             Row(model: model)
                         }
                     }
                 }
    
                 if model.selected.count > 0 {
                     HStack {
                         Text(model.selected.joined(separator: ", "))
                         Spacer()
                         Button(action: {
                             model.clearSelection()
                         }, label: {
                             Text("Clear")
                         })
                     }
                 }
             }
             .padding()
             .onAppear(perform: prepare)
         }
    
         func prepare() {
             model.prepare()
         }
     }
    
     class ContentModel: ObservableObject {
         @Published var rowModels = [RowModel]()
    
         //I'm handling by ID for futher use
         //But you can convert to your Array of Boolean
         @Published var selected = Set<String>()
    
         func prepare() {
             for i in 0..<20 {
                 let row = RowModel(id: "\(i)", name: "Block \(i)", icon: "heart.fill")
    
                 row.$isSelected
                     .removeDuplicates()
                     .receive(on: RunLoop.main)
                     .sink(receiveValue: { [weak self] selected in
                         guard let `self` = self else { return }
                         print(selected)
                         if selected {
                             self.selected.insert(row.name)
                         }else{
                             self.selected.remove(row.name)
                         }
                     }).store(in: &row.cancellables)
    
                 rowModels.append(row)
             }
         }
    
         func clearSelection() {
             for r in rowModels {
                 r.isSelected = false
             }
         }
     }
    

不要忘记 import Combine 框架。