英文:
How do I create selection state for items in a SwiftUI LazyVGrid, so it behaves like a list selection
问题
我正在在导航视图的详细信息部分组合一个网格视图。我希望能够在用户点击它们时为单个网格项设置选择状态,类似于列表的工作方式。我不希望多选,因此切换不起作用。我还希望自定义选择样式。
import SwiftUI
struct Tutorials: Identifiable, Hashable {
var id = UUID()
let name: String
let icon: String
}
struct ImageItems: Identifiable, Hashable {
var id = UUID()
let name: String
let icon: String
}
let allLibraries = [Tutorials(name: "所有图书馆项目", icon: "house")]
let lessons = [Tutorials(name: "浴室", icon: "bathtub"),
Tutorials(name: "卧室", icon: "bed.double"),
Tutorials(name: "餐厅", icon: "table.furniture"),
Tutorials(name: "门窗", icon: "door.left.hand.open"),
Tutorials(name: "厨房", icon: "cooktop"),
Tutorials(name: "照明", icon: "lamp.table"),
Tutorials(name: "客厅", icon: "sofa"),
Tutorials(name: "杂项", icon: "ellipsis.circle"),
Tutorials(name: "办公与学习", icon: "printer"),
Tutorials(name: "楼梯", icon: "stairs"),
Tutorials(name: "实用室", icon: "washer")]
let videos = [Tutorials(name: "装饰", icon: "paintbrush"),
Tutorials(name: "家具", icon: "chair"),
Tutorials(name: "杂项", icon: "ellipsis.circle"),
Tutorials(name: "植物与树木", icon: "tree"),
Tutorials(name: "池塘与游泳池", icon: "figure.open.water.swim"),
Tutorials(name: "结构", icon: "door.garage.closed")]
let imageGrid = [ImageItems(name: "装饰", icon: "paintbrush"),
ImageItems(name: "家具", icon: "chair"),
ImageItems(name: "杂项", icon: "ellipsis.circle"),
ImageItems(name: "植物与树木", icon: "tree"),
ImageItems(name: "池塘与游泳池", icon: "figure.open.water.swim"),
ImageItems(name: "结构", icon: "door.garage.closed"),
ImageItems(name: "家具", icon: "chair"),
ImageItems(name: "杂项", icon: "ellipsis.circle"),
ImageItems(name: "植物与树木", icon: "tree"),
ImageItems(name: "池塘与水池", icon: "figure.open.water")]
struct MyDisclosureStyle: DisclosureGroupStyle {
func makeBody(configuration: Configuration) -> some View {
Button {
withAnimation {
configuration.isExpanded.toggle()
}
} label: {
VStack {
Spacer()
.frame(height: 15)
HStack(alignment: .firstTextBaseline) {
configuration.label
Spacer()
Image(systemName: "chevron.right")
.rotationEffect(.degrees(configuration.isExpanded ? 90 : 0))
}
.padding(.top, 0)
.padding(.bottom, 0)
.contentShape(Rectangle())
.onTapGesture {
withAnimation {
configuration.isExpanded.toggle()
}
}
Spacer()
.frame(height: 8)
}
}
.buttonStyle(.plain)
if configuration.isExpanded {
configuration.content
.padding(.leading, 0)
.padding(.top, 0)
.padding(.bottom, 0)
.disclosureGroupStyle(self)
}
}
}
struct ContentView: View {
@Environment(\.colorScheme) var colorScheme
@State private var selectedList: Tutorials? = allLibraries[0]
@State var selectedImage: ImageItems? = imageGrid[0]
@State var expand = true
@State var expand2 = true
@State var expand3 = true
@State private var isHidden = false
var body: some View {
NavigationSplitView {
VStack(alignment: .leading) {
List(selection: $selectedList) {
ForEach(allLibraries, id: \.self) { item in
HStack {
Image(systemName: item.icon)
.frame(width: 16, height: 16, alignment: .center)
.foregroundColor(self.selectedList == item ? Color.white : Color.accentColor)
Text(item.name)
.foregroundColor(self.selectedList == item ? Color.white : nil)
}
.font(.system(size: 13, weight: .medium, design: .rounded))
.foregroundColor(colorScheme == .dark ? .white : .black)
.padding(0)
}
DisclosureGroup("家庭计划图形", isExpanded: $expand) {
ForEach(lessons, id: \.self) { item in
HStack {
Image(systemName: item.icon)
.frame(width: 16, height: 16, alignment: .center)
.foregroundColor(self.selectedList == item ? Color.white : Color.accentColor)
Text(item.name)
.foregroundColor(self.selectedList == item ? Color.white : nil)
}
.font(.system(size: 13, weight: .medium, design: .rounded))
.foregroundColor(colorScheme == .dark ? .white : .black)
.padding(.top, 5)
.padding(.bottom, 5)
}
}
.listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0))
.font(.system(size: 12, weight: .medium, design: .rounded))
.foregroundColor(.secondary)
.disclosureGroupStyle(MyDisclosureStyle())
DisclosureGroup("花园计划图形", isExpanded: $expand2) {
ForEach(videos, id: \.self) { item in
HStack {
Image(systemName: item.icon)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 16, height: 16, alignment: .center)
.foregroundColor(self.selectedList == item ? Color.white : Color.accentColor)
Text(item.name)
.foregroundColor(self.selectedList == item ? Color.white : nil)
}
.font(.system(size: 13, weight: .medium, design: .rounded))
.foregroundColor(colorScheme == .dark ? .white : .black)
.padding(.top, 5)
.padding(.bottom, 5)
<details>
<summary>英文:</summary>
I am putting a grid view together in the details section of a navigation view. I would like to be able to to have a selection state on individual grid items as the user clicks them, similar to the way the List works. I don't want multiple selections, so toggle doesn't work. I would also like to customise the selection style.
```import SwiftUI
struct Tutorials: Identifiable, Hashable {
var id = UUID()
let name: String
let icon: String
}
struct ImageItems: Identifiable, Hashable {
var id = UUID()
let name: String
let icon: String
}
let allLibraries = [Tutorials(name: "All Library Items", icon: "house")]
let lessons = [Tutorials(name: "Bathroom", icon: "bathtub"),
Tutorials(name: "Bedroom", icon: "bed.double"),
Tutorials(name: "Dining Room", icon: "table.furniture"),
Tutorials(name: "Doors & Windows", icon: "door.left.hand.open"),
Tutorials(name: "Kitchen", icon: "cooktop"),
Tutorials(name: "Lighting", icon: "lamp.table"),
Tutorials(name: "Living Room", icon: "sofa"),
Tutorials(name: "Miscellaneous", icon: "ellipsis.circle"),
Tutorials(name: "Office & Study", icon: "printer"),
Tutorials(name: "Stairs", icon: "stairs"),
Tutorials(name: "Utility Room", icon: "washer")]
let videos = [Tutorials(name: "Decoration", icon: "paintbrush"),
Tutorials(name: "Furniture", icon: "chair"),
Tutorials(name: "Miscellaneous", icon: "ellipsis.circle"),
Tutorials(name: "Plants & Trees", icon: "tree"),
Tutorials(name: "Ponds & Pools", icon: "figure.open.water.swim"),
Tutorials(name: "Structures", icon: "door.garage.closed")]
let imageGrid = [ImageItems(name: "Decoration", icon: "paintbrush"),
ImageItems(name: "Furniture", icon: "chair"),
ImageItems(name: "Miscellaneous", icon: "ellipsis.circle"),
ImageItems(name: "Plants & Trees", icon: "tree"),
ImageItems(name: "Ponds & Pools", icon: "figure.open.water.swim"),
ImageItems(name: "Structures", icon: "door.garage.closed"),
ImageItems(name: "Furniture", icon: "chair"),
ImageItems(name: "Miscellaneous", icon: "ellipsis.circle"),
ImageItems(name: "Plants & Trees", icon: "tree"),
ImageItems(name: "Ponds & Pools", icon: "figure.open.water")]
struct MyDisclosureStyle: DisclosureGroupStyle {
func makeBody(configuration: Configuration) -> some View {
Button {
withAnimation {
configuration.isExpanded.toggle()
}
} label: {
VStack {
Spacer()
.frame(height: 15)
HStack(alignment: .firstTextBaseline) {
configuration.label
Spacer()
Image(systemName:"chevron.right")
.rotationEffect(.degrees(configuration.isExpanded ? 90 : 0))
}
.padding(.top, 0)
.padding(.bottom, 0)
.contentShape(Rectangle())
.onTapGesture {
withAnimation {
configuration.isExpanded.toggle()
}
}
Spacer()
.frame(height: 8)
}
}
.buttonStyle(.plain)
if configuration.isExpanded {
configuration.content
//indents the disclosure content
.padding(.leading, 0)
.padding(.top, 0)
.padding(.bottom, 0)
.disclosureGroupStyle(self)
}
}
}
struct ContentView: View {
@Environment(\.colorScheme) var colorScheme
@State private var selectedList:Tutorials? = allLibraries[0]
@State var selectedImage:ImageItems? = imageGrid[0]
@State var expand = true
@State var expand2 = true
@State var expand3 = true
@State private var isHidden = false
var body: some View {
NavigationSplitView {
VStack(alignment: .leading) {
List(selection:$selectedList) {
ForEach(allLibraries, id: \.self) { item in
HStack {
Image(systemName:item.icon)
.frame(width:16, height:16, alignment: .center)
.foregroundColor(self.selectedList == item ? Color.white : Color.accentColor)
Text(item.name)
.foregroundColor(self.selectedList == item ? Color.white : nil)
}
.font(.system(size: 13,weight: .medium, design: .rounded))
.foregroundColor(colorScheme == .dark ? .white : .black)
.padding(0)
}
DisclosureGroup("Home Plan Graphics", isExpanded: $expand) {
ForEach(lessons, id: \.self) { item in
HStack {
Image(systemName:item.icon)
.frame(width:16, height:16, alignment: .center)
.foregroundColor(self.selectedList == item ? Color.white : Color.accentColor)
Text(item.name)
.foregroundColor(self.selectedList == item ? Color.white : nil)
}
.font(.system(size: 13,weight: .medium, design: .rounded))
.foregroundColor(colorScheme == .dark ? .white : .black)
.padding(.top, 5)
.padding(.bottom, 5)
}
}
.listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0))
.font(.system(size: 12, weight: .medium, design: .rounded))
.foregroundColor(.secondary)
.disclosureGroupStyle(MyDisclosureStyle())
DisclosureGroup("Garden Plan Graphics", isExpanded: $expand2) {
ForEach(videos, id: \.self) { item in
HStack {
Image(systemName:item.icon)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width:16, height:16, alignment: .center)
.foregroundColor(self.selectedList == item ? Color.white : Color.accentColor)
Text(item.name)
.foregroundColor(self.selectedList == item ? Color.white : nil)
}
.font(.system(size: 13, weight: .medium, design: .rounded))
.foregroundColor(colorScheme == .dark ? .white : .black)
.padding(.top, 5)
.padding(.bottom, 5)
}
}
.listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0))
.font(.system(size: 12, weight: .medium, design: .rounded))
.foregroundColor(.secondary)
.disclosureGroupStyle(MyDisclosureStyle())
}
}
.navigationSplitViewColumnWidth(min: 250, ideal: 250, max: 400)
.padding([.leading, .trailing], 5)
}
detail: {
let columns = Array(
repeating: GridItem.init(.adaptive(minimum: 120), spacing: 20),
count: 1)
ScrollView {
LazyVGrid(columns: columns, spacing: 20) {
ForEach(imageGrid, id: \.self) { item in
GridItemView(item: item)
}
}
.padding()
}
.background(colorScheme == .dark ? nil : Color.white)
}
}
}
struct GridItemView: View {
@Environment(\.colorScheme) var colorScheme
let item: ImageItems
var body: some View {
GeometryReader { reader in
VStack(spacing: 5) {
Image(systemName: item.icon)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 40, height:40, alignment: .center)
.foregroundColor(colorScheme == .dark ? nil : .black)
.fontWeight(.light)
Text(item.name)
.font(.system(size: 13, weight: .medium, design: .rounded))
.foregroundColor(colorScheme == .dark ? nil : .black)
}
.frame(width: reader.size.width, height: reader.size.height)
.background(colorScheme == .dark ? nil : Color.white)
}
.frame(height:100)
.overlay(
RoundedRectangle(cornerRadius: 10)
.stroke(colorScheme == .dark ? .white.opacity(0.2) : .black.opacity(0.2), lineWidth: 2)
)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
答案1
得分: 1
首先,请将您的结构体命名为单数形式,所选图像是一个名为 ImageItem
的对象,由于有一个默认值,因此无需将其设置为可选的。
@State private var selectedImage: ImageItem = imageGrid[0]
结构体中的 id
成员应为常量 (let
)。
LazyVGrid
不支持选择,但在实践中,单个选择只是一种特殊的单元格样式。
我的建议是在 GridItemView
中添加一个点击手势,将 selectedImage
设置为所点击的网格项。此外,向 GridItemView
添加一个 isSelected
属性,并根据其状态来设置项的样式。
LazyVGrid(columns: columns, spacing: 20) {
ForEach(imageGrid, id: \.self) { item in
GridItemView(item: item, isSelected: item == selectedImage)
.onTapGesture {
selectedImage = item
}
}
}
.padding()
}
英文:
First of all please name your structs in singular form, the selected image is one ImageItem
and as there is a default value there is no need to make it optional
@State private var selectedImage: ImageItem = imageGrid[0]
And the id
members in the structs should be constants (let
).
LazyVGrid
doesn't support selection, but in practice a single selection is just a special cell style.
My suggestion is to add a tap gesture to the GridItemView
which sets selectedImage
to the tapped grid item. Further add an isSelected
property to GridItemView
and style the item depending on the state
LazyVGrid(columns: columns, spacing: 20) {
ForEach(imageGrid, id: \.self) { item in
GridItemView(item: item, isSelected: item == selectedImage)
.onTapGesture {
selectedImage = item
}
}
}
.padding()
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论