Giter VIP home page Giter VIP logo

coordinator's Introduction

platforms: iOS|tvOS SwiftPM ready Carthage compatible CocoaPods compatible

Coordinator

Implementation of Coordinator design pattern. It is the application architecture pattern for iOS, carefully designed to fit into UIKit; so much it could easily be UICoordinator.

Since this is core architectural pattern, it’s not possible to explain its usage with one or two clever lines of code. Give it a day or two; analyze and play around. I’m pretty sure you’ll find it worthy of your time and future projects.

Installation

  • version 7.x and up is made with Swift 5.5 concurrency in mind (async / await)
  • versions before that (6.x) use closures

Manually

Just drag Coordinator folder into your project — it‘s only a handful of files.

If you prefer to use dependency managers, see below. Releases are tagged with Semantic Versioning in mind.

Swift Package Manager

Ready, just add this repo URL as Package.

CocoaPods

CocoaPods is a dependency manager for Cocoa projects. For usage and installation instructions, visit their website. To integrate Coordinator into your Xcode project using CocoaPods, specify it in your Podfile:

pod 'Coordinator', 	:git => 'https://github.com/radianttap/Coordinator.git'

Setting up with Carthage

Carthage is a decentralized dependency manager that automates the process of adding frameworks to your Cocoa application.

You can install Carthage with Homebrew using the following command:

$ brew update
$ brew install carthage

To integrate Coordinator into your Xcode project using Carthage, specify it in your Cartfile:

github "radianttap/Coordinator"

Documentation

The why and how and...

License

MIT, as usual.

Give back

If you found this code useful, please consider buying me a coffee or two. ☕️😋

coordinator's People

Contributors

bjarkehs avatar jeanazzopardi avatar krin-san avatar marcsteven avatar mjjimenez avatar nischu avatar radianttap avatar tinora avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

coordinator's Issues

Bloated Namespace

Hello.

At scale, would this not bloat the namespace for all UIResponders, since each has been extended with all of these functions? It could one day become hundreds of public functions on each UIView, UIViewController, and Coordinator.

Tutorial request - A sample xcproject?

Hi there

I am a bit stumped as to how to use ApplicationCoordinator or what it should inherit from. In the readme the ApplicationCoordinator is suffixed with three dots ... so I'm not sure what it is subclassing.

I am wondering, is there any chance of a sample xcproject that perhaps covers a few cases?

IE:

  • Two journeys (1. A root Main menu and 2. Master -> Detail page)?
  • How to hide back button/navigation altogether from one journey, but not the other?
  • If two journeys have different storyboards altogether?

With thanks

Track `popViewController` when user taps Back bar button on iOS >= 10

In current implementation of this library on iOS >= 10 in NavigationCoordinator class this function
func willShowController(_ viewController: UIViewController, fromViewController: UIViewController?)

is never called because
navigationController.transitionCoordinator?.notifyWhenInteractionChanges
only works for pop gesture. It leads to inconsistent NavigationCoordinator's state because self.viewControllers array is never cleared and never synched with VC hierarchy.

Bubbling events in the responder chain

Hi, I just wanted to ask if you're open to changing how actions are passed through the responder chain to the coordinator.

Currently, UIResponder is extended to adopt all the methods needed to be called by the cells on their corresponding coordinator. My problem with this is that it means all subclasses of UIResponder are now also able to call these methods even though they are unrelated to their own responsibilities.

I got some inspiration from this article ( https://blog.veloxdb.com/2016/05/12/bubbling-events-using-uiresponder-in-swift/ ) and was thinking if it could also be applied in this project.

For example, from the sample project, cart events could be defined in a protocol:

protocol CartEventHandler {
    func cartBuyNow(_ product: Product, sender: Any?)
    func cartAdd(product: Product, color: ColorBox, sender: Any?, completion: (Bool) -> Void)
    func cartToggle(sender: Any?)
}

and the AppCoordinator would then adopt protocol like so:

extension AppCoordinator: CartEventHandler {
    
    func cartBuyNow(_ product: Product, sender: Any?) {
         ...
    }
    
    func cartAdd(product: Product, color: ColorBox, sender: Any?, completion: (Bool) -> Void) {
        ...
    }
    
    func cartToggle(sender: Any?) {
        ...
    }
}

The UIResponder could have an additional function that walks the coordinating responder chain to find a matching responder that adopts this protocol.

public extension UIResponder {
	public var coordinatingResponder: UIResponder? {
		return next
	}
    
    func nextActionableResponder<T>() -> T? {
        guard let responder = self.coordinatingResponder else {
            return nil
        }
        
        return (responder as? T) ?? responder.nextActionableResponder()
    }
}

The PromoCell would then invoke the buy now method on the coordinator via the nextActionableResponder() function.

fileprivate extension PromoCell {
	@IBAction func didTapBuyNow(_ sender: UIButton) {
		guard let product = product else { return }

                (nextActionableResponder() as CartEventHandler?)?.cartBuyNow(product, sender: self)
	}
}

Thanks again for making the library. This is probably the best open source example of Coordinators I can currently find.

Declarations from extensions cannot be overridden yet

Hi Aleksandar,
Thank you for the great coordinator. I am trying to follow your steps in the example.
You have added for example:

extension UIResponder {
	///	Adds given Product to the temporary cart and immediatelly shows Payment screen 
	func cartBuyNow(_ product: Product, sender: Any?) {
		coordinatingResponder?.cartBuyNow(product, sender: sender)
	}

	///	Adds given Product to the cart
	func cartAdd(product: Product, color: ColorBox, sender: Any?, completion: (Bool) -> Void) {
		coordinatingResponder?.cartAdd(product: product, color: color, sender: sender, completion: completion)
	}

	///	show/hide the cart
	func cartToggle(sender: Any?) {
		coordinatingResponder?.cartToggle(sender: sender)
	}
}

Then in the applicationCoordinator you override them:
```
// UIResponder coordinating messages

override func cartBuyNow(_ product: Product, sender: Any?) {
	//	re-route to CartManager and/or CartCoordinator

	let ac = UIAlertController(title: nil, message: "cart-BuyNow", preferredStyle: .alert)
	ac.addAction( UIAlertAction(title: "OK", style: .default) )
	rootViewController.present(ac, animated: true)
}

override func cartAdd(product: Product, color: ColorBox, sender: Any?, completion: (Bool) -> Void) {
	//	re-route to CartManager and/or CartCoordinator

	let ac = UIAlertController(title: nil, message: "cart-Add", preferredStyle: .alert)
	ac.addAction( UIAlertAction(title: "OK", style: .default) )
	rootViewController.present(ac, animated: true)
}

override func cartToggle(sender: Any?) {

	let ac = UIAlertController(title: nil, message: "cart-Toggle", preferredStyle: .alert)
	ac.addAction( UIAlertAction(title: "OK", style: .default) )
	rootViewController.present(ac, animated: true)
}
With no problems at all.

When I try to do the same I get the above error. Could you please point to me what I am missing? 

Thank you.

Add shields.io badges

I would love to include at least the following Shields.io badges

  • licence
  • pod + version
  • version in general

Cocoapods badges they have prebuilt seem to be targeting cocoapods.org, where I can't publish this library.
Not sure how to build the badge targeting the repo here directly.

Return back the UINavigationController delegate to parent coordinator when share the UINavigationController

If parent and children share the same UINavigationController, children should return back the delegate of UINavigationController to parent after stopped.

I think the open override func stop(with completion: @escaping () -> Void) method of NavigationCoordinator should like this:

open override func stop(with completion: @escaping () -> Void) {
     if let parent = self.parent as? NavigationCoordinator,
         parent.rootViewController == rootViewController 
     {
         rootViewController.delegate = parent
     }
     else
     {
         rootViewController.delegate = nil
     }

	for vc in viewControllers {
		guard let index = rootViewController.viewControllers.index(of: vc) else { continue }
		rootViewController.viewControllers.remove(at: index)
	}

	viewControllers.removeAll()

	super.stop(with: completion)
}

Is it right?

Non-animated pop is not detected in NavigationCoordinator

I've noticed one critical issue with NavigationCoordinator: handlePopBack is not being called when popViewController(animated: false). In this case navigationController.transitionCoordinator will be nil and pop detection mechanism will not work properly. As the result, we'll get a different viewControllers arrays on coordinator and navigation controller.

p.s.: Default pop animation was disabled to apply a custom CATransition animation.

I see only one good way to solve that by handling the case when fromViewController == nil separately. Whenever viewController which is being shown already exists in viewControllers array, that shall be treated as a pop event.

Feel free to propose a better solution. Meanwhile I will implement this one and test in my app.

How to compile and run the example app?

I've opened Coordinator.xcodeproj but am unable to compile the project: "No such module 'TinyConstraints'" (and the same for the other external dependencies).

I noticed that there are 4 frameworks listed under the Frameworks map in the project navigator, but they reference non-existing frameworks. For example Carthage/Build/iOS/TinyConstraints.framework, which is not part of the repo. So is there some step missing in the readme? Do I need to do something with Carthage or something?

handlePopBack is not called when 2 coordinators share the same rootViewController

Improvements in 09b040f broke the following use case:

  • App has a single UINavigationController shared between home and account navigation coordinators
  • home coordinator shows the initial screens
  • account coordinator gets control over home.rootViewController when home creates and starts the account on user event (imagine showing an Account screen over the main app screen)
  • User uses the swipe-to-pop to close controller shown by account coordinator

Context before popping:

(lldb) po home.viewControllers
[HomeViewController]

(lldb) po account.viewControllers
[AccountViewController]

(lldb) po navigationController.viewControllers
[HomeViewController, AccountViewController]

When user performs swipe-to-pop gesture, didShowController gets called on account coordinator, but it returns on the check if lastIndex <= index { return } and therefore it:

  • don't remove AccountViewController from viewControllers
  • don't call handlePopBack(to:)
  • don't call parent?.coordinatorDidFinish(...)

UITabBarController as Root or Child Coordinator

Thanks for sharing this design.
I am very keen on learning your implementation of the pattern.

I can't say for sure bc I haven't experienced the flow in use, but it seems this style is quite unique and understandable.

Working my way through the code, it seems like this style can be mixed and match with other architectural styles.

Can show me some sample code or some thoughts on how to incorporate UITabBarController as the root coordinator or as one of the child coordinators?

thanks!

Crash on Accessing Parent Coordinator

I experienced crashes from time to time when accessing a UIViewController's parentCoordinator property.

As it turns out, the parentCoordinator is realized as an Objective-C Associated Object using the OBJC_ASSOCIATION_ASSIGN association policy, which, in newer Objective-C parlance, means unsafe-unretained.

While I agree that the parent coordinator's lifetime should always exceed the child coordinator's lifetime, there is no way to enforce that. Any object may establish a strong reference to the child coordinator (for instance, by submitting an asynchronous operation) that may keep the child coordinator alive although the parent was instructed to stopChild.

This finally leads to a crash for the next caller that accesses the parentCoordinator property, as the AssociatedKeys.ParentCoordinator contains a stale pointer once the parent coordinator goes away.

I would propose to establish a real weak association where, in this use-case, the parentCoordinator becomes nil. Unfortunately, there is a little quirk required because the only safe association policy is OBJC_ASSOCIATION_RETAIN, requiring a trampoline object that establishes the actual weak reference.

The only equally safe alternative I see would be to make the parentCoordinator a strong reference, but that would lead to retain cycles for obvious reasons.

What are your thoughts?

I am linking a pull request as my proposed solution, open for discussion.

Kind regards
Nico

Track Dismissal of RootViewController

Quite a few of Coordinator libraries have a problem keeping the Coordinator hierarchy in sync with the ViewController hierarchy (e.g. these ones). How do you think we should prevent that a Coordinator's rootViewController gets dismissed by some dismiss call or similar without the Coordinator noticing it?

Unable to compile CoordinatorExample.xcodeproj

I am getting this error message when I compile with carthage:

Reason: Project /Carthage/Checkouts/Coordinator/Example/CoordinatorExample.xcodeproj cannot be opened because it is missing its project.pbxproj file.

Indeed the project.pbxproj file is not there

Cocoapods error

Analyzing dependencies
Pre-downloading: `Coordinator` from `https://github.com/radianttap/Coordinator.git`
[!] Failed to load 'Coordinator' podspec:
[!] Invalid `Coordinator.podspec` file: undefined method `swift_versions=' for #<Pod::Specification name="Coordinator">
Did you mean?  swift_version=
               swift_version.

 #  from /var/folders/6q/7t___nh11t1bc05sfnsqn3y00000gn/T/d20190403-3620-1v204j0/Coordinator.podspec:17
 #  -------------------------------------------
 #    s.swift_version  = '5.0'
 >    s.swift_versions = ['4.2', '5.0']
 #  end
 #  -------------------------------------------

Rewinding state after stop/stopChild

I wonder if Coordinator.stop() & Coordinator.stopChild() should rewind rootViewController.parentCoordinator value.

Currently rootViewController.parentCoordinator = nil after Coordinator.stop() but it can be that it had different value before Coordinator.start() / Coordinator.startChild() was called.

E.g.
AppCoordinator initialized with rootViewController = AppNavController
and ChildCoordinatorinitialized with the same rootViewController = AppNavController
then after ChildCoordinator.didFinish
AppNavController.parentCoordinator will be nil instead of AppCoordinator.

Of course it can be mitigated with overriding

    override func stopChild(coordinator: Coordinating, completion: @escaping () -> Void) {
        super.stopChild(coordinator: coordinator) {
            self.rootViewController.parentCoordinator = self
        }
    }

Maybe capturing rootViewController.parentCoordinator value in start() / startChild() and setting it back in stop() / stopChild() would be more reliable/foolproof option?

Support Carthage

I think Carthage is a great tool to manage dependency ,so most developer like to use it ,So Pls think about whether it should support Carthage?

Can't use Struct as params in UIResponder's method

Something not so good. The params of method extended from UIResponder have to be NSObject.
Well, I can't use struct as model type now.

public struct User {
    let name = ""
}

public extension UIResponder {
    // Error
    // Xcode tips: Method cannot be marked @objc because the type of the parameter cannot be represented in Objective-C
    @objc func navigate(_ user: User) {
        coordinatingResponder?.navigate(user)
    }

    // OK
    @objc func navigateOC(_ list: [String]) {
        coordinatingResponder?.navigateOC(list)
    }
}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.