Как представить счетчик загрузки во время восстановления состояния, ожидая запросов CoreData/Network?
Я уже спрашивал о состоянии восстановления и CoreData 4 года назад здесь
Стратегии сохранения и восстановления состояния с объектами Core Data в UIManagedDocument
В конце концов мое приложение делает то, что я описал, и состояние восстанавливает URIRepresentations для любых объектов CoreData, которые он хочет сохранить. Эти объекты могут быть разрешены только после загрузки CoreData (через UIManagedDocument и его документ, загруженный обратный вызов). Все работает нормально, хотя иногда при загрузке документа CoreData представления пусты.
Для меня большая проблема заключается в том, что пользователь может пытаться взаимодействовать с представлениями моего приложения во время этого состояния неопределенности, и при этом он часто может аварийно завершить его работу, поскольку новые представления настраиваются с нулевыми свойствами CoreData, которые необходимо настроить во время работы.,
Мне нужно решение, чтобы исправить это, добавив настраиваемую блокировку кнопок и т. Д. В каждое представление, хотя CoreData все еще не загружен, может с этим справиться, но это довольно много повторяющихся работ, и с точки зрения пользовательского опыта это не самое лучшее. Я представляю предупреждение при нажатии ввода, и мы все еще ожидаем загрузки CoreData.
Моим предпочтительным решением было бы как-то переопределить восстановление ViewController и внедрить новый верхний viewController в восстановленную иерархию, которая может показывать счетчик до тех пор, пока не загрузится CoreData. Я не вижу примеров для этого или описания соответствующих методов для поддержки такой стратегии в документации.
В конечном счете, если я смогу сказать, когда восстанавливается viewController, является ли он top viewController, то, возможно, тогда я мог бы выдвинуть спиннер модальной загрузки viewController. Не уверен, что это подходящее время, чтобы выдвинуть новый VC, хотя я думаю, что я мог бы отложить ViewWillAppear или некоторый другой небольшой обратный вызов с задержкой по таймеру. Единственная проблема, возможно, в том, что вы видите, как исходное состояние просмотра восстанавливается, а затем переключается на счетчик… если я смогу заставить переход исчезнуть, счетчик может быть не таким уж неприятным.
Кто-нибудь получил какие-либо предложения по этому поводу? Это то, что некоторые другие приложения делают все время, например, Facebook, когда они восстанавливаются и идут в сеть, чтобы перезагрузить ваши сообщения для чтения.
Спасибо за ваше время
С уважением
Джим
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.")