NavigationLink 中的 SwiftUI 按钮
Posted
技术标签:
【中文标题】NavigationLink 中的 SwiftUI 按钮【英文标题】:SwiftUI Button inside a NavigationLink 【发布时间】:2020-04-11 13:40:30 【问题描述】:我有一个列表项视图,它显示有关嵌入在 navigationLink 中的任务的一些基本信息。
我想使用 navigationLink 中的按钮来切换 task.isComplete
,而不进行任何导航。
这是我目前的代码:
var body: some View
NavigationLink(destination: TaskDetailView())
HStack
RoundedRectangle(cornerRadius: 5)
.frame(width: 15)
.foregroundColor(getColor(task: task))
VStack
Text(task.name!)
.font(.headline)
Spacer()
Spacer()
Button(action:
self.task.isComplete.toggle()
)
if task.isComplete == true
Image(systemName: "checkmark.circle.fill")
else
Image(systemName: "circle")
.foregroundColor(getColor(task: task))
.font(.system(size: 22))
目前,按钮操作不会执行,因为只要按下按钮,navigationLink 就会将您带到目标视图。我尝试将按钮放在 navigationLink 之外 - 这允许操作发生,但导航仍然发生。
有没有办法让这成为可能?
谢谢。
【问题讨论】:
【参考方案1】:请将每个项目视图放入 VStack
而不是 List
或 Form
。
【讨论】:
【参考方案2】:试试这个方法:
var body: some View
NavigationLink(destination: TaskDetailView())
HStack
RoundedRectangle(cornerRadius: 5)
.frame(width: 15)
.foregroundColor(getColor(task: task))
VStack
Text(task.name!)
.font(.headline)
Spacer()
Spacer()
Button(action: )
if task.isComplete == true
Image(systemName: "checkmark.circle.fill")
else
Image(systemName: "circle")
.foregroundColor(getColor(task: task))
.font(.system(size: 22))
.onTapGesture
self.task.isComplete.toggle()
【讨论】:
虽然现在可以按下按钮,但不幸的是,无论何时按下,navigationLink 也会被触发。 我没有看到你的所有代码,但截获的 TapGesture 更新后,即使在你的原始变体中也应该有帮助。【参考方案3】:要达到你想要的结果,你必须使用 .onTapGesture 处理程序而不是 NavigationLink 上的按钮。下面的例子对我有用。
尝试替换:
Button(action:
self.task.isComplete.toggle()
)
if task.isComplete == true
Image(systemName: "checkmark.circle.fill")
else
Image(systemName: "circle")
与:
Image(systemName: task.isComplete ? "checkmark.circle.fill" : "circle")
.onTapGesture self.task.isComplete.toggle()
【讨论】:
简单而优雅。应该是公认的答案。 这样做的缺点是您不会获得按钮的自动关闭状态(因为它不再是按钮) @sam-w,答案是 1 岁。谢谢【参考方案4】:我遇到了同样的问题,当我寻找解决方案时,这个问题基本上是第一名。因此,在找到了一个相当优雅的解决方案后,这似乎是一个离开它的好地方。
问题似乎是NavigationLink
的手势被安装,以至于它忽略了它的子视图手势:它有效地使用.gesture(_:including:)
和GestureMask
的.gesture
,这意味着它将优先于任何类似优先级的内部手势。
最终,您需要的是一个安装了.highPriorityGesture()
的按钮来触发其操作,同时保持按钮的常用 API:传入单个操作方法以在触发时运行,而不是定义一个普通的 Image 并摆弄手势识别器来更改状态等。要使用标准的Button
行为和 API,您需要更深入一点,并直接定义一些按钮的行为。令人高兴的是,SwiftUI 为此提供了合适的钩子。
有两种方法可以自定义按钮的外观:您可以实现ButtonStyle
或PrimitiveButtonStyle
,然后将其传递给.buttonStyle()
修饰符。这两种协议类型都允许您定义将样式应用于按钮的Label
视图的方法,无论定义为什么。在常规的ButtonStyle
中,您会收到标签视图和isPressed
布尔值,允许您根据按钮的按下状态更改外观。 PrimitiveButtonStyle
提供了更多控制,但代价是必须自己跟踪触摸状态:您会收到标签视图和 trigger()
回调,您可以调用它来触发按钮的操作。
后者是我们想要的:我们将拥有按钮的手势,并能够跟踪触摸状态并确定何时触发它。然而,对于我们的用例而言,重要的是,我们负责将该手势附加到视图上——这意味着我们可以使用 .highPriorityGesture()
修饰符来执行此操作,并为按钮赋予比链接本身更高的优先级。
对于一个简单的按钮,这实际上是相当简单的实现。这是一个简单的按钮样式,它使用内部视图来管理按下/按下状态,使按钮在触摸时半透明,并使用高优先级手势来实现按下/触发状态更改:
struct HighPriorityButtonStyle: PrimitiveButtonStyle
func makeBody(configuration: PrimitiveButtonStyle.Configuration) -> some View
MyButton(configuration: configuration)
private struct MyButton: View
@State var pressed = false
let configuration: PrimitiveButtonStyle.Configuration
var body: some View
let gesture = DragGesture(minimumDistance: 0)
.onChanged _ in self.pressed = true
.onEnded value in
self.pressed = false
if value.translation.width < 10 && value.translation.height < 10
self.configuration.trigger()
return configuration.label
.opacity(self.pressed ? 0.5 : 1.0)
.highPriorityGesture(gesture)
工作发生在内部MyButton
视图类型中。它维护一个pressed
状态,定义一个拖动手势(用于跟踪向下/向上/结束事件)切换该状态并在手势结束时从样式配置中调用trigger()
方法(假设它仍然看起来像一个'轻敲')。然后它返回按钮提供的标签(即原始Button
的label:
参数的内容)并附加两个新修饰符:.opacity()
或1
或0.5
,具体取决于按下状态,以及定义的手势为.highPriorityGesture()
。
如果您不想为触地状态提供不同的外观,这可以更简单一些。如果不需要 @State
属性来保存它,您可以在 makeBody()
实现中执行所有操作:
struct StaticHighPriorityButtonStyle: PrimitiveButtonStyle
func makeBody(configuration: PrimitiveButtonStyle.Configuration) -> some View
let gesture = TapGesture()
.onEnded _ in configuration.trigger()
return configuration.label
.opacity(pressed ? 0.5 : 1.0)
.highPriorityGesture(gesture)
最后,这是我用来测试上面第一个实现的预览。在实时预览中运行,您会看到在触地期间按钮文本变暗:
struct HighPriorityButtonStyle_Previews: PreviewProvider
static var previews: some View
NavigationView
List
NavigationLink(destination: Text("Hello"))
Button(action: print("hello") )
Text("Button!")
.foregroundColor(.accentColor)
.buttonStyle(HighPriorityButtonStyle())
请注意,如果您想在用户开始拖动等时取消手势,则需要做更多的工作。那完全是另一回事。但是,根据手势值的translation
属性更改pressed
状态相对简单,并且只有在按下时结束才会触发。我会把它作为练习留给读者?
【讨论】:
【参考方案5】:有同样的问题,这是我找到的答案:
这个人的所有功劳和他的回答https://***.com/a/58176268/11419259
struct ScaleButtonStyle: ButtonStyle
func makeBody(configuration: Self.Configuration) -> some View
configuration.label
.scaleEffect(configuration.isPressed ? 2 : 1)
struct Test2View: View
var body: some View
Button(action: )
Image("button1")
.buttonStyle(ScaleButtonStyle())
似乎.buttonStyle(ScaleButtonStyle())
在这里创造了所有魔力。
这就是我在我的项目中使用的方式,而且效果很好。
HStack
NavigationLink(destination: DetailView())
VStack
Text(habits.items[index].name)
.font(.headline)
Spacer()
Text("\(habits.items[index].amount)")
.foregroundColor(.purple)
.underline()
Divider()
Button(action: habits.items[index].amount += 1 )
Image(systemName: "plus.square")
.resizable()
.scaledToFit()
.scaleEffect(self.scaleValue)
.frame(height: 20)
.foregroundColor(.blue)
.buttonStyle(ScaleButtonStyle())
【讨论】:
以上是关于NavigationLink 中的 SwiftUI 按钮的主要内容,如果未能解决你的问题,请参考以下文章
SwiftUI 中的 NavigationLink 不接受参数
表单/表格中的 SwiftUI 多个 NavigationLink - 条目保持突出显示
使用 XCTestCases 类中的可访问性标识符访问 NavigationLink - SwiftUI
LazyVGrid 中的 NavigationLink 循环返回所有条目,SwiftUI
NavigationLink SwiftUI 中的三元运算符
为啥我的 SwiftUI 应用程序在 `NavigationView` 中的 `navigationBarItems` 内放置 `NavigationLink` 后向后导航时崩溃?