如何在SwiftUI中将NavigationLink显示为按钮

81

我在这里读了很多有关SwiftUI导航的内容,并尝试了一些方法,但没有一项能按预期工作。

基本上,我有一个包含锻炼列表的视图,你可以通过点击行来显示单个锻炼。使用NavigationView和NavigationLink一起可以按预期工作。

现在,我想在详细视图上放一个按钮来开始锻炼。这应该打开一个带有计时器的视图。该视图应该用与详细视图相同的动画呈现,并在导航栏中显示锻炼的名称和返回按钮。

我可以在详细信息页面上使用NavigationLink视图实现此目的,但链接始终显示为具有右侧箭头的全宽度行。我希望它变成一个按钮,但NavigationLink似乎抵制样式。

struct WorkoutDetail: View {
    var workout: Workout

    var body: some View {
        VStack {
            NavigationLink(destination: TimerView()) {
                Text("Starten")
            }.navigationBarTitle(Text(workout.title))
        }
    }
}

struct WorkoutList: View {
    var workoutCollection: WorkoutCollection

    var body: some View {
        NavigationView {
            List(workoutCollection.workouts) { workout in
                NavigationLink(destination: WorkoutDetail(workout: workout)) {
                    WorkoutRow(workout: workout)
                }
            }.navigationBarTitle(Text("Workouts"))
        }
    }
}

更新:这里是一个截图,用于说明我的意思:

当前布局:使用默认右箭头的'Starten'。期望的布局:蓝色按钮,黄色背景。

workout 是如何被填充的?是否有一个模型(通常是某种形式的 @ObjectBinding)在背后支持?换句话说,你是如何获取应用程序的 workout 状态的? - user7014451
我已更新帖子,展示列表视图以展示锻炼如何被填充。workoutCollection被加载并在SceneDelegate类中传递给列表视图。 - G. Marc
10个回答

106

您无需将视图放置在 NavigationLink 内部,以使其在按下时触发导航。

我们可以将属性与我们的 NavigationLink 绑定,每当更改该属性时,我们的导航都会触发,无论执行什么操作。例如:

struct SwiftUI: View {
    @State private var action: Int? = 0
    
    var body: some View {
        
        NavigationView {
            VStack {
                NavigationLink(destination: Text("Destination_1"), tag: 1, selection: $action) {
                    EmptyView()
                }
                NavigationLink(destination: Text("Destination_2"), tag: 2, selection: $action) {
                    EmptyView()
                }
                
                Text("Your Custom View 1")
                    .onTapGesture {
                        //perform some tasks if needed before opening Destination view
                        self.action = 1
                    }
                Text("Your Custom View 2")
                    .onTapGesture {
                        //perform some tasks if needed before opening Destination view
                        self.action = 2
                    }
            }
        }
    }
}
无论何时您更改Bindable属性(即操作),NavigationLink将比较其tag的预定义值与绑定的属性action,如果两者相等,则进行导航。

因此,您可以按照自己的喜好创建视图,并且可以从任何视图触发导航,而无需考虑任何操作,只需玩转与NavigationLink绑定的属性即可。


谢谢 - 我已经寻找这个很久了。 - LateNate
10
这是一个有趣的解决方案,用于解决在Swift中不应该出现的问题。当我在Swift 5.2中实现此解决方案时,偶尔会出现错误:SwiftUI在推送NavigationLink时遇到了问题,请报告错误。 - FontFamily
1
使用标签是一种风险解决方案,因为可能会有另一个组件,其中您可以获得混合行为或不同的方法,不建议这样做。相反,创建一个包装器,允许您使用一个特定的ID进行工作,以便外部无法复制它。 - Kross
1
@Kross 不理解这里的风险。在应用商店上有两个复杂的应用程序使用此方法,从未在任何地方遇到过混合行为。标签是在组件内创建的,并与该同一组件的 NavigationLinks 相关联,因此无法从任何其他组件中访问。因此,外部组件可以自由复制标签并将其附加到其 NavigationLinks 中,这不会对当前组件的行为和导航产生影响。 如果这不是您提到的风险,请发布您的解决方案,这将有助于更好地理解。 - mohit kejriwal
1
你如何在动态生成的列表上执行此操作,例如使用 ForEach(self.list, id: \.postId) { item in?我该如何避免硬编码的操作和标签? - user2619824

72

我认为准确的方法是使用 buttonStyle,例如

NavigationLink(destination: WorkoutDetail(workout: workout)) {
  WorkoutRow(workout: workout)
}
.buttonStyle(ButtonStyle3D(background: Color.yellow))

8
这正是我在寻找的。没想到你可以为导航链接添加按钮样式。哇!将大大简化代码! - snarik
1
同意。在SwiftUI 2中,这现在是最好的答案。 - Collierton
1
最好的。我们可以为按钮实现您的自定义样式,因此对我来说,这是最简单和最干净的解决方案。 - atereshkov
2
这在SwiftUI 3中仍然有效吗?我仍然会在最右边看到“>”符号,但按钮本身确实已经被样式化了。 - G. Marc
这应该是被选中的答案,因为它很可能是2022年正确和惯用的做法。 - Jay Lee
1
请展示您获取ButtonStyle3D的位置。 - Ed Halferty

25

我自己已经玩了几天了。 我认为这就是你要找的东西。

struct WorkoutDetail: View {
var workout: Workout

var body: some View {
    NavigationView {
       VStack {
           NavigationLink(destination: TimerView()) {
               ButtonView()
           }.navigationBarTitle(Text(workout.title))
        }
    }
}

并创建您想在NavigationLink中显示的视图

struct ButtonView: View {
var body: some View {
    Text("Starten")
        .frame(width: 200, height: 100, alignment: .center)
        .background(Color.yellow)
        .foregroundColor(Color.red)
}
注:导航视图上方的任何内容似乎都会显示在导航中的所有页面上,从而使链接中NavigationView的大小变小。

1
谢谢,但不幸的是这并不完全是我的问题。我已经用截图更新了我的问题。问题是我得到了一个链接行,文本左对齐,箭头右对齐。但我想要的是一个普通的按钮。 - G. Marc
还不确定答案,但现在我也会寻找解决方案。如果您从视图开始并转到新视图(总共2个视图),则上面的代码可以正常工作,就像您想要的那样。如果您从带有导航链接的列表视图开始,并转到workoutDetail视图,然后是计时器视图,则会放入>(总共3个视图)。尝试使用上述代码进行测试项目,您就会明白我的意思。 - ShadowDES
是的,它正好按照我的要求工作,现在唯一的问题是按钮的外观。 - G. Marc
在你的WorkoutList中,将“List”更改为“ForEach”。这样就可以消除所有的>。这可能不是最终解决方案,但可能会让你更接近目标。 - ShadowDES
谢谢,我相信这应该是正确的答案。 - rahimli

22
在按钮标签中使用 NavigationLink。
Button(action: {
    print("Floating Button Click")
}, label: {
    NavigationLink(destination: AddItemView()) {
         Text("Open View")
     }
})

2
rbaldwin 我已经尝试了几种情况,甚至加入了更多组件,而且它也正常工作。也许你可以上传你代码的这一部分,这样我就能帮助你了。 - yOshi
小问题:行在点击后取消选择 - malhal
非常感谢!我一直在按钮上放置NavigationLink,从未想过将其放置在标签内部! - marticztn
尝试了一下,绝对有效。但因为我将按钮样式设置得很宽,横向加了一些填充来适应文本。效果非常好。谢谢Yoshi! - undefined

9

这个解决方案与Pankaj Bhalala的解决方案非常相似,但移除了ZStack:

struct ContentView: View {
    @State var selectedTag: String?

    var body: some View {
        Button(action: {
            self.selectedTag = "xx"
        }, label: {
            Image(systemName: "plus")
        })
        .background(
            NavigationLink(
                destination: Text("XX"),
                tag: "xx",
                selection: $selectedTag,
                label: { EmptyView() }
            )
        )
    }
}

等待更简洁的无状态解决方案。你觉得怎么样?


6
你可以使用ZStack将NavigationLink用作按钮:
@State var tag:Int? = nil
ZStack {
  NavigationLink(destination: MyModal(), tag: 1, selection: $tag) {
    EmptyView()
  }

  Button(action: {
    self.tag = 1
  }, label: {
    Text("show view tag 1")
  })
}

希望这能帮助到您。

5
我不知道为什么所有这些答案都把这件事情搞得很复杂。在SwiftUI 2.0中,你只需要将按钮放置在导航链接内部即可!
NavigationLink(destination: TimerView()) {
    Text("Starten")
}

您可以像为任何其他元素设置样式一样,将 SwiftUI 样式应用于 Text 对象。

6
因为这是一个关于SwiftUI 1.0的问题。 - Yrb
10
因为你使用的方法会得到附属箭头,即使在SwiftUI 2.0中也是如此。 - ngb
如果您需要一个按钮执行除了推送到导航堆栈之外的其他操作,这个方法不会起作用。 - Justin Vallely

1
您可以像这样直接将导航链接添加到按钮中:
    @State var tag:Int? = nil

    ...

    NavigationLink(destination: Text("Full List"), tag: 1, selection: $tag) {
        MyButton(buttonText: "Full list") {
            self.tag = 1
        }
    }

0

我把List改成了ScrollView,然后它就可以工作了。


0

我偏爱的语法是:对于所有事情都使用闭包

NavigationLink {
    WorkoutDetail(workout: workout)
} label: {
    WorkoutRow(workout: workout)
}
.buttonStyle(ButtonStyle3D(background: Color.yellow))

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接