0

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

UIManagedDocument 中 Core Data 对象的状态保存和恢复策略

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

对我来说最大的问题是用户可以在这种边缘状态下尝试与我的应用程序的视图进行交互,并且这样做通常会导致它崩溃,因为新视图设置为使用空的 CoreData 属性,这些属性需要在它们进行设置时进行设置.

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

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

最终,如果我可以判断何时恢复 viewController 是否是顶级 viewController,那么也许我可以推送一个模态加载微调器 viewController。不确定这是否是推送新 VC 的合适时间,但我想我可以遵从 ViewWillAppear 或其他一些小型计时器延迟回调。唯一的问题可能是您看到原始视图状态恢复然后更改为微调器.. 如果我可以使 segue 淡出微调器在这可能不会太刺耳。

有人对此有任何建议吗?这是其他一些应用程序在恢复并转到网络重新加载您的帖子以阅读时一直在做的事情,例如 Facebook。

谢谢你的时间

问候

吉姆

4

1 回答 1

1

您发现自己所处的情况似乎足以有理由重新考虑您为此做了什么。我正在使用我猜想类似的情况,因为我在单独的线程中加载所有核心数据对象,因此使用完成就像

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.")
于 2019-01-15T10:36:44.710 回答