Welcome back to our RubyMotion Tutorial for Beginners series!
In the first part of the series, you learned the basics of getting started with RubyMotion, and created a view controller with a few styled views.
In this second and final part of the series, you will add the rest of the logic to this app, including making the label count down and getting the app fully wrapped up.
Let’s get back in motion! :]
Building a Countdown Timer
At this point, you have a label and a button, and the label has some static text on it. You want the label to count down from 25 minutes.
It’s clear there should be some sort of object responsible for handling the countdown, but you haven’t defined that object yet. While it’s tempting to add that functionality to MainViewController
, that’s not really the controller’s responsibility. The controller’s job is to respond to events and direct what should happen next.
Run the following command in Terminal:
mkdir app/models |
You’ll use the models directory to store the models containing the application logic of your app.
Run the following command in Terminal to create a new class that will serve as your countdown timer:
touch app/models/pomodoro_timer.rb |
Open PomodoroTimer
and add the following lines of code:
class PomodoroTimer end |
PomodoroTimer
has no superclass; it’s just a plain-old-ruby-object, or PORO for short.
The first thing PomodoroTimer
needs is an attribute to store its current value. To do this, add the following lines to app/models/pomodoro_timer.rb:
attr_accessor :count |
The attr_accessor
macro declares a getter and setter for count
. The equivalent to this in Objective-C is:
@interface PomodoroTimer : NSObject @property NSInteger count; @end |
Now, add the following method to app/models/pomodoro_timer.rb:
def initialize @count = Time.secsIn25Mins end |
By default count
should be set to the number of seconds in 25 minutes, so you use the method you’ve just defined on NSDate
to set this in initialize
.
Weak References
PomodoroTimer
also needs a delegate to report when certain events occur.
Add the following code just below the spot where you declared count
:
attr_reader :delegate |
You’re using attr_reader
here because the default setter for your delegate isn’t appropriate in this case. Using attr_accessor
would create a setter that holds the delegate — in this case, an instance of MainViewController
in a strongly referenced instance variable. But since you’re going to define PomodoroTimer
as a property of MainViewController
, using attr_accessor
would create a circular dependency leading to memory leaks and crashes!
To avoid that mess, add the following method to pomodoro_timer.rb:
def delegate=(object) @delegate = WeakRef.new(object) end |
Here you define your own setter for delegate
and set it as a weak reference. In Ruby, everything is an object, and weak references are no exception.
Add the following property to the PomodoroTimer class:
attr_accessor :ns_timer |
This property, as the name suggests, will hold an NSTimer
object that handles the countdown by firing once a second for 25 minutes.
Add the following method to the PomodoroTimer class next:
def start invalidate if ns_timer self.ns_timer = NSTimer.timerWithTimeInterval(1, target: self, selector: 'decrement', userInfo: nil, repeats: true) NSRunLoop.currentRunLoop.addTimer(ns_timer, forMode: NSDefaultRunLoopMode) delegate.pomodoro_timer_did_start(self) if delegate end |
This handles the creation of a new timer. Here’s what’s going on in the code above:
- If the
PomodoroTimer
already has anns_timer
instance, callinvalidate
. - Set
ns_timer
to a newNSTimer
instance that callsdecrement
once per second. - Add the
NSTimer
to the current run loop, and if the delegate has been set then sendpomodoro_timer_did_start
to the delegate so it’s aware that the timer started.
You’ve yet to define PomodoroTimer#invalidate
and PomodoroTimer#decrement
.
Add the following below the start
method you just wrote:
def invalidate ns_timer.invalidate delegate.pomodoro_timer_did_invalidate(self) if delegate end |
This method simply passes invalidate
on to ns_timer
and then notifies the delegate
that the timer has been invalidated as long as a delegate has been set.
Finally, define the decrement
method as follows:
private def decrement self.count -= 1 return if delegate.nil? if count > 0 delegate.pomodoro_timer_did_decrement(self) else delegate.pomodoro_timer_did_finish(self) end end |
This simple method decrements the value of count
by 1 each time it’s called. If there’s a delegate present and the count is greater than 0, it notifies the delegate that pomodoro_timer_did_decrement
. If the count is 0 then it notifies the delegate that pomodoro_timer_did_finish
.
Note the private
directive above; since decrement
should only be used internally within the class itself, you make this method private by adding the directive above the class definition.
Run rake
to build and launch your app; you can now play around with the new class you defined above. To do this, execute the following command in Terminal (with rake and the Simulator active) to initialize a new PomodoroTimer and assign it to a local variable:
p = PomodoroTimer.new |
Inspect the value of p.count
using the commands below:
p.count |
The value should be 10 as expected:
# => 10 |
Call start
on p to start the countdown sequence as follows:
p.start |
To see the countdown timer working, evaluate p.count
repeatedly — but don’t wait, you only have 10 seconds! :]
p.count # => 8 p.count # => 6 p.count # => 2 |
Now that you know your timer is working, you can use it in your app.
Adding a PomodoroTimer to MainViewController
Open main_view_controller.rb and declare the following property on MainViewController:
class MainViewController < UIViewController attr_accessor :pomodoro_timer # ... end |
This holds the timer instance for this controller.
In the first part of this tutorial series, you added nextResponder
to MainView
as the target for touch actions, with the action name of timer_button_tapped
. It’s finally time to define that method.
Still in main_view_controller.rb, add the following code below loadView
:
def timer_button_tapped(sender) if pomodoro_timer && pomodoro_timer.valid? pomodoro_timer.invalidate else start_new_pomodoro_timer end end |
You call the above action when the user taps the timer_button
. If pomodoro_timer
has a value — i.e. is not nil — and it references a valid PomodoroTimer
, then invalidate the PomodoroTimer. Otherwise, create a new PomodoroTimer
instance.
Add the private
directive just below the method you just added as shown below:
# ... def timer_button_tapped(sender) if pomodoro_timer && pomodoro_timer.valid? pomodoro_timer.invalidate else start_new_pomodoro_timer end end private # ... |
This separates the public and private methods.
Finally, add the following method after the private
directive:
def start_new_pomodoro_timer self.pomodoro_timer = PomodoroTimer.new pomodoro_timer.delegate = self pomodoro_timer.start end |
start_new_pomodoro_timer
assigns a new PomodoroTimer
instance to pomodoro_timer
, sets its delegate
to self
, and then starts the timer. Remember, tapping the button calls this method to you need to start the countdown as well.
Run rake
to build and launch your app, then tap the Start Timer button to see what happens:
2014-09-11 16:40:58.276 Pomotion[17757:70b] *** Terminating app due to uncaught exception 'NoMethodError', reason: 'pomodoro_timer.rb:22:in `start': undefined method `pomodoro_timer_did_start' for #<MainViewController:0x93780a0> (NoMethodError) |
Hmm, something’s wrong with your app. Can you guess what the problem is?
When you start pomodoro_timer
, it calls delegate methods on MainViewController — but those methods don’t yet exist. In Ruby, this results in a NoMethodError exception.
Add the following delegate methods above the private
keyword in main_view_controller.rb:
def pomodoro_timer_did_start(pomodoro_timer) NSLog("pomodoro_timer_did_start") end def pomodoro_timer_did_invalidate(pomodoro_timer) NSLog("pomodoro_timer_did_invalidate") end def pomodoro_timer_did_decrement(pomodoro_timer) NSLog("pomodoro_timer_did_decrement") end def pomodoro_timer_did_finish(pomodoro_timer) NSLog("pomodoro_timer_did_finish") end |
The NSLog
statements will print out a line to the console, just to show you that the methods are in fact being called.
Run rake
once again and tap Start Timer; you should see the NSLog statements written out to the console as they’re called:
Build ./build/iPhoneSimulator-8.1-Development Build vendor/PixateFreestyle.framework Build vendor/NSDate+SecsIn25Mins Compile ./app/controllers/main_view_controller.rb Link ./build/iPhoneSimulator-8.1-Development/Pomotion.app/Pomotion Create ./build/iPhoneSimulator-8.1-Development/Pomotion.app/Info.plist (main)> 2014-11-13 13:52:44.778 Pomotion[9078:381797] pomodoro_timer_did_start 2014-11-13 13:52:45.779 Pomotion[9078:381797] pomodoro_timer_did_decrement 2014-11-13 13:52:46.779 Pomotion[9078:381797] pomodoro_timer_did_decrement 2014-11-13 13:52:47.779 Pomotion[9078:381797] pomodoro_timer_did_decrement 2014-11-13 13:52:48.779 Pomotion[9078:381797] pomodoro_timer_did_decrement (nil)? 2014-11-13 13:52:49.778 Pomotion[9078:381797] pomodoro_timer_did_decrement 2014-11-13 13:52:50.778 Pomotion[9078:381797] pomodoro_timer_did_decrement (nil)? 2014-11-13 13:52:51.778 Pomotion[9078:381797] pomodoro_timer_did_decrement |
If you still get an exception, make sure you followed the instructions above about pasting the methods before the private
keyword.
There’s just one more bit of housekeeping before moving on. In timer_button_tapped
you ask if pomodoro_timer
is valid?
, but you haven’t yet defined a valid?
method on PomodoroTimer; if you tap the button twice RubyMotion will throw a NoMethodError
.
Add the following code just beneath start
in pomodoro_timer.rb:
def valid? ns_timer && ns_timer.valid? end |
In this case, a valid
result means that the PomodoroTimer
has an NSTimer
and that the timer is valid
. Ensure you’ve added this method above the private
directive, so that you can call this method on any instance of PomodoroTimer
from within other objects.
Updating the Button
In MainView
you specified that timer_button
should have a different title if the state is UIControlStateSelected
, but you aren’t setting that state yet. The button should become selected when the timer starts, and go back to the normal state when the timer is stopped or invalidated for any reason.
Add the following method to main_view_controller.rb:
def timer_button view.timer_button end |
This is just a helpful wrapper method that calls timer_button
on the view. While it may seem arbitrary to do so, this actually helps the code adhere to The Law Of Demeter.
Replace the pomodoro_timer_did_start
and pomodoro_timer_did_invalidate
delegate methods with the following:
def pomodoro_timer_did_start(pomodoro_timer) NSLog("pomodoro_timer_did_start") timer_button.selected = true end def pomodoro_timer_did_invalidate(pomodoro_timer) NSLog("pomodoro_timer_did_invalidate") timer_button.selected = false end |
Run rake
, then tap Start Timer; you’ll see the button’s title change as shown below:
Updating the Label
The countdown timer isn’t terribly useful at present since the on-screen timer doesn’t count down yet. Fortunately, that’s your very next task!! :]
Add the following method to app/controllers/main_view_controller.rb:
def timer_label view.timer_label end |
This updates timer_label
in MainView
should be updated with the current count of pomodoro_timer
each time the timer decrements.
Still in main_view_controller.rb, modify pomodoro_timer_did_decrement
to look like the code below:
def pomodoro_timer_did_decrement(pomodoro_timer) mins = pomodoro_timer.count / 60 secs = pomodoro_timer.count % 60 timer_label.text = "%02d:%02d" % [mins, secs] end |
Here you take the value of pomodoro_timer.count
and break it down into separate minutes and seconds values. You then set the text of timer_label
to a formatted string so that the minutes and seconds values will always appear as double digits.
Run rake
again and tap Start Timer; you should see the timer count down from 00:10 to 00:00 as shown below:
It looks good, but watch the console carefully and you’ll see the timer continues to decrement past zero and pomodoro_timer_did_finish
executes multiple times.
(main)> 2014-11-13 13:57:37.038 Pomotion[9408:386412] pomodoro_timer_did_start 2014-11-13 13:57:47.039 Pomotion[9408:386412] pomodoro_timer_did_finish 2014-11-13 13:57:48.038 Pomotion[9408:386412] pomodoro_timer_did_finish 2014-11-13 13:57:49.038 Pomotion[9408:386412] pomodoro_timer_did_finish |
Ah — you’re not invalidating the timer. To fix this, modify pomodoro_timer_did_finish
like so:
def pomodoro_timer_did_finish(pomodoro_timer) pomodoro_timer.invalidate end |
Now when the timer reaches zero, you’ll invalidate it from within MainViewController
.
Run rake
and try the above scenario again; verify that the counter stops decrementing at zero and pomodoro_timer_did_finish
is called just once.
Another small issue with the timer right now is that timer_label
only changes when the timer has counted down from 10 to 9; the user doesn’t get to see the initial value of 10 seconds.
To solve that, you’ll rewrite a bit of code to set the label as soon as the controller receives pomodoro_timer_did_start
.
Still in main_view_controller.rb define a new private method named update_timer_label
and move the code from pomodoro_timer_did_decrement
into the new method as follows:
def pomodoro_timer_did_decrement(pomodoro_timer) end # ... private def update_timer_label mins = pomodoro_timer.count / 60 secs = pomodoro_timer.count % 60 timer_label.text = "%02d:%02d" % [mins, secs] end # ... |
Now edit pomodoro_timer_did_decrement
, pomodoro_timer_did_invalidate
, and pomodoro_timer_did_start
so they call update_timer_label
as shown below:
# ... def pomodoro_timer_did_start(pomodoro_timer) timer_button.selected = true update_timer_label end def pomodoro_timer_did_invalidate(pomodoro_timer) timer_button.selected = false update_timer_label end def pomodoro_timer_did_decrement(pomodoro_timer) update_timer_label end # ... |
Run rake
then tap Start Timer to see that the timer now starts from 00:10:
Making it Look the Part
The timer is working well, but it could do with a bit of dressing up.
Add the following code to app/controllers/main_view_controller.rb, just below loadView
:
def viewDidLoad super self.title = "Pomotion" end |
Run rake
and you should see the title appear at the top of the screen:
That adds a bit of life to the app, but the navigation bar could use a face lift.
Add the following CSS to resources/default.css to brighten up the navigation bar:
navigation-bar { background-color: #7F0000; } navigation-bar title { color: white; } |
These two CSS values give the navigation bar a red tint,and color the title bar text white.
Run rake
to see the results:
Extending Core Classes
You can give your app a professional touch by gradually changing the color of the timer’s label from green to red as the timer counts down to 00:00.
To achieve this, you’ll need to create a method that will take two colors and mix them based on a specified proportion. Since this behavior is relevant to UIColor, you should add your new method as a class method there. However, this time around you can extend the class the Ruby way! :]
First, create a new directory under app called core_extensions:
mkdir app/core_extensions |
This is a sensible place to define all of the extensions you add to the core iOS and Ruby classes.
Create a file in that directory named ui_color.rb:
touch app/core_extensions/ui_color.rb |
The basic design of this effect is that timer_label
can have one of four color combinations: all red, all green, a red-green mix, or grey (which will be used when the timer is inactive).
Add the following helper class methods to the UIColor
class in app/core_extensions/ui_color.rb:
class UIColor def self.pomo_grey_color @pomo_grey_color ||= UIColor.colorWithRed(0.5, green: 0.5, blue: 0.5, alpha: 1.0) end def self.pomo_green_color @pomo_green_color ||= UIColor.colorWithRed(0.0, green: 0.666, blue: 0.0, alpha: 1.0) end def self.pomo_red_color @pomo_red_color ||= UIColor.colorWithRed(0.666, green: 0.0, blue: 0.0, alpha: 1.0) end end |
This makes it easier to reference the custom red, green and grey colors in Pomotion’s color scheme.
Now you need to define a class method that will mix red and green proportionally to return a new UIColor
value.
First, define the following method below self.pomo_red_color
:
def self.new_from_two_colors(color_1, color_2, proportion) # 1 color_1_r = Pointer.new(:float) color_1_g = Pointer.new(:float) color_1_b = Pointer.new(:float) color_1_a = Pointer.new(:float) # 2 color_1.getRed(color_1_r, green: color_1_g, blue: color_1_b, alpha: color_1_a) # 3 color_2_r = Pointer.new(:float) color_2_g = Pointer.new(:float) color_2_b = Pointer.new(:float) color_2_a = Pointer.new(:float) color_2.getRed(color_2_r, green: color_2_g, blue: color_2_b, alpha: color_2_a) # 4 new_red = color_1_r.value + (color_2_r.value - color_1_r.value) * proportion # 5 new_green = color_1_g.value + (color_2_g.value - color_1_g.value) * proportion new_blue = color_1_b.value + (color_2_b.value - color_1_b.value) * proportion new_alpha = color_1_a.value + (color_2_a.value - color_1_a.value) * proportion # 6 UIColor.colorWithRed(new_red, green: new_green, blue: new_blue, alpha: new_alpha) end |
Taking each commented section in turn, you’ll see the following:
- First, you initialize four
Pointer
objects. Pointers are handled quite differently in RubyMotion than in Objective-C — in Ruby, everything is an object and pointers are no exception. To create a new Pointer, simply initialize a new instance of the Pointer class and pass in the type as a parameter. The type can be either an Objective-C Runtime Type, or a Ruby symbol with the type name. To access the pointer’s value you simply call thevalue
property. - Next, you call the
UIColor
methodgetRed:green:blue:alpha
to pluck the RGBA values forcolor_1
and then assign them to their respective pointers. - Now do the same for
color_2
. - The
proportion
parameter is a float value between 0 and 1. You can think of this as “What percentage ofcolor_2
do we mix intocolor_1
?”The following calculation determines how much red the new color should contain:
color_1_r.value + (color_2_r.value - color_1_r.value) * proportion
Here’s some real numbers to help make sense of the calculation above. If
color_1
is bright red (RGBA: 255, 0, 0, 1) andcolor_2
is pure white (RGBA: 0, 0, 0, 1), thencolor_1_r
andcolor_2_r
will be255
and0
respectively. The color that’s a 50% blend ofcolor_1
andcolor_2
will be255 + (0 - 255) * 0.5 = 127.5
. - Perform the same calculation for the green, blue and alpha values.
- Finally, return a new
UIColor
object with the new, proportional values.
Run rake
and test your methods from Terminal as below:
UIColor.pomo_red_color # => #<UIDeviceRGBColor:0x9099e10> UIColor.pomo_green_color # => #<UIDeviceRGBColor:0x97b59c0> UIColor.pomo_grey_color # => #<UIDeviceRGBColor:0x94ed360> |
That looks good — but what about new_from_two_colors
?
UIColor.new_from_two_colors(UIColor.pomo_red_color, UIColor.pomo_green_color, 0.5) # => #<UIDeviceRGBColor:0x909a510> |
If you can stand to do the hexadecimal math yourself, you’ll see that the numbers above work out correctly. :]
Back in main_view_controller.rb, add the following lines to the bottom of update_timer_label
to change the text color of timer_label
as the timer decrements:
if pomodoro_timer.count > 0 proportion = pomodoro_timer.count / Time.secsIn25Mins.to_f color = UIColor.new_from_two_colors(UIColor.pomo_red_color, UIColor.pomo_green_color, proportion) else color = UIColor.pomo_grey_color end timer_label.textColor = color |
To determine the color above, you first check that pomodoro_timer
‘s count is greater than zero; if so, then calculate the color proportion as the result of the time remaining divided by the total starting time.
Then assign color
, which is a proportionate mixture of UIColor.pomo_red_color
, and UIColor.pomo_green_color
, to a new instance ofUIColor
.
Finally, set textColor
of timer_label
to the new color
value.
4 / 5
would return 0
, while 6 / 5
would return 1
. To perform float division instead, simply convert the denominator to a float.Run rake
and tap the Start Timer button to see how your effect looks:
Hey — that adds a lot of polish to your app! :] However, update_timer_label
is starting to get a little messy and, strictly speaking, it shouldn’t be up to the controller to calculate the text color.
You can make this code a lot neater, and more MVC compliant, by extending the behavior of timer_label
through defining a subclass of UILabel.
Create a new file in app/views and name it timer_label.rb using Terminal:
touch app/views/timer_label.rb |
Open timer_label.rb and declare a new class TimerLabel
as a subclass of UILabel:
class TimerLabel < UILabel end |
Your goal is to extract all of the logic from MainViewController
that updates the timer_label
, and instead place it in a custom method in TimerLabel
. This makes the controller code much easier to follow, easier to maintain — and easier to test.
Still in app/views/timer_label.rb, add the following code to the class implementation:
def update_for_count(count) update_text_for_count(count) update_color_for_count(count) end private def update_text_for_count(count) mins = count / 60 secs = count % 60 self.text = "%02d:%02d" % [mins, secs] end def update_color_for_count(count) if count > 0 proportion = count / Time.secsIn25Mins.to_f color = UIColor.new_from_two_colors(UIColor.pomo_red_color, UIColor.pomo_green_color, proportion) else color = UIColor.pomo_grey_color end self.color = color end |
This defines a new public method for timer_label
— update_for_count
— which takes the count value from a PomodoroTimer
object and calls two private methods: update_text_for_count
and update_color_for_count
.
This is the exact same update_timer_label
code from MainViewController
; the only difference is that you set the properties on self
, instead of on update_timer_label.
This should help you appreciate how much simpler Ruby code can be, when compared to Objective-C.
Modify the implementation of main_view.rb to use your new TimerLabel
class instead of UILabel
as shown below:
class MainView < UIView # ... def timer_label @timer_label ||= TimerLabel.alloc.initWithFrame(CGRectZero).tap do |label| label.styleId = 'timer_label' label.text = '00:00' end end # ... end |
Next, update update_timer_label
in main_view_controller.rb to use the update_for_count
method you defined above:
def update_timer_label timer_label.update_for_count(pomodoro_timer.count) end |
Run rake
to build and launch your app; test your app to make sure it works as before. The changes you’ve made shouldn’t have any impact on the appearance or performance of your app, but should make the code easier to maintain in the future!
Updating the Start Timer Button
The title of timer_button
changes depending on the state of the button. It would be nice to polish your app a little further and change the background color of the button when it’s selected.
To add this custom behavior, you’ll create a new subclass of UIButton that can indicate a selected state. Can you guess where this new custom view class should go?
If you said “the app/views directory”, you’d be right!
Create the following file using Terminal:
touch app/views/selectable_button.rb |
Open selectable_button.rb add the following code:
class SelectableButton < UIButton def selected=(bool) super.tap do self.backgroundColor = bool ? UIColor.pomo_red_color : UIColor.pomo_green_color end end end |
In the above code, selected=
takes a boolean parameter, calls super's
selected=
method, then sets backgroundColor
to red if bool
is true, and to green if it’s false.
Note: You’re calling super
here, but there’s no method named selected=
defined in UIButton
. What’s going on?
Naming setters as set[Something]
is a C-style idiom. In Ruby, the preference is to name setters like [something]=
instead. RubyMotion cleverly offers support for both idioms and aliases them as well so you can choose to use one or the other as you see fit.
Ruby also offers method aliases in the form of [something]?
for methods named is[Something]
.
Modify app/views/main_view.rb to use your new SelectableButton class instead of UIButton as shown below:
def timer_button @timer_button ||= SelectableButton.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 |
Run rake
to build and launch your app; tap Start Timer and you’ll see the button background turn to red like so:
Once the timer reaches zero, the button background returns to green.
Alerting the User
Your app is really starting to take shape — but there’s a little more functionality to implement. One of the key principles of the Pomodoro Technique is that you should take a five minute break after your 25 minute block of work is over. You should suggest this to your user so they don’t slip back into their old habits. UIAlertController is the perfect tool for this.
Add the following property method to main_view_controller.rb:
def alert_controller @alert_controller ||= UIAlertController.alertControllerWithTitle("Pomodoro Complete!", message: "Time to take a short break.", preferredStyle: UIAlertControllerStyleAlert).tap do |alert| ok_action = UIAlertAction.actionWithTitle("OK", style:UIAlertActionStyleDefault, handler: nil) alert.addAction(ok_action) end end |
You’ll want to show this alert once the timer has finished counting down to zero, so pomodoro_timer_did_finish
would be a perfect place to call this method.
Add the following line to pomodoro_timer_did_finish
:
self.presentViewController(alert_controller, animated: TRUE, completion: nil) |
Run rake
to build and launch your app, then tap Start Timer; wait ten seconds and you should see your new alert controller make an appearance like so:
And that’s it — you’ve completed your first RubyMotion application!
Where to Go From Here?
Hopefully you enjoyed working through this tutorial, and can appreciate the value RubyMotion provides, not just in time saved, but also in the emotional and mental energy you save by working with a friendlier, easier to use syntax.
If you would like to view the completed project, you can download a copy of it here.
To build and launch your RubyMotion app on a physical device, you can use the rake device
command which you can learn more about here.
There are loads of great RubyMotion resources online; I’d recommend you check out RubyMotion Dispatch – A weekly newsletter as well as the developer documentation on the RubyMotion website.
If you have any questions or comments, feel free to join the discussion below!
RubyMotion Tutorial for Beginners: Part 2 is a post from: Ray Wenderlich
The post RubyMotion Tutorial for Beginners: Part 2 appeared first on Ray Wenderlich.