
注意NavigationView已经废弃,替代品是NavigationStack。
目录
[NavigationView 标题和按钮](#NavigationView 标题和按钮)
NavigationView 标题和按钮
导航视图指的是顶部的一两行区域,称为顶部导航或导航栏。顶部一般会包括页面标题和导航按钮,下面的示例是基本的用法:
Swift
struct SwiftUIViewNavigation: View {
var body: some View {
NavigationView(){
VStack{
Text("Hello, World!")
Spacer()
Text("Hello, World!")
}
.navigationBarTitle("123",displayMode: .inline)//标记1
.navigationBarItems(trailing: titleLineView)//标记2
.navigationBarItems(leading: titleLineView, trailing: titleLineView)//标记3
}
}
private var titleLineView: some View{
Button(action:{}){
VStack{
Image(systemName: "square.and.arrow.up")
Image(systemName: "square.and.arrow.up")
}
}
}
}
代码中关键三行代码做了标注,分别设置了标题、显示方式、前后按钮。注意这些修饰是在NavigationView的内部进行的,如果有多个navigationBarTitle()设置了不同的标题,实测内层的生效,而示例中连用两个navigationBarItems的效果是叠加。
上面代码的效果:

标题显示模式
上面的代码设置显示模式为inline,标题和按钮会显示在同一行,如果改为automatic或large,则效果为:

可以注意到分成了两行,标题独占一行。
导航栏按钮
导航栏前后按钮都是视图,上面的代码用了一个稍微复杂的视图演示了视图超出导航栏大小时的效果。
上面的代码用VStack组合了两个图标,超出了导航栏高度,所以显示不全。正常情况应该用HStack来组合多个按钮,下面的代码增加了绑定来显示哪个按钮被点击:
Swift
struct SwiftUIViewNavigation: View {
@State var info : String = "info"
var body: some View {
NavigationView(){
VStack{
Text(info)
Spacer()
Text("Hello, World!")
}
.navigationBarTitle("123",displayMode: .automatic)
.navigationBarItems(trailing: titleLineView)
.navigationBarItems(leading: titleLineView, trailing: titleLineView)
}
}
private var titleLineView: some View{
HStack{
Button(action:{info="a"}){
Image(systemName: "square.and.arrow.up.fill")
}
Button(action:{info="b"}){
Image(systemName: "square.and.arrow.up")
}
}
}
}
点击效果:

注意,实时预览可能没有反应
今天修改代码的时候实时预览经常出问题,要多些改动实时预览才会生效(敲一些空格回车)。
实现页面导航
NavigationLink
NavigationLink可以实现跳转,需要两个参数:标签和跳转目标视图(当然,标签也是个视图):
Swift
struct NavigationLink<Label, Destination> where Label : View, Destination : View
用法很简单,标签可以用字符串,视图可以是已经做好的任何页面。
完整导航页面示例
下面是一个完整的导航页面的例子,里面包含了一些布局和模块化代码的技巧。
Swift
import SwiftUI
struct SwiftUIViewNavigation: View {
@State var info: String = "info"
var body: some View {
NavigationView {
HStack {
VStack(alignment: .leading, spacing: 20) {
VStack(alignment: .leading, spacing: 20) {
MenuItemView(imageName: "person", itemName: "账号设置"){ InputView() }
MenuItemView(imageName: "person", itemName: "账号设置"){ InputView() }
MenuItemView(imageName: "person", itemName: "账号设置"){ InputView() }
Spacer()
}
.navigationBarTitle("导航", displayMode: .inline)
.navigationBarItems(
leading: titleLineViewLeft,
trailing: titleLineViewWright
)
HStack {
Text(info).padding(10)
Spacer()
}.background(.cyan)
}
.padding(.all, 20)
Spacer()
}.border(.red)
}
}
struct MenuItemView<destinationView: View>: View {
var imageName: String
var itemName: String
@ViewBuilder public var destinationview: () -> destinationView
var body: some View {
NavigationLink(destination: destinationview) {
HStack(spacing: 20) {
Image(systemName: imageName)
Text(itemName)
}
}
}
}
private var titleLineViewLeft: some View {
HStack {
Button(action: { info = "返回" }) {
Image(systemName: "chevron.backward")
}
}
}
private var titleLineViewWright: some View {
HStack {
Button(action: { info = "搜索" }) {
Image(systemName: "magnifyingglass")
}
Button(action: { info = "分享" }) {
Image(systemName: "square.and.arrow.up")
}
}
}
}
效果:

三个导航链接是一样的,点击之后是:

InputView 视图代码是这样的:
Swift
import SwiftUI
struct InputView: View {
@State var text="a\r\nb"
var body: some View {
VStack
{
Text("\(text)")
.padding()
.frame(width: 300,height: 100)
.border(.black,width: 5)
.multilineTextAlignment(.leading)
TextEditor(text:$text)
.padding()
.frame(width: 300,height: 100)
.border(.black,width: 5)
TextField("Hello, World!",text:$text)
.padding()
.frame(width: 300,height: 100)
.border(.black,width: 5)
}
}
}
#Preview {
InputView()
}
独立的预览效果是这样的:

通过导航进入和独立页面的区别是上面有个返回按钮(和导航页我们自己做的图标看起来是一样的,但是这个是系统提供的),点击返回按钮可以回到导航页面。
导航嵌套
前面使用的InputView是个简单页面,如果换成另一个导航页面会怎么样?将导航目标替换为自身:
Swift
MenuItemView(imageName: "person", itemName: "账号设置"){ SwiftUIViewNavigation() }
效果:

多点几次:

非常明显,导航效果是嵌套的,目标页面完整嵌套在子视图区域。
嵌套视图的标题
嵌入当然也应该有自己的标题。之前我们已经知道多次设置navigationBarTitle内层的生效,嵌套的就处于内层,所以前台页面的设置会替代导航页面的设置。
给InputView视图加上navigationBarTitle设置:
Swift
struct InputView: View {
@State var text="a\r\nb"
var body: some View {
VStack
{
。。。。。。
}
.navigationBarTitle("InputView", displayMode: .inline)
}
}
注意这里设置了displayMode,这是不会改变导航页的显示布局的。也就是说,如果设置成large或自动,而导航页面是inline,由于嵌套视图的位置已经确定,并没有留出large的显示位置,嵌套视图的标题会被遮盖,从而无法看到。
正常的显示效果:

技巧
用Spacer使Stack最大化
HStack和VStack默认不是尽可能大的,内部子视图对齐方式只能在有效范围内对齐,而不是我们期待的整个屏幕。当我们想实现基于屏幕的靠左或靠右时,需要先把Stack最大化。
frame是一个办法,但是非常不理想,通过嵌套一个H/VStack加上Spacer是最直白的。
View用作参数并给View传递参数
View大量用作参数,包括很多习惯上是个字符串的其实也是View参数(比如导航链接的标签参数)。
我们前面已经用过不带参数的子视图,非常简单,直接构造一个View即可:
Swift
private var titleLineViewLeft: some View {
HStack {
Button(action: { info = "返回" }) {
Image(systemName: "chevron.backward")
}
}
}
但是需要参数化的视图就比较麻烦,因为内部实现对视图渲染有要求。代码可以照下面的葫芦来画瓢:
Swift
struct MenuItemView<destinationView: View>: View {
var imageName: String
var itemName: String
@ViewBuilder public var destinationview: () -> destinationView
var body: some View {
NavigationLink(destination: destinationview) {
HStack(spacing: 20) {
Image(systemName: imageName)
Text(itemName)
}
}
}
}
调用:
MenuItemView(imageName: "person", itemName: "账号设置"){ InputView() }
视图参数如果有多个,排在第一个的是默认的,后面的都需要名字加冒号再加大括号。
自定义返回按钮
打开的新视图嵌套在导航框架里,具有一个标准的返回按钮。我们可以隐藏这个按钮并替换成我们自己的视图。
修改InputView:
Swift
struct InputView: View {
@Environment(\.presentationMode) var presentationMode //用来实现返回的环境值
@State var text="a\r\nb"
var body: some View {
VStack
{
。。。。。。
}
.navigationBarTitle("InputView", displayMode: .inline)
.navigationBarBackButtonHidden() //隐藏默认返回按钮
.navigationBarItems(leading: titleLineViewLeft) //添加自定义返回按钮
}
//自定义返回按钮
private var titleLineViewLeft: some View {
HStack {
Button(action: { self.presentationMode.wrappedValue.dismiss()}) {
Image(systemName: "chevron.backward")
}
}
}
}
因为我们用的图标和系统是一样的,看不出区别,所以可以屏蔽掉.navigationBarBackButtonHidden()看看效果:

出现了两个一模一样的返回按钮,说明代码生效了。此时我们还可以试一下侧滑返回,是有效的。
我们再把.navigationBarBackButtonHidden()这句放出来,默认返回按钮就没有了,但是侧滑返回也没有了。当然有办法用代码实现侧滑返回,不过,如果不是游戏应用,有点吃饱了撑的。