
Get Started with Ruby Motion!
Ask any Ruby developer, especially one with a background in Java, C or C++, and they’ll tell you just how much better their life is since discovering Ruby. Ruby’s syntax is concise, simple, and consistent, which makes it very easy to learn and to use.
Despite it’s simplicity, Ruby has extensive metaprogramming features; this makes it a very powerful language for developing scalable, developer-friendly applications.
RubyMotion helps iOS and Mac developers create full-featured, native apps that are as slick and performant as apps written in Objective-C or Swift — only they’re written in the fun, elegant and flexible Ruby language. Goodbye, Xcode! :]
In this 2-part RubyMotion tutorial series, you’ll build a simple iPhone application from scratch using RubyMotion and learn the basics of RubyMotion.
You’ll need the following basic knowledge to get the most out of this RubyMotion tutorial:
- Experience in developing for iOS in Objective-C
- Basic understanding of Ruby
- Basic knowledge of CSS
- Basic experience with Terminal
If you need a Ruby refresher, there’s a great free guide at CodeSchool. As for iOS development, we have tons of iOS tutorials to help you along.
With those prerequisites met, you’re ready to begin!
Getting Started
RubyMotion isn’t free; to get started you’ll have to purchase a license from RubyMotion. It’s $199.99, but in my opinion it’s a worthwhile investment for the time savings it offers.
Download and install RubyMotion as per the Getting Started guide on the RubyMotion site.
While you’re waiting for RubyMotion to finish downloading, you can learn some of the similarities — and the differences — between RubyMotion and Ruby below.
Differentiating RubyMotion vs. Ruby
RubyMotion uses a Ruby-like syntax. In order to bridge the gap between Ruby and Objective-C, RubyMotion has a few differences from the reference implementation of Ruby – the key difference being how you name method parameters.
While Ruby 2.0 does support named parameters, RubyMotion considers the parameters as part of the method name itself, just as they are in Objective-C.
Consider the following method definitions:
def tableView(table_view, cellForRowAtIndexPath: indexPath) # ... end def tableView(tableView, heightForRowAtIndexPath: indexPath) # ... end |
In Ruby 2.0.0 and later, the second method definition would overwrite the first. However, RubyMotion would consider them as two distinct methods.
RubyMotion also handles the parameter names differently: in Ruby, the parameter key (heightForRowAtIndexPath
in this case) becomes the local variable available in the method, while in RubyMotion the parameter value itself is available to the method, much like Objective-C.
Finally, RubyMotion is a compiled language; while Ruby is an interpreted language.
Introducing Pomotion
Your task in this RubyMotion tutorial is to develop a productivity tool based on the famous Pomodoro Technique. The app consists of a 25-minute countdown timer which can help you develop a more productive pattern of working for a focused period and then taking a short rest.
Open up Terminal, move to a directory of your choice (I used ~/Gavin/Code/
) and run the following command:
motion create Pomotion |
You should see the following output:
Create Pomotion Create Pomotion/.gitignore Create Pomotion/app/app_delegate.rb Create Pomotion/Gemfile Create Pomotion/Rakefile Create Pomotion/resources/Default-568h@2x.png Create Pomotion/spec/main_spec.rb |
The motion
command creates a basic template app containing the bare essentials to get you started.
Change to the newly created directory using the cd
command as follows:
cd Pomotion |
Then open the directory in Finder:
open . |
You should see the following files:
Here’s what’s in each of those files:
Gemfile
If you’ve used Cocoapods before, you can think of the Gemfile as being similar to a Podfile.
Ruby offers thousands of libraries or gems, most of which are open source code projects maintained by the Ruby community. Many of these gems are directly compatible with RubyMotion and a few are now being written especially for RubyMotion.
Using the bundler gem with this Gemfile lets you easily include other gems in your project and manage your app’s dependencies. This makes it easier to share code between developers.
Rakefile
Rake is Ruby’s answer to Make.
A Rakefile
is equivalent to a Makefile
. Rake lets you specify tasks, set configurations, and add dependencies within your application. Rake ships with OS X so you should be able to run it from Terminal without any problems.
Open the Rakefile in your favorite text editor; you’ll see it already has one pre-set configuration option: the app name Pomotion
. There are several default configurations you can also change yourself; you can find a list of them in the RubyMotion Project Management Guide.
Head back to Terminal and run the following command:
rake config |
This shows you your current configuration like so:
background_modes : [] build_dir : "./build" codesign_certificate : "iPhone Developer: Gavin Morrice (X999X9X9XX)" delegate_class : "AppDelegate" deployment_target : "7.1" device_family : :iphone embedded_frameworks : [] entitlements : {} external_frameworks : [] files : ["./app/app_delegate.rb"] fonts : [] framework_search_paths : [] frameworks : ["UIKit", "Foundation", "CoreGraphics"] icons : [] identifier : "com.yourcompany.Pomotion" interface_orientations : [:portrait, :landscape_left, :landscape_right] libs : [] manifest_assets : [] motiondir : "/Library/RubyMotion" name : "Pomotion" prerendered_icon : false provisioning_profile : "/Users/Gavin/Library/MobileDevice/Provisioning Profiles/XXXX.mobileprovision" resources_dirs : ["./resources"] sdk_version : "7.1" seed_id : "XXXXXXXX" short_version : nil specs_dir : "./spec" status_bar_style : :default version : "1.0" weak_frameworks : [] xcode_dir : "/Applications/Xcode.app/Contents/Developer" |
Some of those configuration settings will likely be familiar to you, others, like motiondir
and xcode_dir
are unique to RubyMotion.
Overriding Configuration Options
You’ll override a couple of these defaults with your own preferences. Since Pomotion is still under development, the version number should probably be lower than 1.0.0
. Also, you won’t support landscape orientation just yet.
Open Rakefile and add the following lines within the Motion... do
block:
app.interface_orientations = [:portrait] app.version = "0.1.0" |
Your entire Rakefile should now look like this:
# -*- coding: utf-8 -*- $:.unshift("/Library/RubyMotion/lib") require 'motion/project/template/ios' begin require 'bundler' Bundler.require rescue LoadError end Motion::Project::App.setup do |app| # Use `rake config' to see complete project settings. app.name = 'Pomotion' app.interface_orientations = [:portrait] app.version = "0.1.0" end |
Run rake config
to verify the changes you just made to the configuration. You should now see that version
and interface_orientations
have been changed for your app – and all it took was a quick modification to the Rakefile.
The “app” Directory
app is where most of your application’s code should live; you’ll store your views, controllers, and models there. RubyMotion will automatically load all files in this directory with a .rb
extension.
The “build” Directory
build by default will contain all of your compiled code; you create it the first time you run rake
against your project. However, you can specify an alternate directory for your compiled code in the build_dir
parameter of your Rakefile.
From time to time you might want to clear the build directory and rebuild the whole application from scratch; simply run rake clean
within your project directory in Terminal to accomplish this.
The “resources” Directory
resources is where you add any extra resources to your bundle, including icons, images, fonts and other files.
The “spec” Directory
spec is where you store specs, or tests, for your app. The Ruby community has a long-established culture of writing well-tested code, which includes writing the tests before writing any code.
You won’t cover Ruby tests in this tutorial — that’s a whole other subject best left for another time! :]
Hello World Example
No tutorial would be complete without a “Hello World” example, and this one is no exception.
Open app/app_delegate.rb and add the following lines above application:didFinishLaunchingWithOptions
:
def hello_world_label @hello_world_label ||= begin frame = CGRectMake(20,200,280,40) label = UILabel.alloc.initWithFrame(frame) label.text = "Hello world" label.textColor = UIColor.whiteColor label.textAlignment = UITextAlignmentCenter label end end def window @window ||= UIWindow.alloc.initWithFrame(UIScreen.mainScreen.bounds) end |
Here’s what the above code does:
First, you define a method named hello_world_label
, which is a getter method for a UILabel that the app will display on the screen. Unlike a standard getter though, which would simply return the value of @hello_world_label
, this method goes a step further. The ||=
(or-equals) operator here means “if the variable on the left is truthy return its value; otherwise execute the code to the right of the operator and assign its value to the variable for future calls”.
This is a great way to lazy-load a property in that its value is only set when you actually call the method; in addition, this is a memoized method which caches its value, making future calls to this method execute faster.
Next, you define a method named window
, which returns the main UIWindow object for this application. Again, you use the ||=
operator to set a default value if one is not already set.
Note: The name of this method is hello_world_label
, not helloWorldLabel
as you might be used to seeing. Why?
In Objective-C, you usually write method variable names in CamelCase
; in Ruby it’s general practice to write them in snake_case
. Since RubyMotion is a hybrid between the two, you’ll often see RubyMotion developers use either CamelCase or snake_case: there doesn’t seem to be an agreed upon convention.
What’s my preference? Both! :] When writing code that’s part of the iOS API such as when defining a tableView:cellForRowAtIndexPath
method, I’ll use CamelCase — even for the parameters — since that’s how it was defined in the API. I’ll use snake_case with code that’s specific to the application. This clearly delineates my code from iOS API code — and it means I can re-use my plain old Ruby classes in other non-RubyMotion applications without having to modify them.
Still in the same file, update application:didFinishLaunchingWithOptions:
so that it looks like the following:
def application(application, didFinishLaunchingWithOptions: launchOptions) window.addSubview(hello_world_label) window.makeKeyAndVisible true end |
Here you add hello_world_label
as a subview to the window
object and tell the application to make the window
object visible.
Save your work, then switch over to Terminal and run the following command:
rake |
In a few seconds you’ll see your shiny new RubyMotion “Hello World” app running in the iOS Simulator as below:
Type exit
in Terminal to stop both Rake and the simulator.
That takes care of your Hello World app; you can now take your new-found knowledge and press on with building Pomotion.
Adding a Main View Controller
Your first task in Pomotion is to create a main view-controller for the main screen.
To keep things organized — and to stay in line with Ruby on Rails’ file structure — you’ll create a new directory to store your controller files.
Return to Terminal and run the following command:
mkdir app/controllers |
This creates a new directory controllers inside the existing app directory.
Run the following command in Terminal:
touch app/controllers/main_view_controller.rb |
This creates a new file for your view controller.
Open main_view_controller.rb and add the following code:
class MainViewController < UIViewController end |
The code above creates a class MainViewController
that inherits from UIViewController
. Note that you’ve kept the same name for your controller class as the file that contains your class.
Next, return to app_delegate.rb and delete the entire hello_world_label
method along with the code you added in application:didFinishLaunchingWithOptions:
; they won’t be required for Pomotion.
Your complete app_delegate.rb should now look like this:
class AppDelegate def window @window ||= UIWindow.alloc.initWithFrame(UIScreen.mainScreen.bounds) end def application(application, didFinishLaunchingWithOptions:launchOptions) true end end |
Now, add the following code to app_delegate.rb:
def main_view_controller @main_view_controller ||= MainViewController.alloc.initWithNibName(nil, bundle: nil) end def navigation_controller @navigation_controller ||= UINavigationController.alloc. initWithRootViewController(main_view_controller) end |
Here you simply create getter methods for the MainViewController
and a UINavigationController
to present it.
Finally, add the following to application:didFinishLaunchingWithOptions:
:
def application(application, didFinishLaunchingWithOptions:launchOptions) window.rootViewController = navigation_controller window.makeKeyAndVisible true end |
In the code above you tell window
to display navigation_controller
and become visible.
Run rake
in Terminal to launch the simulator:
For now, all you’ll see is a UINavigationBar
with a blank screen; The window
is loading navigation_controller
as expected, but the displayed view is transparent.
One option is to create them in code, like you’ll do in this tutorial. A second option is to use Interface Builder with RubyMotion – to learn how to do that, check out this GitHub page.
Pixate Freestyle
To build your view you’ll use Pixate Freestyle. Pixate, lets you style iOS views using CSS; it’s just like styling a webpage.
To install Pixate, open Gemfile in your text editor and add the following line to the bottom:
gem 'motion-pixatefreestyle' |
Run the following command in Terminal:
bundle |
This updates the Gemfile.lock and installs any missing gems.
Next, create the following directory in your project:
mkdir vendor |
Download the latest release of the Pixate Freestyle framework; unzip it and move PixateFreestyle.framework into your newly created vendor folder.
Finally, add the following line to your Rakefile, just before the closing end
:
app.pixatefreestyle.framework = 'vendor/PixateFreestyle.framework' |
This simply tells motion-pixatefreestyle
where to find the Pixate framework.
Run rake
to check that everything installed properly; you should see the following:
Build ./build/iPhoneSimulator-8.1-Development Build vendor/PixateFreestyle.framework Compile ./app/pixatefreestyle_code.rb Link ./build/iPhoneSimulator-8.1-Development/Pomotion.app/Pomotion Create ./build/iPhoneSimulator-8.1-Development/Pomotion.app/PkgInfo Create ./build/iPhoneSimulator-8.1-Development/Pomotion.app/Info.plist Create ./build/iPhoneSimulator-8.1-Development/Pomotion.app.dSYM Simulate ./build/iPhoneSimulator-8.1-Development/Pomotion.app |
It’s time to dress up the main screen so that it looks presentable.
Adding Views Programmatically
Just like controllers, views should have a directory of their own within the app directory.
Execute the following command in Terminal:
mkdir app/views |
Next, execute the following command to create a file to contain the MainView of MainViewController:
touch app/views/main_view.rb |
Open main_view.rb and add the following code:
class MainView < UIView def initWithFrame(frame) super.tap do self.styleId = 'main_view' end end end |
MainView
is a custom view for the MainViewController which inherits from UIView. Although they share the same name prefix of “Main”, this isn’t a requirement in RubyMotion; it’s just a nice convention to keep things organized in your app.
Pixate Freestyle adds two properties to all UIView subclasses: styleId
and styleClass
. These let you apply styles to any view. In the code above you set styleId
on MainView when you override initWithFrame
.
Note: tap
was introduced in Ruby 1.9; it lets you call methods on an object within a block and returns that object at the end of the block’s execution. This comes in really handy with things like initializers in iOS, which should always return self
…which you always remember to do, right? :]
It’s a heck of a lot easier than writing the following:
def initWithFrame(frame) super self.styleId = 'main_view' return self end |
You can now move on to styling MainView using simple CSS!
Add a new CSS stylesheet to the resources directory named default.css as follows:
touch resources/default.css |
And add the following code to the new stylesheet:
#main_view { background-color: white; } |
This is some simple CSS that sets the background color to white for the #main_view id, which will apply to the main view you just created.
Open app/main_view_controller.rb and add the following method to the implementation of MainViewController
:
def loadView self.view = MainView.alloc.initWithFrame(CGRectZero) end |
This should look familiar; you’re re-defining loadView
in the view controller to load a custom view instance. In this case, the frame argument is CGRectZero, which sets the width and height of the view to 0.
Run rake
to see your view in action; MainViewController
now has a white background:
Next you need a label on the screen to show the timer countdown as well as a button to start and stop the timer.
Open app/views/main_view.rb and add the following method:
def timer_label @timer_label ||= UILabel.alloc.initWithFrame(CGRectZero).tap do |label| label.styleId = 'timer_label' label.text = '00:00' end end |
Just as before, you’ve defined a getterfor the label and you’ve used a memoization pattern to cache the UILabel
in an instance variable. The label has a CSS style ID of timer_label
and its text shows 00:00 — just like a real timer set to zero.
You set the frame to CGRectZero upon initialization since you’ll use using Pixate and CSS to style the label including setting its size and origin.
While you’re at it, you should add the timer button to your view as well.
Add the following code to app/views/main_view.rb:
def timer_button @timer_button ||= UIButton.buttonWithType(UIButtonTypeCustom).tap do |button| button.styleId = 'timer_button' button.setTitle('Start Timer', forState: UIControlStateNormal) button.setTitle("Interrupt!" , forState: UIControlStateSelected) button.addTarget(nextResponder, action: 'timer_button_tapped:', forControlEvents: UIControlEventTouchUpInside) end end |
In the above method you’ve set several titles for UIControlStateSelected
; you’ll be using the selected
state shortly. nextResponder
is the target object that responds to tap events, which in this case is the MainViewController displaying the view.
You could have just as easily defined a timer_button_tapped:
action in MainView but, strictly speaking, that would have been a violation of MVC. Since it’s the controller’s job to respond to user input, that’s where you should define the action.
Still in app/views/main_view.rb, modify initWithFrame
as follows:
def initWithFrame(frame) super.tap do self.styleId = 'main_view' addSubview(timer_label) addSubview(timer_button) end ends |
Here you add the label and button views you just created as subviews of MainView
.
Finally, add the following styles to resources/default.css:
#timer_label { top: 160px; left: 60px; width: 200px; height: 60px; font-size: 60px; text-align: center; color: #7F7F7F; } #timer_button { top: 230px; left: 60px; width: 200px; height: 40px; color: white; background-color: #007F00; } |
This styles the two views you added previously.
Run rake
to launch your app; your app should now look like the following:
Things are starting to look pretty nice! The timer is a bit static — your next job is to get it to count down.
Getting the Timer to Count Down
The Pomodoro Technique states that work should be accomplished in 25 minute blocks, so that’s where your timer value will start.
This value is quite important to the application’s logic, and might be referenced in more than one place; therefore it makes sense to write a helper method for NSDate so you don’t have to hardcode time calculations throughout your code.
Importing C Code into Your Project
The category below extends NSDate with an extra method, secsIn25Mins
:
+ (int) secsIn25Mins { return TARGET_IPHONE_SIMULATOR ? 10 : 1500; } |
The above method simply defines an extra class method on NSDate that returns the number of seconds in 25 minutes (25 x 60 = 1500 seconds). Since you won’t want to wait a full 25 minutes every time you want to test the app during development, the method will return 10 seconds when the app runs in the simulator:
It would be a shame to have to port this code to RubyMotion, just so that you can use it in this app. This particular example is only a few lines, but other libraries or extensions could potentially be several thousand lines. Fortunately, RubyMotion lets you import Objective-C code directly into your app!
Create a new directory within the vendor directory and name it NSDate+SecsIn25Mins:
mkdir vendor/NSDate+SecsIn25Mins/ |
Then create two files named NSDate+SecsIn25Mins.h and NSDate+SecsIn25Mins.m:
touch vendor/NSDate+SecsIn25Mins/NSDate+SecsIn25Mins.h touch vendor/NSDate+SecsIn25Mins/NSDate+SecsIn25Mins.m |
Open NSDate+SecsIn25Mins.h and add the following code:
#import <Foundation/Foundation.h> @interface NSDate (SecsIn25Mins) + (int) secsIn25Mins; @end |
Now, paste the following code into NSDate+SecsIn25Mins.m:
#import "NSDate+SecsIn25Mins.h" @implementation NSDate (SecsIn25Mins) + (int) secsIn25Mins { return TARGET_IPHONE_SIMULATOR ? 10 : 1500; } @end |
Finally, add the following line to the bottom of Rakefile, just before the closing “end”:
app.vendor_project('vendor/NSDate+SecsIn25Mins', :static) |
Run rake
to build your app and launch it in the Simulator; RubyMotion automatically includes the code you added in the vendor directory.
To see your Obj-C methods at work, run the following command in Terminal (sill in rake, with the simulator active):
NSDate.secsIn25Mins |
You should see the following result returned in Terminal:
# => 10 |
Just as you defined in NSDate+SecsIn25Mins
, this returns 10
since you’re running in the Simulator.
Note: If you run the command Time.secsIn25Mins
, you’ll get a return value of 10
as well, even though Time
is a Ruby class and not part of the iOS API. What’s going on?
RubyMotion cleverly merged the class hierarchies of both Ruby and iOS, so the Time
class inherits from NSDate
; this means the methods from both classes are available to Time
objects.
Where To Go From Here?
Here is the sample project up until this point in this RubyMotion tutorial series.
Stay tuned for the next part of the series, where you’ll make the timer count down and fully wrap up this app.
In the meantime, if you have any questions or comments, please join the forum discussion below!
RubyMotion Tutorial for Beginners: Part 1 is a post from: Ray Wenderlich
The post RubyMotion Tutorial for Beginners: Part 1 appeared first on Ray Wenderlich.