SwiftUI学习笔记03 – 如何在SwiftUI中访问Window

1,623次阅读
没有评论

SwiftUI学习笔记03 – 如何在SwiftUI中访问Window

SwiftUI学习笔记 01我提过现阶段的SwiftUI,无法直接在View里直接访问所属的Window。如果开发的是一个iOS App还好,需要hack到Window的地方不多,但在Mac上跟Window交互就实在太普遍了。别的不说,仅仅是调用AppKit的很多接口都少不了Window参数,比如在Mac上打开/保存文件用到的NSOpenPannel/NSSavePannel,我们常常会把它挂在到当前Window上:

func beginSheetModal(for window: NSWindow)

SwiftUI学习笔记03 – 如何在SwiftUI中访问Window

这个接口接受一个window参数,可以展示一个好看的系统保存文件窗口,并挂载在当前App window上(图左)。如果我们拿不到window,那就只能调用runModal()方法,这样唤出的窗口跟我们App主窗口是分离的,不太优雅(图右)。尤其对于面向文档的App来说,这种体验很不苹果。

除了调用Cocoa的方法,有时候我们也需要根据Window Size进行部分UI Elements的调整,就像获取super view的frame一样自然。所以在SwiftUI中获取window势在必行。

本文涉及Sample Code请看这个👉🏻gist

一、放弃SwiftUI App入口,改用NSHostingController

利用SwiftUI提供的NSHostingController,我们可以走老一套AppKit的路,不用SwiftUI来打开View,而是先创建一个NSWindow,然后再把SwiftUI View通过NSHostingController放上去。

SwiftUI学习笔记03 – 如何在SwiftUI中访问Window

通过在MainWindowController这个级别持有MainViewModelwindow,我们就能很方便地实现两者的交互。非常“简单粗暴”,但有效。

但是这种方法只适用于非SwiftUI App入口创建的Window,比如展示一个Settings Window或者一个About Window。但是如果我需要拿到SwiftUI一开始创建的Root Window,采用这种方法就必须推倒重来,改用AppKit启动App。

这样一来,SwiftUI方便的commandGroup, shortcut, WindowGroup之类的新特性我们就享受不到了,有没有保留SwiftUI入口的方案呢?

二、参考GeometryReader实现一个WindowReader

当我们需要根据super view的frame进行sub view布局时,SwiftUI提供了GeometryReader这样的工具。

SwiftUI学习笔记03 – 如何在SwiftUI中访问Window

SwiftUI学习笔记03 – 如何在SwiftUI中访问Window

上述代码使得左边的Text占super view的33% width, 右边占67%。(Example来自这里)

如果我能实现一个WindowReader { window in … }是不是就无缝衔接,果味十足了🤔

我们来看看GeometryReader的声明:

SwiftUI学习笔记03 – 如何在SwiftUI中访问Window

关键在@ViewBuilder这个修饰符。

SwiftUI的View是一个protocol,我们熟悉的body是一个带有@ViewBuilder修饰的属性:

@ViewBuilder @MainActor var body: Self.Body { get }

所以要实现GeometryReader的效果,我们就需要新建一个类似的结构:

SwiftUI学习笔记03 – 如何在SwiftUI中访问Window

那么怎么获取当前View的Window呢?我们可以通过NSView实例的window属性来拿到。如果是nil说明这个NSView已经被移除,如果不为空则是它所在Window的实例。

上述WindowReader这个结构体是SwiftUI的View,为了能在SwiftUI View里访问NSView,我们需要使用NSViewRepresentable这个protocol。UIKit里也有类似的UIViewRepresentable协议,可以实现SwiftUI与AppKit/UIKit的混用。

首先我们创建一个NSView的Subclass,为的是通过这个NSView拿到当前的Window:

SwiftUI学习笔记03 – 如何在SwiftUI中访问Window

这样当该NSViewviewDidMoveToWindow()被调用时,我们就可以往windowViewModel里记录当前的Window。

然后我们创建一个WindowViewRepresentable,以便SwiftUI的View可以访问到这个WindowView:

SwiftUI学习笔记03 – 如何在SwiftUI中访问Window

最后,我们在WindowReaderbody里面,创建一下这个WindowViewRepresentable:

SwiftUI学习笔记03 – 如何在SwiftUI中访问Window

最终我们就可以像使用GeometryReader一样,在SwiftUI里使用WindowReader

SwiftUI学习笔记03 – 如何在SwiftUI中访问Window

这种解法学自aheze/Popovers这个项目,感兴趣的读者可以阅读源码以及这个issue,以及本文相关的gist: SwiftUI Notes 03

三、通过Introspect曲线救国

直接在SwiftUI的布局代码中获取window我们通过WindowReader实现了,但我还有些方法是通过ViewModel或者Button的Action Block实现的,虽然通过WindowReader我也可以给每个需要用到Window的View全部无脑嵌套一层,但是有没有其他方法呢?

比如我能否通过View Modifier来实现呢?

第一篇笔记里我们介绍过这个SwiftUI-Introspect项目,它通过给SwiftUI的View里注入(inject)一个NSView/UIView然后再通过AppKit/UIKit的方法向上寻找对应平台的实现,从而获取List背后的NSTableView/UITableView这样的功能。

所以只要我们的View里用到了Introspect framework支持的View我们就能直接拿到它,然后再获取它的Window属性,比如:

SwiftUI学习笔记03 – 如何在SwiftUI中访问Window

如果View用到了ScrollView我们就能这样把window拿到并赋值给viewModel。Instrospect的原理是在updateNSView()被调用时回调这个block,所以如果这个View经常刷新它就会频繁回调,viewModel要记得去重后再update。

四、有没有更通用一点的解法?

Instrospect的做法当然不保险,只要苹果升级系统修改实现直接就报废。但我们可以学习它的通过扩展View来实现类似的效果。

跟 #2 类似,我们同样需要一个NSView作为基础,通过它来获取window:

SwiftUI学习笔记03 – 如何在SwiftUI中访问Window

我们在viewDidMoveToWindow回调的时候,调用getWindow()block,把它当前的window回调给SwiftUI。

同样的,我们也需要把它用NSViewRepresentable包装一下给SwiftUI:

SwiftUI学习笔记03 – 如何在SwiftUI中访问Window

SwiftUI这边,我们这次不使用@ViewBuilder,而是扩展SwiftUI的View,给它添加实例方法:

SwiftUI学习笔记03 – 如何在SwiftUI中访问Window

这里我们的inject方法采用Introspect framework的,用overlay()覆盖一个frame为0的空白View,跟上面的background()做法异曲同工。最终效果如下:

SwiftUI学习笔记03 – 如何在SwiftUI中访问Window

直接通过View的getWindow() block即可获取当前View所在的Window,然后ViewModel就可以为所欲为啦!哈哈哈

五、What’s Next?

SwiftUI目前还做不到API 100%覆盖UIKit/AppKit,我想它的目标应该也不会如此。但是可以想见,SwiftUI的API未来会越来越丰富,而且也在每年迭代进化中。去年WWDC的NavigationSplitViewNavigationStack就是对此前NavigationView的改进。

一开始我接触SwiftUI,还是免不了要推倒方案,重回UIKit/AppKit的实现,但是如果咬咬牙,想一下是否能通过NSViewRepresentable来bridge两套UI框架,打通了之后真的成就感满满。既不需要放弃SwiftUI便利的新能力,又能用上原生平台框架更强大更丰富的自定义能力。

有了这个东西,其实已经可以绕过大部份SwiftUI目前还解决不了的问题了。

六、相关链接

The post SwiftUI学习笔记03 – 如何在SwiftUI中访问Window first appeared on 枫言枫语

Read More 

正文完
可以使用微信扫码关注公众号(ID:xzluomor)
post-qrcode
 0
评论(没有评论)

文心AIGC

2023 年 4 月
 12
3456789
10111213141516
17181920212223
24252627282930
文心AIGC
文心AIGC
人工智能ChatGPT,AIGC指利用人工智能技术来生成内容,其中包括文字、语音、代码、图像、视频、机器人动作等等。被认为是继PGC、UGC之后的新型内容创作方式。AIGC作为元宇宙的新方向,近几年迭代速度呈现指数级爆发,谷歌、Meta、百度等平台型巨头持续布局
文章搜索
热门文章
潞晨尤洋:日常办公没必要上私有模型,这三类企业才需要 | MEET2026

潞晨尤洋:日常办公没必要上私有模型,这三类企业才需要 | MEET2026

潞晨尤洋:日常办公没必要上私有模型,这三类企业才需要 | MEET2026 Jay 2025-12-22 09...
面向「空天具身智能」,北航团队提出星座规划新基准丨NeurIPS’25

面向「空天具身智能」,北航团队提出星座规划新基准丨NeurIPS’25

面向「空天具身智能」,北航团队提出星座规划新基准丨NeurIPS’25 鹭羽 2025-12-13 22:37...
钉钉又发新版本!把 AI 搬进每一次对话和会议

钉钉又发新版本!把 AI 搬进每一次对话和会议

钉钉又发新版本!把 AI 搬进每一次对话和会议 梦晨 2025-12-11 15:33:51 来源:量子位 A...
商汤Seko2.0重磅发布,合作短剧登顶抖音AI短剧榜No.1

商汤Seko2.0重磅发布,合作短剧登顶抖音AI短剧榜No.1

商汤Seko2.0重磅发布,合作短剧登顶抖音AI短剧榜No.1 十三 2025-12-15 14:13:14 ...
跳过“逐字生成”!蚂蚁集团赵俊博:扩散模型让我们能直接修改Token | MEET2026

跳过“逐字生成”!蚂蚁集团赵俊博:扩散模型让我们能直接修改Token | MEET2026

跳过“逐字生成”!蚂蚁集团赵俊博:扩散模型让我们能直接修改Token | MEET2026 一水 2025-1...
最新评论
ufabet ufabet มีเกมให้เลือกเล่นมากมาย: เกมเดิมพันหลากหลาย ครบทุกค่ายดัง
tornado crypto mixer tornado crypto mixer Discover the power of privacy with TornadoCash! Learn how this decentralized mixer ensures your transactions remain confidential.
ดูบอลสด ดูบอลสด Very well presented. Every quote was awesome and thanks for sharing the content. Keep sharing and keep motivating others.
ดูบอลสด ดูบอลสด Pretty! This has been a really wonderful post. Many thanks for providing these details.
ดูบอลสด ดูบอลสด Pretty! This has been a really wonderful post. Many thanks for providing these details.
ดูบอลสด ดูบอลสด Hi there to all, for the reason that I am genuinely keen of reading this website’s post to be updated on a regular basis. It carries pleasant stuff.
Obrazy Sztuka Nowoczesna Obrazy Sztuka Nowoczesna Thank you for this wonderful contribution to the topic. Your ability to explain complex ideas simply is admirable.
ufabet ufabet Hi there to all, for the reason that I am genuinely keen of reading this website’s post to be updated on a regular basis. It carries pleasant stuff.
ufabet ufabet You’re so awesome! I don’t believe I have read a single thing like that before. So great to find someone with some original thoughts on this topic. Really.. thank you for starting this up. This website is something that is needed on the internet, someone with a little originality!
ufabet ufabet Very well presented. Every quote was awesome and thanks for sharing the content. Keep sharing and keep motivating others.
热评文章
预见未来:96位前沿先锋超万字核心观点总结,抢抓未来产业新高地

预见未来:96位前沿先锋超万字核心观点总结,抢抓未来产业新高地

预见未来:96位前沿先锋超万字核心观点总结,抢抓未来产业新高地 henry 2025-12-11 10:27:...
Meta公开抄阿里Qwen作业,还闭源了…

Meta公开抄阿里Qwen作业,还闭源了…

Meta公开抄阿里Qwen作业,还闭源了… Jay 2025-12-11 11:48:25 来源:量子位 Ja...
MEET2026挤爆了,AI圈今年最该听的20+场演讲&对谈都在这

MEET2026挤爆了,AI圈今年最该听的20+场演讲&对谈都在这

MEET2026挤爆了,AI圈今年最该听的20+场演讲&对谈都在这 西风 2025-12-11 15:...
钉钉又发新版本!把 AI 搬进每一次对话和会议

钉钉又发新版本!把 AI 搬进每一次对话和会议

钉钉又发新版本!把 AI 搬进每一次对话和会议 梦晨 2025-12-11 15:33:51 来源:量子位 A...