From e8baa17770b5cd5f962d58a008fe0b380d79dd48 Mon Sep 17 00:00:00 2001 From: Bharat Mediratta Date: Fri, 23 Dec 2016 19:04:27 -0800 Subject: [PATCH 1/4] Monitor changes and update the widget in real time. Use KVO to watch the UserDefaults object and update the view as appropriate. --- .../StatusViewController.swift | 49 +++++++++++++++---- .../NSUserDefaults+StatusExtension.swift | 4 ++ 2 files changed, 44 insertions(+), 9 deletions(-) diff --git a/Loop Status Extension/StatusViewController.swift b/Loop Status Extension/StatusViewController.swift index 0de511aa87..0f5c88a376 100644 --- a/Loop Status Extension/StatusViewController.swift +++ b/Loop Status Extension/StatusViewController.swift @@ -20,24 +20,57 @@ class StatusViewController: UIViewController, NCWidgetProviding { @IBOutlet weak var batteryHUD: BatteryLevelHUDView! @IBOutlet weak var subtitleLabel: UILabel! + var context: StatusExtensionContext? + var defaults: UserDefaults? + override func viewDidLoad() { super.viewDidLoad() subtitleLabel.alpha = 0 subtitleLabel.textColor = UIColor.secondaryLabelColor } + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + + defaults = UserDefaults(suiteName: Bundle.main.appGroupSuiteName) + if let defaults = defaults { + defaults.addObserver( + self, + forKeyPath: defaults.statusExtensionContextObservableKey, + options: [.new, .initial], + context: nil) + } + } + + override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + + if let defaults = defaults { + defaults.removeObserver(self, forKeyPath: defaults.statusExtensionContextObservableKey) + } + } + + override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { + update() + } + override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() } func widgetPerformUpdate(completionHandler: (@escaping (NCUpdateResult) -> Void)) { + let result = update() + completionHandler(result) + } + + @discardableResult + func update() -> NCUpdateResult { guard - let context = UserDefaults(suiteName: Bundle.main.appGroupSuiteName)?.statusExtensionContext + let context = defaults?.statusExtensionContext else { - completionHandler(NCUpdateResult.failed) - return + return NCUpdateResult.failed } - + // We should never have the case where there's glucose values but no preferred // unit. However, if that case were to happen we might show quantities against // the wrong units and that could be very harmful. So unless there's a preferred @@ -45,8 +78,7 @@ class StatusViewController: UIViewController, NCWidgetProviding { guard let preferredUnitString = context.preferredUnitString else { - completionHandler(NCUpdateResult.failed) - return + return NCUpdateResult.failed } if let glucose = context.latestGlucose { @@ -88,10 +120,9 @@ class StatusViewController: UIViewController, NCWidgetProviding { } else { subtitleLabel.alpha = 0 } - + // Right now we always act as if there's new data. // TODO: keep track of data changes and return .noData if necessary - completionHandler(NCUpdateResult.newData) + return NCUpdateResult.newData } - } diff --git a/Loop/Extensions/NSUserDefaults+StatusExtension.swift b/Loop/Extensions/NSUserDefaults+StatusExtension.swift index 76a01b454e..02c9a3b69d 100644 --- a/Loop/Extensions/NSUserDefaults+StatusExtension.swift +++ b/Loop/Extensions/NSUserDefaults+StatusExtension.swift @@ -14,6 +14,10 @@ extension UserDefaults { case StatusExtensionContext = "com.loopkit.Loop.StatusExtensionContext" } + var statusExtensionContextObservableKey: String { + return Key.StatusExtensionContext.rawValue + } + var statusExtensionContext: StatusExtensionContext? { get { if let rawValue = dictionary(forKey: Key.StatusExtensionContext.rawValue) { From 3658b8652f2e1a49917644c7a6381a6499f76deb Mon Sep 17 00:00:00 2001 From: Bharat Mediratta Date: Fri, 23 Dec 2016 22:46:05 -0800 Subject: [PATCH 2/4] Clean up observer implementation 1. call addObserver() in viewDidLoad() 2. call removeObserver() in deinit 3. don't specify options in addObserver() 4. provide a context to the observer code --- .../StatusViewController.swift | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/Loop Status Extension/StatusViewController.swift b/Loop Status Extension/StatusViewController.swift index 0f5c88a376..0853c42ed0 100644 --- a/Loop Status Extension/StatusViewController.swift +++ b/Loop Status Extension/StatusViewController.swift @@ -27,30 +27,29 @@ class StatusViewController: UIViewController, NCWidgetProviding { super.viewDidLoad() subtitleLabel.alpha = 0 subtitleLabel.textColor = UIColor.secondaryLabelColor - } - - override func viewWillAppear(_ animated: Bool) { - super.viewWillAppear(animated) - + defaults = UserDefaults(suiteName: Bundle.main.appGroupSuiteName) if let defaults = defaults { defaults.addObserver( self, forKeyPath: defaults.statusExtensionContextObservableKey, - options: [.new, .initial], - context: nil) + options: [], + context: &context) } } - override func viewWillDisappear(_ animated: Bool) { - super.viewWillDisappear(animated) - + deinit { if let defaults = defaults { defaults.removeObserver(self, forKeyPath: defaults.statusExtensionContextObservableKey) } } override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { + if context != &self.context { + super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context) + return + } + update() } From a24207798397724174ebfdd29b9b3490877c55e4 Mon Sep 17 00:00:00 2001 From: Bharat Mediratta Date: Sat, 24 Dec 2016 10:33:11 -0800 Subject: [PATCH 3/4] Move early-return semantics into a guard statement. --- Loop Status Extension/StatusViewController.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Loop Status Extension/StatusViewController.swift b/Loop Status Extension/StatusViewController.swift index 0853c42ed0..28b7c79b4d 100644 --- a/Loop Status Extension/StatusViewController.swift +++ b/Loop Status Extension/StatusViewController.swift @@ -45,7 +45,7 @@ class StatusViewController: UIViewController, NCWidgetProviding { } override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { - if context != &self.context { + guard context == &self.context else { super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context) return } From 8447048d1b4fd8330d9394a5dc567847009dfd86 Mon Sep 17 00:00:00 2001 From: Bharat Mediratta Date: Fri, 30 Dec 2016 11:15:49 -0800 Subject: [PATCH 4/4] Use an explicit, separately named context for observer. --- Loop Status Extension/StatusViewController.swift | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Loop Status Extension/StatusViewController.swift b/Loop Status Extension/StatusViewController.swift index 8d111effb4..f05295e403 100644 --- a/Loop Status Extension/StatusViewController.swift +++ b/Loop Status Extension/StatusViewController.swift @@ -20,8 +20,9 @@ class StatusViewController: UIViewController, NCWidgetProviding { @IBOutlet weak var batteryHUD: BatteryLevelHUDView! @IBOutlet weak var subtitleLabel: UILabel! - var context: StatusExtensionContext? + var statusExtensionContext: StatusExtensionContext? var defaults: UserDefaults? + final var observationContext = 1 override func viewDidLoad() { super.viewDidLoad() @@ -37,18 +38,18 @@ class StatusViewController: UIViewController, NCWidgetProviding { self, forKeyPath: defaults.statusExtensionContextObservableKey, options: [], - context: &context) + context: &observationContext) } } deinit { if let defaults = defaults { - defaults.removeObserver(self, forKeyPath: defaults.statusExtensionContextObservableKey) + defaults.removeObserver(self, forKeyPath: defaults.statusExtensionContextObservableKey, context: &observationContext) } } override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { - guard context == &self.context else { + guard context == &observationContext else { super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context) return }