NavigationLink 中的 SwiftUI 按钮

Dyl*_*owe 11 swiftui

我有一个列表项的视图,该视图显示有关嵌入在导航链接中的任务的一些基本信息。

我想使用 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))
            }
        }
    }
Run Code Online (Sandbox Code Playgroud)

当前,不会执行按钮操作,因为只要按下按钮,导航链接就会将您带到目标视图。我尝试将按钮放在 navigationLink 之外 - 这允许操作发生,但导航仍然发生。

有没有办法使这成为可能?

谢谢。

Ale*_*tor 8

要达到您想要的结果,您必须在 .onTapGesture 处理程序中使用其他内容,而不是 NavigationLink 上的按钮。下面的例子对我有用。

尝试更换:

Button(action: {
    self.task.isComplete.toggle()
}) {
    if task.isComplete == true {
        Image(systemName: "checkmark.circle.fill")
    } else {
        Image(systemName: "circle")
    }
}
Run Code Online (Sandbox Code Playgroud)

和:

Image(systemName: task.isComplete ? "checkmark.circle.fill" : "circle")
    .onTapGesture { self.task.isComplete.toggle() }
Run Code Online (Sandbox Code Playgroud)

  • 简单又灵活。应该是公认的答案。 (3认同)
  • 这样做的缺点是您不会获得按钮的自动按下状态(因为它不再是按钮) (2认同)

Jim*_*vey 8

我遇到了同样的问题,这个问题基本上是我搜索解决方案时的第一个问题。因此,找到了一个相当优雅的解决方案,这似乎是一个离开它的好地方。

问题似乎NavigationLink是安装了's 手势,以便忽略其子视图手势:它有效地.gesture(_:including:)与 a GestureMaskof一起使用.gesture,这意味着它将优先于任何具有类似优先级的内部手势。

最终你需要的是一个.highPriorityGesture()安装了一个按钮来触发它的动作,同时保持按钮的常用 API:传递一个单一的动作方法来在它被触发时运行,而不是定义一个普通的 Image 并摆弄一个手势识别器来改变状态,等等。要使用标准Button行为和 API,您需要更深入一些,并直接定义一些按钮的行为。令人高兴的是,SwiftUI 为此提供了合适的钩子。

有两种方法可以自定义按钮的外观:您可以实现 aButtonStyle或 a PrimitiveButtonStyle,然后将其传递给.buttonStyle()修饰符。这两种协议类型都允许您定义将样式应用于按钮Label视图的方法,无论定义为什么。在常规中ButtonStyle,您会收到标签视图和一个isPressed布尔值,允许您根据按钮的触摸状态更改外观。APrimitiveButtonStyle提供了更多控制,但必须自己跟踪触摸状态:您收到标签视图和一个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)
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

工作发生在内部MyButton视图类型中。它维护一个pressed状态,定义一个拖动手势(用于跟踪向下/向上/结束事件),trigger()该手势切换该状态并在手势结束时从样式配置调用该方法(假设它仍然看起来像“点击”)。然后它返回按钮提供的标签(即label: { }原始参数的内容Button),并附加两个新的修饰符: an .opacity()of1或0.5取决于按下状态,以及定义的手势作为 a .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)
    }
}
Run Code Online (Sandbox Code Playgroud)

最后,这是我用来测试上面第一个实现的预览。在实时预览中运行,您会在触地时看到按钮文本变暗:

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())
                }
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

请注意,如果您想在用户开始拖动等时取消手势,则需要做更多的工作。这是另一回事。但是,pressed根据手势值的translation属性更改状态相对简单,并且仅在按下时结束时才触发。我将把它留给读者作为练习


spn*_*nkr 5

添加.allowsHitTesting(false)到您的Button,当用户点击它时,NavigationLink 将被触发。

您拥有的任何按钮状态(例如活动样式)仍然有效。

NavigationLink(destination: TheBestViewEver(), label: {
    
    Button("My Button", action: {
        
    })
    .allowsHitTesting(false)
    
})
Run Code Online (Sandbox Code Playgroud)

适用于 Xcode 14.1、Swift 5、iOS 15.0+。可能适用于旧版本。


E.C*_*oms -3

请将每个项目视图放在 aVStack而不是 aList或中Form。