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 而不是 ListForm

【讨论】:

【参考方案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 为此提供了合适的钩子。

有两种方法可以自定义按钮的外观:您可以实现ButtonStylePrimitiveButtonStyle,然后将其传递给.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() 方法(假设它仍然看起来像一个'轻敲')。然后它返回按钮提供的标签(即原始Buttonlabel: 参数的内容)并附加两个新修饰符:.opacity()10.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` 后向后导航时崩溃?