SwiftUI七使用UI控件

2024-06-12 07:04
文章标签 使用 ui 控件 swiftui

本文主要是介绍SwiftUI七使用UI控件,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

代码下载

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

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

展示用户简介

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

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()}
}

这篇关于SwiftUI七使用UI控件的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1053510

相关文章

Python虚拟环境终极(含PyCharm的使用教程)

《Python虚拟环境终极(含PyCharm的使用教程)》:本文主要介绍Python虚拟环境终极(含PyCharm的使用教程),具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,... 目录一、为什么需要虚拟环境?二、虚拟环境创建方式对比三、命令行创建虚拟环境(venv)3.1 基础命令3

Python Transformer 库安装配置及使用方法

《PythonTransformer库安装配置及使用方法》HuggingFaceTransformers是自然语言处理(NLP)领域最流行的开源库之一,支持基于Transformer架构的预训练模... 目录python 中的 Transformer 库及使用方法一、库的概述二、安装与配置三、基础使用:Pi

关于pandas的read_csv方法使用解读

《关于pandas的read_csv方法使用解读》:本文主要介绍关于pandas的read_csv方法使用,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录pandas的read_csv方法解读read_csv中的参数基本参数通用解析参数空值处理相关参数时间处理相关

使用Node.js制作图片上传服务的详细教程

《使用Node.js制作图片上传服务的详细教程》在现代Web应用开发中,图片上传是一项常见且重要的功能,借助Node.js强大的生态系统,我们可以轻松搭建高效的图片上传服务,本文将深入探讨如何使用No... 目录准备工作搭建 Express 服务器配置 multer 进行图片上传处理图片上传请求完整代码示例

SpringBoot条件注解核心作用与使用场景详解

《SpringBoot条件注解核心作用与使用场景详解》SpringBoot的条件注解为开发者提供了强大的动态配置能力,理解其原理和适用场景是构建灵活、可扩展应用的关键,本文将系统梳理所有常用的条件注... 目录引言一、条件注解的核心机制二、SpringBoot内置条件注解详解1、@ConditionalOn

Python中使用正则表达式精准匹配IP地址的案例

《Python中使用正则表达式精准匹配IP地址的案例》Python的正则表达式(re模块)是完成这个任务的利器,但你知道怎么写才能准确匹配各种合法的IP地址吗,今天我们就来详细探讨这个问题,感兴趣的朋... 目录为什么需要IP正则表达式?IP地址的基本结构基础正则表达式写法精确匹配0-255的数字验证IP地

使用Python实现全能手机虚拟键盘的示例代码

《使用Python实现全能手机虚拟键盘的示例代码》在数字化办公时代,你是否遇到过这样的场景:会议室投影电脑突然键盘失灵、躺在沙发上想远程控制书房电脑、或者需要给长辈远程协助操作?今天我要分享的Pyth... 目录一、项目概述:不止于键盘的远程控制方案1.1 创新价值1.2 技术栈全景二、需求实现步骤一、需求

Spring LDAP目录服务的使用示例

《SpringLDAP目录服务的使用示例》本文主要介绍了SpringLDAP目录服务的使用示例... 目录引言一、Spring LDAP基础二、LdapTemplate详解三、LDAP对象映射四、基本LDAP操作4.1 查询操作4.2 添加操作4.3 修改操作4.4 删除操作五、认证与授权六、高级特性与最佳

Qt spdlog日志模块的使用详解

《Qtspdlog日志模块的使用详解》在Qt应用程序开发中,良好的日志系统至关重要,本文将介绍如何使用spdlog1.5.0创建满足以下要求的日志系统,感兴趣的朋友一起看看吧... 目录版本摘要例子logmanager.cpp文件main.cpp文件版本spdlog版本:1.5.0采用1.5.0版本主要

Java中使用Hutool进行AES加密解密的方法举例

《Java中使用Hutool进行AES加密解密的方法举例》AES是一种对称加密,所谓对称加密就是加密与解密使用的秘钥是一个,下面:本文主要介绍Java中使用Hutool进行AES加密解密的相关资料... 目录前言一、Hutool简介与引入1.1 Hutool简介1.2 引入Hutool二、AES加密解密基础