NavigationLink中的SwiftUI按钮

时间:2019-12-18 23:53:35

标签: swiftui

我有一个列表项视图,该列表项显示了有关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之外-这允许进行操作,但是导航仍在进行。

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

谢谢。

5 个答案:

答案 0 :(得分:5)

我遇到了同样的问题,这个问题基本上是我寻找解决方案时的热门话题。因此,找到了一个合理的解决方案后,这似乎是个好地方。

问题似乎是安装了NavigationLink的手势,使其忽略了其子视图手势:它有效地使用.gesture(_:including:)GestureMask的{​​{1}},意味着它将优先于具有相似优先级的所有内部手势。

最终,您需要的是一个安装了.gesture的按钮来触发其动作,同时保持该按钮的常用API:传递单个动作方法以在触发时运行,而不是定义一个普通的Image和小提琴使用手势识别器来更改状态等。要使用标准的.highPriorityGesture()行为和API,您需要更深入一点,并直接定义按钮的某些行为。幸运的是,SwiftUI为此提供了合适的钩子。

有两种方法可以自定义按钮的外观:您可以实现ButtonButtonStyle,然后将其传递到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()的{​​{1}}参数的内容),并附加了两个新的修饰符:label: { }或{{ 1}},具体取决于按下状态以及定义为Button的手势。

如果您不想为触地状态提供其他外观,则可以使此操作简单一些。无需.opacity()属性来保存该属性,您就可以在1实现内进行所有操作:

0.5

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

.highPriorityGesture()

请注意,如果您要在用户开始拖动等操作时取消手势,则还有更多工作要做。这就是另外一个球类游戏。但是,基于手势值的@State属性更改makeBody()状态是相对简单的,并且仅在按下时结束才触发。我将其留给读者练习

答案 1 :(得分:1)

尝试这种方法:

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()
                }
            }
        }
    }

答案 2 :(得分:1)

要获得所需的结果,您必须对.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() }

答案 3 :(得分:0)

请将每个项目视图放在VStack而不是ListForm中。

答案 4 :(得分:0)

有同样的问题,这是我找到的答案:

感谢这个人和他的回答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())

                
}