Insights

Tutorial: Building a RubyMotion iPhone App Using ProMotion

Jamon Holmgren

Category: Tutorials

By: Jamon Holmgren, posted November 2, 2012, updated May 27, 2013

I’m Jamon Holmgren, one of the creators of ProMotion, a RubyMotion gem that makes iPhone development less Objective-C-like and more Ruby-like. I’m going to demonstrate building a simple iPhone app using ProMotion.

What’s ProMotion good for? ProMotion abstracts a ton of boilerplate UIViewController, UINavigationController, and other iOS code into a simple, Ruby-like DSL. We built an app called BigDay! app using ProMotion and Parse.com’s PAAS.

You can find the source for this tutorial on GitHub.

Let’s dive in.

Prerequisites

You need a RubyMotion license ($199.99). It’s worth the money. Buy it and install it. You also need a modern Mac computer and XCode 4.5 installed.

Create New App

In Terminal, navigate to your projects folder and create a new RubyMotion app:

motion create promotion-tutorial

Now “cd” into the new folder it creates.

cd promotion-tutorial

Add Bundler

Open up the editor of your choice in that folder. I use the great Sublime Text 2 and have an alias set up for opening folders.

subl .

The folder structure looks like this:

Open up the Rakefile and add the following right below the require 'motion/project' part:


require "bundler"
Bundler.require

Now create a Gemfile in the app’s root folder and put the following:


source :rubygems

gem "ProMotion", "~> 0.6.4"

Go to Terminal and run bundle. This will install ProMotion.

AppDelegate

Go to /app/app_delegate.rb. This is the first file that gets loaded when your app runs. Replace everything in that file with this:



class AppDelegate < ProMotion::Delegate
  
  def on_load(app, options)
    open HomeScreen.new(nav_bar: true)
  end
  
end

The open method loads new screens into the view. In this case, we want a UINavigationController and nav bar, so we tell the new screen that.

HomeScreen

In /app, create a new folder called “screens”. This is where you’ll put all of your app’s primary screens. Now create a file called “home_screen.rb” in that folder and put the following in it:


class HomeScreen < ProMotion::GroupedTableScreen
  # We’ll add stuff here later
end

This will be the first screen loaded into your app. We’re making this screen a GroupedTableScreen.

Add a method inside HomeScreen called table_data and add the following:


def table_data
  [{
    title: "ProMotion",
    cells: [
      { title: "About ProMotion", action: :about_promotion },
      { title: "About Jamon", action: :about_jamon }
    ]
  }, {
    title: "Help",
    cells: [
      { title: "Support", action: :support },
      { title: "Feedback", action: :feedback }
    ]
  }]
end

If your screen is some type of TableScreen, the table_data method automatically gets called by ProMotion to build the table for iOS’s UITableViewController. It’s very powerful, but we’ll focus on the basics for now.

The outside [] is an array of table groups. The hashes inside take two fields: title and cells. The title (if present) puts a title on the group and the cells is an array of cells. Each cell takes several arguments such as title, action, arguments, accessory, accessory_action, accessory_default.

For now, we’ll just give each cell a title and an action.

Run the App

Let’s spin up the app and see what happens.


rake

You should see the following:

Heck ya! We’re iOS devs now. Try tapping the buttons. If you watch your REPL (the Terminal output) you’ll see that we haven’t implemented the action methods in our screen yet.

Let’s do that.

Customize the HomeScreen

But first, let’s give the HomeScreen a better title. Go to the top of the home_screen.rb file and add it:


class HomeScreen < ProMotion::GroupedTableScreen
  title "ProMotion Tutorial"

That’s it. Just title and what you want at the top of the screen.

Now, let’s add those methods:


def about_promotion
  p "About ProMotion tapped!"
end
def about_jamon
  p "About Jamon tapped!"
end
def support
  p "Support tapped!"
end
def feedback
  p "Feedback tapped!"
end

“p” is like “puts” in Rails...it outputs to the console. It’s really handy for debugging. You can also use $stderr.puts which works better in some cases.

Run the app again (Ctrl+C in Terminal to kill it and then rake again). Now, when you tap each button you’ll get a nicer message.

Let’s make one of these open a new screen.

Opening New Screens

The first button is About ProMotion so let’s make that actually work. Replace the p command with an open command like this:


def about_promotion
  open AboutProMotionScreen
end

Create another file in /app/screens called about_pro_motion_screen.rb and add this code:


class AboutProMotionScreen < ProMotion::Screen
  title "About ProMotion"
end

Re-start the simulator and tap the first item in the table. You should see a black screen slide in with the right title. Yeah! I love watching these screens slide in. It makes me feel powerful.

Styling Screens

This is a regular (non-table) screen, so we’ll need some visual elements to make it look good. In iOS apps you build your UI elements using UIKit’s objects. You can either subclass them (probably the best for complex apps) or manipulate instances of them directly (fine for small apps).

Let’s just make a few instances using the really handy ProMotion add_element method. Add this method to your new screen:


def will_appear
  self.view.backgroundColor = UIColor.darkGrayColor
  add UILabel.alloc.initWithFrame(CGRectMake(25, 50, 275, 150)), {
    text: "ProMotion is a new way to easily organize and develop RubyMotion apps using the concept of screens.",
    border_style: UITextBorderStyleRoundedRect,
    background_color: UIColor.whiteColor,
    font: UIFont.systemFontOfSize(14),
    number_of_lines: 0,
    line_break_mode: UILineBreakModeWordWrap,
    layer: {
      border_width: 5,
      corner_radius: 15,
      border_color: UIColor.grayColor
    }
  }
end

Okay, that looks complicated. It’s not really that bad, though. The self.view refers to the background view of the current screen and I set its backgroundColor attribute to a dark gray (yeah, this is going to look epic). The add method takes two arguments: one, the item we’re adding, and two, a hash of attributes to set.

Fire the new version of the app up and tap into the About RubyMotion screen. Yep, it’s beautiful:

Well, okay, maybe not. But, as a teaser, here’s an example of a screen that was styled in pretty much the same way:

Not too shabby.

Communicating Between Screens

Sometimes screens need to tell each other something. Say we want to tell the previous screen that we saved some data and it needs to refresh. Let’s mock it up here (although we won’t actually save any data).

In the AboutProMotionScreen’s will_appear method, add the following:


def will_appear
  set_nav_bar_right_button "Save", action: :save

This will add a button on the top right that says “Save”. When tapped, it will call a “save” method, so let’s implement that:


def save
  close saved: true
end

And back in the HomeScreen (the screen we came from), add this method:


def on_return(args={})
  if args[:saved]
    self.title = "Saved!"
  end
end

Run the app now, navigate to About ProMotion, then tap “Save” in the top right. Viola! The title should now say “Saved!”.

Basically, anything given to close will be passed to the on_return method of the original screen. This is an opportunity to refresh data, display something to the user, or do whatever else you need to do. It's fairly limited, so if you need something more powerful look at the NSNotificationCenter (https://github.com/rubymotion/BubbleWrap#nsnotificationcenter

Searchable Table Screen

Let’s add another button to the main menu. Add a line below the “Support” cell line:


cells: [
  { title: "Support", action: :support },
  { title: "Articles", action: :articles },
  { title: "Feedback", action: :feedback }
]

Add an articles method:


def articles
  open ArticlesScreen
end

Create a new file in /app/screens called articles_screen.rb and add this code:


class ArticlesScreen < ProMotion::TableScreen
  title "Articles"
end

Remember that TableScreens need a table_data method:


def table_data
  [{
    title: "Knowledge Base",
    cells: [
      { title: "How to do something", action: :open_article },
      { title: "See it in action", action: :open_article },
      { title: "Learn something new", action: :open_article },
      { title: "Do something else", action: :open_article }
    ]
  }, {
    title: "Frequently Asked Questions",
    cells: [
      { title: "Why is RubyMotion so cool?", action: :open_article },
      { title: "How come Jamon is so smart?", action: :open_article }
    ]
  }]
end

To make things easier I’ve made the actions all call the same method. Since ProMotion passes in the cell, it makes it pretty easy to figure out which one was tapped:


def open_article(args)
  p args[:cell][:title]
end

Of course, normally you’d want to set some arguments of your own, like a database record id. You can do that this way:


{ title: "Learn something new", action: :open_article, arguments: { id: some_id } }

We’ll skip that for now.

So, running the app and going to the Articles screen, we get this:

Let’s make it searchable. This is really hard, so buckle up. Add this to the top of ArticlesScreen:


class ArticlesScreen < ProMotion::TableScreen
  title "Articles"
  searchable placeholder: "Search Articles"

Yeah, that’s it. Just “searchable” and if you want a placeholder, add it. Re-running the app, you should see this:

Wrapping Up

There’s a lot more to ProMotion than that. Go ahead and build out the other screens if you want and check out the README on Github. If you have questions, hit me up on Twitter: @jamonholmgren

Have fun!

Jamon Holmgren

jamon@clearsightstudio.com

@jamonholmgren