ios SwiftUI“滑动操作”和“确认对话框”删除了错误的项目

ma8fv8wu  于 2023-03-09  发布在  iOS
关注(0)|答案(3)|浏览(250)

我正在尝试使用iOS 15.0 swipeActionsconfirmationDialog删除List中的项目。
但发生的是错误的项目被删除。
下面是我的代码:

struct ConversationsSection: View {

@State private var isShowingDeleteActions = false

let items = ["One", "Two", "Three", "Four", "Five"]

var body: some View {
    List(items, id: \.self) { item in
        Text(item)
            .swipeActions(edge: .trailing) {
                Button(role: .destructive) {
                    isShowingDeleteActions = true
                    print("Trying to delete: " + item)
                } label: {
                    Label("Delete", systemImage: "trash")
                }
            }
            .confirmationDialog("Delete item?", isPresented: $isShowingDeleteActions) {
                Button("Confirm Delete", role: .destructive) {
                    print("Actually deleting: " + item)
                    isShowingDeleteActions = false
                }
            }
    }
}

}
输出为:

Trying to delete: Two
Actually deleting: Four
Trying to delete: Five
Actually deleting: Three

所以我滑动一个项目,confirmationDialog就出现了,但是在confirmationDialog里面,另一个项目就通过了,为什么呢?

i7uq4tfw

i7uq4tfw1#

我是这样想的:在ForEach循环中有一个confirmationDialog修饰符,因此有多个确认对话框,它们的显示由一个$isShowingDeleteActions状态变量控制。当这种情况发生时,SwiftUI无法可靠地从设置状态变量的循环示例中显示对话框-因此它可能最终显示一个 different 对话框,并且item值不同。
我知道这有多令人沮丧!
一种解决方法是将confirmationDialog完全移出循环,这样就只有一个修饰符使用$isShowingDeleteActions,问题是不再有对item的直接引用,但是我们可以通过在第二个状态变量中保留一个引用来弥补:

struct ConversationsSection: View {

@State private var isShowingDeleteActions = false
@State private var itemToDelete: Item? = nil

var body: some View {
    List(items, id: \.self) { item in
        Text(item)
            .swipeActions(edge: .trailing) {
                Button(role: .destructive) {
                    itemToDelete = item
                    isShowingDeleteActions = true
                } label: {
                    Label("Delete", systemImage: "trash")
                }
            }
    }
    .confirmationDialog("Delete item?", isPresented: $isShowingDeleteActions) {
        Button("Confirm Delete", role: .destructive) {
            if let item = itemToDelete {
                print("Actually deleting: " + item)
                isShowingDeleteActions = false
                itemToDelete = nil
            }
        }
    }
}
cx6n0qe3

cx6n0qe32#

Scott的解决方案运行良好,但当你想在iPad上显示confirmationDialog时,附件被关闭(因为它现在使用List作为附件)。

可以通过将Text及其修改器和@State属性提取到新视图中来修复此问题,如下所示:

import SwiftUI

struct ItemView: View {
    
    @State private var isShowingDeleteActions = false
    
    var item: String
    
    var body: some View {
        Text(item)
            .frame(minWidth: 0, maxWidth: .infinity, alignment: .leading)
            .swipeActions(edge: .trailing) {
                Button(role: .destructive) {
                    isShowingDeleteActions = true
                    print("Trying to delete: " + item)
                } label: {
                    Label("Delete", systemImage: "trash")
                }
            }
            .confirmationDialog("Delete item?", isPresented: $isShowingDeleteActions) {
                Button("Confirm Delete", role: .destructive) {
                    print("Actually deleting: " + item)
                    isShowingDeleteActions = false
                }
            }
    }
}

struct ConversationsSection: View {

    let items = ["One", "Two", "Three", "Four", "Five"]

    var body: some View {
        NavigationView {
            List(items, id: \.self) { item in
                ItemView(item: item)
            }
        }
    }
}

本文截图:

p3rjfoxz

p3rjfoxz3#

两种方式:
1.将“@State private var isPresented”、“.swipeActions”和“.confirmationDialog”放入列表项中,就像Esera's answer一样。但有一个问题,confirmationDialog经常在一秒钟内弹出并消失,然后在垃圾桶按钮单击之前的swipeActions时弹出。经过一些研究,我发现这是因为swipeActions-.destructive button。使用.none代替,一切都很好。请参见ConfirmationDialog 0。
1.使用@State私有变量选定消息:Int?= nil,在swipeActions中设置它,在confirmationDialog中使用它。问题是这个confirmationDialog for whole list,而不是for a list item,就像Esera's answer一样。
检查我的测试代码:

struct ConfirmationDialog: View {
    var body: some View {
        ScrollView {
            ConfirmationDialog3()
            ConfirmationDialog0()
            ConfirmationDialog1()
            ConfirmationDialog2()
        }
    }
}

struct ConfirmationDialog3: View {
    @State private var messages: [Int] = (0..<5).map { $0 }
    @State private var confirmationShown = false
    @State private var selectedMessage: Int? = nil
    
    var body: some View {
        NavigationView {
            List {
                ForEach(messages, id: \.self) { message in
                    Text("\(message)")
                        .swipeActions {
                            Button(
                                role: .destructive,
                                action: {
                                    selectedMessage = message
                                    confirmationShown = true
                                }
                            ) {
                                Image(systemName: "trash")
                            }
                        }
                }
            }
            .navigationTitle("YEAH!")
            .confirmationDialog(
                "Are you sure?",
                isPresented: $confirmationShown,
                titleVisibility: .visible,
                presenting: selectedMessage
            ) { message in
                Button("Yes, delete: \(message)") {
                    withAnimation {
                        messages = messages.filter { $0 != message }
                    }
                }.keyboardShortcut(.defaultAction)
                Button("No", role: .cancel) {}
            } message: { message in
                Text("\(message)")
            }
        }
    }
}

struct ConfirmationDialog0: View {
    @State private var messages: [Int] = (0..<5).map { $0 }
    
    var body: some View {
        NavigationView {
            List {
                ForEach(messages, id: \.self) { message in
                    itemView0(message: message) { _ in
                        withAnimation {
                            messages = messages.filter { $0 != message }
                        }
                    }
                }
            }
            .navigationTitle("WRONG if .destructive!")
        }
    }
}

struct itemView0: View {
    @State private var confirmationShown = false
    let message: Int
    let onDelete: (Int) -> Void
    
    var body: some View {
        Text("\(message)")
            .swipeActions {
                Button(
                    role: .none,//.destructive,
                    action: { confirmationShown = true }
                ) {
                    Image(systemName: "trash")
                }
                .tint(Color.red)
            }
            .confirmationDialog(
                "Are you sure?",
                isPresented: $confirmationShown,
                titleVisibility: .visible,
                presenting: message
            ) { message in
                Button("Yes, delete: \(message)") {
                    onDelete(message)
                }.keyboardShortcut(.defaultAction)

                Button("No", role: .cancel) {}
            } message: { message in
                Text("\(message)")
            }
    }
}

struct ConfirmationDialog1: View {
    @State private var messages: [Int] = (0..<5).map { $0 }
    @State private var confirmationShown = false
    
    var body: some View {
        NavigationView {
            List {
                ForEach(messages, id: \.self) { message in
                    Text("\(message)")
                        .swipeActions {
                            Button(
                                role: .destructive,
                                action: { confirmationShown = true }
                            ) {
                                Image(systemName: "trash")
                            }
                        }
                        .confirmationDialog(
                            "Are you sure?",
                            isPresented: $confirmationShown,
                            titleVisibility: .visible,
                            presenting: message
                        ) { message in
                            Button("Yes, delete: \(message)") {
                                withAnimation {
                                    messages = messages.filter { $0 != message }
                                }
                            }.keyboardShortcut(.defaultAction)

                            Button("No", role: .cancel) {}
                        } message: { message in
                            Text("\(message)")
                        }
                }
            }
            .navigationTitle("WRONG!")
        }
    }
}

struct ConfirmationDialog2: View {
    @State private var showingConfirmation = false
    @State private var backgroundColor = Color.white
    
    var body: some View {
        Text("Hello, World!")
            .frame(maxWidth: .infinity)
            .frame(height: 100)
            .background(backgroundColor)
            .onTapGesture {
                showingConfirmation = true
            }
            .confirmationDialog("Change background", isPresented: $showingConfirmation) {
                Button("Red") { backgroundColor = .red }
                Button("Green") { backgroundColor = .green }
                Button("Blue") { backgroundColor = .blue }
                Button("Cancel", role: .cancel) { }
            } message: {
                Text("Select a new color")
            }
    }
}

struct ConfirmationDialog_Previews: PreviewProvider {
    static var previews: some View {
        ConfirmationDialog()
    }
}

相关问题