当前位置:网站首页>SwiftUI * SwiftUI 4.0 全新的导航系统
SwiftUI * SwiftUI 4.0 全新的导航系统
2022-08-09 02:45:00 【HumorousGhost】
长久以来,开发者对 SwiftUI 的导航系统颇有微词。由于 NavigationView 的能力限制,开发者需要动用各种技巧乃至内科技才能实现一些本应具备的能力(返回跟视图、向堆栈添加或删除任意视图,返回任意层架视图)。终于在 SwiftUI 4.0 中对导航系统做出了重大改变,提供了已视图堆栈为管理对象的新的 API,让开发者可以轻松实现编程式导航。
一分为二
新的导航系统最直接的变化是废弃了 NavigationView,将其功能分成了两个单独的控件 NavigationStack 和 NavigationSplitView。
NavigationStack 针对的是单栏使用场景,如 iPhone、Apple TV、Apple Watch
NavigationStack {
}
/// 相当于
NavigationView {
}
.navigationViewStyle(.stack)
NavigationSplitView 则针对的是多栏场景,如 iPad、Mac
NavigationSplitView {
SideBarView()
} detail: {
DetailView()
}
/// 对应的两列场景
NavigationView {
SideBarView()
DetailView()
}
.navigationViewStyle(.columns)
NavigationSplitView {
SideBarView()
} content: {
ContentView()
} detail: {
DetailView()
}
/// 对应的是三列场景
NavigationView {
SideBarView()
ContentView()
DetailView()
}
.navigationViewStyle(.columns)
相较于通过 navigationViewStyle 设定 NavigationView 样式的做法,一分为二的方式将让布局表达更加清晰,同时也会强迫开发者为 SwiftUI 应用对 iPad 和 Mac 做更多的适配。
在 iPhone 这类设备中,NavigationSplitView 会自动进行单栏适配。
但是无论是切换动画还是编程式 API 接口等多方面都与 NavigationStack 明显不同。
因此对于支持多硬件平台的应用来说,最好针对不同的场景分别使用对应的导航控件。
两个组件的逻辑
相较于控件名称上的改变,编程式导航 API 才是本次更新的最大亮点。使用新的编程式 API,开发者可以轻松的实现:返回根视图、对视图堆栈的修改等功能。
Apple 为 NavigationStack 和 NavigationSplitView 提供了两种不同的逻辑 API,这点或许会给部分开发者造成困扰。
NavigationView 的编程式导航
NavigationView 其实是具备一定的编程式导航能力的,比如,我们大致可以通过以下几种 NavigationLink 的构造方法来实现有限的编程式跳转:
public init(@ViewBuilder destination: () -> Destination, @ViewBuilder label: () -> Label)
public init(isActive: Binding<Bool>, @ViewBuilder destination: () -> Destination, @ViewBuilder label: () -> Label)
public init<V>(tag: V, selection: Binding<V?>, @ViewBuilder destination: () -> Destination, @ViewBuilder label: () -> Label) where V : Hashable
上述方法有一定的局限性:
- 需要视图进行逐级绑定,开发者如果想实现返回任意层级视图则需要自行管理状态。
- 在声明
NavigationLink时仍需要设定目标视图,会造成不必要的实例创建开销 - 较难实现从视图外调用导航功能
- “能用,但不好用”可能就是对
NavigationView编程式导航比较贴切的总结了。
NavigationStack
NavigationStack 从两个角度入手解决上述问题。
基于类型的响应式目标视图处理机制
// NavigationView 跳转方式
struct ContentView: View {
@State var selectedTarget: Target?
@State var target: Int?
var body: some View {
NavigationView {
List {
NavigationLink("SubView1", tag: Target.subView1, selection: $selectedTarget) {
SubView1()
}
// SwiftUI 在进入当前视图时,无论是否进入目标视图,均将创建其实例,但不进行 body 求值
NavigationLink("SubView2", tag: Target.subView2, selection: $selectedTarget) {
SubView2()
}
NavigationLink("SubView3", tag: 3, selection: $target) {
SubView3()
}
NavigationLink("SubView4", tag: 4, selection: $target) {
SubView4()
}
}
}
}
enum Target {
case subView1,
case subView2
}
}
NavigationStack 实现上述功能将更加清晰、灵活
// NavigationStack 跳转方式
struct ContentView: View {
var body: some View {
NavigationStack {
List {
NavigationLink("SubView1", value: Target.subView1) // 只声明关联的状态只
NavigationLink("SubView2", value: Target.subView2)
NavigationLink("SubView3", value: 3)
NavigationLink("SubView4", value: 4)
}
.navigationDestination(for: Target.self) {
target in // 对同一类型进行统一处理,返回目标视图
switch target {
case .subView1:
SubView1()
case .subView2:
SubView2()
}
}
.navigtionDestination(for: Int.self) {
target in // 为不同的类型添加多个处理模块
switch target {
case 3:
SubView3()
case 4:
SubView4()
default: break
}
}
}
}
enum Target {
case subView1,
case subView2
}
}
NavigationStack 的处理方式有以下特点和优势:
- 由于无需再
NavigationLink中指定目标视图,因此无须创建多余的视图实例 - 对由同一类型的值驱动的目标进行统一管理(可以将堆栈中所有视图的
NavigationLink处理程序统一到根视图中),有利于复杂的逻辑判断,也方便剥离代码 NavigationLink将优先使用最接近的类型目标管理代码。例如根视图,与第三层视图都通过navigationDestination定义了对Int的响应,那么第三层及其之上的视图将使用第三层的处理逻辑。
可管理的视图堆栈系统
相较于基于类型的响应式目标视图处理机制,可管理的视图堆栈系统才是新导航系统的杀手锏。
NavigationStack 支持两种堆栈管理类型:
NavigationPath
通过添加多个navigationDestination,NavigationStack可以对多种类型值进行响应,使用removeLast(_ k: Int = 1)返回指定的层级,使用append进入新的层级
class PathManager: ObservableObject {
@Published var path = NavigationPath()
}
struct ContentView: View {
@StateObject var pathManager = PathManager()
var body: some View {
NavigationStack {
List {
NavigationLink("SubView1", value: Target.subView1) // 只声明关联的状态只
NavigationLink("SubView2", value: Target.subView2)
NavigationLink("SubView3", value: 3)
NavigationLink("SubView4", value: 4)
}
.navigationDestination(for: Target.self) {
target in // 对同一类型进行统一处理,返回目标视图
switch target {
case .subView1:
SubView1()
case .subView2:
SubView2()
}
}
.navigtionDestination(for: Int.self) {
target in // 为不同的类型添加多个处理模块
switch target {
case 3:
SubView3()
case 4:
SubView4()
default: break
}
}
.environmentObject(pathManager)
.task {
// 使用 append 可以跳入指定层级,下面将为 root -> SubView3 -> SubView1 -> SubView2 ,在初始状态添加层级将屏蔽动画
pathManager.path.append(3)
pathManager.path.append(1)
pathManager.path.append(Target.subView2)
}
}
}
enum Target {
case subView1,
case subView2
}
}
struct SubView1: View {
@EnvironmentObject var pathManager:PathManager
var body: some View {
List{
// 仍然可以使用此种形式的 NavigationLink,目标视图的处理在根视图对应的 navigationDestination 中
NavigationLink("SubView2", destination: Target.subView2 )
NavigationLink("subView3",value: 3)
Button("go to SubView3"){
pathManager.path.append(3) // 效果与上面的 NavigationLink("subView3",value: 3) 一样
}
Button("返回根视图"){
pathManager.path.removeLast(pathManager.path.count)
}
Button("返回上层视图"){
pathManager.path.removeLast()
}
}
}
}
- 元素为符合
Hashable的单一类型序列
采用此种堆栈,NavigationStack将只能响应该序列元素的特定类型
class PathManager:ObservableObject{
@Published var path:[Int] = [] // Hashable 序列
}
struct ContentView: View {
@StateObject var pathManager = PathManager()
var body: some View {
NavigationStack(path:$pathManager.path) {
List {
NavigationLink("SubView1", value: 1)
NavigationLink("SubView3", value: 3)
NavigationLink("SubView4", value: 4)
}
// 只能响应序列元素类型
.navigationDestination(for: Int.self) {
target in
switch target {
case 1:
SubView1()
case 3:
SubView3()
default:
SubView4()
}
}
}
.environmentObject(pathManager)
.task{
pathManager.path = [3,4] // 直接跳转到指定层级,赋值更加方便
}
}
}
struct SubView1: View {
@EnvironmentObject var pathManager:PathManager
var body: some View {
List{
NavigationLink("subView3",value: 3)
Button("go to SubView3"){
pathManager.path.append(3) // 效果与上面的 NavigationLink("subView3",value: 3) 一样
}
Button("返回根视图"){
pathManager.path.removeAll()
}
Button("返回上层视图"){
if pathManager.path.count > 0 {
pathManager.path.removeLast()
}
}
Button("响应 Deep Link,重置 Path Stack "){
pathManager.path = [3,1,1] // 会自动屏蔽动画
}
}
}
}
开发者可以根据自己的需求选择对应的视图堆栈类型。
边栏推荐
- Pytest+request+Allure实现接口自动化框架
- Take you do interface test from zero to the first case summary
- Likou Brush Question Record 3.1-----977. Square of ordered array
- SQLite切换日志模式优化
- Working subtotal rtcp length and network byte order
- 【洛谷】P5091 【模板】扩展欧拉定理
- 自动化测试框架总结
- Yii2开启 Schema 缓存
- Recently, I have seen a lot of people who want to study by themselves or enroll in classes but don’t know how to choose. I will tell you about it today.
- 基于JMF视频聊天
猜你喜欢

uart_spi练习

Maya engine modeling

MES对接Simba实现展讯平台 IMEI 写号与耦合测试

Redis中SDS简单动态字符串

What are the most popular automated testing tools in 2022?The most complete and most detailed of the entire network is here

点击div内部默认文本被选中

2022年自然语言处理校招社招实习必备知识点盘点分享

Likou Brush Question Record 3.1-----977. Square of ordered array

书签收藏难整理?这款书签工具管理超方便

接口的安全性测试,应该从哪些方面入手?
随机推荐
YOLOV1详解——Pytorch版
Working subtotal rtcp length and network byte order
搭建Eureka注册中心集群 ,实现负载均衡
grafana的panel点击title,没有反应,没有出现edit选项
gpio子系统和pinctrl子系统(中)
图论相关知识
并查集相关知识点
第一部分:和数组相关的问题
【Untitled】
【信号去噪】基于Sage-Husa自适应卡尔曼滤波器实现海浪磁场噪声抑制及海浪磁场噪声的产生附matlab代码
Take you do interface test from zero to the first case summary
online schema change and create index
第二部分:和查找表相关的问题
最近看到很多人想自学或者报班但是不清楚如何选择,我今天就和大家说说
Building PO layered architecture of automated testing framework from 0
Open3D 均匀采样
Open3D 点云曲率计算
Postman接口测试【官网】最新版本 安装及使用入门教程
数字 01 Vivado2018.2安装及实操
JS 截取数组的最后几个元素