Navigation
- Navigation概述
- 如安在Navigation中嵌入View
- Navigation的可修正状况
- Navigation增加bar-Items
- 怎么将新视图push到NavigationStack上
- 点击List-cell-push新的视图
- 关于Navigation programmatic
- 怎么运用Codable协议保存和加载NavigationStach Paths
- 创立一个两列或三列布局用NavigatinSplitView
- Sidebar的显现和躲藏
- 在NavigationSplitView中自定义一个宽度的视图
- 自定义NavigationSplitView的显现方式
- NavigationSplitView自动挑选视图优先级
- 在View上创立Inspector
概述
文章首要分享SwiftUI Modifier的学习进程,将运用事例的办法进行说明。内容深入浅出,Navigation展现部分调试成果,不过测验代码是完全的。假如想要运转成果,能够移步Github下载code -> github事例链接
1、Navigation概述
导航是许多应用程序的中心,SwiftUI的简略、易用的方面在Navigation
上做的十分好,这意味着运用NavigationLink、运用NavigationPath完全操控导航,运用NavigationSplitcView支持第二视图和第三视图布局等。
这意味着许多时候能够疏忽Navigation相关的场景,由于十分简略,专心于需求更多思考的部分,例如:
- 制作具有合理默许值的可自定义的toolbar
- 经过状况康复保存和加载用户的导航
- 决定怎么显现侧边栏,或在需求时增加额定的Content视图
- 结合NavigationSplitView和NavigationStack取得想要的结构
struct FFNavigation: View {
var body: some View {
NavigationStack {
List {
NavigationLink("最简略的Navigation") {
Text("So easy")
}
}
}
}
}
调试成果
2、如安在Navigation中嵌入View
SwiftUI的NavigationStack
或多或少的映射到UIKit的UINavigationController,由于它显现内容,它能够处理视图之间的导航。
2.1、根底款式
以最简略的办法,将Text放入NavigationStack中
struct FFNavigationEmbedView: View {
var body: some View {
NavigationStack {
Text("This is a great app")
}
}
}
2.2、.navigationTitle(“Welcome”)
可是,顶部导航栏是空的。因而,一般会在嵌入的任何内容上运用navigationTitle()
修饰符,因而能够在屏幕顶部增加标题。
struct FFNavigationEmbedView: View {
var body: some View {
NavigationStack {
Text("Swift")
.navigationTitle("Welcome")
}
}
}
2.3、navigationBarTitleDisplayMode
还有第二个修饰符,navigationBarTitleDisplayMode()
,能够操控是否运用大标题仍是小标题的内联标题。例如,默许情况下,任何视图以继承其大标题的显现方式呈现的,或许假如是初始视图,则运用大标题。可是,假如更愿意手动启用或禁用大标题,应该运用.navigationBarTitleDisoplayMode()
struct FFNavigationEmbedView: View {
var body: some View {
NavigationStack {
Text("SwiftUI")
.navigationTitle("Meta BBlv")
.navigationBarTitleDisplayMode(.inline)
}
}
}
这将产生小的导航标题,但能够运用.large来设置一个大标题
调试成果
3、Navigation的可修正状况
SwiftUI的NavigationStack能够运用navigationTitle()显现一个简略的字符串,但同一修饰符也可接受字符串绑定,以便能够经过点击来修正标题
struct FFNavigationEdit: View {
@State private var title = "Wecome"
var body: some View {
//例如,显现“欢迎”的默许标题,能够点击该标题进行更改。
NavigationStack {
Text("Hello, World!")
.navigationTitle($title)
.navigationBarTitleDisplayMode(.inline)
//正在修正某些内容的名称,我主张增加.toolvarRole(.editor)让SwiftUI知道你即将要做的的事情,以便动态更改的标题更精确。
.toolbarRole(.editor)
}
}
}
只要当导航栏以内联方式运转时,导航标题修正才有用。仅适用于iOS和iPadOS
调试成果
4、Navigation增加bar-Items
toolbar()
修饰符能够将单个或多个item按钮增加到navigationStack的前面和后边,以及视图的其他部分。这些可能是可点击的按钮,但没有限制–能够增加任何类型的视图。
4.1、在导航条的后边增加了两个按钮
struct FFNavigationAddBehind: View {
var body: some View {
NavigationStack {
Text("SwiftUI")
.navigationTitle("Welcome")
.toolbar(content: {
Button("About") {
print("About tapped!")
}
Button("Help") {
print("Help tapped!")
}
})
}
}
}
4.2、在导航条的前面增加了一个按钮
假如想操控按钮的确切方位,能够经过将其包装在ToolBarItem
并拟定所需的方位来做到这一点。例如,创立一个按钮,并强制将其放在导航栏前面。
struct FFNavigationAddFront: View {
var body: some View {
NavigationStack {
Text("SwiftUI")
.navigationTitle("Welcome")
.toolbar(content: {
ToolbarItem(placement: .topBarLeading) {
Button("Help") {
print("Help tapped!")
}
}
})
}
}
}
4.3、多个item放在相同的方位(ToolbarItenmGroup)
假如想在不同方位放置多个item-button,只需求依据屡次重复ToolBarItem,并每次指定不同的方位。要将多个item放在相同的方位,需求将他们包装在ToolbarItenmGroup
中
struct FFNavigationToolBarItem: View {
var body: some View {
NavigationStack {
Text("SwiftUI")
.navigationTitle("Welcome")
.toolbar(content: {
ToolbarItemGroup(placement: .primaryAction) {
Button("About") {
print("About tapped!")
}
Button("Help") {
print("About tapped!")
}
}
})
}
}
}
这运用.primaryAction
,将依据平台(iOS等)以为最重要的按钮的方位来定位按钮。
4.4、primaryAction与secondaryAction优先级设置item
还有一个.secondaryAction
放置,专为有用但不需求操作而规划,在iOS上,将导致该组中的按钮折叠为单个具体信息按钮
struct FFNavigationViewPlacement: View {
var body: some View {
Text("SwiftUI")
.navigationTitle("Welcome")
.toolbar(content: {
ToolbarItemGroup(placement: .primaryAction) {
Button("About") {
print("About tapped!")
}
Button("Help") {
print("About tapped!")
}
}
ToolbarItemGroup(placement: .secondaryAction) {
Button("Settings") {
print("Credits tapped!")
}
Button("Email Me") {
print("Email tapped!")
}
}
})
}
}
调试成果
5、怎么将新视图push到NavigationStack上
SwiftUI能够运用NavigationLink
将任何视图推送到navigationStack上,在其最简略的方式中,能够为其标题供给字符串和方针视图作为跟随闭包。
//创立一个简略的DetailView结构,然后经过NavigationStack显现它
struct DetailView_013: View {
var body: some View {
Text("This is the detail view")
}
}
struct FFnavigationPushView: View {
var body: some View {
NavigationStack {
VStack {
NavigationLink("Show Detail View") {
DetailView_013()
}
//假如需求对Label进行更多的自定义,那么运用跟随闭包。例如,运用Label视图而不是简略的字符串
NavigationLink {
DetailView_013()
} label: {
Label("Show Detail View - Label", systemImage: "globe")
}
//SwiftUI将自动将链接款式设置为按钮,以便用户知道他们是交互式的。
//能够经过将.buttonStule(.plain)应用在NavigationLink禁用此行为
}
.navigationTitle("navigation")
}
}
}
6、点击List-cellpush新的视图
SwiftUI的NavigationLink可在List-cell中运用,以便在点击cell时显现新的视图。假如NavigationLink包括整个cell,体系会自动理解为整个cell可点击,以显现新视图。有两种办法能够指定导航目的地,经过明确链接,或许假如方针是iOS16以及更高版别,能够运用navigationDestination()
struct PlayerView_013: View {
let name: String
var body: some View {
Text("Select player: \(name)")
.font(.largeTitle)
}
}
struct FFNavigationForList: View {
let players = [
"Roy Kent",
"Richard Montlaur",
"Dani Rojas",
"Jamie Tartt"
]
var body: some View {
//创立一个包括List的NavigationStack,答应用户挑选Player
NavigationStack {
List(players, id: \.self) { player in
NavigationLink(player, value: player)
}
.navigationDestination(for: String.self, destination: PlayerView_013.init)
.navigationTitle("Select a Player")
Divider()
//能够经过附加navigationDestination()来监督多种不同的数据类型
List {
NavigationLink("Show an integer", value: 29)
NavigationLink("show a String", value: "Meta BBLv")
NavigationLink("Show a Double", value: Double.pi)
}
.navigationDestination(for: Int.self) { Text("Received Int: \($0)") }
.navigationDestination(for: String.self) { Text("Received String \($0)") }
.navigationDestination(for: Double.self) { Text("Receicved Double \($0)") }
}
}
}
假如不需求高度自定义的导航,并且能够在iOS16以及更高的版别运转,强烈主张运用navigationDestination(),由于SwiftUI会懒加载方针视图。
7、关于Navigation programmatic
能够运用SwiftUI的NavigationLink以编程的办法将新视图推送到NavigationStack,这意味着能够在准备好时触发导航,而不是仅当点击了button或cell时。从iOS16以及更高的版别,咱们能够将一系列的Hashable
数据直接传递给NavigationStack,以操控那些数据当时在stack上。
7.1、绑定Path,跟踪显现
struct FFNavigationProgrammatic: View {
@State private var presentedNumbers = [1, 4, 8]
var body: some View {
//跟踪正在显现的数字,并首先将1、4、8push到stack上。
NavigationStack(path: $presentedNumbers) {
List(1..<50) { i in
NavigationLink(value: i) {
Label("Row \(i)", systemImage: "\(i).circle")
}
}
.navigationDestination(for: Int.self) { i in
Text("Detail \(i)")
}
//当代码运转时,将看到“Detail8”,点回来看到“Detail 4”,点回来看到“Detail 1”,点回来看到List
}
.navigationTitle("NavigationForPath")
}
}
7.2、在detail页面直接跳转下一个detail(驾考答题需求)
这种办法很强壮,由于能够在任何时候修正NavigationStack来push一个自定义视图。他是一个简略的数组,所以能够add、insert、delete item
或许依据显现需求做使命操作。途径数组开端为空,在点击的detail中增加了一个button,能够直接在当时detailpush(n+1)个detail
。
struct FFNavigationProgrammaticForNext: View {
@State private var presentedNumbers = [Int]()
var body: some View {
NavigationStack(path: $presentedNumbers) {
List(1..<50) { i in
NavigationLink(value: i) {
Label("Row \(i)", systemImage: "\(i).circle")
}
.navigationDestination(for: Int.self) { i in
VStack {
Text("Detail \(i)")
Button("Go to Next") {
presentedNumbers.append(i + 1)
}
}
}
.navigationTitle("NavigationForNext")
}
}
}
}
7.3、NavigationPath
假如将一种很数据类型push到Stack,则能够运用简略数组作为navigation path,但假如需求heterogeneous数据,则能够运用名为NavigationPath的特殊类型擦除包装器。这能够处理任何可hash的数据,所以能够增加一些Staing、Int和custom struct,只需契合hashable都能够
struct FFNavigationProgrammaticForHash: View {
@State private var navPath = NavigationPath()
var body: some View {
NavigationStack(path: $navPath) {
Button("Jump to random") {
navPath.append(Int.random(in: 1..<50))
}
List(1..<50) { i in
NavigationLink(value: "Row \(i)") {
Label("row \(i)", systemImage: "\(i).circle")
}
.navigationDestination(for: Int.self) { i in
Text("Int detail \(i)")
}
.navigationDestination(for: String.self) { o in
Text("String detail \(i)")
}
.navigationTitle("NavigationForHashable")
}
}
}
}
能够随意的调整path–在哪里附加了一个value,但假如需求的话,能够一次附加多个值,像旧的UIKit的pop到根视图操控器这样的东西,就像从你的途径中清楚一切东西相同的简略-navPath.removeLast(navpath.count)
这样就能够做到。
调试成果
8、怎么运用Codable协议保存和加载NavigationStach Paths
当运用NavigationPath
对象管理SwiftUI的NavigationStack途径时,运用Codable协议保存和加载整个途径–能够存储完好的NavigationStack并在需求的时刻康复它,这样用户就能精确的回到离开的当地。最好的处理办法是将存储封装在一个独自的ObservableObject
类中,这个类能够负责从视图中加载和保存途径数据。
构建PathStore
例如,这个类在创立时加载一个保存的途径,并在他的NavigationPath属性改变时保存途径
class PathStore: ObservableObject {
@Published var path = NavigationPath() {
didSet {
save()
}
}
private let savePath = URL.documentsDirectory.appending(path: "SavedPathStore")
init() {
if let data = try? Data(contentsOf: savePath) {
if let decoded = try? JSONDecoder().decode(NavigationPath.CodableRepresentation.self, from: data) {
path = NavigationPath(decoded)
}
}
}
func save() {
guard let representation = path.codable else { return }
do {
let data = try JSONEncoder().encode(representation)
try data.write(to: savePath)
} catch {
print("Failed to save navigation data")
}
}
}
这是一个整齐的可重用类,只需写入NavigationPath的数据的Codable类型,就能够了。
构建DetailViewCodable_013
创立一个简略的Detail视图,能够显现用户挑选的数字,同时经过挑选另一个数字进行更深的导航,然后将其与PathStore类一同运用,以便自动加载和保存导航
struct DetailViewCodable_013: View {
var id: Int
var body: some View {
VStack {
Text("View \(id)")
.font(.largeTitle)
NavigationLink("Junp to random", value: Int.random(in: 1...100))
}
}
}
调试代码
假如运转哪个代码,会看到能够浏览任意多的DetailView级别,数据会自动被存储–能够退出App并回来,导航历史将坚持完好。
struct FFNavigationCodable: View {
@StateObject private var pathStore = PathStore()
var body: some View {
NavigationStack(path: $pathStore.path) {
DetailViewCodable_013(id: 0)
.navigationDestination(for: Int.self, destination: DetailViewCodable_013.init)
.navigationTitle("Navigation")
}
}
}
调试成果
9、创立一个两列或三列布局用NavigatinSplitView
SwiftUI的NavigationSplitView
答应咱们在更大的设备上创立多列布局(iPad macOS和大屏幕的iPhone上),可是当空间有限时,将自动折叠成NavigationStack风格的布局。在最简略的方式中,应该供给侧边栏作为它的第一个跟随闭包,的细节视图作为他的第二个闭包。
9.1、Sidebar + Detail
struct FFNavigationSplitView: View {
var body: some View {
NavigationSplitView {
List(1..<50) { i in
NavigationLink("Row \(i)", value: i)
}
.navigationDestination(for: Int.self) {
Text("Select row \($0)")
}
.navigationTitle("Split View")
} detail: {
Text("Please select a row")
}
}
}
在该代码中,“Please select a row”文本仅在用户尚未在侧边栏中进行挑选时显现,可是当用户进行挑选时,将自动被替换。navigationDestination()修饰符将自动在具体信息区域中显现其目的地视图。更棒的是,当空间有限时,会看到整个东西被平展成一个常规的NavigationStack。
调试成果
iPad调试,这图上传一直failure,太大了,想看调试成果,移步GitHub吧,下面的NavigationSplitView都是相同。
9.2、Sidebar + Content + Detail
NavigationSplitView能够在布局中增加第三个视图,它能够经过点击Button来显现。
struct FFNavigationSplitViewContent: View {
var body: some View {
NavigationSplitView {
Text("Sidebar")
} content: {
Text("Primary")
} detail: {
Text("Detail View")
}
}
}
SwiftUI将自动显现一个按钮滑动在你的栏从屏幕的一边。
10、Sidebar的显现和躲藏
当在macOS上和iPad上运用NavigationSplitView时,SwiftUI运用NavigationSplitViewVisibility
的enum来切换显现侧边栏、内容视图和具体视图。有四种方式能够挑选:
- 在.detailonly方式下,具体视图将占用应用程序的一切可用屏幕空间
- 在.doublecolumn方式下,将同时看到内容视图和具体视图
- 在.all方式下,假如存在,体系将测验显现一切三个视图。在没有内容视图(中间视图)的情况下,它将只显现两个。
- 在.automatic,体系将自动依据当时设备和方向做最好的UI布局
struct FFNavigationSplitViewHideAndShow: View {
@State private var columnVisibility = NavigationSplitViewVisibility.detailOnly
var body: some View {
NavigationSplitView(columnVisibility: $columnVisibility) {
Text("Sidebar")
} content: {
Text("Content")
} detail: {
VStack {
Button("Detail Only") {
columnVisibility = .detailOnly
}
Button("Content and Detail") {
columnVisibility = .doubleColumn
}
Button("Show All") {
columnVisibility = .all
}
}
}
}
}
供给colunmVisibility是运用绑定完结的,由于当用户与UI交互时,value将自动更新。 尽管SwiftUI对分屏视图界面的这三个部分运用了不同的名称,但他们与UIKit的对应部分直接匹配:在UIKit中,侧sidebar时”首要的“,content时“弥补的”,detail是“非必须的”
11、在NavigationSplitView中自定义一个宽度的视图
SwiftUI的NavigationSplitView为它呈现的视图运用体系标准宽度,可是经过navigationSPlitViewColumnWidth()修饰符,能够测验自定义它。 体系能够挑选疏忽指定的宽度。在编写文本时,此修饰符在iPhone上被疏忽,而在iPad上仅适用于小于默许巨细的值。
11.1、navigationSplitViewColumnWidth固定巨细
在其最简略的方式,传入一个固定的值给navigationSplitViewColumnWidth()
导致它运用一个固定的巨细,未运用更小或更大。
struct FFNavigationSplitViewCustomize: View {
var body: some View {
NavigationSplitView {
Text("Sidebar")
.navigationSplitViewColumnWidth(100)
} content: {
Text("Content")
.navigationSplitViewColumnWidth(200)
} detail: {
Text("Detail")
}
}
}
11.2、navigationSplitViewColumnWidth规模设定巨细
可是,假如想经过最大值、最小值来设定规模,在macOS上,能够供给最小,最理想和最大的size
struct FFNavigationSplitViewCustomizeMax: View {
var body: some View {
NavigationSplitView {
Text("Sidebar")
.navigationSplitViewColumnWidth(min:100, ideal: 200, max: 300)
} content: {
Text("Content")
.navigationSplitViewColumnWidth(min:100, ideal: 200, max: 300)
} detail: {
Text("Detail")
}
}
}
12、自定义NavigationSplitView的显现方式
SwiftUI的NavigationSplitView有三个选项来操控旁边面栏的显现办法,每个选项都能够运用navigationSplitViewStyle()
修饰符进行调整。
12.1、.prominentDetail
第一个是.prominentDetail
,它告知SwiftUI你想让细节视图一直坚持完好的巨细–侧边栏和内容视图会滑动到Detail视图上,而不是把他推到一边或把它挤得更小。
struct FFNavigationSplitViewDisplayMode: View {
var body: some View {
NavigationSplitView {
Text("Sidebar")
} content: {
Text("Content")
} detail: {
Text("Detail")
}
.navigationSplitViewStyle(.prominentDetail)
}
}
12.2、.balanced
第二个选项是.balanced
,这将在显现侧边栏或Content栏时减少Detail视图的巨细–只需将.prominentDetail切换为.balance
struct FFNavigationSplitViewDisplayModeBalance: View {
var body: some View {
NavigationSplitView {
Text("Sidebar")
} content: {
Text("Content")
} detail: {
Text("Detail")
}
.navigationSplitViewStyle(.balanced)
}
}
默许设置是.automatic,这将依据平台的不同而有所不同,在写Text时,在iPhone上变成了.prominentDetail,在iPad上变成.balance
13、NavigationSplitView自动挑选视图优先级
当有一个NavigationSplitView运转在一个紧凑的size class中。SwiftUI试图揣度分屏视图列中哪一个是最优先的显现,这种揣度一般是正确的,但能够经过分屏视图设置首选紧凑列来操控它。
struct FFNavigationSplitViewCompact: View {
@State private var preferredColumn = NavigationSplitViewColumn.detail
var body: some View {
//例如:这段代码强制将detail视图列为首选,然后覆盖了SwiftUI的默许挑选
NavigationSplitView(preferredCompactColumn: $preferredColumn) {
Text("Sidebar View")
} detail: {
Text("Detail View")
}
}
}
假如供给了一个不存在的值,比方,当只要sidebar和detail,挑选content,那么SwiftUI就会挑选侧边栏。
14、在View上创立Inspector
SwiftUi的inspector()
修饰符能够在任何需求的当地增加查看器视图。这就像Xcode相同,查看器增加到UI的后边,并且能够依据需求与NavigationSplitView和NavigationStack一同运用。
14.1、 基本款式
当按钮被按下时,将显现一个查看器视图。
struct FFInspector: View {
@State private var isShowingInspector = false
var body: some View {
Button("Hello, world!") {
isShowingInspector.toggle()
}
.font(.largeTitle)
.inspector(isPresented: $isShowingInspector) {
Text("Inspector View")
}
}
}
当空间很大时,比方运用全屏的iPad应用程序或macOS时,查看器就放在按钮旁边。可是,当空间有限时,例如在iPhone上,查看器作为一个页面向上滑动(present mode)。
14.1、动态调整width
在支持他的平台上,你能够经过供给一个 (.inspectorColumnWidth(500)) 固定的巨细来占用空间,或许经过供给一个.inspectorColumnWidth(min: 50, ideal: 150, max: 200)
规模来调整查看器的占用空间。
struct FFInspectorIdeal: View {
@State private var isShowingInspector1 = false
var body: some View {
Button("Hi, metaBBLv") {
isShowingInspector1.toggle()
}
.font(.largeTitle)
.inspector(isPresented: $isShowingInspector1) {
Text("Insepctor View BBlv")
.inspectorColumnWidth(min:50, ideal: 150, max: 200)
}
}
}