Routable

A UIViewController->URL router.

  @router.map("profile/:id", ProfileController)

  # Later on...

  # Pushes a ProfileController with .initWithParams(:id => 189)
  @router.open("profile/189")

Why is this awesome? Because now you can push any view controller from any part of the app with just a string: buttons, push notifications, anything.

Installation

gem install routable

And now in your Rakefile, require routable:

$:.unshift("/Library/RubyMotion/lib")
require 'motion/project'
require 'routable'

Motion::Project::App.setup do |app|
  ...
end

Setup

For every UIViewController you want routable with :symbolic params, you need to define .initWithParams({}).

class ProfileController < UIViewController
  attr_accessor :user_id

  def initWithParams(params = {})
    init()
    self.user_id = params[:user_id]
    self
  end
end

Here's an example of how you could setup Routable for the entire application:

class AppDelegate
  def application(application, didFinishLaunchingWithOptions:launchOptions)

    @window = UIWindow.alloc.initWithFrame(UIScreen.mainScreen.bounds)
    @window.makeKeyAndVisible

    # Make our URLs
    map_urls

    # .open(url, animated)
    if User.logged_in_user
      @router.open("menu", false)
    else
      @router.open("login", false)
    end

    true
  end

  def map_urls
    @router = Routable::Router.router
    @router.navigation_controller = UINavigationController.alloc.init

    # :modal means we push it modally.
    @router.map("login", LoginController, modal: true)

    # :shared means it will only keep one instance of this VC in the hierarchy;
    # if we push it again later, it will pop any covering VCs.
    @router.map("menu", MenuController, shared: true)
    @router.map("profile/:id", ProfileController)

    # :resets will reset the navigation stack with the target view controller
    @router.map("messages", MessagesController, resets: true)
    @router.map("message/:id", MessageThreadController)

    # can also route arbitrary blocks of code
    @router.map("logout") do
      User.logout
    end
    @router.map("logout/:id") do |params|
      User.logout(params[:id])
    end

    @window.rootViewController = @router.navigation_controller
  end
end

Configuration of View Controllers

If you need to configure a view controller before the router navigates to it, use a block:

# Configure and push an ImageEditorController
BW::Device.camera.any.picture(media_types: [:movie, :image]) do |result|
  router.open('editor') do |controller|
    controller.image = result[:original_image]
  end
end