jverkoey / nimbus

The iOS framework that grows only as fast as its documentation
nimbuskit.info
Apache License 2.0
6.44k stars 1.3k forks source link

Navigation #10

Closed jverkoey closed 12 years ago

jverkoey commented 13 years ago

Nimbus Navigation Design Document

Version 1.0 - Last updated September 1st, 2011 at 10:29PM EST.

The goal with Nimbus navigation is to provide a powerful navigation feature that complements UIKit's existing navigation functionality. This includes iOS 5's Storyboards as well as pre-iOS 5 standard UINavigationController and modal controller presentation.

Design Considerations

This feature will complement the existing UIKit functionality by providing a thin, optional layer on top of UIKit. The goal is to allow a developer to use the navigator when they want to, and to use their own navigation logic when they don't. This means that we do not want deep integration with UIKit components. For example, we won't provide any UIViewController subclasses that implement navigation functionality like Three20 does. A developer should be completely able to slowly integrate the navigator into an existing project if they so desire. Imagine defining a single route and using it within an app that is otherwise completely custom navigation.

The Routing Interfaces

Nimbus Navigation will model directly off of Three20's navigation system by using URLs to map to view controllers and mapping objects to URLs. The routing functionality itself will be implemented using SOCKit.

The routing interface will look roughly like the following:

/**
 * A routing map maintains a mapping of routes from paths to view controllers and objects to paths.
 *
 * All routes are processed using SOCKit to gather parameter information from the paths. Each
 * navigator object (TODO: Name of the object here) instance has an instance of a NIRoutingMap.
 */
@interface NIRoutingMap : NSObject

#pragma mark Controller Routing

/**
 * Add a route from the given path to the given view controller.
 *
 * ## What happens when the navigator opens a path mapped with this method:
 *
 * A view controller will be created and initialized with the initWithNibName:bundle: method.
 * If willNavigateWithPatternParameters:queryParameters: is implemented on the controller then
 * it will be called.
 *
 * Any parameters matched in the path pattern will be passed to
 * willNavigateWithPatternParameters:queryParameters: on the view controller.
 *
 * If you require more explicit access to parameters then you may wish to consider using
 * fromPath:toViewController:withWillNavigateSelector: instead. Using an explicit selector is a
 * recommended means of using the compiler to enforce parameter types and type safety.
 *
 *      @param path  A path that may specify certain parameters that will be matched and sent to
 *                   willNavigateWithPatternParameters:queryParameters:. See the routing parameters
 *                   section of the navigation documentation (TODO: Add link here).
 *      @param viewController  The view controller that will be instantiated and presented when the
 *                             path is opened in the navigator.
 */
- (void)fromPath:(NSString *)path toViewController:(Class)viewController;

/**
 * Add a route from the given path to the given view controller.
 *
 * ## What happens when the navigator opens a path mapped with this method:
 *
 * A view controller will be created and initialized with the initWithNibName:bundle: method.
 * If willNavigateWithPatternParameters:queryParameters: is implemented on the controller then
 * it will be called. The willNavigateSelector will then be called.
 *
 * Any parameters matched in the path pattern will be passed to
 * willNavigateWithPatternParameters:queryParameters: on the view controller.
 *
 *      @param path  A path that may specify certain parameters that will be matched and sent to
 *                   willNavigateWithPatternParameters:queryParameters:. See the routing parameters
 *                   section of the navigation documentation (TODO: Add link here).
 *      @param viewController  The view controller that will be instantiated and presented when the
 *                             path is opened in the navigator.
 *      @param willNavigateSelector  A selector that should have at least as many arguments as
 *                                   there are parameters in the path pattern. One additional
 *                                   argument may provided which accepts an NSDictionary of
 *                                   NSArrays of query parameter values.
 */
- (void)fromPath:(NSString *)path toViewController:(Class)viewController withWillNavigateSelector:(SEL)willNavigateSelector;

/**
 * Returns a newly allocated controller that has been prepared for navigation using the
 * NIRoutingDestination protocol and willNavigateSelector, if such a selector was provided, with
 * the given path to provide the parameters.
 *
 *      @param path  A path that is used to 1. determine which controller class to instantiate and
 *                   2. provide the parameters to the NIRoutingDestination protocol and
 *                   willNavigateSelector, if such a selector is provided.
 *      @returns A newly allocated controller that has been prepared for navigation using the
 *               NIRoutingDestination protocol and willNavigateSelector, if such a selector
 *               was provided, with the given path to provide the parameters.
 */
- (id)controllerForPath:(NSString *)path;

#pragma mark Object Routing

/**
 * Add a route from an object class to a pattern string.
 *
 * This route is used by pathForObject: to generate a path for consumption by the navigator.
 *
 *      @param objectClass  The class of object that can be used to generate the given pattern
 *                          string.
 *      @param patternString  A pattern string that contains parameters that match property names
 *                            on the given object.
 */
- (void)fromObjectClass:(Class)objectClass toPatternString:(NSString *)patternString;

/**
 * Returns a path generated using the object's properties if a route exists for the given object's
 * class, nil otherwise.
 *
 *      @param object  The object whose values will be used by the pattern to generate the path.
 *      @returns A path generated using the object's properties if a route exists for the
 *               given object's class, nil otherwise.
 */
- (NSString *)pathForObject:(id)object;

@end

/**
 * A set of methods that can optionally be implemented by a routing destination.
 */
@protocol NIRoutingDestination <NSObject>

@optional

/**
 * Called immediately after the view controller is initialized by the navigator and before it
 * is presented.
 *
 * If a selector is provided with the routing map then this method will be called first and the
 * routing map's selector second.
 *
 * The parameters for this method are dictionaries of arrays of values. Even parameters that
 * only exist once in the pattern or query will be arrays of values. This is so that if any
 * parameter names are duplicated we provide a consistent means of accessing these values. The
 * parameter values will stored in the array in the order that they were defined in the path.
 *
 * jverkoey implementation note: Do not prepare these parameter dictionaries unless we need to.
 *      If it turns out that this is the only method that will use such a representation of
 *      parameters then we should only create them if the destination implements this method.
 *
 * jverkoey design consideration: Three20's url mapping technology forced you to define the
 *      selector for a mapped url in the url path and encouraged the use of initializers to
 *      initialize the view controller. For example, one might define a path in Three20 like so:
 *          @"fb://profile/(initWithUserId:)" => [FBProfileController class].
 *      Nimbus will not be going this route. Instead, Nimbus will encourage the use of auxiliary
 *      methods to receive the path parameters. This will allow devs to use standard controller
 *      initializers. This will have the net effect of not making it feel like Nimbus is taking
 *      over your app, instead allowing devs to graciously add functionality to existing
 *      controllers as they see fit.
 *      In Nimbus the Three20 example used above would look more like this:
 *          @"fb://profile/:userid" => [FBProfileController class] @selector(setUserId:)
 *      A multi-parameter url could look like this:
 *          @"fb://profile/:userid/:initialTab" => [FBProfileController class]
 *                                                 @selector(setUserId:initialTab:)
 *
 *      @param patternParameters  { "pattern parameter name" => NSArray of parameter values }
 *      @param queryParameters    { "query parameter name" => NSArray of query values }
 */
- (void)willNavigateWithPatternParameters:(NSDictionary *)patternParameters queryParameters:(NSDictionary *)queryParameters;

@end
blakewatters commented 13 years ago

I will comment in more detail on the SOCKIt implementation (which I assume supercedes the proposal here), but I would strongly advise against using the term "object mapping" to describe string <-> object addressing. In RestKit land, object mapping refers to a conversion between KVC compliant representations of the same data. I think that this is more appropriately termed "Routing" as its aligned with Rails and RestKit's terminology.

jverkoey commented 13 years ago

Ah yes, I wrote this spec as the original design doc for SOCKit and then ran with it over the weekend. I will have to update this doc now with SOCKit in mind. The next step in designing the navigator is, as you said, building the router.

0xdevalias commented 13 years ago

Ah, didn't see this one before. All sounds pretty good from my brief scan over it. I particularly like the choice to use aux methods for the 'variables' in URLs

@"fb://profile/:userid/:initialTab" => [FBProfileController class]

  • @ selector(setUserId:initialTab:)

With the 'selectors', am I right in assuming that to implement these, FBProfileController would just have 2 public methods, setUserId and initialTab?

jverkoey commented 13 years ago

@selector(setUserId:initialTab:) declares that a selector with the following prototype should be called:

- (void)setUserId:(NSInteger)userId initialTab:(NSString *)initialTab;

The argument types can be whatever you want and SOCKit will cast the type accordingly.

jverkoey commented 13 years ago

For reference:

https://github.com/jverkoey/sockit

dwery commented 13 years ago

Nice, should be much easier than Three20 and less painful to implement. Where's the Like button? :D

jverkoey commented 13 years ago

:D It will certainly be less painful to implement. SOCKit alone kills about 10-12 different files from Three20's pattern matching implementation.

jverkoey commented 13 years ago

More to come soon. I have a rough draft of the NINavigator object sitting on my laptop right now that still needs some love.

thonglinhma commented 13 years ago

I'm waiting for NINavigator :)

0xdevalias commented 13 years ago

@thonglinhma

I'm waiting for NINavigator :)

I'm definitely looking forward to it as well. I keep telling myself I will get started on my app once this is done (whether that will actually happen or not is a different story.. :p)

jverkoey commented 13 years ago

It will definitely happen :) I'm getting into a nice routine of working on Nimbus in the mornings. Now that 0.8 has been released I'm planning to start focusing on the navigator for 0.9. I'll likely start working on the navigator full tilt next Tuesday when I get back to California from Waterloo.

0xdevalias commented 13 years ago

Yay :) I'm hoping to be able to get into a routine myself, and hopefully be able to contribute more.

djMax commented 13 years ago

How's this going? Starting a new app and trying to decide whether to start w/three20 while waiting for this or just wait and work on components rather than glue.

jverkoey commented 13 years ago

I got sidetracked on something that I think is going to be really cool. I'm hoping to announce it this week. It's called Chameleon and will be part of the Nimbus framework.

jverkoey commented 13 years ago

That being said, the navigation feature is going to be delayed until I get Chameleon v1 merged into Nimbus. I know a lot of people are really hoping for the navigation feature though so don't think I've forgotten about it :)

0xdevalias commented 13 years ago

Can't wait to see what Chameleon has in store for us! :)

You say this is stalled till v1 is merged. I was thinking, if you had to pick a rough eta of when you think you might start on this, and then when you think it might be done it could help give those of us waiting on this an idea of whether it's worth holding out, or using TT in the interim? Just my 2c

jverkoey commented 13 years ago

http://blog.jeffverkoeyen.com/nimbus-chameleon

Let me know what you think :) This will be going out in 0.9.

I think I'm going to start the navigator work next week at this point. I'm going to be moving up to the city this weekend though so I'm not sure what my new routine is going to be like. Hopefully I'll still be able to keep up what I've been doing for the last three weeks hacking on Nimbus in the mornings before work and then at night when I'm not trampoline dodgeballing and the likes.

RolfKoenders commented 13 years ago

The chameleon feature is really cool! Looking forward to it. Also looking forward to the new Navigator with the SOCKit pattern! This is going to be great!

bmeurer commented 13 years ago

Yeah, that Chameleon thing looks really nice!

thonglinhma commented 13 years ago

Great! The chameleon is very cool. I can't wait to see NINavigator & Chameleon :)

0xdevalias commented 13 years ago

Chameleon looks very very cool. Good work! :)

thonglinhma commented 13 years ago

How is it coming along?

NukemHill commented 13 years ago

Thanks. I knew I'd seen mention of this somewhere, but couldn't find it. Still sometimes have trouble navigating through Github's Issue management system.

I've got a bit of a messy app right now. It relies, primarily, on Three20. So I'm doing all of the navigation with TTNavigator. If I want to use NIToolbarPhotoViewController inside of this app (much preferable to TTPhoto...), then I either need to convert NIToolbar... to inherit from TTViewController, or use [viewController presentModalViewController:animated:]. Am I correct in that assumption? I can't seem to just push NIToolbar... onto the nav stack using [viewController.navigationController pushViewController:animated:]. Nothing happens.

rogchap commented 13 years ago

@NukemHill You do not need to inherit from TTViewController to use TTNavigator. You only need to use TTViewController if you are wanting to storing view state. Just map a route to your NIToolbarPhotoViewController:

// AppDelegate.m
[map from:@"app://photo" toViewController:[MyNIToolbarPhotoViewController class]];

// ParentController.m
TTOpenURL(@"app://photo");
NukemHill commented 13 years ago

@rogchap - for whatever reason, I'd thought otherwise. I seem to remember trying this out and failing, so I guess I just assumed it couldn't be done with non-TT ViewControllers. I tried it out last night doing what you said, and it worked like a champ.

Thanks a ton for letting me know. This will definitely save me some grief in the short-term.

It's gonna be a beast converting my codebase over to Nimbus, though. It needs to happen, however, as the bloat in my apps is getting unmanageable. I've got a fundamental data structure and view management system that sits very nicely on top of CoreData. If I can get it transferred over to Nimbus piecemeal, then it'll be a huge win in the long run. And I'll have a library that is very adaptable to new CD-based projects. If I have to do it all at once, well.... :-/

breathing commented 12 years ago

so there is no navigation even for nimbus 1.0? You kept telling us that would happen but suddenly dropped the ball.. we have been waiting and waiting. now i have to switch back to three20. really disappointing.

rogchap commented 12 years ago

@breathing

We have not reached a 1.0 release yet.

Sorry you feel disappointed, but you must remember this is an young open source project where development is done in our own free time; no one is getting paid and any "deadlines" are soft deadlines. We are working as hard as possible to maintain this project so that it can be of maximum use to developers of all abilities. All of the contributors to Nimbus also have full time jobs and other life commitments.

We are always happy to take on board feature requests and priority requests, and we hear your cry for navigation; have you looked at SOCKit?

In the meantime I urge you to get involved with the project; fork the project, add bits that you think are missing and then submit us a pull request.

jverkoey commented 12 years ago

Rog already covered all the important bits, so I'll just point out that you can use Three20's navigation feature relatively standalone (i.e. you don't have to use Three20UI to use Three20Navigator). I can't make any concrete guarantees on the release date for Nimbus' navigation feature so please plan accordingly.

djMax commented 12 years ago

What would be great here is perhaps a couple paragraphs on how you plan on applying Sockit to nimbus, if it's changed or advanced from the stuff at the top of this message. I think with that some people could take a go at implementing what you've described.

jverkoey commented 12 years ago

I've decided to build a simpler navigation mechanism into Nimbus using NITableViewActions.

dwery commented 12 years ago

uhm.. that will slow down migration from Three20 to NimbusKit. Let's see if SOCKit can be applied to NITableViewActions

jverkoey commented 12 years ago

That's a sensible idea. I want to avoid introducing too much complexity into Nimbus caused by the navigation mechanism, so it will likely not be as invasive as Three20's.

dwery commented 12 years ago

Agreed. Navigation might be complex, but simplifies a lot when you need to call different parts of the app to react on notifications (I embed the internal URL in the notifications's dictionary) or want seamless deep linking within the app. Tables are often not the only source of actions.

0xdevalias commented 11 years ago

Was there ever a consensus reached on the best way to implement this sort of thing/with SOCKit/etc?

jnordberg commented 11 years ago

+1, what's the status of this?