ChartsTableViewController.swift 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181
  1. //
  2. // ChartsTableViewController.swift
  3. // LoopKitUI
  4. //
  5. // Copyright © 2017 LoopKit Authors. All rights reserved.
  6. //
  7. import UIKit
  8. import Combine
  9. import HealthKit
  10. /// Abstract class providing boilerplate setup for chart-based table view controllers
  11. open class ChartsTableViewController: UITableViewController, UIGestureRecognizerDelegate {
  12. public var displayGlucoseUnitObservable: DisplayGlucoseUnitObservable? {
  13. didSet {
  14. guard let displayGlucoseUnitObservable = displayGlucoseUnitObservable else { return }
  15. displayGlucoseUnitObservable.$displayGlucoseUnit
  16. .sink { [weak self] displayGlucoseUnit in self?.unitPreferencesDidChange(to: displayGlucoseUnit) }
  17. .store(in: &cancellables)
  18. }
  19. }
  20. private lazy var cancellables = Set<AnyCancellable>()
  21. open override func viewDidLoad() {
  22. super.viewDidLoad()
  23. if let unit = displayGlucoseUnitObservable?.displayGlucoseUnit {
  24. self.charts.setGlucoseUnit(unit)
  25. }
  26. let gestureRecognizer = UILongPressGestureRecognizer()
  27. gestureRecognizer.delegate = self
  28. gestureRecognizer.minimumPressDuration = 0.3
  29. gestureRecognizer.addTarget(self, action: #selector(handlePan(_:)))
  30. charts.gestureRecognizer = gestureRecognizer
  31. NotificationCenter.default.publisher(for: UIApplication.didBecomeActiveNotification, object: nil)
  32. .receive(on: RunLoop.main)
  33. .sink { [weak self] _ in
  34. self?.active = true
  35. if self?.visible == true {
  36. self?.reloadData()
  37. }
  38. }
  39. .store(in: &cancellables)
  40. NotificationCenter.default.publisher(for: UIApplication.willResignActiveNotification, object: nil)
  41. .receive(on: RunLoop.main)
  42. .sink { [weak self] _ in
  43. self?.active = false
  44. }
  45. .store(in: &cancellables)
  46. }
  47. open override func didReceiveMemoryWarning() {
  48. super.didReceiveMemoryWarning()
  49. if !visible {
  50. charts.didReceiveMemoryWarning()
  51. }
  52. }
  53. open override func viewWillAppear(_ animated: Bool) {
  54. super.viewWillAppear(animated)
  55. visible = true
  56. }
  57. open override func viewWillDisappear(_ animated: Bool) {
  58. super.viewWillDisappear(animated)
  59. visible = false
  60. }
  61. open override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
  62. super.viewWillTransition(to: size, with: coordinator)
  63. reloadData(animated: false)
  64. }
  65. open override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
  66. super.traitCollectionDidChange(previousTraitCollection)
  67. charts.traitCollection = traitCollection
  68. }
  69. // MARK: - State
  70. // This function should only be called from the main thread
  71. public func unitPreferencesDidChange(to unit: HKUnit?) {
  72. if let unit = unit {
  73. self.charts.setGlucoseUnit(unit)
  74. self.glucoseUnitDidChange()
  75. }
  76. self.reloadData()
  77. }
  78. open func glucoseUnitDidChange() {
  79. // To override.
  80. }
  81. open func createChartsManager() -> ChartsManager {
  82. fatalError("Subclasses must implement \(#function)")
  83. }
  84. lazy public private(set) var charts = createChartsManager()
  85. // References to registered notification center observers
  86. public var notificationObservers: [Any] = []
  87. open var active: Bool = true {
  88. didSet {
  89. reloadData()
  90. }
  91. }
  92. public var visible = false {
  93. didSet {
  94. reloadData()
  95. }
  96. }
  97. // MARK: - Data loading
  98. /// Refetches all data and updates the views. Must be called on the main queue.
  99. ///
  100. /// - Parameters:
  101. /// - animated: Whether the updating should be animated if possible
  102. open func reloadData(animated: Bool = false) {
  103. }
  104. // MARK: - UIGestureRecognizer
  105. public func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
  106. /// Only start the long-press recognition when it starts in a chart cell
  107. let point = gestureRecognizer.location(in: tableView)
  108. if let indexPath = tableView.indexPathForRow(at: point) {
  109. if let cell = tableView.cellForRow(at: indexPath), cell is ChartTableViewCell {
  110. return true
  111. }
  112. }
  113. return false
  114. }
  115. public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
  116. return true
  117. }
  118. @objc func handlePan(_ gestureRecognizer: UIGestureRecognizer) {
  119. switch gestureRecognizer.state {
  120. case .possible, .changed:
  121. // Follow your dreams!
  122. break
  123. case .began, .cancelled, .ended, .failed:
  124. for case let row as ChartTableViewCell in self.tableView.visibleCells {
  125. let forwards = gestureRecognizer.state == .began
  126. UIView.animate(withDuration: forwards ? 0.2 : 0.5, delay: forwards ? 0 : 1, animations: {
  127. let alpha: CGFloat = forwards ? 0 : 1
  128. row.titleLabel?.alpha = alpha
  129. row.subtitleLabel?.alpha = alpha
  130. })
  131. }
  132. @unknown default:
  133. break
  134. }
  135. }
  136. }
  137. fileprivate extension ChartsManager {
  138. func setGlucoseUnit(_ unit: HKUnit) {
  139. for case let chart as GlucoseChart in charts {
  140. chart.glucoseUnit = unit
  141. }
  142. }
  143. }