SwiftUI 自定义 List 不能在 ForEach 中使用 Binding

Posted

技术标签:

【中文标题】SwiftUI 自定义 List 不能在 ForEach 中使用 Binding【英文标题】:SwiftUI custom List cannot use Binding in ForEach 【发布时间】:2020-08-12 09:19:51 【问题描述】:

我正在尝试构建一个自定义列表,用户可以在其中选择一个条目,该行将展开并显示一个选择器。这个选择器应该更新一个存储时间信息的对象(TimeItem)。

但是,我无法在 ForEach 循环中通过 Picker 使用绑定,我不知道为什么。 Xcode 中的错误消息是“编译器无法在合理的时间内对该表达式进行类型检查;请尝试将表达式分解为不同的子表达式”。

我还尝试使用ForEach(Array(items.enumerated()), id: \.1) 而不是ForEach(items) 来获取当前行的索引,但这会弄乱删除动画(但只是有时!?)。

我不想为每一行使用相同的绑定(例如self.$selectedElement.minutes) - 每行都应该有自己的绑定。

有人知道如何解决这个问题吗?感谢您的帮助!

class TimeItem: Identifiable, Equatable, ObservableObject 
    static func == (lhs: TimeItem, rhs: TimeItem) -> Bool 
        lhs.id == rhs.id
    

    let id = UUID()
    @Published var minutes: Int = 0
    @Published var seconds: Int = 30


struct ContentView: View 
    @State var items = [TimeItem]()
    @State var selectedElement: TimeItem?

    var body: some View 
        ScrollView()
            VStack
                ForEach(items) elem in
                    
                    ZStack
                        
                        Rectangle()
                            .cornerRadius(12)
                            .frame(height: elem == selectedElement ? 120 : 40)
                            .foregroundColor(Color.gray.opacity(0.15))

                        Text("\(elem.minutes)")
                            .opacity(elem == selectedElement ? 0 : 1)
                            .transition(AnyTransition.scale)
                        
                        if(elem == selectedElement)
                            HStack
                                Picker(selection: elem.$minutes, label: Text("")) // <- I can't use Binding with "elem"
                                ForEach(0..<60) i in
                                    Text("\(i)")
                                
                            
                            .frame(width: 120)
                            .clipped()
                            
                                Picker(selection: .constant(0), label: Text(""))
                                    ForEach(0..<60) i in
                                        Text("\(i)")
                                    
                                
                                .frame(width: 120)
                                .clipped()
                            
                            .frame(height: 120)
                            .clipped()
                        
                        

                        HStack
                            Button(action: 
                                self.items.removeAll  $0.id == elem.id 
                            )
                            
                                Image(systemName: "minus.circle.fill")
                                    .foregroundColor(Color.red)
                                    .font(.system(size: 22))
                                    .padding(.leading, 10)
                            
                            Spacer()
                        

                    
                    .padding(.horizontal)
                    .padding(.top)
                    .contentShape(Rectangle())
                    .onTapGesture 
                        withAnimation(.spring())
                            self.selectedElement = elem
                        
                    
                
            
            Spacer()

            Button(action: 
                self.items.append(TimeItem())
            )
            
                ZStack
                    Rectangle()
                        .cornerRadius(12)
                        .frame(height: 40)
                        .foregroundColor(Color.gray.opacity(0.15))

                    Text("Add")

                    HStack
                        Image(systemName: "plus.circle.fill")
                            .foregroundColor(Color.green)
                            .font(.system(size: 22))
                            .padding(.leading, 10)

                        Spacer()
                    
                .padding()
            
        .animation(.spring(), value: items)
    


【问题讨论】:

【参考方案1】:

你应该按照编译器所说的去做:将表达式(即大视图)分解为不同的子表达式(即较小的子视图)

这里是固定组件(使用 Xcode 11.4 / ios 13.4 测试)

struct ContentView: View 
    @State var items = [TimeItem]()
    @State var selectedElement: TimeItem?

    var body: some View 
        ScrollView()
            VStack
                ForEach(items) elem in
                    ItemRowView(elem: elem, selectedElement: self.$selectedElement)
                        self.items.removeAll  $0.id == elem.id 
                    
                
            
            Spacer()
            AddItemView 
                self.items.append(TimeItem())
            
        .animation(.spring(), value: items)
    


struct SelectedElementView: View 
    @ObservedObject var elem: TimeItem

    var body: some View 
        HStack
            Picker(selection: $elem.minutes, label: Text(""))
                ForEach(0..<60) i in
                    Text("\(i)")
                
            
            .frame(width: 120)
            .clipped()

            Picker(selection: .constant(0), label: Text(""))
                ForEach(0..<60) i in
                    Text("\(i)")
                
            
            .frame(width: 120)
            .clipped()
        
        .frame(height: 120)
        .clipped()
    


struct AddItemView: View 
    let action: ()->()
    var body: some View 
        Button(action: action)
        
            ZStack
                Rectangle()
                    .cornerRadius(12)
                    .frame(height: 40)
                    .foregroundColor(Color.gray.opacity(0.15))

                Text("Add")

                HStack
                    Image(systemName: "plus.circle.fill")
                        .foregroundColor(Color.green)
                        .font(.system(size: 22))
                        .padding(.leading, 10)

                    Spacer()
                
            .padding()
        
    


struct ItemRowView: View 
    @ObservedObject var elem: TimeItem
    @Binding var selectedElement: TimeItem?
    let action: ()->()

    var body: some View 
        ZStack

            Rectangle()
                .cornerRadius(12)
                .frame(height: elem == selectedElement ? 120 : 40)
                .foregroundColor(Color.gray.opacity(0.15))

            Text("\(elem.minutes)")
                .opacity(elem == selectedElement ? 0 : 1)
                .transition(AnyTransition.scale)

            if(elem == selectedElement)
                SelectedElementView(elem: elem)
            


            HStack
                Button(action: action)
                
                    Image(systemName: "minus.circle.fill")
                        .foregroundColor(Color.red)
                        .font(.system(size: 22))
                        .padding(.leading, 10)
                
                Spacer()
            

        
        .padding(.horizontal)
        .padding(.top)
        .contentShape(Rectangle())
        .onTapGesture 
            withAnimation(.spring())
                self.selectedElement = self.elem
            
        
    

【讨论】:

有趣!谢谢你的帮助!我什至没有考虑将视图分解为子视图,因为对我来说,当代码出现其他问题时,通常会出现该错误消息。

以上是关于SwiftUI 自定义 List 不能在 ForEach 中使用 Binding的主要内容,如果未能解决你的问题,请参考以下文章

iOS 16 中 SwiftUI 4.0 Toolbar 自定义背景色以及 List 自动编辑操作的原生支持

iOS 16 中 SwiftUI 4.0 Toolbar 自定义背景色以及 List 自动编辑操作的原生支持

带有选择器的 SwiftUI 列表:选择不适用于自定义类型(macOS)

SwiftUI实战-自定义弹窗

用 SwiftUI ViewModifier 自定义弹窗

用 SwiftUI ViewModifier 自定义弹窗