SwiftUI 借鉴了 React 等 UI 框架的概念,通过 state 的变化,对 View 进行响应式的渲染。主要通过 @State, @StateObject, @ObservedObject 和 @EnvironmentObject 等属性包装器 (property wrapper) 将属性包装成状态来实现。

@State 和 @StateObject

@State 和 @StateObject 是比较常用的属性包装器。

两者的区别是:

  • @State: 主要用于修饰值类型那种简单属性。
  • @StateObject: 和 @ObservedObject 一样,主要用于引用类型那种复杂属性。

举例说明。在一个 SwiftUI View 中声明属性:

@State var name: String

那么,每次 name 发生变化时,View 都会重新渲染。

但假如有一个类:

class Student {
var name: String = ""
}

当它的实例被用 @State 修饰时:

@State var student: Student = Student()

则 View 不会随着 student.name 的变化而变化,因为实例 student 本身并没有发生变化。为了让 View 随 student 的属性变化,就要用到 @StateObject 来修饰:

@StateObject var student: Student = Student()

同时还要给 Student 的属性添加 @Published 修饰:

class Student: ObservableObject {
@Published var name: String = Student()
}

如此,每次 student.name 发生变化时,View 就会随之重新渲染了。

@Binding 和 @ObjectBinding

有时候我们在子 View 中需要用到父 View 的属性,并且不仅仅单方面的显示,还要有双向的影响,即子 View 对属性的更改,能反应到父 View 上。

在以下情况下:

// Parent View
struct ForumView: View {
@State var username: String = "" var body: some View {
Text(username)
InputView(name: username)
}
} // Child View
struct InputView: View {
@State var name: String = "" var body: some View {
Text(name)
Button(action: {
name = "Tom"
}) {
Text("Change Name")
}
}
} // Preview: ForumView(username: "Jack")

父 View 虽然能把自己属性的值传给子 View,但是子 View 在改变其属性值时,仅能够改变它自身,而不能影响到父 View。若要影响到父 View,就需要用到 @Binding 了:

// Parent View
struct ForumView: View {
@State var username: String = "" var body: some View {
Text(username)
InputView(name: $username)
}
} // Child View
struct InputView: View {
@Binding var name: String var body: some View {
Text(name)
Button(action: {
name = "Tom"
}) {
Text("Change Name")
}
}
} // Preview: ForumView(username: "Jack")

此时,你通过子 View 改变的值 (name),就同时也能改变到父 View 的属性 (username) 了。

简而言之,@Binding 就是对其他属性的一种引用式的绑定。注意用法:它在声明时,不需要赋初始值,在用到时,要加前缀 $

@Binding 对应 @State,则 @ObjectBinding 便对应 @ObservedObject 和 @StateObject 了,毋庸赘言。

@ObservedObject 和 @StateObject

用 @ObservedObject 和 @StateObject 包装的属性都需要其对象类实现 ObservableObject 协议。本质上,他们都是用来让对象状态化的包装器。但在使用时,有一定区别。

简单地说,@ObservedObject 会在 View 每次被重新渲染时重新构造,它包装的 Model 是跟着 View 走的,而 @StateObject 则不会,它一旦被创建,就由 SwiftUI 接管,不会随着 View 的刷新渲染而重建。

为什么会这样,因为 View 作为 struct 是一个值类型的对象,他被销毁时,它内部的对象也会被销毁,而 @StateObject 等于是给 View 内部的对象加了一层保护,使其不受 View 生命周期的影响。

有时我们通过 NavigationView 来回切换页面,会发现 @StageObject 对象也被重置了,像是随着 View 刷新而重建一样,其实那是 SwiftUI 的行为。

比较而言,我觉得 @StateObject 更好,因为它和 View 解耦了,更方便控制。

@EnvironmentObject

@EnvironmentObject 有 @StateObject 那种脱离 View 生命周期的特性,但在使用上更为灵活。举例来说:

  • View A: 创建了 @StateObject var thing: Thing,包含 View B
  • View B: 包含 View C
  • View C: 需要用到 View A 的 thing 对象。

一般来说,为了让 View C 用到 View A 的 thing,就需要从 View A 开始传递 thing 给 View B, 再由 View B 传给 View C 使用。这是不是太麻烦了,View B 凭空多了一个它用不到但却能访问的对象 thing。@EnvironmentObject 的存在就是为了解决这个问题。

在 View A 中:

var thing: Thing = Thing(tag: "e")
var body: some View {
NavigationView {
ViewB()
}.environmentObject(thing)
}

通过 .environmentObject(), thing 变成了环境对象。接下来我们在 View C 中就可以直接使用了:

@EnvironmentObject var thing: Thing
var body: some View {
// thing.tag: "e"
Text(thing.tag)
}

可以看到 View C 中 @EnvironmentObject var thing: Thing 不用初始化 thing,因为这个 thing 就是 ViewA 中的 thing。EnvironmentObject 就像把一个对象全局化了一样。

参考

本文主要参考了以下文章和视频:

SwiftUI 中一些和响应式状态有关的属性包装器的用途的更多相关文章

  1. SwiftUI 简明教程之属性包装器

    本文为 Eul 样章,如果您喜欢,请移步 AppStore/Eul 查看更多内容. Eul 是一款 SwiftUI & Combine 教程 App(iOS.macOS),以文章(文字.图片. ...

  2. bootstrap中如何让响应式图片(img-responsive)水平居中

    我们在用bootstrap排版内容的时候,有的时候在内容中需要图片水平居中对齐. 一般情况下,我们的图片都使用了 .img-responsive 类来实现响应式图片.如果需要实现响应式图片水平居中,那 ...

  3. 如何在Hexo中实现自适应响应式相册功能

    用最清晰简洁的方法整合一个响应式相册 效果 技术选型 由于我选用的主题使用了fancyBox作为图片弹出展示的框架,查看后表示很不错,能满足需要 http://fancyapps.com/fancyb ...

  4. Angular11 模板表单、响应式表单(自定义验证器)、HTTP、表单元素双向绑定

    1 模板表单 模型通过指令隐式创建 技巧01:需要在模块级别引入 FormsModule ,通常在共享模块中引入再导出,然后在需要用到 FormsModule 的模块中导入共享模块就可以啦 impor ...

  5. Vue2.0源码阅读笔记(二):响应式原理

      Vue是数据驱动的框架,在修改数据时,视图会进行更新.数据响应式系统使得状态管理变的简单直接,在开发过程中减少与DOM元素的接触.而深入学习其中的原理十分有必要,能够回避一些常见的问题,使开发变的 ...

  6. Vue响应式原理及总结

    Vue 的响应式原理是核心是通过 ES5 的保护对象的 Object.defindeProperty 中的访问器属性中的 get 和 set 方法,data 中声明的属性都被添加了访问器属性,当读取 ...

  7. 响应式编程(Reactive Programming)(Rx)介绍

    很明显你是有兴趣学习这种被称作响应式编程的新技术才来看这篇文章的. 学习响应式编程是很困难的一个过程,特别是在缺乏优秀资料的前提下.刚开始学习时,我试过去找一些教程,并找到了为数不多的实用教程,但是它 ...

  8. 学习响应式编程 Reactor (1) - 响应式编程

    响应式编程 命令式编程(Imperative Programing),是一种描述计算机所需做出的行为的编程范式.详细的命令机器怎么(How)去处理以达到想要的结果(What). 声明式编程(Decla ...

  9. Unity基于响应式编程(Reactive programming)入门

    系列目录 [Unity3D基础]让物体动起来①--基于UGUI的鼠标点击移动 [Unity3D基础]让物体动起来②--UGUI鼠标点击逐帧移动 时光煮雨 Unity3D让物体动起来③—UGUI DoT ...

随机推荐

  1. Java中字符串替换方法

    replaceAll方法 public String replaceAll(String regex, String replacement) replace方法 public String repl ...

  2. 测开之数据类型· 第3篇《列表推导式、字典推导式、2种方式创建生成器》

    坚持原创输出,点击蓝字关注我吧 作者:清菡 博客:oschina.云+社区.知乎等各大平台都有. 目录 一.列表推导式 二.字典推导式 三.2种方式创建生成器 1.生成器表达式 2.函数里面,通过 y ...

  3. 什么是babel

    什么是babel babel是一款基于node开发的工具,其功能是对es6的新语法和新特性进行转码.

  4. C# 学习第七天

    P96 面向对象的概念 ①先有的 面向过程 --------> 然后才衍生出面向对象的思想 ②面向过程:面向的是完成这件事儿的过程,强调的是完成这件事儿的动作 比如说 把大象塞进冰箱去 ③面向过 ...

  5. yii2 设置的缓存无效,返回false,不存在

    为了那些因为标题点进来的小伙伴,我直接把问题解决方案写在开头: 问题描述, $cache->add($key,'value',1800);这样设置了值后,后面无论怎么取这个$key,取出来的结果 ...

  6. 实验题目:python面向对象程序设计

    1.定义并实现一个矩形类Rectangle,其私有实例成员为矩形的左下角与右上角两个点的坐标,能设置左下角和右上角两个点的位置,能根据左下角与右上角两个点的坐标计算矩形的长.宽.周长和面积,另外根据需 ...

  7. Python爬取热搜存入数据库并且还能定时发送邮件!!!

    一.前言 微博热搜榜每天都会更新一些新鲜事,但是自己处于各种原因,肯定不能时刻关注着微博,为了与时代接轨,接受最新资讯,就寻思着用Python写个定时爬取微博热搜的并且发送QQ邮件的程序,这样每天可以 ...

  8. Python利用xlutils统计excel表格数据

    假设有像上这样一个表格,里面装满了各式各样的数据,现在要利用模板对它进行统计每个销售商的一些数据的总和.模板如下: 代码开始: 1 #!usr/bin/python3 2 # -*-coding=ut ...

  9. FaaS,未来的后端服务开发之道

    说 FaaS 先要说说 PaaS 平台即服务(Platform as a Service)是一种云计算服务,提供运算平台与解决方案堆栈即服务.在云计算的典型层级中,平台即服务层介于软件即服务与基础设施 ...

  10. docker 使用笔记

    docker 使用笔记 1. 与宿主机之间拷贝文件 docker cp test.html 99f952ac05e6cd879f14aa6c9d0db02aaf498634edc4f6cdc9953c ...