我遇到了同样的问题,当我寻找解决方案时,这个问题基本上是第一名。因此,在找到了一个相当优雅的解决方案后,这似乎是一个离开它的好地方。
问题似乎是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 状态相对简单,并且只有在按下时结束才会触发。我会把它作为练习留给读者?