SwiftUI七使用UI控件

代码下载

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

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

展示用户简介

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

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

struct Profile {
    var username: String
    var prefersNotifications = true
    var seasonalPhoto = Season.winter
    var 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.default
    
    var body: some View {
        Text("Profile for: \(draftProfile.username)")
    }
}

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

struct ProfileSummary: View {
    var profile: Profile
    
    var 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.default
    
    var 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: String
    
    var 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 modelData
    var profile: Profile
    
    var 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 = false
    
    var 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 in
                    CategoryRow(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.default
    
    var 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.default
    
    var 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.default
    
    var 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: Profile
    var 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 in
                    Text(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.default
    
    var 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.default
    
    var body: some View {
        VStack(alignment: .leading, spacing: 20) {
            HStack {
                if editMode?.wrappedValue == .active {
                    Button("Cancel", role: .cancel) {
                        draftProfile = modelData.profile
                        editMode?.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()
    }
}

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

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

相关文章

PCB 蚀刻因子(Etch Factor)

The standard definition for Etch Factor is to specify it as the ratio of trace thickness / amount of over-etching. This gives the following formula: Etch Factor T/[0.5(W1-W2)] 例如&#xff0c;W1 5.2mil&#xff0c; W1 4.2mil&#xff0c; T 3.7mil&#…

四维轻云|支持多源数据融合、城市级实景三维模型展示

四维轻云是一款轻量化的地理空间数据管理云平台&#xff0c;具有项目管理、数据上传、场景搭建、发布分享、素材库等功能模块&#xff0c;支持多用户在线协作管理&#xff0c;实现了轻量化、便捷化的空间数据应用。 1、多源数据融合 平台支持管理、展示各类空间数据&#xff…

差动放大器

差动器的出现是为了解决直接耦合电路存在的零点漂移问题&#xff0c;另外&#xff0c;差动放大器还有灵活的输入&#xff0c;输出方式。 一&#xff0c;基本差动放大器 差动放大器在电路结构上具有对称性&#xff0c;三极管VT1&#xff0c;VT2同型号&#xff0c;R1R2,R3R4,R5…

【天池科普】1. 为啥人人都要学AI

大家好&#xff01;欢迎来到天池的AI科普系列课程&#xff0c;本期是第一期内容。在这个信息爆炸的时代&#xff0c;人工智能&#xff08;AI&#xff09;不仅是技术进步的标志&#xff0c;更是推动社会向前发展的强大引擎。无论你是AI领域的新手&#xff0c;还是有一定基础的学…

助力高考,一组彩色的文字

1、获取文本内容 首先&#xff0c;获取每个<div>元素的文本内容&#xff0c;并清空其内部HTML&#xff08;innerHTML ""&#xff09;。 2、创建<span>元素 然后&#xff0c;它遍历文本的每个字符&#xff0c;为每个字符创建一个新的<span>元素…

【Hudi】编译

目录 编译安装编译环境准备编译Hudi上传源码包修改pom文件新增repository加速依赖下载修改依赖的组件版本 修改源码兼容hadoop3手动安装Kafka依赖1&#xff09;下载jar包2&#xff09;install到maven本地仓库 解决spark模块依赖冲突 执行编译命令编译成功 编译安装 编译环境准…

用友U8 表单视图名查询方法

比如要获取【采购订单】表名和视图名 具体操作如下&#xff1a; 先打开写字板&#xff0c;然后进入U8的采购订单做单界面&#xff0c;按住键盘上的&#xff0c;CtrlshiftC&#xff0c;有的是CtrlC&#xff0c;点增加 然后CtrlV到写字板 key就是采购订单的值 打开SQL 输入语句…

AI绘画工具Ideogram测评:和Midjourney不分伯仲的AI图像工具之一

Ideogram 是一款令人印象深刻的人工智能图像工具&#xff0c;但尽管它于去年 8 月推出并具有不可思议的文本渲染能力&#xff0c;但它并没有引起其他一些更引人注目的 GenAI 服务的关注。 随着该公司推出其生成式人工智能模型 1.0 版本&#xff0c;这种情况即将发生改变&#…

Qt OPC UA初体验

介绍 OPC UA全称Open Platform Unified Architecture&#xff0c;开放平台统一架构&#xff0c;是工业自动化领域通用的数据交换协议&#xff0c;它有两套主要的通信机制&#xff1a;1.客户端-服务器通信&#xff1b;2.发布订阅。Qt对OPC UA通信标准也提供了支持&#xff0c;目…

【雷达原理】一维CFAR检测算法——对比分析与代码实现

目录 引言一、仿真实例1.1 均匀背景杂波1.2 杂波边缘1.3 多干扰目标杂波 二、MATLAB代码参考文献 引言 推荐博文【目标检测】雷达目标CFAR检测算法 上述文章介绍了四种典型CFAR检测算法的特点及性能对比。本文将利用MATLAB进行仿真&#xff0c;通过实例验证和对比这几种算法的…

❤ npm运行打包报错归纳

❤ 前端运行打包报错归纳 &#xff08;安装依赖&#xff09;Cannot read property ‘pickAlgorithm’ of null" npm uninstall //删除项目下的node_modules文件夹 npm cache clear --force //清除缓存后 npm install //重新安装 备用安装方式 npm install with --for…

互联网产品经理转型为AI产品经理,我的心得体会

前言 作为一个非AI技术出身的人工智能产品经理&#xff0c;在转行之前会面对很对自我怀疑。在做了4年人工智能产品经理之后&#xff0c;也做了点总结&#xff0c;主要介绍AI产品经理在设计过程中的一些要点&#xff0c;和互联网产品经理9相似的工作内容就没有刻意的去提&#…

PG sql调优案例学习

一&#xff0c;开发范式 1.不要轻易把字段嵌入到表达式 例&#xff1a;在sal列上有索引,但是条件语句中把sal列放在了表达式当中,导致索引被压抑,因为索引里面储存的是sal列的值,而不是sal加上100以后的值。 在条件中查询谁的工资1002000。这样写即使在sal上有索引也会走全表…

cf 欧几里得距离

说明&#xff1a;欧几里得距离本质就是两点间距离 distancesqrt( sum(ai-bi)2 ) Problem - F - Codeforces 代码

跨平台电商数据对比:淘宝与他者的较量

——比较分析淘宝和其他电商平台&#xff08;如京东、拼多多&#xff09;的数据&#xff0c;探索各自的优势和市场定位 在当今的电子商务领域&#xff0c;跨平台电商数据对比成为了企业制定策略和优化运营的重要工具。淘宝作为中国最大的电商平台之一&#xff0c;与京东、拼多…

嵌入式仪器模块:数据记录模块和自动化测试软件

• 32 位分辨率 • 250 KSPS 采样率 • 可以同时并且连续地记录两个通道的电压输入 • 实时上传原始数据至 PC 端 通道22 输入阻抗 电压22 kΩ10 MΩ电流0.2 Ω输入范围电压 250 mV 4.5 V电流1.5 A耦合DCDC带宽450 Hz385 HzADC 分辨率32 Bits24 Bits采样率10 kSPS250 kSPS测…

还在为复制粘贴烦恼吗?这5个工具帮你轻松搞定

在日常工作中&#xff0c;CtrlC和CtrlV无疑是我们使用最为频繁的快捷键组合。 复制粘贴&#xff0c;轻松快捷。 但是在使用中&#xff0c;也会有一点不便&#xff0c;那就是无法保存剪贴历史内容。 比如我说复制之后&#xff0c;我想要想要找回这一次复制之前的内容&#xf…

【APP逆向】央视频播放量增加,逆向全过程解密

✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。 🏆《博客》:Python全…

Vue03-HelloWord

一、Hello World 1-1、示例1 1、现有html容器&#xff1b; 2、再有vue实例。 new Vue({});中的{}是配置对象。配置对象是&#xff1a;key&#xff1a;value的格式。 el&#xff1a;element元素。id对应#&#xff0c;class对应. 把容器中变化的数据&#xff0c;交给Vue实例去保…

OpenCV学习(4.1) 改变颜色空间

1.目标 在本教程中&#xff0c;你将学习如何将图像从一个色彩空间转换到另一个&#xff0c;像BGR↔灰色&#xff0c;BGR↔HSV等除此之外&#xff0c;我们还将创建一个应用程序&#xff0c;以提取视频中的彩色对象你将学习以下功能&#xff1a;cv2.cvtColor&#xff0c;**cv2.i…