SwiftUI七使用UI控件

ops/2024/9/23 22:37:31/

ui/SwiftUILearn">代码下载

在应用中,用户可以创建一个简介来描述他们自已的个人情况。为了让用户可以编辑自己的简介,需要添加一个编辑模式并设计一个偏好设置界面。这里使用多种通用控件来展示用户的各种数据,并在用户保存他们所做的数据修改时更新地标数据模型。

按照步骤在下面的项目工程中一步步进行实践,项目文件。

展示用户简介

应用在本地存储了一些配置和用户偏好设置。在用户编辑这些数据前,会被展示在一个没有编辑按钮的概要视图上。
请添加图片描述

1、在工程的 Model 组中创建一个名为Profile.swift的文件,并在这个新文件中定义一个用户配置:

struct Profile {var username: Stringvar prefersNotifications = truevar seasonalPhoto = Season.wintervar goalDate = Date()static let `default` = Profile(username: "g_kumar")enum Season: String, CaseIterable, Identifiable {case spring = "🌷"case summer = "🌞"case autumn = "🍂"case winter = "☃️"var id: String { rawValue }}
}

2、接下来,在Views组下创建一个名为Profiles的新组,然后向该组添加一个名为ProfileHost的视图,该视图带有显示存储的概要文件的用户名的文本视图。ProfileHost视图将承载概要信息的静态摘要视图和编辑模式。在稍后引入模型数据 Profile 之前,可以将这里的 draftProfile 设置为默认的 Profile 作为占位符:

struct ProfileHost: View {@State var draftProfile = Profile.defaultvar body: some View {Text("Profile for: \(draftProfile.username)")}
}

3、在Profiles组中创建另一个名为 ProfileSummary 的视图,该视图会持有一个Profile实例,并显示用户的基本信息。Profile概要视图持有一个Profile对像的原因是,因为它的父视图ProfileHost管理着视图的状态,它不能与Profile进行绑定:

struct ProfileSummary: View {var profile: Profilevar body: some View {ScrollView {VStack(alignment: .leading, spacing: 10) {Text(profile.username).bold().font(.title)Text("Notifications: \(profile.prefersNotifications ? "On": "Off" )")Text("Seasonal Photos: \(profile.seasonalPhoto.rawValue)")Text("Goal Date: ") + Text(profile.goalDate, style: .date)}}}
}

4、更新ProfileHost文件,显示新的概要视图:

struct ProfileHost: View {@State var draftProfile = Profile.defaultvar body: some View {VStack(alignment: .leading, spacing: 20) {ProfileSummary(profile: draftProfile)}.padding()}
}

5、在 Hikes 文件夹中创建一个名为HikeBadge的新视图,这个新视图由Badge视图和一些描述性文字构成。Badge仅仅是一个图形,在HikeBadge视图中的文本与accessibility(label:)属性修改器一起,可以让这个徽章对用户更加清晰。注意frame(width:height:)的两种不同的用法用来配置徽章以不同的缩放尺寸显示,徽章的绘制逻辑产生的结果取决于它所呈现的frame的大小。为了确保理想的外观,在300 x 300点的frame中渲染。要获得最终图形所需的尺寸,然后缩放渲染结果并将其放置在相对较小的frame中:

struct HikeBadge: View {var name: Stringvar body: some View {VStack {Badge().frame(width: 300, height: 300).scaleEffect(1.0/3.0).frame(width: 100, height: 100)Text(name).font(.caption).accessibilityLabel("Badge for \(name).")}}
}

6、更新ProfileSummary文件,添加几个不同的徽章代表用户得到的不同徽章。通过包含HikeView来完成ProfileSummary。为了使用 hike 数据,还需要添加一个ModelData环境属性:

struct ProfileSummary: View {@Environment(ModelData.self) var modelDatavar profile: Profilevar body: some View {ScrollView {VStack(alignment: .leading, spacing: 10) {Text(profile.username).bold().font(.title)Text("Notifications: \(profile.prefersNotifications ? "On": "Off" )")Text("Seasonal Photos: \(profile.seasonalPhoto.rawValue)")Text("Goal Date: ") + Text(profile.goalDate, style: .date)Divider()VStack(alignment: .leading) {Text("Completed Badges").font(.headline)ScrollView(.horizontal) {HStack {HikeBadge(name: "First Hike")HikeBadge(name: "Earth Day").hueRotation(Angle(degrees: 90))HikeBadge(name: "Tenth Hike").grayscale(0.5).hueRotation(Angle(degrees: 45))}.padding(.bottom)}}Divider()VStack {Text("")HikeView(hike: ModelData().hikes[0])}}}}
}#Preview {ProfileSummary(profile: Profile.default).environment(ModelData())
}

7、在CategoryHome中,使用toolbar修改器向导航栏添加一个用户配置文件按钮,并在用户点击它时显示ProfileHost视图,添加listStyle修改器以选择更适合内容的列表样式:

struct CategoryHome: View {@Environment(ModelData.self) var modelData: ModelData@State private var showingProfile = falsevar body: some View {NavigationSplitView {List {modelData.features[0].image.resizable().scaledToFill().frame(height: 200).clipped().listRowInsets(EdgeInsets())ForEach(modelData.categories.keys.sorted(), id: \.self) { key inCategoryRow(categoryName: key, items: modelData.categories[key] ?? [])}.listRowInsets(EdgeInsets())}.listStyle(.inset).navigationTitle("Featured").toolbar(content: {Button {showingProfile.toggle()} label: {Label("User Profile", systemImage: "person.crop.circle")}}).sheet(isPresented: $showingProfile, content: {ProfileHost().environment(modelData)})} detail: {Text("Select a Landmark")}}
}

添加编辑模式

用户需要能够在浏览模式和编辑模式之间进行切换来查看或者修改用户简介的信息。通过在ProfileHost上添加一个Edit Button,然后创建一个用来编辑简介信息的页面。
请添加图片描述

1、选择ProfileHost并将模型数据作为环境属性添加到预览中。尽管这个视图没有使用带有@Environment属性包装器的属性,但是这个视图的子视图ProfileSummary使用了。因此,如果没有这个修改器,预览将失败。

2、添加一个环境视图属性,该属性与环境的.editmode无关。SwiftUI在环境中可以使用@Environment属性包装器对访问的值提供存储。前面使用@Environment来检索存储在环境中的类。在这里,使用它来访问内置于环境中的editMode值,以读取或写入编辑作用域。

3、创建一个Edit按钮,用于打开和关闭环境的editMode值。EditButton控制在上一步中访问的editMode环境值。

struct ProfileHost: View {@Environment(\.editMode) var editMode@State var draftProfile = Profile.defaultvar body: some View {VStack(alignment: .leading, spacing: 20) {ProfileSummary(profile: draftProfile)}.padding()}
}#Preview {ProfileHost().environment(ModelData())
}

4、更新UserData类,包含一个Profile实例,即使用户简介页面消失后也可以存储编辑后的信息:

class ModelData {var landmarks: [Landmark] = load("landmarkData.json")var hikes: [Hike] = load("hikeData.json")var profile = Profile.defaultvar categories: [ String: [Landmark] ] {Dictionary(grouping: landmarks) { $0.category.rawValue }}var features: [Landmark] {landmarks.filter { $0.isFeatured }}
}

5、从环境变量中读取用户简介信息,并把数据传递给ProfileHost视图的控件上进行展示。为了在编辑状态下修改简介信息后确认修改前避免更新全局状态(例如在编辑用户名的过程中),编辑视图在一个备份属性中进行相应的修改操作,确认修改后,才把备份属性同步到全局应用状态中。

6、添加一个条件视图,可以用来显示静态用户简介视图或者是编辑模式的用户简介视图。当前的编辑模式只支持静态文本框的编辑

struct ProfileHost: View {@Environment(\.editMode) var editMode@Environment(ModelData.self) var modelData@State var draftProfile = Profile.defaultvar body: some View {VStack {HStack {Spacer()EditButton()}if editMode?.wrappedValue == .inactive {ProfileSummary(profile: modelData.profile)} else {Text("Profile Editor")}}.padding()}
}

定义简介编辑

用户简介编辑器包含几个单独的控件用来修改对应简介信息。在简介中,一些项例如徽章是不可以编辑修改的,所以它们不会出现在简介编辑器中。为了保持简介在编辑模式和浏览模式的一致性,需要按照简介页面各项相同的顺序进行添加。
请添加图片描述

1、创建一个名为ProfileEditor的新视图,并绑定用户简介中的草稿。视图中的第一个控件是TextField,用来更新用户名字段值。创建TextField时要提供一个标签和一个绑定字符串。

2、添加一个切换开关,用来设置用户是否接收相关地标事件的推送通知。这个Toggle控件打开和关闭正好对应着布尔值的true或false。

3、把一个Picker和一个Text放在VStack结构里,让这个地标可以选择不同季节。

4、最后,在季节图片选择器下方添加一个DatePicker,用来修改地标的目标浏览日期

struct ProfileEditor: View {@Binding var profile: Profilevar dateRange: ClosedRange<Date> {let min = Calendar.current.date(byAdding: .year, value: -1, to: profile.goalDate)!let max = Calendar.current.date(byAdding: .year, value: 1, to: profile.goalDate)!return min...max}var body: some View {List {HStack {Text("Username")Spacer()TextField("Username", text: $profile.username).foregroundStyle(.secondary).multilineTextAlignment(.trailing)}Toggle(isOn: $profile.prefersNotifications, label: {Text("Enable Notifications")})Picker("Seasonal Photo", selection: $profile.seasonalPhoto) {ForEach(Profile.Season.allCases) { season inText(season.rawValue).tag(season)}}DatePicker(selection: $profile.goalDate, in: dateRange, displayedComponents: .date) {Text("Goal Date")}}}
}#Preview {ProfileEditor(profile: .constant(.default))
}

更新ProfileHost中的条件内容,让它包含条件编辑器并把简单的绑定关系传递给简介编辑器。现在当你点击Edit按钮,简介视图就会变成编辑模式了。:

struct ProfileHost: View {@Environment(\.editMode) var editMode@Environment(ModelData.self) var modelData@State var draftProfile = Profile.defaultvar body: some View {VStack {HStack {Spacer()EditButton()}if editMode?.wrappedValue == .inactive {ProfileSummary(profile: modelData.profile)} else {ProfileEditor(profile: $draftProfile)}}.padding()}
}

延迟编辑传播

编辑模式时,使用用户简介信息的备份进行修改,当用户确认进行修改后,再用修改的备份信息覆盖真正的用户信息。直到用户退出编辑模式前都不让编辑的备份生效。
请添加图片描述

1、在ProfileHost视图上添加一个取消按钮。不像编辑模式按钮提供的完成按钮,取消按钮不会应用修改后的简介备份信息到实际的简介数据上。

2、将正确的简介数据使用onAppear(perform:)和onDisappear(perform:)填充到编辑器,当用户点击完成按钮后持久更新用户简介数据。否则下一次进入编辑模式时,将使用上一次的用户简介数据来展示。

struct ProfileHost: View {@Environment(\.editMode) var editMode@Environment(ModelData.self) var modelData@State var draftProfile = Profile.defaultvar body: some View {VStack(alignment: .leading, spacing: 20) {HStack {if editMode?.wrappedValue == .active {Button("Cancel", role: .cancel) {draftProfile = modelData.profileeditMode?.animation().wrappedValue = .inactive}}Spacer()EditButton()}if editMode?.wrappedValue == .inactive {ProfileSummary(profile: modelData.profile)} else {ProfileEditor(profile: $draftProfile).onAppear(perform: {draftProfile = modelData.profile}).onDisappear(perform: {modelData.profile = draftProfile})}}.padding()}
}

http://www.ppmy.cn/ops/48329.html

相关文章

【微信小程序】页面导航

声明式导航 导航到 tabbar 页 tabBar页面指的是被配置为tabBar的页面。 在使用<navigator>组件跳转到指定的tabBar页面时&#xff0c;需要指定url属性和open-type属性&#xff0c;其中&#xff1a; url 表示要跳转的页面的地址&#xff0c;必须以/开头open-type表示跳…

Http File Server 2.0(HFS)使用说明

Http File Server 2.0&#xff08;HFS&#xff09;是一个专为个人用户设计的HTTP文件服务器软件。以下是关于Http File Server 2.0的清晰介绍&#xff0c;包括其特点、安装和使用方法&#xff1a; 1. 软件特点 专为个人用户设计&#xff1a;提供方便的档案传输系统&#xff0…

爬山算法介绍

爬山算法&#xff08;Hill Climbing Algorithm&#xff09;是一种常用于寻找问题最优解的启发式算法。以下是对爬山算法的详细介绍&#xff0c;包括其原理、步骤、种类、应用场景、优缺点及改进方法。 原理 爬山算法是一种基于贪婪策略的优化算法&#xff0c;其核心思想是从一…

人工智能在肿瘤细胞分类中的应用|顶刊速递·24-06-06

小罗碎碎念 推文主题——人工智能在肿瘤细胞分类中的应用。 重点关注 临床方向的同学/老师建议重点关注第四篇&第六篇文章&#xff0c;最近DNA甲基化和蛋白组学与AI的结合&#xff0c;在顶刊中出现的频率很高&#xff0c;建议思考一下能否和自己的课题结合。 工科的同学重…

Oracle数据库查询常用语句

Oracle数据库查询常用语句 文章目录 Oracle数据库查询常用语句一、时间查询1、查询当天得数据 二、 一、时间查询 1、查询当天得数据 1、字段名为PLAN_DAY&#xff0c;字段类型为DATE 使用SYSDATE函数来获取当前日期&#xff0c;并且使用比较运算符来过滤出当天的记录。Oracle…

轮到国产游戏统治Steam榜单

6月10日晚8点&#xff0c;《黑神话:悟空》实体版正式开启全款预售,预售开启不到5分钟,所有产品即宣告售罄。 Steam上&#xff0c;《黑神话:悟空》持续占据着热销榜榜首的位置。 但在《黑神话:悟空》傲人的光环下&#xff0c;还有一款国产游戏取得出色的成绩。 6月10日&#…

正向遍历和反向遍历

正向遍历 let nums [0,1,0,3,12] for (idx, num) in nums.enumerated() {print("\(idx) - \(num)") } // 0 - 0 // 1 - 1 // 2 - 0 // 3 - 3 // 4 - 12另一种方式 let nums [0,1,0,3,12] nums.enumerated().forEach { idx, integer in// print idx and int... }反…

SpringBootWeb 篇-深入了解 Redis 五种类型命令与如何在 Java 中操作 Redis

&#x1f525;博客主页&#xff1a; 【小扳_-CSDN博客】 ❤感谢大家点赞&#x1f44d;收藏⭐评论✍ 文章目录 1.0 Redis 概述 1.1 Redis 下载与安装 2.0 Redis 数据类型 3.0 Redis 常见五种类型的命令 3.1 字符串操作命令 3.2 哈希操作命令 3.3 列表操作命令 3.4 集合操作命令 …