0

描述

我有以下代码:

NavigationView {
    List {
        ForEach(someList) { someElement in
            NavigationLink(destination: someView()) {
                someRowDisplayView()
            } //: NavigationLink
        } //: ForEach
    } //: List
} //: NavigationView

基本上,它显示先前由用户输入填充的类对象的动态列表。我有一个“addToList()”函数,它允许用户添加一些元素(比如说姓名、电子邮件等),一旦用户确认它将元素添加到这个列表中。然后我有一个视图,它通过ForEach显示列表,然后为每个元素创建一个NavigationLink,如上面的代码示例所示。

一旦点击了这个列表的一个元素,用户应该被正确地重定向到一个目标视图,正如NavigationLink所暗示的那样。所有这一切都很好。

我想要的是

这是我面临一个问题的地方:我希望用户能够编辑此列表中一行的内容,而不必删除该行并重新添加另一行。

我决定使用 SwiftUI 的EditMode()功能。所以这就是我想出的:

@State private var editMode = EditMode.inactive
[...]
NavigationView {
    List {
        ForEach(someList) { someElement in
            NavigationLink(destination: someView()) {
                someRowDisplayView()
            } //: NavigationLink
        } //: ForEach
    } //: List
    .toolbar {
        ToolbarItem(placement: .navigationBarLeading) {
            EditButton()
        }
    }
    .environment(\.editMode, $editMode)
} //: NavigationView

当我单击“编辑”按钮时,会正确触发EditMode 。但我注意到列表行在编辑模式下不可点击,这很好,因为我不希望它在编辑模式下跟随NavigationLink 。

虽然我想要的是将用户重定向到允许编辑点击行的视图,或者更好的是,将编辑表呈现给用户

我试过的

由于我无法在编辑模式下点击该行,因此我尝试了几种技巧,但都没有按照我的意愿得出结论。这是我的尝试。

.simultaneousGesture

我尝试将修改后的.simultaneousGesture添加到我的NavigationLink并切换@State变量以显示版本表。

@State private var isShowingEdit: Bool = false
[...]
.simultaneousGesture(TapGesture().onEnded{
    isShowingEdit = true
})
.sheet(isPresented: $isShowingEdit) {
    EditionView()
}

这根本行不通。似乎这个.simultaneousGesture以某种方式破坏了 NavigationLink,点击成功就像五次中的一次。

即使在编辑模式上添加条件也不起作用,例如:

if (editMode == .active) {
    isShowingEdit = true
}

在非版本模式下,NavigationLink 仍然存在漏洞。但我注意到,一旦进入编辑模式,它就会像我想要的那样做。

View 上的修饰符条件扩展

在上一次失败之后,我的第一个想法是我需要仅在编辑模式下触发点击手势,因此在非编辑模式下,视图甚至不知道它有点击手势。

我决定向View 结构添加一个扩展,以便为修饰符定义条件(我在 Stackoverflow 的某处找到了此代码示例):

extension View {
    @ViewBuilder func `if`<Content: View>(_ condition: Bool, transform: (Self) -> Content) -> some View {
        if condition {
            transform(self)
        } else {
            self
        }
    }
}

然后在我的主代码中,而不是之前尝试过的 .simultaneousGesture 修饰符,我将其添加到NavigationLink

.if(editMode == .active) { view in
    view.onTapGesture {
        isShowingEdit = true
    }
}

它有效,一旦用户在编辑模式下点击一行,我就能够显示此版本视图,并且正常的非编辑模式仍然有效,因为它正确触发了 NavigationLink。

剩下的问题

但是有些事情让我感到困扰,它感觉不像是自然或原生的行为。一旦被点击,该行没有显示任何反馈,就像它在跟随NavigationLink时在非编辑模式下显示的那样:即使在很短的时间内它也不会突出显示。
点击手势修改器只是执行我所询问的代码,没有动画、反馈或任何东西

  • 我希望用户知道并查看点击了哪一行,并且我希望它像点击 NavigationLink 时一样突出显示:只需让它看起来就像该行实际上是 "点击"。我希望这是有道理的。

  • 我还希望在用户点击整行时触发代码,而不仅仅是文本可见的部分。目前,点击该行的空白字段不会执行任何操作。它必须有文字。

一个更好的解决方案是阻止我对视图结构应用这样的条件修饰符和扩展,因为如果可能的话,我肯定更喜欢更自然和更好的方法。

我是 Swift 的新手,所以也许有更简单或更好的解决方案,我愿意遵循最佳实践。

在这种情况下,我怎么能设法完成我想要的?
谢谢您的帮助。

附加信息

我目前正在使用 List 上的.onDelete.onMove实现以及 SwiftUI 版本模式,我想继续使用它们。
我正在使用 Swift 语言和 SwiftUI 框架为最低iOS 14开发应用程序。
如果您需要更多代码示例或更好的解释,请随时提出,我将编辑我的问题。

最小的工作示例

Yrb 问道。

import SwiftUI
import PlaygroundSupport

extension View {
    @ViewBuilder func `if`<Content: View>(_ condition: Bool, transform: (Self) -> Content) -> some View {
        if condition {
            transform(self)
        } else {
            self
        }
    }
}

struct SomeList: Identifiable {
    let id: UUID = UUID()
    var name: String
}

let someList: [SomeList] = [
    SomeList(name: "row1"),
    SomeList(name: "row2"),
    SomeList(name: "row3")
]

struct ContentView: View {
    @State private var editMode = EditMode.inactive
    @State private var isShowingEditionSheet: Bool = false
    
    var body: some View {
        NavigationView {
            List {
                ForEach(someList) { someElement in
                    NavigationLink(destination: EmptyView()) {
                        Text(someElement.name)
                    } //: NavigationLink
                    .if(editMode == .active) { view in
                        view.onTapGesture {
                            isShowingEditionSheet = true
                        }
                    }
                } //: ForEach
                .onDelete { (indexSet) in }
                .onMove { (source, destination) in }
            } //: List
            .toolbar {
                ToolbarItem(placement: .navigationBarLeading) {
                    EditButton()
                }
            }
            .environment(\.editMode, $editMode)
            .sheet(isPresented: $isShowingEditionSheet) {
                Text("Edition sheet")
            }
        } //: NavigationView
    }
}

PlaygroundPage.current.setLiveView(ContentView())
4

1 回答 1

0

正如我们在评论中讨论的那样,您的代码执行列表中的所有操作,除了在点击披露 V 形时突出显示该行。要更改行的背景颜色,请使用.listRowBackground(). 要仅突出显示一行,您需要idForEach. 然后,您有一个可选@State变量来保存行 ID 的值,并在您的.onTapGesture. 最后,您使用 aDispatchQueue.main.asyncAfter()在一定时间后将变量重置为 nil。这给了你一个瞬间的亮点。

然而,一旦你走这条路,你也必须为你管理背景突出显示NavigationLink。这带来了自身的复杂性。您需要使用NavigationLink(destination:,isActive:,label:)初始化程序,使用 setter 和 getter 创建绑定,并在 getter 中运行您的高亮代码。

struct ContentView: View {
    @State private var editMode = EditMode.inactive
    @State private var isShowingEditionSheet: Bool = false
    @State private var isTapped = false
    @State private var highlight: UUID?

    var body: some View {
        NavigationView {
            List {
                ForEach(someList) { someElement in
                    // You use the NavigationLink(destination:,isActive:,label:) initializer
                    // Then create your own binding for it
                    NavigationLink(destination: EmptyView(), isActive: Binding<Bool>(
                        get: { isTapped },
                        // in the set, you can run other code
                        set: {
                            isTapped = $0
                            // Set highlight to the row you just tapped
                            highlight = someElement.id
                            // Reset the row id to nil
                            DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
                                highlight = nil
                            }
                        }
                    )) {
                        Text(someElement.name)
                    } //: NavigationLink
                    // identify your row
                    .id(someElement.id)
                    .if(editMode == .active) { view in
                        view.onTapGesture {
                            // Set highlight to the row you just tapped
                            highlight = someElement.id
                            // Reset the row id to nil
                            DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
                                highlight = nil
                            }
                            isShowingEditionSheet = true
                        }
                    }
                    .listRowBackground(highlight == someElement.id ? Color(UIColor.systemGray5) : Color(UIColor.systemBackground))
                } //: ForEach
                .onDelete { (indexSet) in }
                .onMove { (source, destination) in }
            } //: List

            .toolbar {
                ToolbarItem(placement: .navigationBarLeading) {
                    EditButton()
                }
            }
            .environment(\.editMode, $editMode)
            .sheet(isPresented: $isShowingEditionSheet) {
                Text("Edition sheet")
            }
        } //: NavigationView
    }
}
于 2021-11-26T18:43:09.597 回答