【问题标题】:SwiftUI Button inside a NavigationLinkNavigationLink 中的 SwiftUI 按钮
【发布时间】: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 之外 - 这允许操作发生,但导航仍然发生。

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

谢谢。

【问题讨论】:

    标签: swiftui


    【解决方案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://stackoverflow.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())
        
                        
        }
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2020-12-29
          • 2020-07-25
          • 2020-01-08
          • 2020-02-22
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多