SwiftUI使ForEach List行可在编辑模式下正确单击进行编辑



描述

我有以下代码:

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

当我单击Edit按钮时,EditMode会正确触发。但我注意到列表行在编辑模式下是不可单击的,这很好,因为我不希望它在编辑模式中遵循NavigationLink

尽管我想要的是将用户重定向到一个允许编辑所点击行的视图,或者更好的是,向用户显示一个版本表

我尝试过的

由于我无法在编辑模式下点击这一行,我尝试了几个技巧,但都没有达到我想要的效果。这是我的尝试。

.同时手势

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

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

这只是不起作用。似乎这个.synchronousGesture以某种方式破坏了NavigationLink,点击成功率为五分之一。

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

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

在非编辑模式下,NavigationLink仍然被窃听。但我注意到,一旦进入版本模式,它就有点像我想要的那样。

视图上的修改器条件扩展

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

我决定在View结构中添加扩展,以便定义修饰符的条件(我在Stackoverflow上的某个地方发现了这个代码示例):

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

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

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

它起到了作用,当用户在编辑模式下点击一行时,我能够显示这个编辑视图,而正常的非编辑模式仍然起作用因为它正确地触发了NavigationLink。

剩余问题

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

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

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

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

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

在这种情况下,我怎样才能完成我想要的
感谢您的帮助。

其他信息

我目前正在使用列表中的.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())

正如我们在评论中所讨论的,您的代码会执行列表中的所有操作,除了在点击披露V形符时高亮显示该行。要更改行的背景色,请使用.listRowBackground()。为了只突出显示一行,您需要在ForEach中的数组中使id之外的行可识别。然后,您有一个可选的@State变量来保存行id的值,并在.onTapGesture中设置行id。最后,使用DispatchQueue.main.asyncAfter()在一定时间后将变量重置为零。这会给你一个瞬间的亮点。

然而,一旦你走上这条路,你也必须管理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
}
}

最新更新