简体   繁体   English

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

[英]SwiftUI custom List cannot use Binding in ForEach

I'm trying to build a custom list where the user can select an entry and the row will expand and show a picker.我正在尝试构建一个自定义列表,用户可以在其中 select 一个条目,该行将展开并显示一个选择器。 This picker should update an object (TimeItem) which stores the time information.此选择器应更新存储时间信息的 object (TimeItem)。

However, I was not able to use Binding in the ForEach Loop with Picker and I don't know why.但是,我无法使用 Picker 在 ForEach 循环中使用绑定,我不知道为什么。 The error message in Xcode is "The compiler is unable to type-check this expression in reasonable time; try breaking up the expression into distinct sub-expressions". Xcode 中的错误消息是“编译器无法在合理的时间内对该表达式进行类型检查;尝试将表达式分解为不同的子表达式”。

I also tried to use ForEach(Array(items.enumerated()), id: \.1) instead of ForEach(items) to get the index of the current row but that would mess up the delete animation (but only sometimes?.).我还尝试使用ForEach(Array(items.enumerated()), id: \.1)而不是ForEach(items)来获取当前行的索引,但这会弄乱删除 animation (但只是有时? )。

I do not want to use the same Binding for each row (for ex. self.$selectedElement.minutes ) - every row should have its own Binding.我不想为每一行使用相同的绑定(例如self.$selectedElement.minutes ) - 每行都应该有自己的绑定。

Does anybody know how to fix this issue?有谁知道如何解决这个问题? Thanks for helping!感谢您的帮助!

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

That case when you should do what compiler said: break up expression (ie. that big view) into distinct sub-expressions (ie. smaller subviews)这种情况下你应该按照编译器所说的去做:将表达式(即那个大视图)分解成不同的子表达式(即较小的子视图)

Here is fixed components (tested with Xcode 11.4 / iOS 13.4)这是固定组件(使用 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
            }
        }
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM