Skip to content

SwiftUI 中 View 之间的通信

之前做前端开发,最近写用 SwiftUI 写 macOS App, SwiftUI是声明式UI 框架,借鉴了很多web 前端的技术,也实现了组件化,MVVM架构,reactive 数据响应,但是swift 毕竟没有 JavaScript 灵活,SwiftUI 的一些写法和web 端有区别,今天总结一些 web 端组件话长讨论的 视图组件之间的 通讯往问题。

视图/组件 之间的通讯,大致可以分为三类,父视图传递消息给子视图、子视图转递消息给父视图、多层级不同分支的视图之间消息传递

父传子视图传递消息

父视图传子视图,在SwiftUI中,就是子视图声明属性变量,父视图用的时候代上参数。这里可以理解为 Vue 组件的 props,但是使用方式有区别。

子视图传递消息给父视图

在 web框架中,Vue 子组件通过事件传递消息给父组件, react 通过Callback 函数完成子传父消息通信。SwiftUI中有三种方式,分别是 @Binding ,Closure 回调函数,共享observable 数据。

@Binding

binding 的属性,在子视图中改变,会修改父视图中的数据,在父视图中使用的时候要用 $+<变量名>。 这种方式不好的地方,父视图无法根据子视图的改变做出其他反应,存在缺陷,没有Vue 组件中的自定义事件传递消息好理解。

使用回调(Closure)

这种方式类似react 中的 Callback 函数,父视图可以在接收到子视图的后做其他操作。

效果:

共享 Observable 数据

通过共享的Observable 数据,不同视图之间的数据改变,都会影响到其他视图,这有点web 框架 状态管理工具 pinia 的味道。

效果如下:

跨视图级别的消息传

在多层级消息传递上,Vue 提供了 Inject/Provide, react 有 context ,SwiftUI 有环境变量。

使用 environment() 方法为全局注入全局对象,在任何层级的子视图中都能通过@Environment(AppData.self) 引入全局的对像, 在任何子视图中修改,其他用到该全局对象的视图视图也跟着变化。这种方式有点依赖注入的味道,和 Vue 提供了 Inject/Provide, react 有 context

几乎是一样的。

效果如下:

总结

从前端框架看 SwiftUI View,三种web 组件常用的通信方式,SwiftUI 也有对应的方法,但总的来说,没有web 框架 接口设计的好用,@binding只能用作修改父视图的参数值, 闭包写起来有点啰唆没有事件方便易于理解,这可能跟 swift 和JavaScript 的语言特性有关。另外 MacOS 14 和iOS 17 引入了 Observation来做简单的视图 状态管理,但复杂的场景中 依然需要Combine,导致用户有很多的选项,但容易混淆。