forked from DO-SOPT-iOS-Part/RyuHeeJae_assignment
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[Refactor] Coordinator 기본 코드 세팅 DO-SOPT-iOS-Part#20
- Loading branch information
Showing
7 changed files
with
324 additions
and
13 deletions.
There are no files selected for viewing
13 changes: 0 additions & 13 deletions
13
Projects/Features/BaseFeatureDependency/Sources/Coordinator.swift
This file was deleted.
Oops, something went wrong.
22 changes: 22 additions & 0 deletions
22
Projects/Features/BaseFeatureDependency/Sources/Coordinator/Foundation/Coordinator.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
// | ||
// Coordinator.swift | ||
// ProjectDescriptionHelpers | ||
// | ||
// Created by 류희재 on 2023/10/30. | ||
// | ||
|
||
import Foundation | ||
|
||
/// 모든 Coordinator가 따르는 Coordinator 프로토콜입니다. | ||
public protocol Coordinator: AnyObject { | ||
/// start 메서드는 Coordinator가 담당하는 플로우를 시작하게 합니다. 대개의 경우 화면전환이 이뤄집니다. | ||
func start() | ||
|
||
/// DeepLink와 함께 화면전환을 하기 위한 star 메서드입니다. | ||
/// - Parameter option: DeepLinkOption을 추가하여 DeepLink 기능을 사용할 수 있습니다. | ||
// func start(with option: DeepLinkOption?) | ||
|
||
/// CoordinatorStaringOption을 지정하여 원하는 화면전환 액션을 수행합니다. | ||
/// - Parameter style: modal 또는 Root 등의 옵션을 지정합니다. | ||
func start(by style: CoordinatorStartingOption) | ||
} |
56 changes: 56 additions & 0 deletions
56
Projects/Features/BaseFeatureDependency/Sources/Coordinator/Option/BaseCoordinator.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
// | ||
// BaseCoordinator.swift | ||
// BaseFeatureDependency | ||
// | ||
// Created by 류희재 on 7/5/24. | ||
// Copyright © 2024 Weather-iOS. All rights reserved. | ||
// | ||
|
||
import Foundation | ||
|
||
open class BaseCoordinator: Coordinator { | ||
|
||
// MARK: - Vars & Lets | ||
|
||
public var childCoordinators = [Coordinator]() | ||
|
||
// MARK: - Public methods | ||
|
||
/// 자식 코디네이터의 의존성을 추가하여 메모리에서 해제되지 않도록 합니다. | ||
public func addDependency(_ coordinator: Coordinator) { | ||
for element in childCoordinators { | ||
if element === coordinator { return } | ||
} | ||
childCoordinators.append(coordinator) | ||
} | ||
|
||
/// 자식 코디네이터의 의존성을 제거하여 메모리에서 해제되도록 합니다. | ||
public func removeDependency(_ coordinator: Coordinator?) { | ||
guard childCoordinators.isEmpty == false, let coordinator = coordinator else { return } | ||
|
||
for (index, element) in childCoordinators.enumerated() { | ||
if element === coordinator { | ||
childCoordinators.remove(at: index) | ||
break | ||
} | ||
} | ||
} | ||
|
||
// MARK: - Coordinator | ||
|
||
open func start() { | ||
// start(with: nil) | ||
} | ||
|
||
// open func start(with option: DeepLinkOption?) { | ||
// | ||
// } | ||
|
||
open func start(by style: CoordinatorStartingOption) { | ||
|
||
} | ||
|
||
public init(childCoordinators: [Coordinator] = [Coordinator]()) { | ||
self.childCoordinators = childCoordinators | ||
} | ||
} |
16 changes: 16 additions & 0 deletions
16
...s/Features/BaseFeatureDependency/Sources/Coordinator/Option/CoordinatorFinishOutput.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
// | ||
// CoordinatorFinishOutput.swift | ||
// BaseFeatureDependency | ||
// | ||
// Created by 류희재 on 7/5/24. | ||
// Copyright © 2024 Weather-iOS. All rights reserved. | ||
// | ||
|
||
import Foundation | ||
|
||
/// Coordinator가 자신의 플로우를 마쳤을 때 호출됩니다. 대개의 경우 removeDependency()가 호출됩니다. | ||
public protocol CoordinatorFinishOutput { | ||
var finishFlow: (() -> Void)? { get set } | ||
} | ||
|
||
public typealias DefaultCoordinator = BaseCoordinator & CoordinatorFinishOutput |
16 changes: 16 additions & 0 deletions
16
...Features/BaseFeatureDependency/Sources/Coordinator/Option/CoordinatorStartingOption.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
// | ||
// CoordinatorStartingOption.swift | ||
// BaseFeatureDependency | ||
// | ||
// Created by 류희재 on 7/5/24. | ||
// Copyright © 2024 Weather-iOS. All rights reserved. | ||
// | ||
|
||
import Foundation | ||
|
||
public enum CoordinatorStartingOption { | ||
case root | ||
case modal | ||
case push | ||
case rootWindow(animated: Bool, message: String?) | ||
} |
214 changes: 214 additions & 0 deletions
214
Projects/Features/BaseFeatureDependency/Sources/Coordinator/Router/Router.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,214 @@ | ||
// | ||
// Router.swift | ||
// BaseFeatureDependency | ||
// | ||
// Created by 류희재 on 7/5/24. | ||
// Copyright © 2024 Weather-iOS. All rights reserved. | ||
// | ||
|
||
import UIKit | ||
|
||
import Core | ||
import SafariServices | ||
|
||
/// RouterProtocol은 Coordinator에서 화면전환의 종류를 지정합니다. | ||
public protocol RouterProtocol: ViewControllable { | ||
|
||
var topViewController: UIViewController? { get } | ||
|
||
func present(_ module: ViewControllable?) | ||
func present(_ module: ViewControllable?, animated: Bool) | ||
func present(_ module: ViewControllable?, animated: Bool, modalPresentationSytle: UIModalPresentationStyle) | ||
func present(_ module: ViewControllable?, animated: Bool, completion: (() -> Void)?) | ||
|
||
func push(_ module: ViewControllable?) | ||
func push(_ module: ViewControllable?, transition: UIViewControllerAnimatedTransitioning?) | ||
func push(_ module: ViewControllable?, transition: UIViewControllerAnimatedTransitioning?, animated: Bool) | ||
func push(_ module: ViewControllable?, transition: UIViewControllerAnimatedTransitioning?, animated: Bool, completion: (() -> Void)?) | ||
|
||
func popModule() | ||
func popModule(transition: UIViewControllerAnimatedTransitioning?) | ||
func popModule(transition: UIViewControllerAnimatedTransitioning?, animated: Bool) | ||
|
||
func dismissModule(animated: Bool) | ||
func dismissModule(animated: Bool, completion: (() -> Void)?) | ||
|
||
func setRootModule(_ module: ViewControllable?, animated: Bool) | ||
func setRootModule(_ module: ViewControllable?, hideBar: Bool, animated: Bool) | ||
|
||
func popToRootModule(animated: Bool) | ||
func popToModule(module: ViewControllable?, animated: Bool) | ||
|
||
func showTitles() | ||
func hideTitles() | ||
} | ||
|
||
/// RouterProtocol을 채택하여 Coordinator가 모르는 화면전환의 기능을 수행합니다. RootController를 가지고 다양한 기능을 수행합니다. | ||
public | ||
final class Router: NSObject, RouterProtocol { | ||
|
||
|
||
|
||
// MARK: - Vars & Lets | ||
|
||
private weak var rootController: UINavigationController? | ||
private var completions: [UIViewController : () -> Void] | ||
private var transition: UIViewControllerAnimatedTransitioning? | ||
|
||
public var topViewController: UIViewController? { | ||
self.rootController?.topViewController | ||
} | ||
|
||
// MARK: - ViewControllable | ||
|
||
public var viewController: UIViewController { | ||
return self.rootController ?? UIViewController() | ||
} | ||
public var asNavigationController: UINavigationController { | ||
return rootController ?? UINavigationController(rootViewController: viewController) | ||
} | ||
|
||
// MARK: - RouterProtocol | ||
|
||
public func present(_ module: ViewControllable?) { | ||
self.present(module, animated: true) | ||
} | ||
|
||
public func present(_ module: ViewControllable?, animated: Bool) { | ||
guard let controller = module?.viewController else { return } | ||
self.rootController?.present(controller, animated: animated, completion: nil) | ||
} | ||
|
||
public func present(_ module: ViewControllable?, animated: Bool, modalPresentationSytle: UIModalPresentationStyle) { | ||
guard let controller = module?.viewController else { return } | ||
controller.modalPresentationStyle = modalPresentationSytle | ||
self.rootController?.present(controller, animated: animated, completion: nil) | ||
} | ||
|
||
public func present(_ module: ViewControllable?, animated: Bool, modalPresentationSytle: UIModalPresentationStyle, modalTransitionStyle: UIModalTransitionStyle) { | ||
guard let controller = module?.viewController else { return } | ||
controller.modalPresentationStyle = modalPresentationSytle | ||
controller.modalTransitionStyle = modalTransitionStyle | ||
self.rootController?.present(controller, animated: animated, completion: nil) | ||
} | ||
|
||
public func present(_ module: ViewControllable?, animated: Bool, completion: (() -> Void)?) { | ||
guard let controller = module?.viewController else { return } | ||
self.rootController?.present(controller, animated: animated, completion: completion) | ||
} | ||
|
||
public func push(_ module: ViewControllable?) { | ||
self.push(module, transition: nil) | ||
} | ||
|
||
public func push(_ module: ViewControllable?, transition: UIViewControllerAnimatedTransitioning?) { | ||
self.push(module, transition: transition, animated: true) | ||
} | ||
|
||
public func push(_ module: ViewControllable?, transition: UIViewControllerAnimatedTransitioning?, animated: Bool) { | ||
self.push(module, transition: transition, animated: animated, completion: nil) | ||
} | ||
|
||
public func push(_ module: ViewControllable?, transition: UIViewControllerAnimatedTransitioning?, animated: Bool, completion: (() -> Void)?) { | ||
self.transition = transition | ||
|
||
guard let controller = module?.viewController, | ||
(controller is UINavigationController == false) | ||
else { assertionFailure("Deprecated push UINavigationController."); return } | ||
|
||
if let completion = completion { | ||
self.completions[controller] = completion | ||
} | ||
self.rootController?.pushViewController(controller, animated: animated) | ||
|
||
self.transition = nil | ||
} | ||
|
||
public func popModule() { | ||
self.popModule(transition: nil) | ||
} | ||
|
||
public func popModule(transition: UIViewControllerAnimatedTransitioning?) { | ||
self.popModule(transition: transition, animated: true) | ||
} | ||
|
||
public func popModule(transition: UIViewControllerAnimatedTransitioning?, animated: Bool) { | ||
self.transition = transition | ||
|
||
if let controller = rootController?.popViewController(animated: animated) { | ||
self.runCompletion(for: controller) | ||
} | ||
|
||
self.transition = nil | ||
} | ||
|
||
public func popToModule(module: ViewControllable?, animated: Bool = true) { | ||
if let controllers = self.rootController?.viewControllers , let module = module { | ||
for controller in controllers { | ||
if controller == module as! UIViewController { | ||
self.rootController?.popToViewController(controller, animated: animated) | ||
self.runCompletion(for: controller) | ||
break | ||
} | ||
} | ||
} | ||
} | ||
|
||
public func dismissModule(animated: Bool) { | ||
self.dismissModule(animated: animated, completion: nil) | ||
} | ||
|
||
public func dismissModule(animated: Bool, completion: (() -> Void)?) { | ||
self.rootController?.dismiss(animated: animated, completion: completion) | ||
} | ||
|
||
public func setRootModule(_ module: ViewControllable?, animated: Bool) { | ||
self.setRootModule(module, hideBar: false, animated: animated) | ||
} | ||
|
||
public func setRootModule(_ module: ViewControllable?, hideBar: Bool, animated: Bool) { | ||
guard let controller = module?.viewController else { return } | ||
self.rootController?.setViewControllers([controller], animated: animated) | ||
self.rootController?.isNavigationBarHidden = hideBar | ||
} | ||
|
||
public func popToRootModule(animated: Bool) { | ||
if let controllers = self.rootController?.popToRootViewController(animated: animated) { | ||
controllers.forEach { controller in | ||
self.runCompletion(for: controller) | ||
} | ||
} | ||
} | ||
|
||
public func showTitles() { | ||
self.rootController?.isNavigationBarHidden = false | ||
self.rootController?.navigationBar.prefersLargeTitles = true | ||
self.rootController?.navigationBar.tintColor = UIColor.black | ||
} | ||
|
||
public func hideTitles() { | ||
self.rootController?.isNavigationBarHidden = true | ||
} | ||
|
||
// MARK: - Private methods | ||
|
||
private func runCompletion(for controller: UIViewController) { | ||
guard let completion = self.completions[controller] else { return } | ||
completion() | ||
completions.removeValue(forKey: controller) | ||
} | ||
|
||
// MARK: - Init methods | ||
|
||
public init(rootController: UINavigationController) { | ||
self.rootController = rootController | ||
self.completions = [:] | ||
super.init() | ||
} | ||
} | ||
|
||
extension Router: UINavigationControllerDelegate { | ||
public func navigationController(_ navigationController: UINavigationController, animationControllerFor operation: UINavigationController.Operation, from fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? { | ||
return self.transition | ||
} | ||
} |