Skip to content

HelioMesquita/MarcoPolo

Repository files navigation

Swift Version License Swift Package Manager CocoaPods Compatible

MarcoPolo


Swiftmazing Logo

MarcoPolo is a library that combines coordinator and internal deep linking in a unique and simple solution.

Propose to solve

Developing a scalable solution is a problem, especially when it becomes modular. One reason is that the way the iOS system manages navigation is very limited.

To workaround this limitation, We can use the coordinator pattern. This pattern handles all screen navigation and is typically used with MVVM, creating the famous MVVM-C.

The coordinator pattern solves the problem for middle and small applications, but it gets messy when the app gets too big. The standard solution to this problem is creating a coordinator for each feature and having a primary coordinator take all of them. But when We decide to use multiple coordinators is the step to start a modular application.

The modular solution works well until we get some needs like feature A open feature B and feature B open feature A. Then, we could import feature B into feature A and solve this problem, but this is not true because we can not have cyclic dependence. To fix this problem, We can use internal deep-link to handle the next screen without creating dependencies between modules, avoiding dependency hell and cycle dependency.

This library implements both patterns in a straightforward way

Features

  • Coordinator
  • Deep-linking using url scheme

Requirements

  • iOS 10.0+
  • Xcode 14.2+

Installation

CocoaPods

You can use CocoaPods to install MarcoPolo by adding it to your Podfile:

platform :ios, '10.0'
use_frameworks!
pod 'MarcoPolo'

Swift Package Manager

dependencies: [
    .package(url: "https://github.com/HelioMesquita/MarcoPolo/.git", .branch("main"))
]

How to use

Before you starting using MarcoPolo you must configure some URLScheme to allow the deep-liking work.

First follow those steps in image. You can choose the name that you want.

Swiftmazing Logo

After configuring the deep-linking, you must create the main/principal coordinator to hold all features and implement it in your app delegate or scene delegate.

import MarcoPolo
import UIKit

class MainCoordinator: DeeplinkCoordinator {
  var viewControllers: [any DeeplinkViewController.Type] = [
    ViewController.self // First view controller when opens the app
  ]
  lazy var coordinators: [DeeplinkCoordinator] = [
    // All your features coordinators here
  ]
  var navigation: UINavigationController

  required init(navigation: UINavigationController) {
    self.navigation = navigation
  }

  func open(_ viewController: any DeeplinkViewController.Type, arguments: Any?) {
    let vc = viewController.init()
    navigation.pushViewController(vc, animated: true)
  }
}

Your view controller will appear like this

import MarcoPolo
import UIKit

class ViewController: UIViewController, DeeplinkViewController, DeeplinkOpener {
  typealias DeeplinkParameterReceiveType = String //Type of argument that deep-link will add in your class

  static var path: String = "main" // Deep-link path to find your class
}

Using SceneDelegate

import OSLog
import UIKit

class SceneDelegate: UIResponder, UIWindowSceneDelegate {
  var coordinator = MainCoordinator(navigation: UINavigationController())
  var window: UIWindow?

  func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
    guard let firstUrl = URLContexts.first?.url else {
      return
    }
    if coordinator.canOpenURL(firstUrl) {
      coordinator.handleURL(firstUrl, arguments: UIApplication.shared.arguments)
    } else {
      os_log("Deeplink not found", log: OSLog.default, type: .error)
    }
  }

  func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
    guard let windowScene = (scene as? UIWindowScene) else { return }

    let window = UIWindow(windowScene: windowScene)

    coordinator.open(ViewController.self, arguments: nil)
    window.rootViewController = coordinator.navigation

    self.window = window
    window.makeKeyAndVisible()
  }
}

Using AppDelegate

import OSLog
import UIKit

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

  var window: UIWindow?
  var coordinator = MainCoordinator(navigation: UINavigationController())
  
  func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
    window = UIWindow(frame: UIScreen.main.bounds)
    coordinator.open(ViewController.self, arguments: nil)
    window.rootViewController = coordinator.navigation
    self.window = window
    window.makeKeyAndVisible()
    return true
  }

  func application(_ application: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool {
    if coordinator.canOpenURL(url) {
      coordinator.handleURL(url, arguments: UIApplication.shared.arguments)
      return true
    } else {
      os_log("Deeplink not found", log: OSLog.default, type: .error)
      return false
    }
}

Now everything is set up and you are ready to go

Just dont forget to add your new screen in the coordinator 😅

Contribute

We would love you for the contribution to MarcoPolo, check the LICENSE file for more info.

https://github.com/HelioMesquita/MarcoPolo

About

MarcoPolo is a library that combines coordinator and internal deep linking in a unique and simple solution.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published