{"id":777,"url":"https://github.com/Wasappli/WAAppRouting","last_synced_at":"2025-07-30T19:32:19.219Z","repository":{"id":36812396,"uuid":"41119267","full_name":"Wasappli/WAAppRouting","owner":"Wasappli","description":"iOS routing done right. Handles both URL recognition and controller displaying with parsed parameters. All in one line, controller stack preserved automatically!","archived":false,"fork":false,"pushed_at":"2016-05-19T17:25:42.000Z","size":201,"stargazers_count":587,"open_issues_count":4,"forks_count":26,"subscribers_count":20,"default_branch":"master","last_synced_at":"2024-12-01T18:19:52.478Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Objective-C","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Wasappli.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2015-08-20T21:15:20.000Z","updated_at":"2024-06-03T10:22:03.000Z","dependencies_parsed_at":"2022-07-20T10:32:06.710Z","dependency_job_id":null,"html_url":"https://github.com/Wasappli/WAAppRouting","commit_stats":null,"previous_names":[],"tags_count":5,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Wasappli%2FWAAppRouting","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Wasappli%2FWAAppRouting/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Wasappli%2FWAAppRouting/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Wasappli%2FWAAppRouting/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Wasappli","download_url":"https://codeload.github.com/Wasappli/WAAppRouting/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":228178889,"owners_count":17881105,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-01-05T20:15:31.095Z","updated_at":"2024-12-04T19:31:57.261Z","avatar_url":"https://github.com/Wasappli.png","language":"Objective-C","funding_links":[],"categories":["App Routing","Objective-C","URL Scheme"],"sub_categories":["Other free courses","Getting Started"],"readme":"# [![WAAppRouting](https://github.com/Wasappli/WAAppRouting/blob/master/images/WAAppRouting.png?raw=true)](#)\n\n[![Version](https://img.shields.io/cocoapods/v/WAAppRouting.svg?style=flat)](http://cocoapods.org/pods/WAAppRouting)\n[![License](https://img.shields.io/cocoapods/l/WAAppRouting.svg?style=flat)](http://cocoapods.org/pods/WAAppRouting)\n[![Platform](https://img.shields.io/cocoapods/p/WAAppRouting.svg?style=flat)](http://cocoapods.org/pods/WAAppRouting)\n\n**Developed and Maintained by [Ipodishima](https://github.com/ipodishima) Founder \u0026 CTO at [Wasappli Inc](http://wasapp.li).** (If you need to develop an app, [get in touch](mailto:contact@wasapp.li) with our team!)\n\nSo what is this library useful for? Good question. Let's answer by asking an other question. Have you been struggled at some point with the following issues?\n\n- Well, I need to add some shortcuts to some parts of my apps and it seems crappy to manually allocate the path and select the controllers I need.\n- I'm tired of using the push view controller method.\n- I wish I had some kind of url handling to get to specific parts of my app just as easily as snapping a finger.\n- If it could even handle a controller stack this would just be awesome.\n- I found a library but it's not working with my custom container...\n- I found a great library but the route matching is not working with my requirements...\n\nAll this points are answered by `WAAppRouting` (and more)\n\n## Which Uses?\n- For all iOS: enable linking in your app. It is always useful to tell your app to go to `home/events/3/register` with some magic involved.\n- For iOS 9: supports deeplinks (like [Twitter app](https://itunes.apple.com/us/app/twitter/id333903271?mt=8)). Opening this URL [Me on twitter](http://twitter.com/ipodishima) would opened directly the app instead of the website.\n- For iOS 9: respond to a [search event](#search). By using [CoreSpotlight](https://developer.apple.com/library/prerelease/ios/documentation/General/Conceptual/AppSearch/index.html#//apple_ref/doc/uid/TP40016308-CH4-SW1), users can go to your app by opening from a search result like a booking. At this point, you need to consider to `goto://bookings/bookingFromSearchID`. Please take a look at an implementation of routing using a library which automatically index your `CoreData` stack [WACoreDataSpotlight](https://github.com/Wasappli/WACoreDataSpotlight).\n- For iOS 9 and more specifically new iPhones 6S and 6+S: respond to [3D Touch](#3d-touch)\n\n\n## Table of Contents\n1. [The story](#the-story)\n2. [Install and use](#install-and-use)\n3. [Go deeper](#go-deeper)\n\n#The story\n## What motivated me\nLet's be honest, there are several routing libraries on Github to handle some of the behaviors described. But none of them fit all my requirements. So I wrote this library with some things in mind:\n\n- Handle a **stack** of controllers. \n\nThis is not ok to open the app on a hotel detail if there is not even a back button, or if the back button sends me back to where I was before opening the app. I just want the app to be opened so that when I hit back, I'm on the hotels list for the hotel city...\n\n- Do not force you to get this working **with my** route matcher, or **my** route handler. \n\nIf you want to provide your own, you should be able to do it.\nThis last point is very important to me. I used (and use) too many libraries which are tightly tied to their technologies. Plus, the more they are dependant on their implementation, the less it is testable.\nThis is why you'll see many protocols with a default implementation provided.\n\n- iOS 9 is coming (or here already when you are reading this). And with iOS 9 comes this great feature called [universal links](https://developer.apple.com/library/prerelease/ios/documentation/General/Conceptual/AppSearch/UniversalLinks.html#//apple_ref/doc/uid/TP40016308-CH12). Well, I wanted something clean to address this new feature.\n\n## Inspiration\nHistorically, I first used [HHRouter](https://github.com/Huohua/HHRouter) and implemented my own stack controller management. Then, by rewriting code to support iOS 9, I saw that it was just a bunch of lines with no error management, tightly tied to the controller hierarchy, not very readable, etc.\n\nI decided to drop it and get something more fun. I found [DeepLinkKit](https://github.com/usebutton/DeepLinkKit) and used it until I realized it wasn't fitting my stack requirement.\nSo I rewrote a custom route handler to deal with it and finally arrived to the conclusion that 80% of DeepLinkKit was not used anymore. This is when I decided to drop it and write my own.\n\nSo you might recognize some concepts of the two libraries, especially in the router handler, even if the implementation has nothing to do with DeepLinkKit.\n\n# Install and use\n## Requirements alongs with the default implementation\n- The implementation I provide uses `UINavigationController` to handle the stack and can be used on `UITabBarController` as well.\n- The route matching works on `:itemID` and uses `*` as the wildcard character.\n\n## Installation\n### CocoaPods\nUse CocoaPods, this is the easiest way to install the router.\n\n`pod 'WAAppRouting'`\n\nIf you want to link `WAAppRouting` into an iOS app extension (or a shared framework that is linked to an app extension), you'll need to ensure that the `WA_APP_EXTENSION` flag is set when you compile the framework.  To do so using CocoaPods, add this to your `Podfile`:\n\n```ruby\npost_install do |installer|\n  installer.pods_project.targets.each do |target|\n    if target.name =~ /WAAppRouting/\n      target.build_configurations.each do |config|\n        config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= []\n        config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] \u003c\u003c 'WA_APP_EXTENSION=1'\n      end\n    end\n  end\nend\n```\n\nOlder versions of cocoapods may require you to use `installer.project` instead of `installer.pods_project`, so if you get an error complaining that `pods_project` does not exist, update your cocoapods gem.\n\n### Setup the router: easiest method\nImport `#import \u003cWAAppRouting/WAAppRouting.h\u003e` and you are good to start.\n\nYou also need to configure a URL scheme (I won't go into detail about this - there is plenty of documentation out there)\n\nA navigation controller is a good start:\n\n```\nUINavigationController *navigationController = [[UINavigationController alloc] init];\n```\n    \nYou'll need first to allocate a route matcher. You can use the default I wrote or create your own:\n\n```objc\n// Create the default router\nself.router = [WAAppRouter defaultRouter];\n```\n\nRegister you path using the syntax:\n- `url_path_component{ClassName}`\n- `url_path_component1{ClassName1}/url_path_component2{ClassName2}`\n \nOptionaly, you can trigger the modal presentation using `!` character. \nFor example: `url_path_component{ClassName}/modal{ModalClass}!` would result when calling `appscheme://url_path_component/modal` to `ModalClass` instance presented modally after placing `ClassName` in the navigation controller stack.\n\n```objc\n// Register the path\n[self.router.registrar\n registerAppRoutePath:\n @\"list{WAListViewController}/:itemID{WAListDetailViewController}/extra{WAListDetailExtraViewController}\"\n presentingController:navigationController];\n```\n\nAdd a block handler if needed\n\n```objc\n// Register some blocks\n[self.router.registrar registerBlockRouteHandler:^(WAAppLink *appLink) {\n    // Do something every time we are in list/something\n}\n                            \t\t\tforRoute:@\"list/*\"];\n```\n\nFinally set the navigation controller as the root controller:\n\n```objc\nself.window.rootViewController = navigationController;\n```\n\nYou can now use it and open the app with:\n\n```objc\n[self application:(UIApplication *)self openURL:[NSURL URLWithString:@\"appscheme://list\"] sourceApplication:nil annotation:nil];\n```\n\nDon't forget to use the router!\n\n```objc\n- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation {\n    return [self.router handleURL:url];\n}\n```\n\nEach controller you use should implement `WAAppRouterTargetControllerProtocol` (it is a good idea to have a base view controller)\nSo implement this method and voilà:\n\n```\n- (void)reloadFromAppLinkRefresh {\n    // You can do something with self.appLink\n    // But more important: with self.appLinkRoutingParameters which has merged route|query|default parameters\n    NSString *articleTitle = self.appLinkRoutingParameters[@\"article_title\"];\n}\n```                      \n### Setup the router: more control methods\nStart with the easiest method but replace the \"create paths\" by creating entities entities\n\n```objc\n// Create the entities\nWAAppRouteEntity *list1Entity =\n[WAAppRouteEntity routeEntityWithName:@\"list\"\n                                 path:@\"list\"\n                sourceControllerClass:nil\n                targetControllerClass:[WAListViewController class]\n                 presentingController:navigationController\n             prefersModalPresentation:NO\n             defaultParametersBuilder:nil\n                    allowedParameters:nil];\n\nWAAppRouteEntity *list1DetailEntity =\n[WAAppRouteEntity routeEntityWithName:@\"listDetail\"\n                                 path:@\"list/:itemID\"\n                sourceControllerClass:[WAListViewController class]\n                targetControllerClass:[WAListDetailViewController class]\n                 presentingController:navigationController\n             prefersModalPresentation:NO\n             defaultParametersBuilder:^id\u003cWAAppRouterParametersProtocol\u003e {\n                 \n                 NSMutableDictionary *defaultParameters = [NSMutableDictionary new];\n                 defaultParameters[@\"defaultParam\"]  = @1;\n                 defaultParameters[@\"defaultParam2\"] = @\"Default parameter 2\";\n                 return defaultParameters;\n             }\n                    allowedParameters:nil];\n```\n\nAdd the entities to the registrar\n\n```objc\n// Register the entities\n[self.router.registrar registerAppRouteEntity:list1Entity];\n[self.router.registrar registerAppRouteEntity:list1DetailEntity];\n```\n\n## Samples\nFour samples are available:\n\n- `SimpleExample`: This is a sample which handle a list, it's detail and an extra. This could be seen as an article lists, its detail and comments.\n- `SimpleExampleParameters`: This sample is the same as `SimpleExample` but is using the `WAAppLinkParameters` (the one more thing of this library).\n- `MoreComplexExample`: This sample demonstrates how to deal with a tab bar controller + how to handle modals.\n- `PPRevealSample`: This sample acts as a demonstration that with a little bit of effort, custom container can fit into the routing library?\n \n## Documentation\nThe code is heavily documented, you should find all your answers. Otherwise, open an issue and I'll respond as quickly as possible.\n\n# Go deeper\n## Pre-configure all controllers with objects.\nYou might want to pass values to the controllers when they are allocated.\nFor example in a project I'm involved in, we have an image cache that the controllers needs to display images. This image cache is allocated by the App delegate (to avoid singletons and get more testable code).\nFor doing this, you need to add a block implementation to the route handler:\n\n```objc\n    [routeHandler setControllerPreConfigurationBlock:^(UIViewController *controller, WAAppRouteEntity *routeEntity, WAAppLink *appLink) {\n        if ([controller isKindOfClass:[WABaseViewController class]]) {\n            ((WABaseViewController *)controller).imageCache = imageCache;\n        }\n    }];\n```\n\n## Forbid specific entities to be shown\nYou can ask not to handle some routes at runtime by setting this block (for example you might not want to display some controllers if not logged in):\n\n```objc\n    [routeHandler setShouldHandleAppLinkBlock:^BOOL(WAAppRouteEntity *entity) {\n        // Could return NO if not logged in for example\n        return YES;\n    }];\n```\n\n## Wildcard URL\nYou can have wildcard urls like `list/*/extra` meaning that for any value instead of the `*`, the entity or the block would be executed. Avoid using it with entities but rather with block.\nAn url in form of `list/*` will match both `list/path` and `list/path/extra`\n\nHere is an example of an alert triggered each time we are after `list/`\n\n```objc\n[registrar registerBlockRouteHandler:^(WAAppLink *appLink) {\n        [RZNotificationView showNotificationOn:RZNotificationContextAboveStatusBar\n                                       message:[NSString stringWithFormat:@\"You are dealing with item ID %@\", appLink[@\"articleID\"]]\n                                          icon:RZNotificationIconInfo\n                                        anchor:RZNotificationAnchorNone\n                                      position:RZNotificationPositionTop\n                                         color:RZNotificationColorYellow\n                                    assetColor:RZNotificationContentColorDark\n                                     textColor:RZNotificationContentColorDark\n                                withCompletion:^(BOOL touched) {\n                                    \n                                }];\n    }\n                                forRoute:@\"list/*\"];\n```\n\n## Customize router behavior\nAs said, I hate libraries you cannot customize without forking and diverging from the original source.\nThat said, you can customize the router in two ways: custom route matcher and custom route handler.\n\n### Custom route matcher\nMy implementation deals with basics. Meaning that it won't support `key=value1, value2` for the query for example. It is also case sentitive.\nIf you have your own URL configuration like `list/$itemID` implementing a new route matcher is a good idea!\n\nTo start, read the `WAAppRouteMatcherProtocol` class. You have two methods you need to implement: `matchesURL: fromPathPattern:` and `parametersFromURL: withPathPattern:`.\nAs you can see in my implementation, I'm using `WARoutePattern` to match the URL. It's kind of inspired by SocKit (for the naming convention).\n\nThen, you can easily create the router with:\n\n```objc\n// Allocate your route matcher\nMyRouteMatcher *routeMatcher = [MyRouteMatcher new];\n\n// Create the Registrar\nWAAppRouteRegistrar *registrar  = [WAAppRouteRegistrar registrarWithRouteMatcher:routeMatcher];\n\n// Create the route handler\nWAAppRouteHandler *routeHandler = [WAAppRouteHandler routeHandlerWithRouteRegistrar:registrar];\n\n// Create the router\nWAAppRouter *router = [WAAppRouter routerWithRegistrar:registrar\n                                          routeHandler:routeHandler];\n```\n\n### Custom route handler\nIf for example, you don't want to handle a stack, or use something else than a `UINavigationController`, then consider creating your own route handler.\nStart by adopting `WAAppRouteHandlerProtocol` protocol. And then read `WAAppRouteHandler` to get inspiration.\n\n## iOS 9 support\nI still need to run some tests, but the idea is to have a router for the classic url scheme, and another one for universal links.\n\n### 3D Touch\nBy implementing [3D Touch](https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/Adopting3DTouchOniPhone/), you can have users opening your app directly on some actions like `new tweet`, `search for tweets`, `get direction`, ...\nAll you have to do is follow the documentation for `UIApplicationShortcutItem` [here](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIApplicationShortcutItem_class/index.html#//apple_ref/occ/cl/UIApplicationShortcutItem) and then:\n\n```objc\n- (void)application:(UIApplication * _Nonnull)application\nperformActionForShortcutItem:(UIApplicationShortcutItem * _Nonnull)shortcutItem\n  completionHandler:(void (^ _Nonnull)(BOOL succeeded))completionHandler {\n    if ([shortcutItem.type isEqualToString:@\"newTweet\"]) {\n        // goto://home/newTweet\n    }\n}\n```\n\n### Search\nUsing [WACoreDataSpotlight](https://github.com/Wasappli/WACoreDataSpotlight) for example (the samples uses `WAppRouting`), you can respond to `open app from search item` events.\n\n```objc\n- (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void (^)(NSArray * _Nullable))restorationHandler {\n    NSManagedObject *object = [self.mainIndexer objectFromUserActivity:userActivity];\n    \n    if ([object isKindOfClass:[Company class]]) {\n        [AppLink goTo:@\"companies/%@\", ((Company *)object).name];\n    }\n\n    return YES;\n} \n```\n\n## Special configuration consideration\n### Custom container controller\nYou should definitively take a look at the `PPRevealSample` project to get an idea of how to get this working. I'm also here to help if needed.\nBasic idea is try to allocate all the navigation controllers you need and pass them as the presenting controller. Then, this would behave like a tab bar (see it's category).\nIf not, then you are in the `PPRevealSideViewController` context where the navigation controller gets allocated on the fly. The idea is to pass the container as the `presentingController` property and implement the `WAAppRoutingContainerPresentationProtocol` protocol (you need the optional method as well).\n\n### Modal with navigation controller\nYou cannot (yet) present a modal controller and then push a detail one. Like presenting a login view but pushed onto the signup controller.\n\n### Reuse controllers at different locations\nBecause the stack retrieval is implemented as dealing with controller class unicity, you cannot have two or more `WAAppRouteEntity` with the same target class when the source controller class is not nil.\nIf you need to reuse the controller at differents places, consider creating simple subclasses of a main controller which handles all the behavior.\n\n## The one more thing: avoid having parameters keys hardcoded\n###Purpose\nThere is a one more thing to this library which is `WAAppLinkParameters` class.\nThe idea behind is to avoid hardcoding the values. \nThe behavior is based on an implementation of `WAAppRouterParametersProtocol` protocol, which means that you can provide your own or subclass `WAAppLinkParameters` which provides default behavior and all protocol methods implementation.\nLet's have a look to an example you can find on `SimpleExampleParameters` sample project.\n\n### Example\nFirst, create a subclass:\n\n```objc\n@interface ArticleAppLinkParameters : WAAppLinkParameters\n\n@property (nonatomic, strong) NSNumber *articleID;\n@property (nonatomic, strong) NSString *articleTitle;\n@property (nonatomic, strong) NSNumber *displayType;\n\n@end\n```\n\nYou can see here three objects which should be mapped to the url keys\nYou need to override the `mappingKeyProperty` getter to provide a mapping `url_key: object_property`:\n\n```objc\n- (NSDictionary *)mappingKeyProperty {\n    return @{\n             @\"articleID\": @\"articleID\",\n             @\"article_title\": @\"articleTitle\",\n             @\"display_type\": @\"displayType\"\n             };\n}\n```\n\nThere is a category I wrote on `UIViewController` which configures the object with merging for you.\nSo, you can now get the value directly with:\n\n```objc\nself.label.text = [NSString stringWithFormat:@\"ArticleID: %@\", ((ArticleAppLinkParameters *)self.appLinkRoutingParameters).articleID];`\n```\n\nYou can copy the parameters, and set values for a future use:\n\n```objc\nArticleAppLinkParameters *params = [(ArticleAppLinkParameters *)self.appLinkRoutingParameters copy];\nparams.articleTitle = [NSString stringWithFormat:@\"My super article %ld\", (long)indexPath.row];\n```\n\nGrab the query with a white list:\n\n```objc\nNSString *query = [params queryStringWithWhiteList:@[@\"articleID\", @\"articleTitle\", @\"displayType\"];\n```\n\n### Advantages\n- Update at any time the key on URLs without touching your code.\n- Avoid regressions when you add new parameters in the URL.\n- Easily build queries for moving from one controller to another.\n- Provide default values to a controller initialization. All your configuration is done in one place.\n\n### Notes\n- Only `NSString` and `NSNumber` parameters are supported at this time (no `NSDate` for example)\n- This could seems to be a pain in the ass to implement rather than using the parameters directly. True but keep in mind I thought about this one in a large project with big maintenance and evolutions plan involved. \n\n#Contributing : Problems, Suggestions, Pull Requests?\n\nPlease open a new Issue [here](https://github.com/Wasappli/WAAppRouting/issues) if you run into a problem specific to WAAppRouting.\n\nFor new features pull requests are encouraged and greatly appreciated! Please try to maintain consistency with the existing code style. If you're considering taking on significant changes or additions to the project, please ask me before by opening a new Issue to have a chance for a merge.\n\n#That's all folks !\n\n- If your are happy don't hesitate to send me a tweet [@ipodishima](http://twitter.com/ipodishima)!\n- Distributed under MIT licence.\n- Follow Wasappli on [facebook](https://www.facebook.com/wasappli)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FWasappli%2FWAAppRouting","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FWasappli%2FWAAppRouting","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FWasappli%2FWAAppRouting/lists"}