阅读此篇之前,建议先阅读基础篇。双击打开文件SwiftUI版本的实现(基础篇)
本篇,我们要实现类似“Welcome to Xcode”这样的欢迎界面。当用户直接打开应用时,显示欢迎界面,而如果用户双击指定类型的文件,则不显示这个欢迎界面,而直接显示应用的主界面。
问题分析
首先想到的就是,开机直接显示欢迎界面,然后在其中处理文件的打开。测试显示,此路不通。
规则1: openURL的调用会自动选择接受该参数的WindowGroup
Window("WelcomeViewWindow", id: "WelcomeViewWindow") {
WelcomeView(showFileImporter: $showFileImporter)
}
.windowStyle(.hiddenTitleBar)
.windowResizability(.contentSize)
WindowGroup(for: URL.self) { $fileURL in
MainView(fileURL: $fileURL)
.environment(\.managedObjectContext, persistenceController.container.viewContext)
}
虽然两个View都支持openURL,但是之后第二组的openURL会被调用,因为传递来的URL参数,只有第二组支持。
同时,也出现了一个问题。就是虽然只有第二组的openURL会被调用,但是欢迎窗口总是打开。
规则2: 位于首位的WindowGroup总是被打开
这是因为规则2的存在。解决办法也很简单,将两个WindowsGroup掉个个即可。同时,欢迎界面的openURL也可以删掉了。因为根本不会调用到。
遇到苹果API的bug
这样,打开文件的问题就解决了。不过,我们最初希望的是用户直接打开应用的时候,显示欢迎界面,而现在显示的是空白的主界面。这不是我们想要的。
解决办法也很简单,我们在主界面的onAppear阶段,检测fileURL
,如果它是nil,就证明是直接打开的应用,我们就调用欢迎界面,然后通过dismiss关闭主界面。
.onAppear {
if fileURL == nil {
openWindow(id: "WelcomeViewWindow")
dismiss()
}
}
可问题是,实际测试时,主界面每次都不能成功关闭。通过阅读dismiss的文档,我发现,dismiss能关闭窗口的前提是,ScenePhase必须是.active的状态。经过检测发现,主界面当前的ScenePhase却是.background的状态。
DismissAction
这明显是苹果API出了bug,于是我和苹果提交了反馈。有bug,生活还得继续。既然dismiss关闭不了,我们可以直接关闭窗口。我的解决方案是绑定Window,然后直接关闭这个Window。
private func quit() {
if let window {
window.close()
} else {
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(100), execute: DispatchWorkItem(block: quit))
}
}
SwiftUIWindowBinder
解决openURL和onAppear调用顺序的问题
需要注意的事,SwiftUI中,二者是先onAppear,然后才调用openURL。所以,如果我们需要改变这个顺序,就需要将onAppear运行的代码,加入到DispatchQueue.main.async中,然后推后一个周期。