Skip to content

Instantly share code, notes, and snippets.

@amfathi
Last active May 8, 2024 18:09
Show Gist options
  • Save amfathi/1e128a399357c1861304173060571766 to your computer and use it in GitHub Desktop.
Save amfathi/1e128a399357c1861304173060571766 to your computer and use it in GitHub Desktop.
Coordinator (NavigationCoordinator + TabBarCoordinator)
import UIKit
// MARK: - Base Coordinator
class NavigationCoordinator: NSObject, Coordinator {
weak var parentCoordinator: Coordinator?
var childCoordinators = [Coordinator]()
var navigationController: BaseNavigationController
weak var startViewController: UIViewController?
init(navigationController: BaseNavigationController) {
self.navigationController = navigationController
super.init()
self.navigationController.delegate = self
}
func start() {}
}
// MARK: - Handling Coordinator memeory release
extension NavigationCoordinator: UINavigationControllerDelegate {
func navigationController(_ navigationController: UINavigationController, didShow viewController: UIViewController, animated: Bool) {
guard let startViewController = startViewController else {
print("Warning: startViewController is not set. Coordinator \(self.classForCoder) will not be able to deinit.")
return
}
guard
let fromViewController = navigationController.transitionCoordinator?.viewController(forKey: .from),
!navigationController.viewControllers.contains(fromViewController)
else { return }
// If the popped viewController is the viewController from which this coordinator started then we finish this coordinator to release it.
if fromViewController == startViewController {
finish()
}
}
func dismiss(animated: Bool = true) {
navigationController.dismiss(animated: animated) {
self.finish()
}
}
}
/// If minimum iOS Target is iOS13, then use SceneDelegete only.
/// Otherwise use both
// MARK: - AppDelegate
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
var appCoordinator: AppCoordinator?
private func launchAppCoordinator() {
// If not available iOS 13.0
if #available(iOS 13.0, *) { } else {
let window = UIWindow()
appCoordinator = AppCoordinator(window: window)
appCoordinator?.start()
self.window = window
}
}
}
// MARK: - SceneDelegate
@available(iOS 13.0, *)
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
var appCoordinator: AppCoordinator?
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
guard let scene = (scene as? UIWindowScene) else { return }
launchAppCoordinator(scene)
}
private func launchAppCoordinator(_ scence: UIWindowScene) {
let window = UIWindow(windowScene: scene)
appCoordinator = AppCoordinator(window: window)
appCoordinator?.start()
self.window = window
}
}
protocol Coordinator: AnyObject {
var parentCoordinator: Coordinator? { get set }
var childCoordinators: [Coordinator] { get set }
func start()
func finish()
}
extension Coordinator {
// Default implementation is that the coordinator removes itself from its parent's childCoordinators.
func finish() {
parentCoordinator?.childCoordinators.removeAll { $0 === self }
}
}
import UIKit
class AppCoordinator: Coordinator {
var parentCoordinator: Coordinator?
var childCoordinators = [Coordinator]()
var navigationController: BaseNavigationController?
private let window: UIWindow
init(window: UIWindow) {
self.window = window
}
func start() {
navigationController = BaseNavigationController()
window.rootViewController = navigationController
window.makeKeyAndVisible()
let viewModel = SplashViewModel(coordinator: self)
let viewController = SplashViewController(viewModel: viewModel)
navigationController?.viewControllers = [viewController]
navigationController?.modalPresentationStyle = .fullScreen
}
func startLoginCoordinator() {
guard let navigationController = navigationController else { return }
let coordinator = LoginCoordinator(navigationController: navigationController)
coordinator.parentCoordinator = self
coordinator.start()
childCoordinators = [coordinator]
}
func startTabBarCoordinator() {
navigationController?.viewControllers = []
navigationController = nil
let coordinator = TabBarCoordinator(window: window)
coordinator.parentCoordinator = self
coordinator.start()
childCoordinators = [coordinator]
}
func logout() {
childCoordinators = []
start()
}
func finish() {}
}
import UIKit
class LoginCoordinator: NavigationCoordinator {
// MARK: - Start
override func start() {
let viewModel = LoginViewModel(coordinator: self)
let viewController = LoginViewController(viewModel: viewModel)
navigationController.pushViewController(viewController, animated: true)
navigationController.viewControllers = [viewController]
startViewController = viewController
}
// MARK: - Child coordinators
func startForgotPasswordCoordinator() {
let coordinator = ForgotPasswordCoordinator(navigationController: navigationController)
coordinator.parentCoordinator = self
coordinator.start()
childCoordinators.append(coordinator)
}
func startRegistrationCoordinator() {
let coordinator = RegistrationCoordinator(navigationController: navigationController)
coordinator.parentCoordinator = self
coordinator.start()
childCoordinators.append(coordinator)
}
func login() {
childCoordinators = []
let appCoordinator = UIApplication.shared.appCoordinator
appCoordinator?.startTabBarCoordinator()
finish()
}
}
import UIKit
// MARK: - BaseTabBar
class BaseTabBarController: UITabBarController {
private(set) var coordinators = [NavigationCoordinator]()
override func viewDidLoad() {
super.viewDidLoad()
setupCustomization()
setupViewControllers()
}
func setupCustomization() {}
func setupViewControllers() {}
}
// MARK: - SampleTabBar
class SampleTabBarController: BaseTabBarController {
override func setupCustomization() {
tabBar.tintColor = .mainAccent
}
override func setupViewControllers() {
let apartmentCoordinator = ApartmentsCoordinator(navigationController: BaseNavigationController())
apartmentCoordinator.navigationController.tabBarItem = UITabBarItem(title: "Apartments", image: UIImage(named: "tabbar-apartments"), selectedImage: nil)
coordinators.append(apartmentCoordinator)
if User.current.isType(.admin) {
let usersCoordinator = UsersCoordinator(navigationController: BaseNavigationController())
usersCoordinator.navigationController.tabBarItem = UITabBarItem(title: "Users", image: UIImage(named: "tabbar-users"), selectedImage: nil)
coordinators.append(usersCoordinator)
}
let profileCoordinator = ProfileCoordinator(navigationController: BaseNavigationController())
profileCoordinator.navigationController.tabBarItem = UITabBarItem(title: "Profile", image: UIImage(named: "tabbar-profile"), selectedImage: nil)
coordinators.append(profileCoordinator)
let settingsCoordinator = SettingsCoordinator(navigationController: BaseNavigationController())
settingsCoordinator.navigationController.tabBarItem = UITabBarItem(title: "Settings", image: UIImage(named: "tabbar-settings"), selectedImage: nil)
coordinators.append(settingsCoordinator)
viewControllers = coordinators.map { $0.navigationController }
coordinators.forEach { $0.start() }
}
}
import UIKit
class TabBarCoordinator: NSObject, Coordinator {
weak var parentCoordinator: Coordinator?
var childCoordinators = [Coordinator]()
let tabBarController = SampleTabBarController()
let window: UIWindow
init(window: UIWindow) {
self.window = window
}
func start() {
window.rootViewController = tabBarController
UIView.transition(
with: window,
duration: 0.4,
options: .transitionCrossDissolve,
animations: {},
completion: nil
)
childCoordinators = tabBarController.coordinators
childCoordinators.forEach { $0.parentCoordinator = self }
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment