SwiftUI里的ForEach使用的注意事项

news/2024/9/21 8:43:01/

在用Swift编程语言的SwiftUI包设计苹果设备的程序时,经常会用到ForEach函数。这个函数的作用是将一个数据集里面的内容一条一条地取出,罗列在程序的页面上,使用方式的详解见[1]。

但ForEach和一般的循环不同之处在于它要求输入里面的数据集里元素必须是Identifiable的,否则不可使用[2]。所谓Identifiable,就是说输入ForEach里的数据集里的每一个元素必须有一个唯一确定的,不会重复的id号,所以通过该id号,就可找到唯一确定的与之对应的元素,因此若要修改或删除元素,不会删错或在修改时涉及无关的元素。

一、可输入ForEach的数据

本文以一个简单的例子,说明什么样的数据可以输入到ForEach中。在该例子中,输入的数据集合里的每个元素都是一个字母,这些元素被一一加入到List里,形成一个列表如图显示。

下面说明几种将数据输入ForEach的方法:

(一)将字符串列表直接输入ForEach

@State var alphaList = ["a", "b", "c"]
var body: some View {List{Section(header: Text("Invaild list")){ForEach(alphaList){alphaEle inText(alphaEle)}} //This is not legal. ForEach should be inputed as a range, or an identifiable. normal list is not ok}
}

这一段代码在Swift中将无法运行,因为Swift里的列表里的元素只是字符串,而这些元素未指定id号,所以Swift里无法根据元素的任何信息唯一确定该元素。

所以,不能直接将字符串的列表输入ForEach。

(二)将字符串元素的字符串本身设为其在列表中的id

@State var alphaList = ["a", "b", "c"]
var body: some View {List{Section(header: Text("Normal list")){ForEach(alphaList, id: \.self){alphaEle in//alphaList itself is not identifiable, so need to define id. Here the id is just the element title. This is not good because the id can repeatText(alphaEle)}}}
}

这段代码可以正常运行。因为在ForEach函数里,虽然输入的数据集未能提供每个元素的id,但在ForEach函数的id参数里,对这个信息进行了补充,使用\.self这个Key Path表明每个元素在这个数据集的id号就是这个字符串本身。关于Key Path的概念,见[3]。另外,博文[4]中讲解了Key Path如何使用。

这个方法虽然可行,但并不建议,因为不同元素的字符串本身一旦出现重复,Swift就无法唯一确定每一个id对应的元素了。

(三)直接用区间作为索引号数据集,然后根据索引号提取元素

@State var alphaList2 = ["a", "b", "c"]
var body: some View {List{Section(header: Text("Normal list")){ForEach(0..<alphaList2.count){idx inText(alphaList2[idx])//This is not good. ForEach, if using a integer range, the range should be constant.}}}Button(action: {alphaList2.append("ff")}, label: {Text("Add for extract idx")})//This button will fail. 
}

这段代码可以正常运行,因为Swift里的ForEach函数支持区间输入。但这样的输入,要求区间必须固定不变。如果在该程序运行时,alphaList2是一个固定不变的列表,那么这样使用ForEach函数是可以的。但如果在程序运行中,需要添加或删除元素,则不应使用区间输入。

在以上代码中,界面上除了定义一个ForEach的List外,还定义了一个按钮,按下后就会在列表中添加元素。但这样的编程方式,按钮按下后,屏幕上也不会有任何变化,因为ForEach函数如果输入的是区间,则不支持变动的区间。

从动画中可看出,无论如何点击按钮"Add for extract idx",列表里的内容都没有变化。

(四)用区间作为索引号数据集,但添加索引号作为id

@State var alphaList3 = ["a", "b", "c"]
var body: some View {List{Section(header: Text("Extract Idx with id")){ForEach(0..<alphaList3.count, id: \.self){idx inText(alphaList3[idx])//this is good, because although integer range is used, an id is specified so that the whole input together can be an identifiable}}}Button(action: {alphaList3.append("ff")}, label: {Text("Add for extract idx with id")})       
}

这段代码可以正常运行,而且列表添加可以正常进行,因为输入ForEach的区间里的每一个元素已经被赋予了id。

从动画中可看出,点击按钮"Add for extract idx with id"后,列表会被添加。

(五)创建一个Identifiable的类,让元素使用这个类

class alpha: Identifiable{public var letter:Stringinit(_ l:String){letter = l}
}@State var alphaList4 = [alpha("a"), alpha("b"), alpha("c")]var body: some View {List{Section(header: Text("identifiable letter")){ForEach(alphaList4){alphaEle inText(alphaEle.letter)//this is good, because alphaList4 is identifiable}}}Button(action: {alphaList4.append(alpha("ff"))}, label: {Text("Add for identifiable objects")})
}

在这段代码中,alphaList4里面的每一个元素都是Identifiable的alpha类元素,所以alphaList4可以直接输入ForEach函数。该代码可以正常运行,且列表添加功能可正常使用。

(六)仍然使用方法(一)但把String类型延伸一个id

Swift中可以对一个已有类型添加一个extension,从而扩充它的属性[5]。这里对String进行扩充。

extension String: Identifiable{public var id: String {UUID().description}//public var id: String{self} //This kind of id is not suggested
}

这样一来,方法(一)就不再报错了。

二、整个程序及总结

import SwiftUIclass alpha: Identifiable{public var letter:Stringinit(_ l:String){letter = l}
}
extension String: Identifiable{public var id: String {UUID().description}//public var id: String{self} //This kind of id is not suggested
}struct ListLab: View {@State var alphaList = ["a", "b", "c"]@State var alphaList2 = ["a", "b", "c"]@State var alphaList3 = ["a", "b", "c"]@State var alphaList4 = [alpha("a"), alpha("b"), alpha("c")]@State var alphaList5 = ["a", "b", "c"]var body: some View {List{//Section(header: Text("Invaild list")){//    ForEach(alphaList){alphaEle in//        Text(alphaEle)//    }//} //This is not legal. ForEach should be inputed as a range, or an identifiable. normal list is not okSection(header: Text("Normal list")){ForEach(alphaList, id: \.self){alphaEle in//alphaList itself is not identifiable, so need to define id. Here the id is just the element title. This is not good because the id can repeatText(alphaEle)}}Section(header: Text("Extract Idx")){ForEach(0..<alphaList2.count){idx inText(alphaList2[idx])//This is not good. ForEach, if using a integer range, the range should be constant.}}Section(header: Text("Extract Idx with id")){ForEach(0..<alphaList3.count, id: \.self){idx inText(alphaList3[idx])//this is good, because although integer range is used, an id is specified so that the whole input together can be an identifiable}}Section(header: Text("identifiable letter")){ForEach(alphaList4){alphaEle inText(alphaEle.letter)//this is good, because alphaList4 is identifiable}}Section(header: Text("identifiable letter with UUID")){ForEach(alphaList5){alphaEle inText(alphaEle)}}}Button(action: {alphaList.append("ff")}, label: {Text("Add for normal list")})Button(action: {alphaList2.append("ff")}, label: {Text("Add for extract idx")})//This button will fail. Button(action: {alphaList3.append("ff")}, label: {Text("Add for extract idx with id")})Button(action: {alphaList4.append(alpha("ff"))}, label: {Text("Add for identifiable objects")})Button(action: {alphaList5.append("ff")}, label: {Text("Add for identifiable objects with uuid")})}
}struct ListLab_Previews: PreviewProvider {static var previews: some View {ListLab()}
}

总之,在SwiftUI中,输入ForEach的数据集里的元素必须Identifiable,即有独一无二的id属性。如果数据本身没有这样的属性,则需要通过函数的id参数自定义属性。

参考资料

[1]ForEach | Apple Developer Documentation

[2]SwiftUI 基础篇之 ForEach

[3]Documentation (Key path)

[4]Swift 中强大的 Key Paths(键路径)机制趣谈(上)_swift keypath-CSDN博客

[5]Swift - 基础之extension - 简书


http://www.ppmy.cn/news/1528325.html

相关文章

Active Directory 实验室设置第一部分- AD林安装

在之前的文章中&#xff0c;已经讨论了活动目录的基本知识。在这篇文章中&#xff0c;我们将讨论如何设置和配置环境&#xff0c;以便我们可以使用它来执行各种攻击方案和检测。我们将讨论如何通过GUI和CLI方式完成。 # 1、Active Directory 设置 让我们从活动目录实验室设置…

mongodb 主从集群,分片集群

1. 2.分片集群&#xff1a; 2.1压缩包搭建集群 https://www.cnblogs.com/hahaha111122222/p/13969911.html 2.2docker 安装集群 MongodB分区分片搭建(docker)_docker 分层 mongodb-CSDN博客 docker搭建mongo分片集群_docker mongo集群-CSDN博客 Docker 安装 MongoDB_docker…

[产品管理-29]:NPDP新产品开发 - 27 - 战略 - 分层战略与示例

目录 1. 公司战略 2. 经营战略 3. 创新战略 4. 新产品组合战略 5. 新产品开发战略 战略分层是企业规划和管理的重要组成部分&#xff0c;它涉及不同层级的战略制定和实施。以下是根据您的要求&#xff0c;对公司战略、经营战略、创新战略、新产品组合战略、新产品开发战略…

ego-planner开源代码之启动参数介绍分析

ego-planner开源代码之启动参数介绍&分析 1. 源由2. 逻辑分析3. 启动参数section 1 三维地图尺寸section 2 里程计话题映射section 3 advanced_param.xml配置section 3.1section 3.2section 3.3section 3.4section 3.5section 3.6section 3.7section 3.8 section 4 轨迹服务…

高质量的翻译:应用程序可用性和成功的关键

在日益全球化的应用市场中&#xff0c;开发一款优秀的产品只是成功的一半。另一半&#xff1f;确保你的用户&#xff0c;无论他们在哪里或说什么语言&#xff0c;都能无缝理解和使用它。这就是高质量翻译的用武之地——不是事后的想法&#xff0c;而是应用程序可用性和最终成功…

如何在微信小程序中实现WebSocket连接

微信小程序作为一种全新的应用形态&#xff0c;凭借其便捷性、易用性受到了广大用户的喜爱。在实际开发过程中&#xff0c;实时通信功能是很多小程序必备的需求。WebSocket作为一种在单个TCP连接上进行全双工通信的协议&#xff0c;能够实现客户端与服务器之间的实时通信。本文…

redis分布式锁(看门枸机制)

分布式锁确保在同一时间只有一个节点能获得对共享资源的独占访问权限&#xff0c;从而解决并发访问问题。 Redisson锁(简称看门狗) 它可以实现锁的延长&#xff0c;确保某个线程执行完才能让其他线程进行抢锁操作 引入看门狗机制后 如何使用&#xff1f; 1、引入依赖包 <…

稳了,搭建Docker国内源图文教程

大家好&#xff0c;之前分享了我的开源作品 Cloudflare Workers Proxy&#xff0c;它的作用是代理被屏蔽的地址&#xff0c;理论上支持代理任何被屏蔽的域名&#xff0c;使用方式也很简单&#xff0c;只需要设置环境变量 PROXY_HOSTNAME 为被屏蔽的域名&#xff0c;最后通过你的…