如何在SwiftUI的LazyVGrid中为项目创建选择状态,使其表现得像列表选择。

huangapple go评论127阅读模式
英文:

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&#39;t want multiple selections, so toggle doesn&#39;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: &quot;All Library Items&quot;, icon: &quot;house&quot;)]

let lessons = [Tutorials(name: &quot;Bathroom&quot;, icon: &quot;bathtub&quot;),
                   Tutorials(name: &quot;Bedroom&quot;, icon: &quot;bed.double&quot;),
                   Tutorials(name: &quot;Dining Room&quot;, icon: &quot;table.furniture&quot;),
                   Tutorials(name: &quot;Doors &amp; Windows&quot;, icon: &quot;door.left.hand.open&quot;),
               Tutorials(name: &quot;Kitchen&quot;, icon: &quot;cooktop&quot;),
               Tutorials(name: &quot;Lighting&quot;, icon: &quot;lamp.table&quot;),
               Tutorials(name: &quot;Living Room&quot;, icon: &quot;sofa&quot;),
               Tutorials(name: &quot;Miscellaneous&quot;, icon: &quot;ellipsis.circle&quot;),
               Tutorials(name: &quot;Office &amp; Study&quot;, icon: &quot;printer&quot;),
               Tutorials(name: &quot;Stairs&quot;, icon: &quot;stairs&quot;),
               Tutorials(name: &quot;Utility Room&quot;, icon: &quot;washer&quot;)]

let videos = [Tutorials(name: &quot;Decoration&quot;, icon: &quot;paintbrush&quot;),
               Tutorials(name: &quot;Furniture&quot;, icon: &quot;chair&quot;),
               Tutorials(name: &quot;Miscellaneous&quot;, icon: &quot;ellipsis.circle&quot;),
              Tutorials(name: &quot;Plants &amp; Trees&quot;, icon: &quot;tree&quot;),
              Tutorials(name: &quot;Ponds &amp; Pools&quot;, icon: &quot;figure.open.water.swim&quot;),
              Tutorials(name: &quot;Structures&quot;, icon: &quot;door.garage.closed&quot;)]

let imageGrid = [ImageItems(name: &quot;Decoration&quot;, icon: &quot;paintbrush&quot;),
              ImageItems(name: &quot;Furniture&quot;, icon: &quot;chair&quot;),
              ImageItems(name: &quot;Miscellaneous&quot;, icon: &quot;ellipsis.circle&quot;),
              ImageItems(name: &quot;Plants &amp; Trees&quot;, icon: &quot;tree&quot;),
              ImageItems(name: &quot;Ponds &amp; Pools&quot;, icon: &quot;figure.open.water.swim&quot;),
              ImageItems(name: &quot;Structures&quot;, icon: &quot;door.garage.closed&quot;),
              ImageItems(name: &quot;Furniture&quot;, icon: &quot;chair&quot;),
              ImageItems(name: &quot;Miscellaneous&quot;, icon: &quot;ellipsis.circle&quot;),
              ImageItems(name: &quot;Plants &amp; Trees&quot;, icon: &quot;tree&quot;),
              ImageItems(name: &quot;Ponds &amp; Pools&quot;, icon: &quot;figure.open.water&quot;)]


struct MyDisclosureStyle: DisclosureGroupStyle {
    func makeBody(configuration: Configuration) -&gt; some View {
            Button {
                withAnimation {
                    configuration.isExpanded.toggle()
                }
            } label: {
                VStack {
                    Spacer()
                        .frame(height: 15)
                    HStack(alignment: .firstTextBaseline) {
                        configuration.label
                        Spacer()
                        Image(systemName:&quot;chevron.right&quot;)
                            .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(&quot;Home Plan Graphics&quot;, 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(&quot;Garden Plan Graphics&quot;, 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()
}

huangapple
  • 本文由 发表于 2023年7月11日 00:58:29
  • 转载请务必保留本文链接:https://go.coder-hub.com/76655857.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定