问题描述
我正在用 SpriteKit 编写一个逻辑益智游戏,该游戏可在 iPad 上玩,并且与时间赛跑,当应用程序进入后台时,我正在努力将拼图巧妙地隐藏起来。
问题是用户不应该双击主页按钮并在应用切换器中看到完整的拼图,因为这将允许他们在没有时钟运行的情况下完成它。
这是我想出的解决方案:
在单例 GameManager
中有一个 SKTexture()
变量,用于保存屏幕截图纹理,而在我的 AppDelegate
中,我有:
func applicationWillResignActive(_ application: UIApplication) {
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "resigning"),object: self)
let tempBackground = UIImageView(image: UIImage(cgImage: GameManager.shared.puzzleImage.cgImage()))
tempBackground.frame = CGRect(x: 0,y: 0,width: 1024,height: 768)
tempBackground.tag = 1000
self.window?.addSubview(tempBackground)
self.window?.bringSubviewToFront(tempBackground)
}
这会在即将退出焦点时发出通知,该通知由我的 GameScene
接收,然后创建屏幕截图并将其存储到我的 GameManager
中的变量中。然后将其作为 subview
添加到 AppDelegate
中。
class GameScene: SKScene {
override func didMove(to view: SKView) {
// Layout puzzle here
NotificationCenter.default.addobserver(self,selector: #selector(appToBackground),name: NSNotification.Name(rawValue: "resigning"),object: nil)
}
@objc private func appToBackground() {
// Save state of puzzle and hide it
GameManager.shared.puzzleImage = SKView().texture(from: self)!
}
}
这一切都有效。但并不出色。双击和添加 tempBackground
之间存在明显的时间延迟 - 应用切换器非常简短地显示拼图的细节,然后将图像更改为隐藏的拼图。
当应用重新获得焦点时,会在 AppDelegate
中调用以下内容:
func applicationDidBecomeActive(_ application: UIApplication) {
if let tempBackground = self.window?.viewWithTag(1000) {
tempBackground.removeFromSuperview()
}
}
但是当应用返回焦点时,会显示 tempBackground
,然后在再次显示隐藏的拼图之前,可以非常简短地了解拼图的所有细节。
我可能完全错误地解决了这个问题,但在阅读了互联网上的各种存档问题和文章后,这似乎是一条正确的道路。
我想知道的是:有什么办法可以让隐藏的拼图立即显示在应用切换器中,并避免在返回拼图时闪现拼图细节?
谢谢。
解决方法
添加子视图逻辑没有问题,但问题在于您在哪里触发它,根据 apple docs,您应该在 applicationDidEnterBackground
中添加子视图,并且应该删除您的子视图并准备您的应用显示在applicationWillEnterForeground
由于我在 Xcode 12 中创建了 Xcode 项目,我的项目有场景委托,这是我使用的代码,O/P 显示在下面的 gif 中
func sceneWillResignActive(_ scene: UIScene) {
guard let view = Bundle.main.loadNibNamed("HiddenView",owner: nil,options: [:])?[0] as? HiddenView else { return }
view.tag = 1000
view.frame = UIApplication.shared.windows[0].frame
UIApplication.shared.windows[0].addSubview(view)
UIApplication.shared.windows[0].bringSubviewToFront(view)
}
func sceneWillEnterForeground(_ scene: UIScene) {
if let tempBackground = UIApplication.shared.windows[0].viewWithTag(1000) {
tempBackground.removeFromSuperview()
}
}
编辑 1:
正如 OP 所提到的,他没有使用 Scene Delegate 我正在更新 AppDelegate
func applicationWillResignActive(_ application: UIApplication) {
guard let view = Bundle.main.loadNibNamed("HiddenView",options: [:])?[0] as? HiddenView else { return }
view.tag = 1000
view.frame = UIApplication.shared.windows[0].frame
UIApplication.shared.windows[0].addSubview(view)
UIApplication.shared.windows[0].bringSubviewToFront(view)
}
func applicationDidBecomeActive(_ application: UIApplication) {
if let tempBackground = UIApplication.shared.windows[0].viewWithTag(1000) {
tempBackground.removeFromSuperview()
}
}
,
我有一个解决方案,虽然感觉更像是一种变通方法,而不是一个合适的解决方案。
问题在于 iOS 在将应用程序发送到后台之前拍摄屏幕快照,并在应用程序返回前台时使用它。
可以使用预定义的视图(请参阅@Sandeep 的解决方案)或在隐藏拼图后创建屏幕的新快照(我最初尝试的解决方案)来替换此快照。
但是,在 applicationDidBecomeActive
中删除此虚拟对象意味着在更新屏幕之前会短暂显示 iOS 拍摄的原始快照。
有一个函数 UIApplication.shared.ignoreSnapshotOnNextApplicationLaunch()
可以输入到 applicationWillResignActive()
函数中,但它似乎并不是一直都在工作——只是大部分时间都在工作。事实上,在 Apple Developer 的论坛上,有一条评论说它是如何不一致的,并且报告了一个错误。但那是一年多前的事了,现在仍然不一致。
相反,在此函数中,不是直接将虚拟对象添加到 self.window?
,而是添加到 rootViewController
的视图中,使用
self.window?.rootViewController!.view.addSubview(dummyImage)
然后当应用程序回到前台时实际上不要删除它。而是使用 Notification
来提醒 GameScene
该应用程序回到前台。然后可以运行一个方法,将 UITapGestureRecognizer
添加到场景中。这反过来可以链接到淡出虚拟图像的方法,然后在完成后将其和 UITapGestureRecognizer
移除。
也许不是最优雅的解决方案,但它似乎有效。