SwiftUI 4.0:两种方式实现子视图导航功能

news2024/11/18 5:31:05

在这里插入图片描述

0. 概览

从 SwiftUI 4.0 开始,觉悟了的苹果毅然抛弃了已“药石无效”的 NavigationView,改为使用全新的 NavigationStack 视图。

诚然,NavigationStack 从先进性来说比 NavigationView 有不小的提升,若要如数家珍得单开洋洋洒洒的一篇来介绍。


关于 SwiftUI 中旧 NavigationView 视图种种人神共愤的“诟病”和弊端,请移步我的其它专题博文观赏:

  • SwiftUI进入多重嵌套视图后如何一键退回到根视图
  • iOS 16.2 在 SwiftUI 子视图中无法关闭弹出的(sheet)导航视图(NavigationView)之解决
  • SwiftUI导航至子视图后状态改变导致导航栈提前弹出的原因及解决
  • SwiftUI实现不同TabView标签页中任意导航层级视图之间自动相互跳转那些事儿
  • SwiftUI中NavigationLink多层嵌套导航无法返回上一层的原因及解决

不过,这些都不是本篇的主旨。在本篇中我们将尝试一起另辟蹊径来完成两种截然不同的导航机制。

在本篇博文,您将学到如下内容:

  • 0. 概览
  • 1. NavigationStack
  • 2. NavigationSplitView 导航之“假象”
  • 3. 洞若观火:在 iPad 上的比较
  • 4. 总结

无需等待,Let’s go!!!😉


1. NavigationStack

从 SwiftUI 4.0 开始, 引入的新 NavigationStack 导航器终于不再以分散杂乱的数据作为导航触发媒介,而是将有序的数据集合作为导航跳转的核心来对待!(所以,一步跳回根视图成了雕虫小技)

其中,NavigationStack 导航器重要的组成部分 NavigationLink 除了为了兼容性暂时保留的传统跳转方式以外,主打以状态本身的值作为导航的基石。这样,对于不同类型状态触发的跳转,我们可以干净而从容的分别处理:

下面举一例。

首先,定义简单的数据结构,Alliance 中包含若干 Hero:

@Observable
final class Hero {
    var name: String
    var power: Int
    
    init(name: String, power: Int) {
        self.name = name
        self.power = power
    }
}

extension Hero: Identifiable {
    var id: String {
        name
    }
}

extension Hero: Hashable {
    static func == (lhs: Hero, rhs: Hero) -> Bool {
        lhs.name == rhs.name && lhs.power == rhs.power
    }
    
    func hash(into hasher: inout Hasher) {
        hasher.combine(name)
        hasher.combine(power)
    }
}


@Observable
final class Alliance: Hashable {
    static func == (lhs: Alliance, rhs: Alliance) -> Bool {
        lhs.title == rhs.title
    }
    
    func hash(into hasher: inout Hasher) {
        hasher.combine(title)
    }
    
    var title: String
    var createAt: Date?
    var heros: [Hero]
    
    init(title: String, heros: [Hero]) {
        self.title = title
        self.createAt = Date.now
        self.heros = heros
    }
}

接下来是与之对应的子视图,注意驱动 NavigationLink 导航的是 Hero 本身,而不是什么“莫名奇妙”的条件变量:

struct HeroDetailView: View {
    let hero: Hero
    
    var body: some View {
        VStack {
            Text("力量: \(hero.power)")
                .font(.largeTitle)
            
        }.navigationTitle(hero.name)
    }
}

struct HeroListView: View {
    @Environment(Alliance.self) var model
    
    var body: some View {        
        VStack {
            List(model.heros) { hero in
                NavigationLink(value: hero) {
                    HStack {
                        Text(hero.name)
                            .font(.headline)
                        Spacer()
                        Text("\(hero.power)")
                            .font(.subheadline)
                            .foregroundStyle(.gray)
                    }
                }
            }
        }
    }
}

接着是主视图:

struct ContentView: View {
    @State var model = Alliance(title: "地球超级英雄", heros: [
        .init(name: "大熊猫侯佩", power: 5),
        .init(name: "孙悟空", power: 1000),
        .init(name: "哪吒", power: 511)
    ])

    var body: some View {
        NavigationStack {
            Form {
                NavigationLink("查看所有英雄", value: model)
            }
            .navigationDestination(for: Alliance.self) { model in
                HeroListView()
                    .environment(model)
            }
            .navigationDestination(for: Hero.self) { hero in
                HeroDetailView(hero: hero)
            }
            .navigationTitle(model.title)
        }
    }
}

从上面源代码中,我们可以看到几处有趣的地方:

  1. 子视图 HeroListView 和主视图 ContentView 都包含了 NavigationLink,但它们驱动状态的类型不一样(分别是 Hero 和 Model),这样不同的驱动源被清晰的区分开了;
  2. 放置 NavigationLink 和实际发生导航跳转的目标位置是分开的(通过 navigationDestination() 修改器),后者被放在了一起便于集中管理;

正是这些新的导航特性确保了导航逻辑代码清楚且集中,为日后自己或其它秃头码农来维护打下夯实基础:

在这里插入图片描述

以上就是第一种导航方法,即利用 NavigationStack + navigationDestination() 修改器方法来合作完成跳转功能。

2. NavigationSplitView 导航之“假象”

可能有的小伙伴们没太在意,SwiftUI 4.0 除了 NavigationStack 以外还新加入了另一个鲜为人知的导航器 NavigationSplitView!使用它,我们可以抛弃 navigationDestination() 去实现完全相同的导航功能。

我们对之前代码略作修改,看看能促成什么新奇的“玩法”:

struct HeroListView: View {
    @Environment(Alliance.self) var model
    @Binding var selection: Hero?
    
    var body: some View {        
        VStack {
            List(model.heros, selection: $selection) { hero in
                NavigationLink(value: hero) {
                    HStack {
                        Text(hero.name)
                            .font(.headline)
                        Spacer()
                        Text("\(hero.power)")
                            .font(.subheadline)
                            .foregroundStyle(.gray)
                    }
                }
            }
        }
    }
}

struct ContentView: View {
    @State var model = Alliance(title: "地球超级英雄", heros: [
        .init(name: "大熊猫侯佩", power: 5),
        .init(name: "孙悟空", power: 1000),
        .init(name: "哪吒", power: 511)
    ])
    
    @State private var selection: Hero?

    var body: some View {
        NavigationSplitView(sidebar: {
            HeroListView(selection: $selection)
                .environment(model)
                .navigationTitle("新导航方式")
        }, detail: {
            if let selection {
                HeroDetailView(hero: selection)
            } else {
                ContentUnavailableView("No Hero", systemImage: "person.badge.key.fill", description: Text("还未选中任何英雄!"))

            }
        })
    }
}

可以看到,修改后的代码与之前有几处不同:

  1. 使用 NavigationSplitView 而不是 NavigationStack;
  2. 没有使用任何 navigationDestination() 修改器方法;
  3. 向 List 构造器传入了 selection 参数,以判断用户选择了哪个 Hero;
  4. 根据 selection 的值驱动 NavigationSplitView 构造器 detail 闭包完成跳转功能;

简单来说:当用户选中任意 Hero 后,通过设置 NavigationSplitView 构造器 detail 闭包中的视图,我们完成了导航机制。

代码执行结果和之前几乎完全相同,这么神奇!?

可惜,你们看到的全是“假象”!!!

3. 洞若观火:在 iPad 上的比较

其实,设置 NavigationSplitView 构造器 detail 闭包的内容原本并不会造成导航跳转,它的原意是在大屏设备上更方便的利用大尺寸屏幕来浏览内容。

编译上面 NavigationSplitView 的实现,在 iPad 上运行看看效果:

在这里插入图片描述

看到了吗?第二种导航机制在大屏设备上原本并不是真正用来导航跳转的,只是在 iPhone 等小屏设备上它的行为退化成了导航!

而第一种导航实现是彻头彻尾、如假包换的“真”导航:

在这里插入图片描述

到底哪种方法更好一些?小伙伴们自有“仁者见仁智者见智”的看法,欢迎大家随后的讨论。

至此,我们完成了文章开头的目标,棒棒哒!!!💯

4. 总结

在本篇博文中,我们在 SwiftUI 4.0 里通过两种不同方式实现了相同的子视图导航功能,任君选择。

感谢观赏,再会!😎

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1059901.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Leetcode---114双周赛

题目列表 2869. 收集元素的最少操作次数 2870. 使数组为空的最少操作次数 2871. 将数组分割成最多数目的子数组 2872. 可以被 K 整除连通块的最大数目 一、收集元素的最小操作次数 直接模拟,倒序遍历即可,代码如下 class Solution { public:int mi…

ROS(5)PX4仿真安装及运行

1、配置,提升下载速度 启动 $ cd clash-for-linux$ sudo bash start.sh$ source /etc/profile.d/clash.sh$ proxy_on 关闭 $ cd clash-for-linux$ sudo bash shutdown.sh$ proxy_off 2、安装PX4开源无人机 git clone https://github.com/PX4/PX4-Autopilot.git…

全栈开发笔记1:首个项目的收获

本文为编程导航实战项目学习笔记。 文章目录 7.跨域问题解决 2023.10.26.项目部署 2023.10.15.统一处理返回值 2023.10.14.开发注册和用户管理 2023.09303.开发登陆注册接口 2023.09.172.数据库设计1.前后端初始化 2023.9.16 7.跨域问题解决 2023.10.2 三种方式: …

ps插件:alpaca增效工具 (完美替代AI创成式填充) 2.8.1 中文版

Alpaca是一个Photoshop插件,提供了多种功能,帮助用户更高效地进行图像处理和设计。可以进行模型训练并无缝地融入图像中。同时还提供文本到图像的生成、图像到图像的变化、涂色、放大、深度图创建等功能,极大地提升了设计和艺术创作的效率和创…

Go,从命名开始!Go的关键字和标识符全列表手册和代码示例!

目录 一、Go的关键字列表和分类介绍关键字在Go中的定位语言的基石简洁与高效可扩展性和灵活性 关键字分类声明各种代码元素组合类型的字面表示基本流程控制语法协程和延迟函数调用 二、Go的关键字全代码示例关键字全代码示例 三、Go的标识符定义基础定义特殊规定关键字与标识符…

【Aseprite像素画】如何取巧做到各种画面效果(小工具的各种技巧)

文章目录 参考链接:具体如下1、水中倒影2、参考图片3多个帧添加动画物品4多个帧删除动画物品5六毛钱受击效果6添加标签7导出特定标志的gif图8忽略标志帧,然后播放9轮廓线10多个图层轮廓线11洋葱皮12替换多个不同帧的色块簇13连接细胞14快速连续删除15冻结…

战火使命兑换码最新,战火使命礼包码

战火使命手游是一款二次元卡牌游戏,玩家可以通过使用兑换码来获取礼包奖励。如果你还不知道如何获取兑换码,下面为你提供最新的礼包码合集。 关注【娱乐天梯】,获取内部福利号 战火使命兑换码最新: 1、兑换码:ZHSM0421…

安装matplotlib_

安装pip 安装matplotlib 安装完毕 导入出现bug......

C++算法 —— 动态规划(10)二维费用背包

文章目录 1、动规思路简介2、一和零3、盈利计划 背包问题需要读者先明白动态规划是什么,理解动规的思路,并不能给刚接触动规的人学习。所以最好是看了之前的动规博客,以及两个背包博客,或者你本人就已经懂得动规了。 1、动规思路简…

弧度、圆弧上的点、圆的半径(r)、弧长(s)之间的关系

要计算弧度和圆弧上的点,需要知道以下几个要素: 圆的半径(r):即圆的中心到圆周上任意一点的距离。 弧长(s):从圆周上的一个点到另一个点所经过的弧长。 弧度(θ&#x…

为什么Spring不建议使用基于字段的依赖注入

在我们通过IDEA编写Spring的代码的时候,假如我们编写了如下代码: IDEA会给我们一个warning警告: 翻阅官方文档;我们会发现: 大意就是强制依赖使用构造器注入,可选依赖使用setter注入那么这是为什么呢&am…

App测试时常用的adb命令你都掌握了哪些呢?

adb 全称为 Android Debug Bridge(Android 调试桥),是 Android SDK 中提供的用于管理 Android 模拟器或真机的工具。 adb 是一种功能强大的命令行工具,可让 PC 端与 Android 设备进行通信。adb 命令可执行各种设备操作&#xff0…

【服务器】在 Linux CLI 下安装 Anaconda

【服务器】在 Linux CLI 下安装 Anaconda 1 系统环境2 下载安装包3 安装 1 系统环境 查看系统信息 cat /etc/os-release2. 查看架构 uname -a # output # Linux localhost.localdomain 4.18.0-193.28.1.el8_2.x86_64 #1 SMP Thu Oct 22 00:20:22 UTC 2020 x86_64 x86_64 x86…

4.Tensors For Beginners-Vector Definition

在上一节,已经了解了前向和后向转换。 什么是向量? 定义1:向量是一个数字列表 这很简洁,也通俗易懂。 现有两个向量: 如果要把这两个向量给加起来,只需把对应位置的元素(组件)给加起来。 而要缩放向量&…

angularjs开发环境搭建

Angularjs是一个前端页面应用开发框架,其使用TypeScript作为开发语言,Angularjs的特性包括,使用组件、模板以及依赖注入的开发框架构建可扩展的web应用,使用易于集成的类库支持页面路由、页面表单、前后端接口交互等各种不同特性&…

MySQL5.7版本与8.0版本在CentOS系统安装

目录 前置要求 1. MySQL5.7版本在CentOS系统安装 1.1 安装 1.1.1 配置yum仓库 1.1.2 使用yum安装MySQL 1.1.3 安装完成后,启动MySQL并配置开机自启动 1.1.4 检查MySQL的运行状态 1.2 配置 1.2.1 获取MySQL的初始密码 1.2.2 登陆MySQL数据库系统 …

Python爬取诗词名句网中三国演义的乱码问题

一、乱码问题 为解决中文乱码问题,可使用chardet.detect()检测文本编码格式 详细: Python爬虫解决中文乱码_脑子不好真君的博客-CSDN博客 二、代码 #爬取三国演义 import requests import chardet from bs4 import BeautifulSoupurlhttps://www.shicim…

【Vue3】自定义指令

除了 Vue 内置的一系列指令 (比如 v-model 或 v-show) 之外&#xff0c;Vue 还允许你注册自定义的指令 (Custom Directives)。 1. 生命周期钩子函数 一个自定义指令由一个包含类似组件生命周期钩子的对象来定义。钩子函数会接收到指令所绑定元素作为其参数。 在 <script …

多通道反向字典模型

方法 将单词的definition embedding输入Bi-LSTM模型&#xff0c;经过处理得到5个分数并加权求和得到最终的置信分数 最后对分数向量进行降序排序&#xff0c;得到word rank 代码实现&#xff1a; _, indices torch.sort(score, descendingTrue) 辅助信息 这是AAAI 2020的论…

【多模态融合】TransFusion学习笔记(1)

工作上主要还是以纯lidar的算法开发,部署以及系统架构设计为主。对于多模态融合(这里主要是只指Lidar和Camer的融合)这方面研究甚少。最近借助和朋友们讨论论文的契机接触了一下这方面的知识&#xff0c;起步是晚了一点&#xff0c;但好歹是开了个头。下面就借助TransFusion论文…