如何在等待CoreData/Network请求的状态恢复期间显示加载微调器



4年前我已经在这里询问过状态恢复和CoreData

UIManagedDocument 中核心数据对象的状态保存和恢复策略

最后,我的应用程序完成了我所描述的操作,并为它想要保存的任何CoreData对象恢复URI表示。这些对象只能在加载CoreData后(通过UIManagedDocument及其文档加载回调)进行解析。一切正常,尽管有时在加载CoreData文档期间视图是空的。

对我来说,最大的问题是,用户可以在这种不稳定状态下尝试与我的应用程序的视图进行交互,这样做往往会使它崩溃,因为新视图的设置带有空的CoreData属性,这些属性需要在设置时进行设置。

我需要一个解决方案来解决这个问题,在CoreData仍然没有加载的情况下,在每个视图上添加自定义的按钮阻塞等。这可以管理它,但需要大量的重复工作,就用户体验而言,这并不是最好的。当按下输入时,我会发出警报,我们仍在等待加载CoreData。

我的首选解决方案是以某种方式覆盖ViewController恢复,并将一个新的顶部ViewController注入到恢复的层次结构中,该层次结构可以显示一个微调器,直到加载CoreData为止。我在文档中没有看到任何这样的例子,也没有看到支持这种策略的适当方法的描述。

最终,如果我能在恢复viewController时判断它是否是顶部的viewController,那么也许我可以推送模式加载微调器viewController。不确定这是否是推送新VC的合适时机,尽管我想我可以推迟ViewWillAppear或其他一些小的延迟回调。唯一的问题可能是您看到原始视图状态恢复,然后更改为微调器。。如果我能让segue褪色,那么这个旋转器可能不会太刺耳。

有人对此有什么建议吗?这是其他一些应用程序(如Facebook)在恢复并进入网络重新加载你的帖子以进行阅读时一直在做的事情。

感谢您抽出时间

问候

Jim

你所处的处境似乎足以让你重新考虑你是如何做到这一点的。我正在使用我想类似的情况,因为我在单独的线程中加载所有核心数据对象,所以完成像一样使用

MyEntity.fetchAll { items,
self.entities = items
self.tableView.reloadData()
}

在这种情况下,很容易做到以下几点:

var entities: [Any]? {
didSet {
self.removeActivityIndicator()
}
}

您可以将所有逻辑放入视图控制器的某个基类中,这样您就可以轻松地重用它

尽管有时静态地做这些事情更好。您可以在所有具有活动指示器的内容上方添加一个新窗口。基本上喜欢自定义警报视图。保留计数系统应该工作得最好:

class ActivityManager {
private static var retainCount: Int = 0 {
didSet {
if(oldValue > 0 && newValue == 0) removeActivityWindow()
else if(oldValue == 0 && newValue > 0) showActivityWindow()
}
}
static func beginActivity() { retainCount += 1 }
static func endActivity() { retainCount -= 1 }
}

在这种情况下,您可以在代码中的任何位置使用该工具。规则是每个"开始"都必须有一个"结束"。例如:

func resolveData() {
ActivityManager.beginActivity()
doMagic {
ActivityManager.endActivity()
}
}

有很多方法可以做到这一点,可能没有"最佳解决方案",因为这取决于您的情况。

使用新窗口显示对话框的示例:

根据评论中的要求,我添加了一个关于如何在新窗口中显示对话框的示例。我正在使用一个新的故事板"对话框",其中包含一个视图控制器AlertViewController。这可能是一个带有一些活动指示器的控制器,但更重要的部分是如何生成窗口、如何显示控制器以及如何取消。

class AlertViewController: UIViewController {
@IBOutlet private var blurView: UIVisualEffectView?
@IBOutlet private var dialogPanel: UIView?
@IBOutlet private var titleLabel: UILabel? // Is in vertical stack view
@IBOutlet private var messageLabel: UILabel? // Is in vertical stack view
@IBOutlet private var okButton: UIButton? // Is in horizontal stack view
@IBOutlet private var cancelButton: UIButton? // Is in horizontal stack view
var titleText: String?
var messageText: String?
var confirmButtonText: String?
var cancelButtonText: String?
override func viewDidLoad() {
super.viewDidLoad()
setHiddenState(isHidden: true, animated: false) // Initialize as not visible
titleLabel?.text = titleText
titleLabel?.isHidden = !(titleText?.isEmpty == false)
messageLabel?.text = messageText
messageLabel?.isHidden = !(messageText?.isEmpty == false)
okButton?.setTitle(confirmButtonText, for: .normal)
okButton?.isHidden = !(confirmButtonText?.isEmpty == false)
cancelButton?.setTitle(cancelButtonText, for: .normal)
cancelButton?.isHidden = !(cancelButtonText?.isEmpty == false)
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
setHiddenState(isHidden: false, animated: true)
}
private func setHiddenState(isHidden: Bool, animated: Bool, completion: (() -> Void)? = nil) {
UIView.animate(withDuration: animated ? 0.3 : 0.0, animations: {
self.blurView?.effect = isHidden ? UIVisualEffect() : UIBlurEffect(style: .light)
self.dialogPanel?.alpha = isHidden ? 0.0 : 1.0
}) { _ in
completion?()
}
}
@IBAction private func okPressed() {
AlertViewController.dismissAlert()
}
@IBAction private func cancelPressed() {
AlertViewController.dismissAlert()
}

}
// MARK: - Window
extension AlertViewController {
private static var currentAlert: (window: UIWindow, controller: AlertViewController)?
static func showMessage(_ message: String) {
guard currentAlert == nil else {
print("An alert view is already shown. Dismiss this one to show another.")
return
}
let controller = UIStoryboard(name: "Dialog", bundle: nil).instantiateViewController(withIdentifier: "AlertViewController") as! AlertViewController
controller.confirmButtonText = "OK"
controller.messageText = message
let window = UIWindow(frame: UIApplication.shared.windows[0].frame)
window.windowLevel = .alert
window.rootViewController = controller
window.makeKeyAndVisible()
self.currentAlert = (window, controller)
}
static func dismissAlert() {
if let currentAlert = self.currentAlert {
currentAlert.controller.setHiddenState(isHidden: true, animated: true) {
self.currentAlert?.window.isHidden = true
self.currentAlert = nil
}
}
}
}

我添加了整个类以备不时之需,但重要的部分是显示一个新窗口:

let window = UIWindow(frame: UIApplication.shared.windows[0].frame) // Create a window
window.windowLevel = .alert // Define which level it should be in
window.rootViewController = controller // Give it a root view controller
window.makeKeyAndVisible() // Show the window

并删除窗口:

window.isHidden = true

只要把窗户藏起来就足够了。假设您没有任何对它的强引用,它将从应用程序堆栈中删除。为了确认这一点,请确保UIApplication.shared.windows.count具有适当的值,在大多数情况下,当显示警报时,该值应为2,否则为1

我对上面代码的测试用法很简单:

AlertViewController.showMessage("A test message. This is testing of alert view in a separate window.")

最新更新