0

SwiftUI学习笔记 01

 1 year ago
source link: https://justinyan.me/post/5629
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
webp

距苹果在2019年WWDC发布SwiftUI已经过去将近4年时间,去年这页Keynote可谓经典永流传:

webp

其实过去十几年,iOS的UIKit一直是苹果发力的方向,反观macOS使用的AppKit,已经多年不做任何更新了。前些年的更新也只是多了一些“安全”限制,带来一两个小features而已。

所以SwiftUI的出现其实对macOS开发者来说无疑是一种福利。当然,苹果的发布会一般都会说在Xcode上点一个按钮,剩下的就magically happend,但苹果的开发者都知道这个magic遇到现实到底有多么难实现。

所以现在使用SwiftUI开发Mac App是什么样的体验呢?我最近在研究这个东西,顺便写出来权当笔记,分享一下。

1. 苹果官方Binary的SwiftUI渗透率

根据 @timac 的Blog分析,iOS 16中官方SwiftUI的使用率相比去年又有了很大的提升:

webp

有226个使用了SwiftUI的Binaries,像Air Drop, FaceTime, Health, Podcasts等App都使用了SwiftUI开发。

macOS这边,AppKit依然是大头,但SwiftUI和Catalyst的占比提升明显:

Mac上的Home, News, Stocks 和 Voice Memos 这些App是用的Catalyst做跨平台开发,同时Reminder, Photos, Notes之类的App也逐渐开始采用SwiftUI混合开发了。

苹果官方数年来的实践也帮助SwiftUI框架取得不小的进步,所以现阶段采用SwiftUI进行官方原生控件的App开发是没有任何问题的。

2. Mac App的main入口

相比大家熟悉的applicationDidFinishLaunching delegate入口,Mac App有更多选择。我们既可以通过Main Menu这个xib文件指定入口,也可以作为命令行启动。

现在我们也可以使用New Project的模板创建一个SwiftUI App for macOS,模板自动创建的入口代码大致如下:

其中App Delegate的成员需要我们自己手动创建,这里不再赘述。

通过SwiftUI创建入口的好处是,我们可以享受SwiftUI Modifiers带来的全部好处,包括窗口管理,Menu Commands的语法糖,Shortcuts语法糖等等。

缺点也是显而易见的:现阶段的SwiftUI,无法直接在View里直接访问所属的Window

大部份时候这并不是问题,但如果你希望在View里操控Window,比如修改大小并展示动画,那么由上述方案创建的View就无法实现了。

如果是通过AppKit创建的其他NSWindow,我们可以通过NSHostingView的接口创建一个SwiftUI View,然后再想办法把这个Window传给它,或者View通过回调来操作Window。但 @main 入口自动创建的第一个Window我们是无能为力的。

所以这种情况下我们还是只能退化到采用Main Menu启动App,然后把 @main 交给App Delegate,再创建一个可控的NSWindowController,然后把第一个View用NSHostingView的方式塞进去来实现对Window的操控。

3. 如何在SwiftUI的View里找到它对应的AppKit/UIKit实现?

现阶段SwiftUI的控件在iOS会被转成UIKit实现,macOS上则是AppKit。比如List,在iOS是UITableView,macOS是NSTableView。

而SwiftUI为了隐藏复杂性,暴露的接口和属性其实是它们的子集。当我们对UI有比较强的个性化设计需求时,我们不得不想办法获取它的平台实现然后进行操作。

比如我希望在我的Mac App里,List 不显示背景颜色,但是SwiftUI并没有提供这样的接口,那就需要曲线救国了。

SwiftUI-Introspect这个项目,通过给View结构注入一个IntrospectionView作为锚点,通过View的superview或ViewController的parent/children等接口来寻找符合条件的View,非常聪明的做法。

“注入”采用的是SwiftUI的 overlay 接口,并把注入用的IntrospectionView设置为size 0,这样目标View就是自己的前一个兄弟节点(previous sibling node)。注入的IntrospectionView是一个UIView/NSView,遵循UIViewRepresentable/NSViewRepresentable,所以只要我们拿到这个IntrospectionView对象,就能调用对应的superview等接口,再进行类型判断,找到对应的View。

比如我们想针对一个 List 找到它对应的 NSTableView,那么代码可以这么写:

不过正如作者所说,这种做法可能会随着SwiftUI新版本的发布而失效。

这跟我们以前通过Method Swizzling等方式深入修改AppKit/UIKit的私有类所需要承担的风险是一样的。

4. The best way to build an app?

如果这个App采用iOS/macOS原生控件,没有太多定制化需求,那么SwiftUI使用起来无比丝滑,甚至可以跟设计师坐在一起慢慢调UI细节,的确是效率神器。

不过一旦这个App进入到细节打磨阶段,那么隐藏了接口复杂性的SwiftUI将是一大阻碍,需要开发者经验积累与SwiftUI Framework进化的共同努力。我在开发SwiftUI App时不止一次地推翻原有的方案,重新采用AppKit的实现。当然,这也是我学习的必经之路,所以接下来我也想持续将我踩过的坑写下来,一方面给自己记录回顾,另一方面也许读者朋友看了可以少走一点弯路。

那么本期到此为止,我们下期再见。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK