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: Int
    var name: String
    var park: String
    var state: String
    var description: String
    var isFavorite: Bool
    
    var category: Category
    enum Category: String, CaseIterable, Codable {
        case lakes = "Lakes"
        case rivers = "Rivers"
        case mountains = "Mountains"
    }

    private var imageName: String
    var image: Image {
        Image(imageName)
    }

    private var coordinates: Coordinates
    var locationCoordinate: CLLocationCoordinate2D {
        CLLocationCoordinate2D(
            latitude: coordinates.latitude,
            longitude: coordinates.longitude)
    }

    struct Coordinates: Hashable, Codable {
        var latitude: Double
        var 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: ModelData
    
    var body: some View {
        NavigationSplitView {
            List {
                ForEach(modelData.categories.keys.sorted(), id: \.self) { key in
                    Text(key)
                }
            }.navigationTitle("Featured")
        } detail: {
            Text("Select a Landmark")
        }

    }
}

#Preview {
    CategoryHome().environment(ModelData())
}

创建地标行

Landmarks在水平滚动的一行中显示每个类别。添加一个新的视图类型来表示行,然后在新视图中显示该类别的所有地标。

重用在创建和组合视图中创建的Landmark视图的部分,以创建熟悉的Landmark预览。

请添加图片描述

1、创建一个名为CategoryItem的新自定义视图,显示一个地标:

struct CategoryItem: View {
    var landmark: Landmark
    
    var 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: String
    var 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 in
                        CategoryItem(landmark: landmark)
                    }
                }
            }.frame(height: 185)
        }
    }
}

#Preview {
    let landmarks = ModelData().landmarks
    return CategoryRow(categoryName: landmarks[0].category.rawValue, items: Array(landmarks.prefix(3)))
}

完成类别视图

将行和特色地标图像添加到类别主页。

1、更新CategoryHome的body,将类别信息传递给行类型的实例:

接下来,在视图的顶部添加一个特色地标。要做到这一点,需要从地标数据中获得更多信息。

2、在Landmark中,添加一个新的isFeatured属性,与添加的其他地标属性一样,这个布尔值已经存在于数据中,只需要声明一个新属性:

struct Landmark: Hashable, Codable, Identifiable {
    var id: Int
    var name: String
    var park: String
    var state: String
    var description: String
    var isFavorite: Bool
    var isFeatured: Bool
    
    var category: Category
    enum Category: String, CaseIterable, Codable {
        case lakes = "Lakes"
        case rivers = "Rivers"
        case mountains = "Mountains"
    }

    private var imageName: String
    var image: Image {
        Image(imageName)
    }

    private var coordinates: Coordinates
    var locationCoordinate: CLLocationCoordinate2D {
        CLLocationCoordinate2D(
            latitude: coordinates.latitude,
            longitude: coordinates.longitude)
    }

    struct Coordinates: Hashable, Codable {
        var latitude: Double
        var 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: ModelData
    
    var body: some View {
        NavigationSplitView {
            List {
                modelData.features[0]
                    .image
                    .resizable()
                    .frame(height: 200)
                    .clipped()
                    .listRowInsets(EdgeInsets())
                
                ForEach(modelData.categories.keys.sorted(), id: \.self) { key in
                    CategoryRow(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: String
    var 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 in
                        NavigationLink(destination: LandmarkDetail(landmark: landmark)) {
                            CategoryItem(landmark: landmark)
                        }
                    }
                }
            }.frame(height: 185)
        }
    }
}

2、使用renderingMode(:)和foregroundColor(:)这两个属性修改器来改变地标类别项的导航样式。做为NavigationLink标签的CategoryItem中的文本会使用Environment中的强调颜色,图片可能以模板图片的方式渲染,这些都可以使用属性修改器来调整,达到最佳效果:

struct CategoryItem: View {
    var landmark: Landmark
    
    var 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 = .featured
    
    enum Tab {
        case featured
        case 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())
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

[MQTT]服务器EMQX搭建SSL/TLS连接过程(wss://)

👉原文阅读 💡章前提示 本文采用8084端口进行连接,是EMQX 默认提供了四个常用的监听器之一,如果需要添加其他类型的监听器,可参考官方文档🔗管理 | EMQX 文档。 本文使用自签名CA,需要提前在L…

数据挖掘--挖掘频繁模式、关联和相关性:基本概念和方法

频繁项集、闭项集和关联规则 频繁项集:出现的次数超过最小支持度计数阈值 闭频繁项集:一个集合他的超集(包含这个集合的集合)在数据库里面的数量和这个集合在这个数据库里面的数量不一样,这个集合就是闭项集 如果这个集合还是频繁的,那么他就是极大频…

暂停系统更新

电脑左下角搜索注册表编辑器 计算机\HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\WindowsUpdate\UX\Settings 找到这个目录 打开FlightSettingsMaxPauseDays,没找到的话就创建一个同名文件夹然后选择10进制填入3550​​​​​​​ 最后进入系统暂停更新界面选择最下面…

AI炒股:用Kimi获取美股的历史成交价格并画出股价走势图

在Kimi中输入提示词: 你是一个Python编程专家,要完成一个编写Python脚本的任务,具体步骤如下: 用akshare库获取谷歌(股票代码:105.GOOG)、亚马逊(股票代码:105.AMZN )、苹果(股票代码:105.AAP…

【静夜思】小时候的回忆

为什么大家都会对自己童年时期的评价很高?甚至是一些模糊都快到想不起来的记忆? 博主是00后,那时候小学的我非常喜欢看动画片,像经典的喜羊羊、熊出没、胡图图等等,太多了。等上了高中后,博主也成为了一名…

02.体验CSS以及Bootstrap框架

目录 CSS固定格式 1)style标签 2)div标签 3)span标签 CSS属性 一、文字属性 1.规范文字样式的属性 2.规定文字粗细的属性 3.规定文字大小的属性 4.规范文字字体的属性 二、文本属性 1.文本装饰属性 2.文本水平对齐属性 3.文本缩进…

[office] 如何才能用EXCEL打开dat文件- #微信#学习方法

如何才能用EXCEL打开dat文件? 方法: 1、打开EXCEL软件; 2、文件,打开,选择要转化的DAT文件; 3、在弹出的向导文件(步骤1)中,选择合适的文件类型(按预览选择&#xf…

纷享销客安全体系:安全运维运营

安全运维运营(Security Operations,SecOps)是指在信息安全管理中负责监控、检测、响应和恢复安全事件的一系列运营活动。它旨在保护组织的信息系统和数据免受安全威胁和攻击的损害。 通过有效的安全运维运营,组织可以及时发现和应对安全威胁,减少安全事…

VSCode搭建开发环境--从PyCharm到拥抱vscode

VSCode搭建开发环境 前言安装扩展全局配置文件单个项目的配置快捷键 前言 最近自己的PyCharm Professional的License过期了,导致没有一个好的开发IDE,于是开始拥抱免费的Visual Studio Code啦。 当然,不可否认的是PyCharm对于开发Python来说…

【产品研发】NPDP价值作用概述

导读:本文结合个人实践和思考对NPDP的价值和作用做了概述说明,对于产品经理而言掌握NPDP的知识体系并且应用到实际工作中,这是非常有必要的。走出以往狭隘的产品研发工作认知,以开放心态学习国际化产品创新开发流程将极大提升产品…

mysql工具----dbForgeStudio2020

dbForgeStudio2020,除了基本的操作外,还具有可调试mysql存储过程的功能,是一个不可夺得的mysql软件工具。 本文的软件将简单介绍软件的安装方式,仅供学习交流,不可做它用。 1.安装软件,安装后&#xff0c…

Flutter vscode环境如何进行真机测试

目录 1. 准备工作 1.1 安装Flutter和VS Code 1.2 安装必要的VS Code扩展 1.3 手机设置 2. 配置VS Code调试环境 3. 手机如何退出开发者模式 1. 准备工作 1.1 安装Flutter和VS Code 确保你已经在电脑上安装了Flutter SDK和VS Code。如果还没有,可以参考以下指…

线性代数|机器学习-P9向量和矩阵范数

文章目录 1. 向量范数2. 对称矩阵S的v范数3. 最小二乘法4. 矩阵范数 1. 向量范数 范数存在的意义是为了实现比较距离,比如,在一维实数集合中,我们随便取两个点4和9,我们知道9比4大,但是到了二维实数空间中&#xff0c…

Typesense-开源的轻量级搜索引擎

Typesense-开源的轻量级搜索引擎 Typesense是一个快速、允许输入错误的搜索引擎,用于构建愉快的搜索体验。 开源的Algolia替代方案& 易于使用的弹性搜索替代方案 官网: https://typesense.org/ github: https://github.com/typesense/typesense 目前已有18.4k…

注册自定义材质实现qgis里不同比例尺下材质不被拉升的效果

前景提要: 在QGIS里的显示效果,用的是示例的/img/textures/line-interval.png材质图片。 下载示例 git clone https://gitee.com/marsgis/mars3d-vue-example.git 相关效果 比如材质是5像素,在1:100000万比例尺下,线显示的长…

Django与MySQL:配置数据库的详细步骤

文章目录 Django-MySQL 配置配置完执行数据迁移,如果报错: Error loading MySQLdb module, Django-MySQL 配置 # settings.pyDATABASES {# 默认配置sqlite3数据库# default: {# ENGINE: django.db.backends.sqlite3,# NAME: BASE_DIR / db.sqli…

【一百零九】【算法分析与设计】树状数组求解前缀最大值,673. 最长递增子序列的个数,树状数组求前缀区间最大值

树状数组求解前缀最大值 树状数组可以求解和前缀区间有关的问题,例如前缀和,前缀区间最值. 可以利用 l o g n log_n logn​的时间复杂度快速查找前缀信息. 利用树状数组查询前缀区间中最大值问题. 树状数组下标1位置存储arr数组下标1位置的最大值. 树状数组2位置存储arr数组1,…

2024 cicsn SuperHeap

文章目录 参考沙箱存在protobuf逆向buy_booksee_bookreturn_bookedit_booksearch_book 思路exp 参考 https://hakuya.work/post/7 https://akaieurus.github.io/2024/05/20/2024%E5%9B%BD%E8%B5%9B%E5%88%9D%E8%B5%9Bpwn-wp/#SuperHeap https://blog.csdn.net/m0_63437215/art…

html--圣诞树

将以下代码保存到txt文件中&#xff0c;并改名为xx.html <html> <head> <title>圣诞树</title> <meta charset"utf-8" > <style> html, body { width: 100%; height: 100%; margin: 0; padding: 0; border: 0; } div { margin: …

Windows 找不到文件‘shell:sendto‘。请确定文件名是否正确后,再试一次

执行“shell:sendto”命令的时候&#xff0c;报错&#xff1a;Windows 找不到文件’shell:sendto’。请确定文件名是否正确后&#xff0c;再试一次 解决办法&#xff1a; 在桌面新建一个记事本文件命名为fix.reg&#xff0c;注意后缀是reg&#xff0c;文件中填写以下内容&…