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 子视图时,就需要将它所有必要的传入参数都考虑周全。

当然,这样本身并没有什么不妥。只不过,假若视图包含海量传入参数可能会出现一些不“银杏化”的地方:

  1. 在视图创建时就需要考虑到它所有的传入参数,即使有些可以暂时“忽略不计”;
  2. 在视图创建时就需要绞尽脑汁让这一坨冗长的传入参数在代码缩进和排版上看起来不那么“毛骨悚然”;
  3. 无法清晰的隔离视图自身创建和其状态创建的不同逻辑;

那么,除了视图“传统”的接口设计方式之外,我们是否还有其它的解决方案呢?

答案是肯定的!

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 对象保持稳定,必须将它用状态来承载!

在本案例中为了达到这一目的,我们可以有两种方法:

  1. 在主视图中将 Model 实例传递到 SubView 中;
  2. 或者在 SubView 中用 @State 修饰 Model 属性;

这里,我们采用第二种方法,将 SubView 中的 model 对象用 @State 属性包装器修饰:

struct SubView: View {@State var model = Model()//...
}

最后运行看一下结果:

在这里插入图片描述

看到了吗?现在无论 SubView 自身如何变化,我们的 Model 状态都不会“始乱终弃”,它的内容始终保持一致!棒棒哒!💯

总结

在本篇博文中,我们讨论了 SwiftUI “传统”的视图接口定义在具有海量传入参数时的一些不便之处,并且用更加“低耦合”的“环保”方法改善了这一情况。相信现在小伙伴们对于 SwiftUI 中视图的构建会有更写意、更灵活的方式啦!

感谢观赏,再会!😎

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

网络入山太困难?看格行随身WiFi如何助力大山教育!

近日,一则关于偏远大山的上网问题冲上了热搜,引发了社会关注。虽然很多山区都已经通了电、通了网,但是在一些贫困的地区,网络基础设施依旧薄弱,村民想要使用固定宽带,仍然十分困难。 而在山区的学生们&…

【漏洞复现】H3C Web网管登录任意文件读取漏洞

Nx01 产品简介 H3C设备的Web网管是指H3C公司生产的网络设备的网络管理界面。它可以通过Web浏览器进行访问和操作,以实现对网络设备的配置、管理和监控。 Nx02 漏洞描述 jquery旧版本存在任意文件读取漏洞,允许攻击者在受害者的服务器上读取任意文件。H3…

SQL数据库基础语法-查询语句

SQL数据库基础语法-查询语句 Group By #对数据进行分组 >select name,count(id) from student group by name; #查询name字段人数,cont函数进行计数 >select * from users group by users; >select * from users where id1 group by 2; >select * from …

Excel练习:双层图表

Excel练习:双层图表 学习视频Excel制作双层图表,很多人都不会,其实只需1步操作就够了!_哔哩哔哩_bilibili ​​ 通过调整两个图形的显示范围实现 增加折现图的负数显示范围,使折现图仅出现在整体图形的上方增加柱形…

MQ最终一致性理论与实践

MQ最终一致性理论与实践 原理 分布式事务无论是2PC&3PC还是TCC,基本都遵守XA协议的思想,但全局事务方案并发性较差; 最终一致性方案指的是将最有可能出错的业务以本地事务的方式完成后,采用不断重试的方式(不限…

视频接入协议之MIPI

MIPI(Mobile Industry Processor Interface)是一种用于移动设备的串行接口标准,旨在提供高速、低功耗、低成本的接口解决方案。MIPI联盟是一个全球性的组织,致力于开发、推广和管理MIPI标准。 MIPI接口包括了多种协议和规范&…

K8S临时小结

k8s是什么?能解决什么问题? k8s是容器管理平台,一套复杂的开源系统 如何更好的维护pod,k8s第二大要素(pod控制器) k8s的很多对容器(pod)管理的高级特性,都是基于控制器…

4.【架构师成长之路】职场新人:如何快速变得专业(上)

文章目录 导言一、快速变得熟练1、研发类工具2、运维类工具3、泛文档类工具 二、能够系统化思考1、提升思考全面性2、提升内容逻辑性 三、最佳实践本文总结说明 导言 前三篇文章我们讲了在校期间及临近毕业时,你需要做一些怎样的准备。而这些准备本身不仅仅是为了毕…

harmony 鸿蒙系统学习 安装ohpm报错 ohpm install failed

一. 安装配置 DevEco Studio 安装包时报错 execute ohpm install failed. Install task failed: ArkTS 3.2.12.5. Install ArkTS dependencies failed. 解决办法 找原因,首先,我的电脑中之前安装过node,也许是因为这个。(其实…

Git 使用教程

一、Git的认识 1.1版本控制 什么是“版本控制”?我为什么要关心它呢? 版本控制是一种记录一个或若干文件内容变化,以便将来查阅特定版本修订情况的系统。 a) 还原:如果你是程序开发者,在新写一个促销活动的java文…

Linux常见基本指令

本文将详细的介绍Linux中各常见指令的用法,并且在每个指令都有使用样例。一共有以下指令: 1. man指令 2.目录基础指令:2.1 pwd指令、2.2 ls指令、2.3 cd指令 3.文件创建与删除:3.1 touch指令、3.2 mkdir指令、3.3 rmdir 指令 &…

Rabbitmq入门与应用(二)-RabbitMQ工作模型

RabbitMQ工作模型 RabbitMQ Tutorials — RabbitMQ Broker RabbitMQ服务。 Connection 生产者或是服务者都需要与Broker建立的TCP连接。 Channel 保持的TCP长连接里面去创建和释放Channel,从而减少资源的消耗。其中Channel是相互隔离的,不能共享。 Queu…

【ansible】自动化运维ansible之playbook剧本编写与运行

目录 一、ansible剧本playbook的组成 二、palybook的基础应用: 实操1:通过palybooks完成nginx的安装 第一种:通过yum安装nginx 第二种:通过编译安装nginx 实操2:playbook定义、引用变量​​​​​​​ 实操3:通过…

C#泛型及其应用:获取并显示员工信信息

目录 一、关于泛型 1.泛型定义 2.泛型与非泛型的区别 3.泛型的应用 (1)泛型类: (2)泛型方法: (3)泛型委托: (4)泛型接口: &a…

五种多目标优化算法(MOGWO、MOJS、NSWOA、MOPSO、MOAHA)性能对比(提供MATLAB代码)

一、5种多目标优化算法简介 1.1MOGWO 1.2MOJS 1.3NSWOA 1.4MOPSO 1.5MOAHA 二、5种多目标优化算法性能对比 为了测试5种算法的性能将其求解9个多目标测试函数(zdt1、zdt2 、zdt3、 zdt4、 zdt6 、Schaffer、 Kursawe 、Viennet2、 Viennet3)&#xff0…

leetcode1049:最后一块石头的重量二

解题思路: 把石头堆分割成差不多的两堆,使得两堆差值最小 dp数组的含义: dp[j]:背包容量为j的背包最大重量(价值)为dp[j] dp[j] max(dp[j],dp[j-stones[i]] stones[i]) 初始化:(dp数组的大小根据题目进行定义&a…

网络安全--网鼎杯2018漏洞复现(二次注入)

一、环境:在线测试平台 BUUCTF在线评测 (buuoj.cn) 二、进入界面先尝试万能账号 1or11# 换格式 hais1bux1 11or11# 三、万能的不行那我们就得想注册了,去register.php去看看 注册个账号 发现用户名回显,猜测考点为用户名处二次注入&…

Java 那些诗一般的 数据类型 (1)

本篇会加入个人的所谓‘鱼式疯言’ ❤️❤️❤️鱼式疯言:❤️❤️❤️此疯言非彼疯言 而是理解过并总结出来通俗易懂的大白话, 小编会尽可能的在每个概念后插入鱼式疯言,帮助大家理解的. 🤭🤭🤭可能说的不是那么严谨.但小编初心是能让更多人…

【JavaScript 语法】

JavaScript 语法 ■ JavaScript 是什么■ JavaScript 语法■ JS 注释■ JS 结束符■ JS 输入输出语句■ JS 代码块■ JS 关键词■ JS 值■ JS 字面量 (混合值)■ JS 变量(变量值)■ JS 文本值 (字符串)■ JS 字符串可以是对象 ■ …

JS文本加密方法探究

在前端开发中,有时候我们需要对敏感文本进行简单的加密,以提高安全性。本文将介绍一种基于 JavaScript 实现的文本加密方法,使用了 Base64、Unicode 和 ROT13 编码。 示例代码 function encodeText(text) {// Base64编码var base64Encoded …