NavigationManager as an in-code alternative for Storyboard
Imagine a world in which you can commit and push your changes to a feature branch, create a pull request, and never having to fear a merge conflict on a storyboard.
The solution? Don’t use them.
Now let it be perfectly clear that I love storyboards, and I almost always use them. I am however always wary of merge conflicts since they’re a real hassle to solve and most of the time you end up doing your changes twice. One of the reasons I love storyboards is that they provide you with a way to delegate navigation logic to them. By executing a so called segue, you tell your storyboard to navigate to a specific view controller in a specific way. I.e.: present a
You can create an instance of the class using the first method. As an argument it takes an instance of a
As you can see, you’re not limited in your implementation of this class. An example here could be that you have a
This protocol requires your class to have a navigation method. This method needs to navigate to the destination ViewController. You can, for instance, program a push from the
This protocol extends
detailViewController
modally.
Now it would be a shame to have to revert to programming all navigation logic in all of your ViewControllers. So at AppFoundry we came up with a solution to this problem: DRYNavigationManager This is a framework for allowing you to create nice separate classes that handle all your “navigational” needs.
Now if you’re like me and you don’t like to read blog posts (you just want to get on with it), you can find the repo here: DRYNavigationManager
Let’s dive into just how the DRYNavigationManager came to be.
#The Requirements
We tried to list all the things a NavigationManger would need to do for us if we’d like to delegate the navigation logic to it. This is what we came up with:
- Single point of contact We wanted to have one class that would handle all our needs. So we didn’t have to manage 20 classes, all responsible for some part of the process.
- Work similar but improved Similar to storyboards that is, and improved in the sense that we don’t want to implement callbacks for passing on parameters and such.
- Clean code, divided into compartments We wanted to avoid having one big class that handles all our navigation.
- Clear development time errors We wanted to provide a clear understanding of what was going wrong at every time, assuming you’re not all kick-ass developers.
- Maximum extendability Navigation is something very specific for each application and we wouldn’t want to impede your creativity
DRYNavigationManager.h
#import "DRYNavigator.h"
@protocol DRYNavigationTranslationDataSource;
@class DRYNavigationDescriptor;
@interface DRYNavigationManager : NSObject
- (instancetype)initWithNavigationTranslationDataSource:(id <DRYNavigationTranslationDataSource>)navigationTranslationDataSource;
- (instancetype)initWithNavigationTranslationDataSource:(id <DRYNavigationTranslationDataSource>)navigationTranslationDataSource navigatorFactory:(id <DRYNavigatorFactory>)navigatorFactory;
- (void)navigateWithNavigationIdentifier:(NSString *)identifier parameters:(NSDictionary *)parameters hostViewController:(UIViewController *)hostViewController errorHandler:(DRYNavigationErrorHandler)errorHandler successHandler:(DRYNavigationSuccessHandler)successHandler;
@end
DRYNavigationTranslationDataSource
which I will get to later. The last method initiates a navigation itself, with a navigation identifier as a starting point. This method will go through all the steps needed to execute a navigation. If you want to see in more detail what those steps are you are more than welcome to roam about in the code.
##DRYNavigationTranslationDataSource
This is the first class you want and need to implement yourself as this will do the specific translation from your predefined navigation identifiers to your own Navigators. You have complete freedom as to how you want to organise this class, however you do need to conform to the DRYNavigationTranslationDataSource
protocol provided by the framework.
@import Foundation;
@protocol DRYNavigationTranslationDataSource <NSObject>
- (NSString *)classNameForNavigationIdentifier:(NSString *)navigationIdentifier;
@end
*.plist
file that contains the mapping from an identifier to a classname. You could even imagine doing a call to the backend to retrieve that information. This could allow you to dynamically change the navigation within your app.
##DRYNavigator/DRYSecureNavigator
Last but definitely not least, the business end of the setup are all your navigators. You would create such a class for every navigation you want to do. Granted this will create a lot of classes even for a simple app, but the advantage you have here is that they are nicely separated from each other and you can organise them in your project tree whatever way you like. When all else fails you can resort to cmd + shift + o
for finding the appropriate class. These classes also need to conform to a protocol provided by the framework, namely DRYNavigator
or DRYSecureNavigator
.
@import Foundation;
@import UIKit;
typedef void (^DRYNavigationErrorHandler)(NSError *);
typedef void (^DRYNavigationSuccessHandler)();
@protocol DRYNavigator <NSObject>
- (void)navigateWithParameters:(NSDictionary *)parameters hostViewController:(UIViewController *)hostViewController errorHandler:(DRYNavigationErrorHandler)errorHandler successHandler:(DRYNavigationSuccessHandler)successHandler;
@end
navigationController
of the hostViewController
or an other style of presentation. The hostViewController
in this context is the viewController that initiates the navigation.
You also have the option to conform to DRYSecureNavigator
@import Foundation;
#import "DRYNavigator.h"
@protocol DRYSecureNavigator <DRYNavigator>
- (void)hasAccessWithParameters:(NSDictionary *)parameters errorHandler:(DRYNavigationErrorHandler)errorHandler successHandler:(DRYNavigationSuccessHandler)successHandler;
@end
DRYNavigator
with another required method that would need to check if navigation can be executed. So, are all required parameters available? Does the initiator/user have access to the component?
The choice is up to you, do you just want to navigate or do you want to force an access check in advance.
#Conclusion
Well, I hope things are pretty clear by now. You can see that all of the requirements are pretty much delivered upon. #winning
We’ve created something to assist you in creating clean code concerning navigation. If this isn’t your cup of tea, no hard feelings. If you have any questions, remarks or just want to tell us how we should have solved this in any other way, you are more than welcome to comment on this post. We’ll try to get back to you!
Thanks for reading