SwiftUI六组合复杂用户界面
代码下载
应用的首页是一个纵向滚动的地标类别列表,每一个类别内部是一个横向滑动列表。随后将构建应用的页面导航,这个过程中可以学习到如果组合各种视图,并让它们适配不同的设备尺寸和设备方向。
下载起步项目并跟着本篇教程一步步实践,或者查看本篇完成状态时的工程代码去学习,项目文件。
添加一个首页视图
已经创建了所有在应用中需要的视图,现在给应用创建一个首页视图,把之前创建的视图整合起来。首页不仅仅包含之前创建的视图,它还提供页面间导航的方式,同时也可以展示各种地标信息。

创建一个名为CategoryHome.swift的自定义视图文件,添加NavigationSplitView,这个NavigationSplitView将会容纳应用中其它不同的视图。配合使用NavigationLink及相关的修改器,就可以构建出应用的页面间导航结构,设置导航栏标题为Featured:
struct CategoryHome: View {var body: some View {NavigationSplitView {Text("Hello, World!").navigationTitle("Featured")} detail: {Text("Select a Landmark")}}
}
创建地标类别列表
应用为了便于用户浏览各种类别的地标,将地标按类别竖向排列形成列表视图,对于每一个类别内的具体地标,又把它们按照水平方向排列,形成横向列表。组合使用垂直栈(vertical statck)和水平栈(horizontal stack)并给列表添加滚动。

首先从landmarkData.json文件读取类别数据。
1、在Landmark中,向Landmark结构添加Category枚举和category属性。landmarkData文件已经为每个地标数据包含了一个category值,该类别值为三个字符串值之一。通过匹配数据文件中的名称,可以让结构体遵守Codable协议来加载数据:
struct Landmark: Hashable, Codable, Identifiable {var id: Intvar name: Stringvar park: Stringvar state: Stringvar description: Stringvar isFavorite: Boolvar category: Categoryenum Category: String, CaseIterable, Codable {case lakes = "Lakes"case rivers = "Rivers"case mountains = "Mountains"}private var imageName: Stringvar image: Image {Image(imageName)}private var coordinates: Coordinatesvar locationCoordinate: CLLocationCoordinate2D {CLLocationCoordinate2D(latitude: coordinates.latitude,longitude: coordinates.longitude)}struct Coordinates: Hashable, Codable {var latitude: Doublevar longitude: Double}
}
2、在ModelData中,添加一个categories计算字典,使用Dictionary结构体的初始化方法init(grouping:by:),把地标数据的类别属性category传入作为分组依据,可以把地标数据按类别分组:
class ModelData {var landmarks: [Landmark] = load("landmarkData.json")var hikes: [Hike] = load("hikeData.json")var categories: [ String: [Landmark] ] {Dictionary(grouping: landmarks) { $0.category.rawValue }}
}
3、在CategoryHome中创建一个modelData属性。现在需要访问categories,稍后还需要访问其他landmark数据。使用List显示地标数据的类别。Landmark.Category是枚举类型,它的值标识列表中每一种类别,可以保证类别不会有重复定义:
struct CategoryHome: View {@Environment(ModelData.self) var modelData: ModelDatavar body: some View {NavigationSplitView {List {ForEach(modelData.categories.keys.sorted(), id: \.self) { key inText(key)}}.navigationTitle("Featured")} detail: {Text("Select a Landmark")}}
}#Preview {CategoryHome().environment(ModelData())
}
创建地标行
Landmarks在水平滚动的一行中显示每个类别。添加一个新的视图类型来表示行,然后在新视图中显示该类别的所有地标。
重用在创建和组合视图中创建的Landmark视图的部分,以创建熟悉的Landmark预览。

1、创建一个名为CategoryItem的新自定义视图,显示一个地标:
struct CategoryItem: View {var landmark: Landmarkvar body: some View {VStack(alignment: .leading) {landmark.image.resizable().frame(width: 155, height: 155).cornerRadius(5)Text(landmark.name).font(.caption)}.padding(.leading, 15)}
}#Preview {CategoryItem(landmark: ModelData().landmarks[0])
}
2、定义一个新的视图类型CategoryRow,用来展示地标类别行的内容。新建行视图需要存放地标具体类别的展示数据。在CategoryRow.swift中将类别的条目放入HStack中,并将其与类别名称分组到VStack中。为行内容指定一个高度,并把行内容嵌入到ScrollView中,以支持横向滑动。预览视图时,可以多增加几个地标数据,用来查看列表的滑动是否正常:
struct CategoryRow: View {var categoryName: Stringvar items: [Landmark]var body: some View {VStack(alignment: .leading) {Text(categoryName).font(.headline).padding(.leading, 15).padding(.top, 5)ScrollView(.horizontal, showsIndicators: false) {HStack(alignment: .top, spacing: 0) {ForEach(items) { landmark inCategoryItem(landmark: landmark)}}}.frame(height: 185)}}
}#Preview {let landmarks = ModelData().landmarksreturn CategoryRow(categoryName: landmarks[0].category.rawValue, items: Array(landmarks.prefix(3)))
}
完成类别视图
将行和特色地标图像添加到类别主页。
1、更新CategoryHome的body,将类别信息传递给行类型的实例:
接下来,在视图的顶部添加一个特色地标。要做到这一点,需要从地标数据中获得更多信息。
2、在Landmark中,添加一个新的isFeatured属性,与添加的其他地标属性一样,这个布尔值已经存在于数据中,只需要声明一个新属性:
struct Landmark: Hashable, Codable, Identifiable {var id: Intvar name: Stringvar park: Stringvar state: Stringvar description: Stringvar isFavorite: Boolvar isFeatured: Boolvar category: Categoryenum Category: String, CaseIterable, Codable {case lakes = "Lakes"case rivers = "Rivers"case mountains = "Mountains"}private var imageName: Stringvar image: Image {Image(imageName)}private var coordinates: Coordinatesvar locationCoordinate: CLLocationCoordinate2D {CLLocationCoordinate2D(latitude: coordinates.latitude,longitude: coordinates.longitude)}struct Coordinates: Hashable, Codable {var latitude: Doublevar longitude: Double}
}
3、在ModelData中,添加一个新的计算特色地标数组,其中只包含将isFeatured设置为true的地标:
class ModelData {var landmarks: [Landmark] = load("landmarkData.json")var hikes: [Hike] = load("hikeData.json")var categories: [ String: [Landmark] ] {Dictionary(grouping: landmarks) { $0.category.rawValue }}var features: [Landmark] {landmarks.filter { $0.isFeatured }}
}
4、在CategoryHome中,将第一个特色地标的图像添加到列表的顶部。在后面的教程中,会将这个视图修改成一个交互式轮播图。目前,这个视图仅仅展示一张缩放和剪裁后的地标图片。把视图的边距设置为0,让展示内容可以尽量贴着屏幕边沿:
struct CategoryHome: View {@Environment(ModelData.self) var modelData: ModelDatavar body: some View {NavigationSplitView {List {modelData.features[0].image.resizable().frame(height: 200).clipped().listRowInsets(EdgeInsets())ForEach(modelData.categories.keys.sorted(), id: \.self) { key inCategoryRow(categoryName: key, items: modelData.categories[key] ?? [])}.listRowInsets(EdgeInsets())}.navigationTitle("Featured")} detail: {Text("Select a Landmark")}}
}
在各节之间添加导航
现在所有类别的地标都可以在首页视图中展示出来,用户还需要能够进入应用其它页面的方法。使用页面导航和相关API来实现用户从应用首页到地标详情页、收藏列表页及用户个人中心页的跳转。

1、在CategoryRow.swift中,把CategoryItem视图包裹在NavigationLink视图中。CategoryItem这时做为跳转按钮的内容,destination指定点击NavigationLink按钮时要跳转的目标视图:
struct CategoryRow: View {var categoryName: Stringvar items: [Landmark]var body: some View {VStack(alignment: .leading) {Text(categoryName).font(.headline).padding(.leading, 15).padding(.top, 5)ScrollView(.horizontal, showsIndicators: false) {HStack(alignment: .top, spacing: 0) {ForEach(items) { landmark inNavigationLink(destination: LandmarkDetail(landmark: landmark)) {CategoryItem(landmark: landmark)}}}}.frame(height: 185)}}
}
2、使用renderingMode(:)和foregroundColor(:)这两个属性修改器来改变地标类别项的导航样式。做为NavigationLink标签的CategoryItem中的文本会使用Environment中的强调颜色,图片可能以模板图片的方式渲染,这些都可以使用属性修改器来调整,达到最佳效果:
struct CategoryItem: View {var landmark: Landmarkvar body: some View {VStack(alignment: .leading) {landmark.image.renderingMode(.original).resizable().frame(width: 155, height: 155).cornerRadius(5)Text(landmark.name).foregroundStyle(.primary).font(.caption)}.padding(.leading, 15)}
}
3、接下来,将修改应用程序的ContentView,以显示一个TabView,让用户在刚刚创建的类别视图和现有的地标列表之间进行选择:
- 切换到ContentView并添加要显示的选项卡枚举。为选项卡选择添加一个状态变量,并给它一个默认值。
- 创建一个TabView来包装LandmarkList和新的CategoryHome。每个视图上的 tag(_😃 修饰符匹配选择属性可以取的一个可能值,因此当用户在用户界面中进行选择时,TabView可以协调显示哪个视图。
- 给每个Tab一个标签。确保实时预览打开,并尝试新的导航。
struct ContentView: View {@State private var selection: Tab = .featuredenum Tab {case featuredcase list}var body: some View {TabView(selection: $selection) {CategoryHome().tabItem { Label("Featured", systemImage: "star") }.tag(Tab.featured)LandmarkList().tabItem { Label("List", systemImage: "list.bullet") }.tag(Tab.list)}}
}#Preview {ContentView().environment(ModelData())
}
相关文章:
SwiftUI六组合复杂用户界面
代码下载 应用的首页是一个纵向滚动的地标类别列表,每一个类别内部是一个横向滑动列表。随后将构建应用的页面导航,这个过程中可以学习到如果组合各种视图,并让它们适配不同的设备尺寸和设备方向。 下载起步项目并跟着本篇教程一步步实践&a…...
高考分数查询结果自动推送至微信
又是一年高考时,祝各位学子金榜题名,天遂人愿! 在您阅读以下内容时,请注意:各省查分API接口可能不相同,本人仅就技术层面谈谈, 纯属无聊,因为实用意义不大,毕竟一年一次,…...
flask_sqlalchemy时间缓存导致datetime.now()时间不变问题
问题是这样的,项目在本地没什么问题,但是部署到服务器过一阵子发现,这个时间会在某一刻定死不变。 重启uwsgi后,发现第一条数据更新到了目前最新时间,过了一会儿再次发送也变了时间,但是再过几分钟再发就会…...
使用 PAI-DSW x Free Prompt Editing图像编辑算法,开发个人AIGC绘图小助理
教程简述 在本教程中,您将学习在阿里云交互式建模平台PAI-DSW x Free Prompt Editing(CVPR2024中选论文算法)图像编辑算法,开发个人AIGC绘图小助理,实现文本驱动的图像编辑功能单卡即可完成AIGC图片风格变化、背景变化…...
Nginx03-动态资源和LNMP介绍与实验、自动索引模块、基础认证模块、状态模块
目录 写在前面Nginx03案例1 模拟视频下载网站自动索引autoindex基础认证auth_basic模块状态stub_status模块模块小结 案例2 动态网站(部署php代码)概述常见的动态网站的架构LNMP架构流程数据库Mariadb安装安全配置基本操作 PHP安装php修改配置文件 Nginx…...
山东大学软件学院项目实训-创新实训-基于大模型的旅游平台(二十九)- 微服务(9)
目录 12. ElastisSearch 12.1 安装es 12.2 部署kibana 12.2.1 部署 12.2. 2 DevTools 12.3 索引库操作 12.3.1 mapping映射 12.3.2 创建索引库 12.3.3 查询索引库 12.3.4 删除索引库 12.3.5 修改索引库 12.4 文档操作 12.4.1 新增文档 12.4.2 查询文档 12.4.3 删…...
Matplotlib常见图汇总
Matplotlib是python的一个画图库,便于数据可视化。 安装命令 pip install matplotlib 常用命令: 绘制直线,连接两个点 import matplotlib.pyplot as plt plt.plot([0,5],[2,4]) plt.show() 运行结果如下: 多条线:…...
MTK联发科MT6897(天玑8300)5G智能移动处理器规格参数
天玑 8300 采用台积电第二代 4nm 制程,基于 Armv9 CPU 架构,八核 CPU 包含 4 个 Cortex-A715 性能核心和 4 个 Cortex-A510 能效核心,CPU 峰值性能较上一代提升 20%,功耗节省 30%。 此外,天玑 8300 搭载 6 核 GPU Mal…...
【AIoT-Robot】3d hand pose
手语是聋哑人士的主要沟通工具,它是利用手部和身体的动作来传达意义。虽然手语帮助它的使用者之间互相沟通,但聋哑人士与一般人的沟通却十分困难,这个沟通障碍是源于大部分人不懂得手语。 1. 手势&&手语 手势:手的姿势 ,通常称作手势。它指的是人在运用手臂时,所…...
使用 tc (Traffic Control)控制网络延时
设置网络延时 1500ms 800ms tc qdisc add dev eth0 root netem delay 1500ms 800msping 测试 ping www.baidu.com取消设置网络延时 sudo tc qdisc del dev eth0 root...
android原生TabLayout之自定义指示器效果
“com.google.android.material.tabs.TabLayout” 这个玩意说起来大家都不陌生。结合viewPager或者单独使用。场景非常多。当然市面上的三方也数不胜数。但是毕竟是亲儿子。用起来终归是顺手一些。下面说一下TabLayout的具体用法细节: 首先,xml布局引入…...
最新 HUAWEI DevEco Studio 使用技巧
最新 HUAWEI DevEco Studio 使用技巧 HUAWEI DevEco Studio 作为我们 harmonyos 应用的开发工具,有必要好好打磨一下。 Chinese(Simplified) 中文汉化插件 GitToolBox 编辑器中显示git历史 保存时自动格式化 写了一堆代码,当保存时,自动帮…...
开源大模型与闭源大模型浅析
引言 技术发展背景 早期语言模型 预训练与微调的范式 开源与闭源模型的兴起 当前的技术前沿 未来发展趋势 开源大模型的特点与优势 技术共享与创新加速 成本效益 社区驱动的发展 透明度和可审计性 促进教育与人才培养 灵活性和自定义 闭源大模型的特点与优势 商…...
docker 命令 ps,inspect,top,logs详解
docker常用命令教程-4 docker ps docker ps 命令用于列出当前正在运行的容器。默认情况下,它只显示正在运行的容器,但你可以使用 -a 或 --all 选项来显示所有容器(包括已停止的容器)。 常用的选项和示例: -a 或 --…...
Windows 找不到文件‘shell:sendto‘。请确定文件名是否正确后,再试一次
执行“shell:sendto”命令的时候,报错:Windows 找不到文件’shell:sendto’。请确定文件名是否正确后,再试一次 解决办法: 在桌面新建一个记事本文件命名为fix.reg,注意后缀是reg,文件中填写以下内容&…...
【算法】模拟算法——外观数组(medium)
题解:模拟算法——外观数组(medium) 目录 1.题目2.题解3.参考代码4.总结 1.题目 题目链接:LINK 2.题解 首先应该理解题意: 就是开始给你一个字符串,然后你对其进行描述。 描述规则是:连续的数字为一组,…...
2024年会计、金融与工商管理国际会议(ICAFBA 2024)
2024年会计、金融与工商管理国际会议 2024 International Conference on Accounting, Finance, and Business Administration 【1】会议简介 2024年会计、金融与工商管理国际会议是一场集合了全球会计、金融与工商管理领域专家学者的学术盛会。此次会议旨在深入探讨会计、金融与…...
关于 spring boot 的 目录详解 和 配置文件 以及 日志
目录 配置文件 spring boot 的配置文件有两种格式,分别是 properties 和 yml(yaml)。这两种格式的配置文件是可以同时存在的,此时会以 properties 的文件为主,但一般都是使用同一种格式的。 格式 properties 语法格…...
如何删除电脑端口映射?
在使用电脑进行网络连接时,有时需要进行端口映射以实现不同设备之间的信息远程通信。当这些端口映射不再需要时,我们需要及时删除它们以确保网络的安全和稳定。本文将介绍如何删除电脑端口映射的方法。 操作系统自带的工具 大多数操作系统都提供了自带…...
xiaolingcoding 图解网络笔记——基础篇
文章目录 参考一、网络模型有哪几层DMANAPI 机制二、键入网址到网页显示,期间发生了什么?1. HTTP2. DNS3. 协议栈4. TCP5. IP6. MAC7. 网卡8. 交换机9. 路由器10. 服务器 与 客户端的互相扒皮(添加、删除头部信息)参考图HTTP 请求…...
内存分配函数malloc kmalloc vmalloc
内存分配函数malloc kmalloc vmalloc malloc实现步骤: 1)请求大小调整:首先,malloc 需要调整用户请求的大小,以适应内部数据结构(例如,可能需要存储额外的元数据)。通常,这包括对齐调整,确保分配的内存地址满足特定硬件要求(如对齐到8字节或16字节边界)。 2)空闲…...
TDengine 快速体验(Docker 镜像方式)
简介 TDengine 可以通过安装包、Docker 镜像 及云服务快速体验 TDengine 的功能,本节首先介绍如何通过 Docker 快速体验 TDengine,然后介绍如何在 Docker 环境下体验 TDengine 的写入和查询功能。如果你不熟悉 Docker,请使用 安装包的方式快…...
【OSG学习笔记】Day 18: 碰撞检测与物理交互
物理引擎(Physics Engine) 物理引擎 是一种通过计算机模拟物理规律(如力学、碰撞、重力、流体动力学等)的软件工具或库。 它的核心目标是在虚拟环境中逼真地模拟物体的运动和交互,广泛应用于 游戏开发、动画制作、虚…...
Qt/C++开发监控GB28181系统/取流协议/同时支持udp/tcp被动/tcp主动
一、前言说明 在2011版本的gb28181协议中,拉取视频流只要求udp方式,从2016开始要求新增支持tcp被动和tcp主动两种方式,udp理论上会丢包的,所以实际使用过程可能会出现画面花屏的情况,而tcp肯定不丢包,起码…...
Admin.Net中的消息通信SignalR解释
定义集线器接口 IOnlineUserHub public interface IOnlineUserHub {/// 在线用户列表Task OnlineUserList(OnlineUserList context);/// 强制下线Task ForceOffline(object context);/// 发布站内消息Task PublicNotice(SysNotice context);/// 接收消息Task ReceiveMessage(…...
【配置 YOLOX 用于按目录分类的图片数据集】
现在的图标点选越来越多,如何一步解决,采用 YOLOX 目标检测模式则可以轻松解决 要在 YOLOX 中使用按目录分类的图片数据集(每个目录代表一个类别,目录下是该类别的所有图片),你需要进行以下配置步骤&#x…...
精益数据分析(97/126):邮件营销与用户参与度的关键指标优化指南
精益数据分析(97/126):邮件营销与用户参与度的关键指标优化指南 在数字化营销时代,邮件列表效度、用户参与度和网站性能等指标往往决定着创业公司的增长成败。今天,我们将深入解析邮件打开率、网站可用性、页面参与时…...
【JavaSE】多线程基础学习笔记
多线程基础 -线程相关概念 程序(Program) 是为完成特定任务、用某种语言编写的一组指令的集合简单的说:就是我们写的代码 进程 进程是指运行中的程序,比如我们使用QQ,就启动了一个进程,操作系统就会为该进程分配内存…...
自然语言处理——文本分类
文本分类 传统机器学习方法文本表示向量空间模型 特征选择文档频率互信息信息增益(IG) 分类器设计贝叶斯理论:线性判别函数 文本分类性能评估P-R曲线ROC曲线 将文本文档或句子分类为预定义的类或类别, 有单标签多类别文本分类和多…...
Spring Boot + MyBatis 集成支付宝支付流程
Spring Boot MyBatis 集成支付宝支付流程 核心流程 商户系统生成订单调用支付宝创建预支付订单用户跳转支付宝完成支付支付宝异步通知支付结果商户处理支付结果更新订单状态支付宝同步跳转回商户页面 代码实现示例(电脑网站支付) 1. 添加依赖 <!…...
