SwiftUI 更自然地向自定义视图传递参数的“另类”方式
概览
在 SwiftUI 中,正是自定义视图让我们的 App 变得与众不同!然而,除了传统的视图接口定义方式以外,我们其实还可以有更“银杏化”的选择。
如上图所示:对于 SubView 子视图所需的参数我们一开始并没有操之过急,而是随后再以独立、灵活的方式将其传入到了 SubView 中,这是怎么做到的呢?
在本篇博文中,您将学到如下内容:
- 概览
- 1. 一个简单的视图需求!
- 2. “传统”的调用方式
- 3. 灵动的方式:按需且独立!
- 4. 再次验证 SwiftUI 视图状态的稳定性
- 总结
闲言少叙,Let‘s go!!!😉
1. 一个简单的视图需求!
我们需要创建一个子视图,它用来显示 Model 可观察对象的内容,同时包括一个界面是否展开的状态,并且可以自定义用户点击的行为:
@Observable
class Model {var name = "hopy"var power = 5
}struct SubView: View {let model = Model()@Binding var isExpanding: Boolvar tapHandler: (()->Void)?//...
}
从上面代码中可以清楚的看到:SubView 子视图包含一个 Model 可观察对象,并且还有 isExpanding 和 tapHandler 属性来分别表示自身展开的状态和用户点击时执行的代码。
我们可以这样实现 SubView 的 body:
var body: some View {VStack {Text(model.name).font(.largeTitle.weight(.bold))if isExpanding {Divider()HStack {Text("POW: \(model.power)").foregroundStyle(.red).font(.headline.weight(.heavy))Spacer()Button("Add POW!") {model.power += 1}.buttonStyle(.borderedProminent)}}}.onTapGesture {tapHandler?()}.padding().background(Color.black.opacity(0.2), in: RoundedRectangle(cornerRadius: 15.0)).overlay {RoundedRectangle(cornerRadius: 15).stroke(.black, lineWidth: 5.0)}.shadow(radius: 5)
}
2. “传统”的调用方式
现在已经定义好了 SubView 视图,我们可以这样在主视图中创建并使用它:
@State var isExpanding = falseSubView(isExpanding: $isExpanding) {print("OK!")
}
SubView 的运行界面如下图所示:
如上代码,我们在创建 SubView 子视图时,就需要将它所有必要的传入参数都考虑周全。
当然,这样本身并没有什么不妥。只不过,假若视图包含海量传入参数可能会出现一些不“银杏化”的地方:
- 在视图创建时就需要考虑到它所有的传入参数,即使有些可以暂时“忽略不计”;
- 在视图创建时就需要绞尽脑汁让这一坨冗长的传入参数在代码缩进和排版上看起来不那么“毛骨悚然”;
- 无法清晰的隔离视图自身创建和其状态创建的不同逻辑;
那么,除了视图“传统”的接口设计方式之外,我们是否还有其它的解决方案呢?
答案是肯定的!
3. 灵动的方式:按需且独立!
回忆一下 SwiftUI 中视图的本质:它其实只是状态的函数,它本身很“廉价”,更重要的是它是一个值对象。
这意味着,我们可以随时创建它们的拷贝,并改变拷贝所包含的属性,然后再用修改后的拷贝替换原有的视图。
首先,我们将 SubView 定义修改为如下形式:
struct SubView: View {let model = Model()private var isExpanding = falseprivate var tapHandler: (()->Void)?
}
这样做的好处是:在 SubView 创建时无需传入任何参数,我们完全将 SubView 自身和其状态分开了。
注意在上面代码中我们用 private 关键字修饰了它的各个属性,那么我们必须找到随后改变它们的方法,这该如何是好呢?
因为私有属性只能在类型内部读写,但类型扩展显然属于“内部”这一范畴,所以我们可以在 SubView 的扩展中大展拳脚:
extension SubView {func isExpanding(_ expanding: Bool) -> Self {var view = selfview.isExpanding = expandingreturn view}func tapHandler(_ handler: @escaping ()->()) -> Self {var view = selfview.tapHandler = handlerreturn view}
}
可以看到:在上面 SubView 视图的扩展方法中我们像讨论过的那样显式拷贝了 SubView 对象的实例,然后更改它的属性,最后返回了更改后的视图。
现在,我们可以这样创建 SubView 视图了:
struct ContentView: View {@State var isExpanding = falsevar body: some View {NavigationStack {VStack {SubView().isExpanding(isExpanding).tapHandler {withAnimation(.snappy) {isExpanding.toggle()}}Button("Expanding!") {withAnimation(.bouncy) {isExpanding.toggle()}}.padding(.top, 100)}.padding()}}
}
于是乎,我们可以“赤裸裸的” 让 SubView 先诞生,然后根据需要再以视图扩展的方式为其“注入”必要的参数。这样我们就可以有的放矢的将重点放在视图的某些属性上,创建逻辑会更加清晰明了。
4. 再次验证 SwiftUI 视图状态的稳定性
如果小伙伴们观察的足够仔细就会发现,上述代码每次子视图的展开属性(isExpanding)发生改变时,其 Model 的 power 值就会被重置:
这是因为,每次 isExpanding 属性改变时 SubView 自身的重建也会导致其 Model 对象的重建。
在 Swift 5.9 新 @Observable 对象在 SwiftUI 使用中的陷阱与解决 这篇博文中,我们进行过 SwiftUI 视图 @Observable 对象稳定性的讨论。我们得出的一个重要结论是:如果想要 @Observable 对象保持稳定,必须将它用状态来承载!
在本案例中为了达到这一目的,我们可以有两种方法:
- 在主视图中将 Model 实例传递到 SubView 中;
- 或者在 SubView 中用 @State 修饰 Model 属性;
这里,我们采用第二种方法,将 SubView 中的 model 对象用 @State 属性包装器修饰:
struct SubView: View {@State var model = Model()//...
}
最后运行看一下结果:
看到了吗?现在无论 SubView 自身如何变化,我们的 Model 状态都不会“始乱终弃”,它的内容始终保持一致!棒棒哒!💯
总结
在本篇博文中,我们讨论了 SwiftUI “传统”的视图接口定义在具有海量传入参数时的一些不便之处,并且用更加“低耦合”的“环保”方法改善了这一情况。相信现在小伙伴们对于 SwiftUI 中视图的构建会有更写意、更灵活的方式啦!
感谢观赏,再会!😎
相关文章:

SwiftUI 更自然地向自定义视图传递参数的“另类”方式
概览 在 SwiftUI 中,正是自定义视图让我们的 App 变得与众不同!然而,除了传统的视图接口定义方式以外,我们其实还可以有更“银杏化”的选择。 如上图所示:对于 SubView 子视图所需的参数我们一开始并没有操之过急&…...

Word第一课
文章目录 1. 文件格式1.1 如何显示文件扩展名1.2 Word文档格式的演变1.3 常见的Word文档格式 3. 文档属性理解文档属性查看文档属性 1. 文件格式 1.1 如何显示文件扩展名 文档格式指的是文件的扩展名,例如下图 对于该文件,.docx就是文件扩展名&#x…...

【Vue3】路由传参的几种方式
路由导航有两种方式,分别是:声明式导航 和 编程式导航 参数分为query参数和params参数两种 声明式导航 query参数 一、路径字符串拼接(不推荐) 1.传参 在路由路径后直接拼接?参数名:参数值 ,多组参数间使用&分隔。 <RouterLink …...
突破编程_C++_面试(高级特性(1))
面试题1:什么是线程以及它在并发编程中的作用是什么 线程( Thread )是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进…...

django请求生命周期流程图,路由匹配,路由有名无名反向解析,路由分发,名称空间
django请求生命周期流程图 浏览器发起请求。 先经过网关接口,Django自带的是wsgiref,请求来的时候解析封装,响应走的时候打包处理,这个wsgiref模块本身能够支持的并发量很少,最多1000左右,上线之后会换成u…...
@ 代码随想录算法训练营第8周(C语言)|Day54(动态规划)
代码随想录算法训练营第8周(C语言)|Day54(动态规划) Day53、动态规划(包含题目 ● 123.买卖股票的最佳时机III ● 188.买卖股票的最佳时机IV ) 123.买卖股票的最佳时机III 题目描述 给定一个数组&#…...
Flask 学习100-Flask-SocketIO 结合 xterm.js 实现网页版Xshell
前言 xterm.js 是一个使用 TypeScript 编写的前端终端组件,可以直接在浏览器中实现一个命令行终端应用。 可以实现 web-terminal 功能,类似于Xshell 操作服务器。 Flask-SocketIO 快速入门与使用基础参考前面这篇https://www.cnblogs.com/yoyoketang/p/18022139 前后端交互…...

Springboot AOP开发
Springboot AOP开发 一 AOP概述 AOP,即面向切面编程,简言之,面向方法编程。 针对方法,在方法的执行前或执行后使用,用于增强方法,或拓展。 二 AOP开发 1.引入 spring-boot-starter-aop 在SpringBoot项…...

office的excel中使用,告诉我详细的解决方案,如何变成转化为金额格式
在Office的Excel中,如果你想将名为"MEREFIELD"的公式结果转换为金额格式,你可以遵循以下详细步骤来实现: 书写MEREFIELD公式: 首先,在Excel中输入或确认你的MEREFIELD公式。例如,假设这个公式是用…...

灾后重建中GIS技术的关键作用与案例分析
地质灾害是指全球地壳自然地质演化过程中,由于地球内动力、外动力或者人为地质动力作用下导致的自然地质和人类的自然灾害突发事件。由于降水、地震等自然作用下,地质灾害在世界范围内频繁发生。我国除滑坡灾害外,还包括崩塌、泥石流、地面沉…...

java环境安装
java环境安装 一、官网下载: jdk,下载jdk,解压到D:\JAVA\Java\jdk目录下。 二、配置: 配置环境变量 鼠标右键我的电脑->属性->高级系统设置->环境变量->系统变量新建变量名JAVA_HOME,变量值为刚才解压的…...

如何在iStoreOS软路由系统中安装cpolar实现公网远程本地电脑桌面
文章目录 简介一、配置远程桌面公网地址二、家中使用永久固定地址 访问公司电脑**具体操作方法是:** 简介 软路由是PC的硬件加上路由系统来实现路由器的功能,也可以说是使用软件达成路由功能的路由器。 使用软路由控制局域网内计算机的好处:…...

appium实现自动化测试原理
目录 1、Appium原理 1.1、Android Appium原理图文解析 1.1.2、原理详解 1.1.2.1、脚本端 1.1.2.2、appium-server 1.1.2.3、中间件bootstrap.jar 1.1.2.4、驱动引擎uiautomator 1.2、 IOS Appium原理 1、Appium原理 1.1、Android Appium原理图文解析 执行测试脚本全过…...

Linux:docker搭建redis集群(3主3从扩容缩容 哈希槽分配)
操作系统:centos7 docker-ce版本:24.0.7 1.准备redis镜像 我这里使用redis 6.0.8 镜像进行操作,如果你也需要镜像,在网络正常情况下直接使用 docker pull redis:6.0.8 即可进行下载,如果你没配置国内加速器&#x…...

Linux程序性能分析60秒+
Linux性能分析大师Brendan Gregg有一篇非常著名的博客,介绍在性能分析开始的60秒内,利用标准的Linux命令行工具,执行一次充分的性能检查,获得系统资源利用率和进程运行情况的整体概念,查看是否存在异常、评估饱和度。本…...

mmap映射文件使用示例
mmap 零拷贝技术可以应用于很多场景,其中一个典型的应用场景是网络文件传输。 假设我们需要将一个大文件传输到远程服务器上。在传统的方式下,我们可能需要将文件内容读入内存,然后再将数据从内存复制到网络协议栈中,最终发送到远…...
Linux命令:stat命令
目录 1 简介2 说明3 实例-L:显示链接指向的文件的信息-f:显示文件系统信息-t:以简洁的形式输出 1 简介 stat命令:显示文件或文件系统的状态 2 说明 使用:stat [OPTION]… FILE 常用选项: -L, --derefer…...
学会自幂数
学会自幂数 题目描述: 写⼀个代码打印1~100000之间的所有的自幂数,中间用空格分隔。 解法思路: 自幂数是又称自客单位数,是指一个整数各个位的立方和等于该整数本身的数。例如,153是自幂数,因为 1^35 ^…...

支付宝支付
文章目录 支付宝介绍接入条件支付宝开发支付流程关于回调 支付测试第三方库的sdk接口加密的两种方式第三方支付宝sdk支付宝支付封装 支付宝介绍 -https://open.alipay.com/develop/manage 扫码登录 -网站支付:https://opendocs.alipay.com/open/270/105899 扫码登录…...
qt中读写锁与互斥锁的区别
在Qt中,读写锁(QReadWriteLock)和互斥锁(QMutex)都是用于多线程编程时控制共享资源访问的工具,但它们在实现上有一些重要的区别。 QMutex(互斥锁): QMutex是最基本的锁…...

【大模型RAG】拍照搜题技术架构速览:三层管道、两级检索、兜底大模型
摘要 拍照搜题系统采用“三层管道(多模态 OCR → 语义检索 → 答案渲染)、两级检索(倒排 BM25 向量 HNSW)并以大语言模型兜底”的整体框架: 多模态 OCR 层 将题目图片经过超分、去噪、倾斜校正后,分别用…...

springboot 百货中心供应链管理系统小程序
一、前言 随着我国经济迅速发展,人们对手机的需求越来越大,各种手机软件也都在被广泛应用,但是对于手机进行数据信息管理,对于手机的各种软件也是备受用户的喜爱,百货中心供应链管理系统被用户普遍使用,为方…...
Java - Mysql数据类型对应
Mysql数据类型java数据类型备注整型INT/INTEGERint / java.lang.Integer–BIGINTlong/java.lang.Long–––浮点型FLOATfloat/java.lang.FloatDOUBLEdouble/java.lang.Double–DECIMAL/NUMERICjava.math.BigDecimal字符串型CHARjava.lang.String固定长度字符串VARCHARjava.lang…...

(二)原型模式
原型的功能是将一个已经存在的对象作为源目标,其余对象都是通过这个源目标创建。发挥复制的作用就是原型模式的核心思想。 一、源型模式的定义 原型模式是指第二次创建对象可以通过复制已经存在的原型对象来实现,忽略对象创建过程中的其它细节。 📌 核心特点: 避免重复初…...

均衡后的SNRSINR
本文主要摘自参考文献中的前两篇,相关文献中经常会出现MIMO检测后的SINR不过一直没有找到相关数学推到过程,其中文献[1]中给出了相关原理在此仅做记录。 1. 系统模型 复信道模型 n t n_t nt 根发送天线, n r n_r nr 根接收天线的 MIMO 系…...

iview框架主题色的应用
1.下载 less要使用3.0.0以下的版本 npm install less2.7.3 npm install less-loader4.0.52./src/config/theme.js文件 module.exports {yellow: {theme-color: #FDCE04},blue: {theme-color: #547CE7} }在sass中使用theme配置的颜色主题,无需引入,直接可…...

Golang——6、指针和结构体
指针和结构体 1、指针1.1、指针地址和指针类型1.2、指针取值1.3、new和make 2、结构体2.1、type关键字的使用2.2、结构体的定义和初始化2.3、结构体方法和接收者2.4、给任意类型添加方法2.5、结构体的匿名字段2.6、嵌套结构体2.7、嵌套匿名结构体2.8、结构体的继承 3、结构体与…...

软件工程 期末复习
瀑布模型:计划 螺旋模型:风险低 原型模型: 用户反馈 喷泉模型:代码复用 高内聚 低耦合:模块内部功能紧密 模块之间依赖程度小 高内聚:指的是一个模块内部的功能应该紧密相关。换句话说,一个模块应当只实现单一的功能…...

Linux基础开发工具——vim工具
文章目录 vim工具什么是vimvim的多模式和使用vim的基础模式vim的三种基础模式三种模式的初步了解 常用模式的详细讲解插入模式命令模式模式转化光标的移动文本的编辑 底行模式替换模式视图模式总结 使用vim的小技巧vim的配置(了解) vim工具 本文章仍然是继续讲解Linux系统下的…...
拟合问题处理
在机器学习中,核心任务通常围绕模型训练和性能提升展开,但你提到的 “优化训练数据解决过拟合” 和 “提升泛化性能解决欠拟合” 需要结合更准确的概念进行梳理。以下是对机器学习核心任务的系统复习和修正: 一、机器学习的核心任务框架 机…...