【问题标题】:SwiftUI: How to select multi items(image) with ForEach?SwiftUI:如何使用 ForEach 选择多个项目(图像)?
【发布时间】:2021-03-03 10:06:51
【问题描述】:

我正在使用选择多个缩略图块的功能处理我的项目。只有选定的缩略图/图像会突出显示。 对于 ChildView,如果用户点击图像,则绑定 activeBlock 应设置为 true/false。 但是,当我选择缩略图时,所有缩略图都会突出显示。我想出了一些想法,例如

@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"))           
            }
        }
}

块视图

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

感谢您的帮助!

【问题讨论】:

    标签: foreach binding swiftui multipleselection


    【解决方案1】:

    最终结果

    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 框架。

    【讨论】:

    • 非常感谢@Quang Ha,这是一个高级解决方案,我可以将其应用于我的下一个功能!非常感谢您的帮助
    【解决方案2】:

    这是一个可能方法的演示 - 我们通过视频计数初始化 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)
                    }
                }
            }
        }
    }
    

    【讨论】:

      猜你喜欢
      • 2020-11-27
      • 1970-01-01
      • 2021-11-25
      • 1970-01-01
      • 2020-03-23
      • 1970-01-01
      • 2014-01-09
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多