Quantcast
Channel: Kodeco | High quality programming tutorials: iOS, Android, Swift, Kotlin, Unity, and more
Viewing all articles
Browse latest Browse all 4399

RubyMotion Tutorial for Beginners: Part 1

$
0
0
Get Started with Ruby Motion!

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.

RubyMotion-Installing

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:

Empty RubyMotion App directories

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:

RW_Rubymotion_Hello

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:

RW_Rubymotion_BlankVC

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.

Note: One of the key benefits of RubyMotion is that it can be used independently of Xcode. However, what do you do for user interfaces?

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.

Note: If you encounter issues running version 2.1.4 with motion-pixatefreestyle 2.1, you might try using version 2.1.3 of the framework instead.

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:

RW_Rubymotion_WhiteBG

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:

RW_Rubymotion_Timer1

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.


Viewing all articles
Browse latest Browse all 4399

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>