Skip to content

Commit c98e309

Browse files
authored
A coordinator handles the app's navigation (#626)
## Summary Navigation was somewhat scattered throughout the app and the gallery had a central role. This merge request introduces a class for handling the app's navigation. Eventually it could also help with scenarios like starting the app from cold start with the running timer for a particular goal shown. *For UI changes including screenshots of before and after is great.* ## Validation Running the app in the simulator. Navigating around: - signing in - signing out - viewing gallery - opening goal from gallery - opening edit datapoint - canceling edit datapoint - opening settings and its children - opening goal from spotlight result tap - opening app from tapping notification Fixes #622 Other Locations: - [ ] EditDatapointVC dismisses directly upon deleting a datapoint - [ ] EditDatapointVC dismisses directly upon successfully updating a datapoint - [ ] AlertVC are still handled around where they are displayed - [ ] GoalVC shows the SafariVC - [ ] TimerVC dismisses itself (indirectly)
1 parent 39bf5a5 commit c98e309

10 files changed

+333
-167
lines changed

BeeSwift.xcodeproj/project.pbxproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
8646D674ADB842B797E2F98D /* AddDataError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8646D674ADB842B797E2F98E /* AddDataError.swift */; };
1414
9B65F2322CFA6427009674A7 /* DeeplinkGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B65F2312CFA6418009674A7 /* DeeplinkGenerator.swift */; };
1515
9B8CA57D24B120CA009C86C2 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 9B8CA57C24B120CA009C86C2 /* LaunchScreen.storyboard */; };
16+
9BD4C4E82D45A09F00B03E99 /* MainCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BD4C4E72D45A09F00B03E99 /* MainCoordinator.swift */; };
1617
9BFB27E92CFE770F0056D10D /* FreshnessIndicatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BFB27E82CFE770F0056D10D /* FreshnessIndicatorView.swift */; };
1718
A10D4E931B07948500A72D29 /* DatapointsTableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A10D4E921B07948500A72D29 /* DatapointsTableView.swift */; };
1819
A10DC2DF207BFCBA00FB7B3A /* RemoveHKMetricViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A10DC2DE207BFCBA00FB7B3A /* RemoveHKMetricViewController.swift */; };
@@ -203,6 +204,7 @@
203204
8646D674ADB842B797E2F98E /* AddDataError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddDataError.swift; sourceTree = "<group>"; };
204205
9B65F2312CFA6418009674A7 /* DeeplinkGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeeplinkGenerator.swift; sourceTree = "<group>"; };
205206
9B8CA57C24B120CA009C86C2 /* LaunchScreen.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = "<group>"; };
207+
9BD4C4E72D45A09F00B03E99 /* MainCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainCoordinator.swift; sourceTree = "<group>"; };
206208
9BFB27E82CFE770F0056D10D /* FreshnessIndicatorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FreshnessIndicatorView.swift; sourceTree = "<group>"; };
207209
A10D4E921B07948500A72D29 /* DatapointsTableView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DatapointsTableView.swift; sourceTree = "<group>"; };
208210
A10DC2DE207BFCBA00FB7B3A /* RemoveHKMetricViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoveHKMetricViewController.swift; sourceTree = "<group>"; };
@@ -447,6 +449,7 @@
447449
A196CB161AE4142E00B90A3E /* BeeSwift */ = {
448450
isa = PBXGroup;
449451
children = (
452+
9BD4C4E72D45A09F00B03E99 /* MainCoordinator.swift */,
450453
9B65F2312CFA6418009674A7 /* DeeplinkGenerator.swift */,
451454
A1E618E51E79E01900D8ED93 /* Cells */,
452455
E46071002B43DA7100305DB4 /* Gallery */,
@@ -959,6 +962,7 @@
959962
A10DC2DF207BFCBA00FB7B3A /* RemoveHKMetricViewController.swift in Sources */,
960963
E412DAE12B86A8F70099E483 /* GoalImageView.swift in Sources */,
961964
A1619EA41BEECC1500E14B3A /* EditDefaultNotificationsViewController.swift in Sources */,
965+
9BD4C4E82D45A09F00B03E99 /* MainCoordinator.swift in Sources */,
962966
A149B3701AEF528C00F19A09 /* SettingsViewController.swift in Sources */,
963967
E46DC80F2AA58DF20059FDFE /* PullToRefreshHint.swift in Sources */,
964968
A1E618E21E78158700D8ED93 /* HealthKitConfigViewController.swift in Sources */,

BeeSwift/Gallery/GalleryViewController.swift

Lines changed: 8 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import BeeKit
1919

2020

2121
class GalleryViewController: UIViewController {
22+
private weak var coordinator: MainCoordinator?
2223
let logger = Logger(subsystem: "com.beeminder.beeminder", category: "GalleryViewController")
2324

2425
public enum NotificationName {
@@ -131,13 +132,15 @@ class GalleryViewController: UIViewController {
131132
versionManager: VersionManager,
132133
goalManager: GoalManager,
133134
healthStoreManager: HealthStoreManager,
134-
requestManager: RequestManager) {
135+
requestManager: RequestManager,
136+
coordinator: MainCoordinator) {
135137
self.currentUserManager = currentUserManager
136138
self.viewContext = viewContext
137139
self.versionManager = versionManager
138140
self.goalManager = goalManager
139141
self.healthStoreManager = healthStoreManager
140142
self.requestManager = requestManager
143+
self.coordinator = coordinator
141144

142145
let fetchRequest = Goal.fetchRequest() as! NSFetchRequest<Goal>
143146
fetchRequest.sortDescriptors = Self.preferredSort
@@ -159,10 +162,7 @@ class GalleryViewController: UIViewController {
159162

160163
NotificationCenter.default.addObserver(self, selector: #selector(self.userDefaultsDidChange), name: UserDefaults.didChangeNotification, object: nil)
161164
NotificationCenter.default.addObserver(self, selector: #selector(self.handleSignIn), name: CurrentUserManager.NotificationName.signedIn, object: nil)
162-
NotificationCenter.default.addObserver(self, selector: #selector(self.handleSignOut), name: CurrentUserManager.NotificationName.signedOut, object: nil)
163-
NotificationCenter.default.addObserver(self, selector: #selector(self.openGoalFromNotification(_:)), name: GalleryViewController.NotificationName.openGoal, object: nil)
164-
NotificationCenter.default.addObserver(self, selector: #selector(self.navigateToGallery), name: GalleryViewController.NotificationName.navigateToGallery, object: nil)
165-
165+
166166
self.view.addSubview(self.stackView)
167167
stackView.snp.makeConstraints { (make) -> Void in
168168
make.top.left.right.equalToSuperview()
@@ -277,20 +277,8 @@ class GalleryViewController: UIViewController {
277277
self.updateGoals()
278278
}
279279

280-
override func viewDidAppear(_ animated: Bool) {
281-
if !currentUserManager.signedIn(context: viewContext) {
282-
let signInVC = SignInViewController(currentUserManager: currentUserManager)
283-
signInVC.modalPresentationStyle = .fullScreen
284-
self.present(signInVC, animated: true, completion: nil)
285-
}
286-
}
287-
288280
@objc func settingsButtonPressed() {
289-
self.navigationController?.pushViewController(SettingsViewController(
290-
currentUserManager: currentUserManager,
291-
viewContext: viewContext,
292-
goalManager: goalManager,
293-
requestManager: requestManager), animated: true)
281+
coordinator?.showSettings()
294282
}
295283

296284
@objc func searchButtonPressed() {
@@ -316,23 +304,13 @@ class GalleryViewController: UIViewController {
316304
}
317305

318306
@objc func handleSignIn() {
319-
self.dismiss(animated: true, completion: nil)
320307
self.fetchGoals()
321308

322309
UNUserNotificationCenter.current().requestAuthorization(options: UNAuthorizationOptions([.alert, .badge, .sound])) { [weak self] (success, error) in
323-
self?.logger.info("Requested persons authorization upon signin to allow local and remote notifications; successful? \(success)")
310+
self?.logger.info("Requested person's authorization upon signin to allow local and remote notifications; successful? \(success)")
324311
}
325312
}
326313

327-
@objc func handleSignOut() {
328-
if self.presentedViewController != nil {
329-
if type(of: self.presentedViewController!) == SignInViewController.self { return }
330-
}
331-
let signInVC = SignInViewController(currentUserManager: currentUserManager)
332-
signInVC.modalPresentationStyle = .fullScreen
333-
self.present(signInVC, animated: true, completion: nil)
334-
}
335-
336314
func updateDeadbeatVisibility() {
337315
self.deadbeatView.isHidden = !isUserKnownDeadbeat
338316
}
@@ -426,39 +404,8 @@ class GalleryViewController: UIViewController {
426404
})
427405
}
428406

429-
@objc func openGoalFromNotification(_ notification: Notification) {
430-
guard let notif = notification as NSNotification? else { return }
431-
var matchingGoal: Goal?
432-
433-
if let identifier = notif.userInfo?["identifier"] as? String {
434-
if let url = URL(string: identifier), let objectID = viewContext.persistentStoreCoordinator?.managedObjectID(forURIRepresentation: url) {
435-
matchingGoal = viewContext.object(with: objectID) as? Goal
436-
}
437-
}
438-
else if let slug = notif.userInfo?["slug"] as? String {
439-
matchingGoal = self.filteredGoals.filter({ (goal) -> Bool in
440-
return goal.slug == slug
441-
}).last
442-
}
443-
if matchingGoal != nil {
444-
self.navigationController?.popToRootViewController(animated: false)
445-
self.openGoal(matchingGoal!)
446-
}
447-
}
448-
449-
@objc func navigateToGallery() {
450-
self.navigationController?.popToRootViewController(animated: true)
451-
}
452-
453407
func openGoal(_ goal: Goal) {
454-
let goalViewController = GoalViewController(
455-
goal: goal,
456-
healthStoreManager: healthStoreManager,
457-
goalManager: goalManager,
458-
requestManager: requestManager,
459-
currentUserManager: currentUserManager,
460-
viewContext: viewContext)
461-
self.navigationController?.pushViewController(goalViewController, animated: true)
408+
coordinator?.showGoal(goal)
462409
}
463410

464411
private func configureDataSource() {

BeeSwift/GoalViewController.swift

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ class GoalViewController: UIViewController, UIScrollViewDelegate, DatapointTabl
3131
private let requestManager: RequestManager
3232
private let currentUserManager: CurrentUserManager
3333
private let viewContext: NSManagedObjectContext
34+
private weak var coordinator: MainCoordinator?
3435

3536
private let timeElapsedView = FreshnessIndicatorView()
3637
fileprivate var goalImageView = GoalImageView(isThumbnail: false)
@@ -64,13 +65,15 @@ class GoalViewController: UIViewController, UIScrollViewDelegate, DatapointTabl
6465
goalManager: GoalManager,
6566
requestManager: RequestManager,
6667
currentUserManager: CurrentUserManager,
67-
viewContext: NSManagedObjectContext) {
68+
viewContext: NSManagedObjectContext,
69+
coordinator: MainCoordinator) {
6870
self.goal = goal
6971
self.healthStoreManager = healthStoreManager
7072
self.goalManager = goalManager
7173
self.requestManager = requestManager
7274
self.currentUserManager = currentUserManager
7375
self.viewContext = viewContext
76+
self.coordinator = coordinator
7477
super.init(nibName: nil, bundle: nil)
7578
}
7679

@@ -361,9 +364,7 @@ class GoalViewController: UIViewController, UIScrollViewDelegate, DatapointTabl
361364
}
362365

363366
@objc func timerButtonPressed() {
364-
let controller = TimerViewController(goal: self.goal, requestManager: self.requestManager)
365-
controller.modalPresentationStyle = .fullScreen
366-
self.present(controller, animated: true, completion: nil)
367+
coordinator?.showTimerForGoal(goal)
367368
}
368369

369370
@objc func refreshButtonPressed() {
@@ -401,10 +402,7 @@ class GoalViewController: UIViewController, UIScrollViewDelegate, DatapointTabl
401402
guard !self.goal.hideDataEntry else { return }
402403
guard let existingDatapoint = datapoint as? DataPoint else { return }
403404

404-
let editDatapointViewController = EditDatapointViewController(goal: goal, datapoint: existingDatapoint, requestManager: self.requestManager, goalManager: self.goalManager)
405-
let navigationController = UINavigationController(rootViewController: editDatapointViewController)
406-
navigationController.modalPresentationStyle = .formSheet
407-
self.present(navigationController, animated: true, completion: nil)
405+
coordinator?.showEditDatapointForGoal(goal: goal, datapoint: existingDatapoint)
408406
}
409407

410408
@objc func dateStepperValueChanged() {

0 commit comments

Comments
 (0)