我有一个列表项视图,该列表项显示了有关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之外-这允许进行操作,但是导航仍在进行。
有没有办法使之成为可能?
谢谢。
答案 0 :(得分:5)
我遇到了同样的问题,这个问题基本上是我寻找解决方案时的热门话题。因此,找到了一个合理的解决方案后,这似乎是个好地方。
问题似乎是安装了NavigationLink
的手势,使其忽略了其子视图手势:它有效地使用.gesture(_:including:)
和GestureMask
的{{1}},意味着它将优先于具有相似优先级的所有内部手势。
最终,您需要的是一个安装了.gesture
的按钮来触发其动作,同时保持该按钮的常用API:传递单个动作方法以在触发时运行,而不是定义一个普通的Image和小提琴使用手势识别器来更改状态等。要使用标准的.highPriorityGesture()
行为和API,您需要更深入一点,并直接定义按钮的某些行为。幸运的是,SwiftUI为此提供了合适的钩子。
有两种方法可以自定义按钮的外观:您可以实现Button
或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()
的{{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
而不是List
或Form
中。
答案 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())
}