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)
}
}
}
}
}
最终结果
首先构建您的元素和模型。我正在使用 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())
}
}
在您的父视图中准备数据和处理操作
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
框架。
我正在开发具有 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)
}
}
}
}
}
最终结果
首先构建您的元素和模型。我正在使用 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()) } }
在您的父视图中准备数据和处理操作
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
框架。