Как представить счетчик загрузки во время восстановления состояния, ожидая запросов CoreData / Network? - PullRequest
0 голосов
/ 15 января 2019

Я уже спрашивал о восстановлении состояния и CoreData 4 года назад здесь

Сохранение состояния и стратегии восстановления с объектами базовых данных в UIManagedDocument

В конце концов мое приложение выполняет то, что я описал, и состояние восстанавливает URIRepresentations для любых объектов CoreData, которые он хочет сохранить. Эти объекты могут быть разрешены только после загрузки CoreData (через UIManagedDocument и его документ, загруженный обратный вызов). Все работает нормально, хотя иногда при загрузке документа CoreData представления пусты.

Большая проблема для меня заключается в том, что пользователь может пытаться взаимодействовать с представлениями моего приложения во время этого состояния неопределенности, и при этом он часто может аварийно завершить работу, поскольку новые представления настраиваются с нулевыми свойствами CoreData, которые необходимо настроить. как они segue.

Мне нужно решение, чтобы это исправить, добавив настраиваемую блокировку кнопок и т. Д. К каждому представлению, хотя CoreData все еще не загружен, может управлять им, но это довольно много повторяющихся работ, и, насколько удобен пользовательский опыт, не самый лучший. Я представляю предупреждение при нажатии ввода, и мы все еще ожидаем загрузки CoreData.

Мое предпочтительное решение состоит в том, чтобы как-то переопределить восстановление ViewController и добавить новый верхний viewController в восстановленную иерархию, которая может показывать счетчик до тех пор, пока не загрузятся CoreData. Я не вижу примеров для этого или описания соответствующих методов для поддержки такой стратегии в документации.

В конечном счете, если я смогу сказать, когда восстанавливается viewController, является ли он top viewController, тогда, возможно, тогда я мог бы нажать на вращающийся счетчик модальной загрузки viewController. Не уверен, что это подходящее время, чтобы выдвинуть новый VC, хотя я думаю, что я мог бы отложить ViewWillAppear или некоторый другой небольшой обратный вызов с задержкой по таймеру. Единственная проблема, возможно, заключается в том, что вы видите, как исходное состояние просмотра восстанавливается, а затем изменяется на счетчик ... если я смогу добиться того, чтобы переход затухал на счетчике, это может быть не слишком неприятным.

Кто-нибудь получил какие-либо предложения по этому поводу? Это то, что некоторые другие приложения делают все время, например, Facebook, когда они восстанавливаются и идут в сеть, чтобы перезагрузить ваши сообщения для чтения.

Спасибо за ваше время

Привет

Jim

1 Ответ

0 голосов
/ 15 января 2019

Ситуация, в которой вы оказались, кажется достаточной причиной, чтобы пересмотреть то, что вы сделали, чтобы достичь этого. Я использую, я думаю, похожую ситуацию, когда я загружаю все основные объекты данных в отдельном потоке, поэтому используются дополнения, такие как

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.")
...