RubyMotion
iOS Development with Ruby
      @markbates
I HATE iOS
DEVELOPMENT!
Why I Hate iOS Development
Why I Hate iOS Development

✦   XCode
Why I Hate iOS Development

✦   XCode
✦   Objective-C
Why I Hate iOS Development

✦   XCode
✦   Objective-C
✦   Cocoa/iOS APIs
XCode
Objective-C
# Create a new array (mutable):
myArray = %w{hello world etc}
myArray << "new value"

# Create a new hash (mutable):
myHash = {"key-a" => "value-a", "key-b" => "value-b"}
myHash["key-c"] = "value-c"
// Create a new array:
NSArray *myArray = [NSArray arrayWithObjects:@"hello",@"world",@"etc",nil];

// Create a mutable array:
NSMutableArray *myArray = [[NSMutableArray alloc] init];
[myArray addObject:@"hello world"];

// Create a dictionary (hash):
NSDictionary *myDictionary = [NSDictionary dictionaryWithObjectsAndKeys:
@"value-a", @"key-a", @"value-b", @"key-b", nil];

// Create a mutable dictionary:
NSMutableDictionary *myDictionary = [NSMutableDictionary dictionary];
[myDictionary setObject: @"value-c" forKey: @"key-c"];
now = -> {
  puts "The date and time is: #{Time.now}"
}

now.call
void (^now)(void) = ^ {
  NSDate *date = [NSDate date];
  NSLog(@"The date and time is %@", date);
};

now();
Cocoa/iOS APIs
require 'net/http'
require 'json'

url = "http://www.example.com/api.json"
response = Net::HTTP.get_response(URI.parse(url))

if response.code.to_i == 200
  json = JSON.parse(response.body)
  puts json.inspect
else
  puts "An Error Occurred (#{response.code}): #{response.body}"
end
- (void)viewDidLoad {
  [super viewDidLoad];
  responseData = [[NSMutableData data] retain];
  NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"http://www.example.com/api.json"]];
  [[NSURLConnection alloc] initWithRequest:request delegate:self];
}

- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
  [responseData setLength:0];
}

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
  [responseData appendData:data];
}

- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
  label.text = [NSString stringWithFormat:@"Connection failed: %@", [error description]];
}

- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
  [connection release];

  NSString *responseString = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding];
  [responseData release];
  
  NSError *error;
  SBJSON *json = [[SBJSON new] autorelease];
  NSArray *myArray = [json objectWithString:responseString error:&error];
  [responseString release];
  
  if (myArray == nil)
    label.text = [NSString stringWithFormat:@"JSON parsing failed: %@", [error localizedDescription]];
  else {
    NSMutableString *text = [NSMutableString stringWithString:@"Array Data:n"];
    
    for (int i = 0; i < [myArray count]; i++)
      [text appendFormat:@"%@n", [myArray objectAtIndex:i]];

    label.text =   text;
  }
}
Laurent Sansonetti

✦   Worked for Apple for 7 years on iLife and
    OS X

✦   Developed MacRuby

✦   Developed RubyCocoa

✦   Belgian
So What Does it Do?
So What Does it Do?
✦   Ruby!!! (NO OBJECTIVE-C!)
So What Does it Do?
✦   Ruby!!! (NO OBJECTIVE-C!)

✦   Keep Your Editor (NO XCODE!)
So What Does it Do?
✦   Ruby!!! (NO OBJECTIVE-C!)

✦   Keep Your Editor (NO XCODE!)

✦   IRB
So What Does it Do?
✦   Ruby!!! (NO OBJECTIVE-C!)

✦   Keep Your Editor (NO XCODE!)

✦   IRB

✦   Terminal Based Workflow (Rake)
So What Does it Do?
✦   Ruby!!! (NO OBJECTIVE-C!)

✦   Keep Your Editor (NO XCODE!)

✦   IRB

✦   Terminal Based Workflow (Rake)

✦   Testing Framework (MacBacon)
So What Does it Do?
✦   Ruby!!! (NO OBJECTIVE-C!)       ✦   Native Applications

✦   Keep Your Editor (NO XCODE!)

✦   IRB

✦   Terminal Based Workflow (Rake)

✦   Testing Framework (MacBacon)
So What Does it Do?
✦   Ruby!!! (NO OBJECTIVE-C!)       ✦   Native Applications

✦   Keep Your Editor (NO XCODE!)    ✦   App Store Approved

✦   IRB

✦   Terminal Based Workflow (Rake)

✦   Testing Framework (MacBacon)
Suck it Objective-C!
NSString *urlAsString = @"http://www.apple.com";
NSURL *url = [NSURL URLWithString:urlAsString];
NSURLRequest *urlRequest = [NSURLRequest requestWithURL:url];
NSOperationQueue *queue = [[NSOperationQueue alloc] init];

[NSURLConnection
 sendAsynchronousRequest:urlRequest
 queue:queue
 completionHandler:^(NSURLResponse *response,
                     NSData *data,
                     NSError *error) {
   
   if ([data length] >0 &&
       error == nil){
     
     /* Get the documents directory */
     NSString *documentsDir =
     [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,
                                          NSUserDomainMask,
                                          YES) objectAtIndex:0];
     
     /* Append the file name to the documents directory */
     NSString *filePath = [documentsDir
                           stringByAppendingPathComponent:@"apple.html"];
     
     /* Write the data to the file */
     [data writeToFile:filePath
            atomically:YES];
     
     NSLog(@"Successfully saved the file to %@", filePath);
     
   }
   else if ([data length] == 0 &&
            error == nil){
     NSLog(@"Nothing was downloaded.");
   }
   else if (error != nil){
     NSLog(@"Error happened = %@", error);
   }
   
 }];
url = NSURL.URLWithString("http://www.apple.com")
request = NSURLRequest.requestWithURL(url)
queue = NSOperationQueue.alloc.init

NSURLConnection.sendAsynchronousRequest(request,
  queue: queue,
  completionHandler: lambda do |response, data, error|
    if(data.length > 0 && error.nil?)
      doc_dir = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,
                                                    NSUserDomainMask,
                                                    true).first
      #p doc_dir
      file_path = doc_dir.stringByAppendingPathComponent("apple.html")

      data.writeToFile(file_path, atomically: true)

      p "Saved file to #{file_path}"
    elsif( data.length == 0 && error.nil? )
      p "Nothing was downloaded"
    elsif(!error.nil?)
      p "Error: #{error}"
    end
  end)
But What About Those Weird
Named Argument Methods?
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
  // do something
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
  // do something
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
  // do something
}
def tableView(tableView, didSelectRowAtIndexPath: indexPath)
  # do something
end

def tableView(tableView, numberOfRowsInSection: section)
  # do something
end

def tableView(tableView, cellForRowAtIndexPath: indexPath)
  # do something
end
Demo Time
Bubble Wrap
https://github.com/rubymotion/BubbleWrap
HTTP
data = {first_name: 'Matt', last_name: 'Aimonetti'}
BubbleWrap::HTTP.post("http://foo.bar.com/", {payload: data}) do |response|
 if response.ok?
   json = BubbleWrap::JSON.parse(response.body.to_str)
   p json['id']
 elsif response.status_code.to_s =~ /40d/
   App.alert("Login failed")
 else
   App.alert(response.error_message)
 end
end
App
> App.documents_path
# "~/iPhone Simulator/5.0/Applications/EEC6454E-1816-451E-BB9A-EE18222E1A8F/Documents"
> App.resources_path
# "~/iPhone Simulator/5.0/Applications/EEC64-1816-451E-BB9A-EE18221A8F/testSuite_spec.app"
> App.name
# "testSuite"
> App.identifier
# "io.bubblewrap.testSuite"
> App.alert("BubbleWrap is awesome!")
# creates and shows an alert message.
> App.run_after(0.5) { p "It's #{Time.now}" }
# Runs the block after 0.5 seconds.
> App.open_url("http://matt.aimonetti.net")
# Opens the url using the device's browser. (accepts a string url or an instance of NSURL.
> App::Persistence['channels'] # application specific persistence storage
# ['NBC', 'ABC', 'Fox', 'CBS', 'PBS']
> App::Persistence['channels'] = ['TF1', 'France 2', 'France 3']
# ['TF1', 'France 2', 'France 3']
Device
> Device.iphone?
# true
> Device.ipad?
# false
> Device.front_camera?
# true
> Device.rear_camera?
# true
> Device.orientation
# :portrait
> Device.simulator?
# true
> Device.retina?
# false
> Device.screen.width
# 320
> Device.screen.height
# 480
> Device.screen.width_for_orientation(:landscape_left)
# 480
> Device.screen.height_for_orientation(:landscape_left)
# 320
Camera
# Uses the front camera
BW::Device.camera.front.picture(media_types: [:movie, :image]) do |result|
 image_view = UIImageView.alloc.initWithImage(result[:original_image])
end

# Uses the rear camera
BW::Device.camera.rear.picture(media_types: [:movie, :image]) do |result|
 image_view = UIImageView.alloc.initWithImage(result[:original_image])
end

# Uses the photo library
BW::Device.camera.any.picture(media_types: [:movie, :image]) do |result|
 image_view = UIImageView.alloc.initWithImage(result[:original_image])
end
JSON

BW::JSON.generate({'foo => 1, 'bar' => [1,2,3], 'baz => 'awesome'})
=> "{"foo":1,"bar":[1,2,3],"baz":"awesome"}"
BW::JSON.parse "{"foo":1,"bar":[1,2,3],"baz":"awesome"}"
=> {"foo"=>1, "bar"=>[1, 2, 3], "baz"=>"awesome"}
Media

# Plays in your custom frame
local_file = NSURL.fileURLWithPath(File.join(NSBundle.mainBundle.resourcePath, 'test.mp3'))
BW::Media.play(local_file) do |media_player|
  media_player.view.frame = [[10, 100], [100, 100]]
  self.view.addSubview media_player.view
end

# Plays in an independent modal controller
BW::Media.play_modal("http://www.hrupin.com/wp-content/uploads/mp3/testsong_20_sec.mp3")
Deferrables
> d = EM::DefaultDeferrable.new
=> #<BubbleWrap::Reactor::DefaultDeferrable:0x6d859a0>
> d.callback { |what| puts "Great #{what}!" }
=> [#<Proc:0x6d8a1e0>]
> d.succeed "justice"
Great justice!
=> nil
Events
> o = Class.new { include EM::Eventable }.new
=> #<#<Class:0x6dc1310>:0x6dc2ec0>
> o.on(:november_5_1955) { puts "Ow!" }
=> [#<Proc:0x6dc6300>]
> o.on(:november_5_1955) { puts "Flux capacitor!" }
=> [#<Proc:0x6dc6300>, #<Proc:0x6dc1ba0>]
> o.trigger(:november_5_1955)
Ow!
Flux capacitor!
=> [nil, nil]
More...
✦   Localization                            ✦   Location

✦   Color                                   ✦   Gestures

✦   UUID Generator                          ✦   RSS Parser

✦   NSNotificationCenter                     ✦   Reactor

✦   Persistence                             ✦   Timers

✦   Observers

✦   String (underscore, camelize, etc...)
Pros & Cons
The Pros
The Pros
✦   Ruby!!
The Pros
✦   Ruby!!

✦   IDE Agnostic
The Pros
✦   Ruby!!

✦   IDE Agnostic

✦   “Wrapper” Gems
The Pros
✦   Ruby!!

✦   IDE Agnostic

✦   “Wrapper” Gems

✦   Fast
The Pros
✦   Ruby!!

✦   IDE Agnostic

✦   “Wrapper” Gems

✦   Fast

✦   IRB
The Pros
✦   Ruby!!               ✦   Easy to test

✦   IDE Agnostic

✦   “Wrapper” Gems

✦   Fast

✦   IRB
The Pros
✦   Ruby!!               ✦   Easy to test

✦   IDE Agnostic         ✦   Growing community

✦   “Wrapper” Gems

✦   Fast

✦   IRB
The Pros
✦   Ruby!!               ✦   Easy to test

✦   IDE Agnostic         ✦   Growing community

✦   “Wrapper” Gems       ✦   Frequent updates

✦   Fast

✦   IRB
The Cons
The Cons
✦   Cost - $199
The Cons
✦   Cost - $199

✦   Existing tutorials in Objective-C
The Cons
✦   Cost - $199

✦   Existing tutorials in Objective-C

✦   Maintainability?
The Cons
✦   Cost - $199

✦   Existing tutorials in Objective-C

✦   Maintainability?

✦   No Debugger (Yet)
The Cons
✦   Cost - $199

✦   Existing tutorials in Objective-C

✦   Maintainability?

✦   No Debugger (Yet)

✦   Difficult to work with Interface Builder
The Verdict
Resources
✦   http://www.rubymotion.com            ✦   http://rubymotionapps.com/projects

✦   http://rubymotion-tutorial.com       ✦   http://pragmaticstudio.com/screencasts/
                                             rubymotion
✦   https://github.com/IconoclastLabs/
    rubymotion_cookbook

✦   https://github.com/railsfactory/
    rubymotion-cookbook

✦   https://twitter.com/RubyMotion

✦   http://rubymotionweekly.com

RubyMotion

  • 1.
  • 4.
  • 5.
    Why I HateiOS Development
  • 6.
    Why I HateiOS Development ✦ XCode
  • 7.
    Why I HateiOS Development ✦ XCode ✦ Objective-C
  • 8.
    Why I HateiOS Development ✦ XCode ✦ Objective-C ✦ Cocoa/iOS APIs
  • 18.
  • 23.
  • 24.
    # Create anew array (mutable): myArray = %w{hello world etc} myArray << "new value" # Create a new hash (mutable): myHash = {"key-a" => "value-a", "key-b" => "value-b"} myHash["key-c"] = "value-c"
  • 25.
    // Create anew array: NSArray *myArray = [NSArray arrayWithObjects:@"hello",@"world",@"etc",nil]; // Create a mutable array: NSMutableArray *myArray = [[NSMutableArray alloc] init]; [myArray addObject:@"hello world"]; // Create a dictionary (hash): NSDictionary *myDictionary = [NSDictionary dictionaryWithObjectsAndKeys: @"value-a", @"key-a", @"value-b", @"key-b", nil]; // Create a mutable dictionary: NSMutableDictionary *myDictionary = [NSMutableDictionary dictionary]; [myDictionary setObject: @"value-c" forKey: @"key-c"];
  • 26.
    now = ->{   puts "The date and time is: #{Time.now}" } now.call
  • 27.
    void (^now)(void) =^ {   NSDate *date = [NSDate date];   NSLog(@"The date and time is %@", date); }; now();
  • 28.
  • 29.
    require 'net/http' require 'json' url= "http://www.example.com/api.json" response = Net::HTTP.get_response(URI.parse(url)) if response.code.to_i == 200   json = JSON.parse(response.body)   puts json.inspect else   puts "An Error Occurred (#{response.code}): #{response.body}" end
  • 30.
    - (void)viewDidLoad {   [superviewDidLoad];   responseData = [[NSMutableData data] retain];   NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"http://www.example.com/api.json"]];   [[NSURLConnection alloc] initWithRequest:request delegate:self]; } - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {   [responseData setLength:0]; } - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {   [responseData appendData:data]; } - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {   label.text = [NSString stringWithFormat:@"Connection failed: %@", [error description]]; } - (void)connectionDidFinishLoading:(NSURLConnection *)connection {   [connection release];   NSString *responseString = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding];   [responseData release];      NSError *error;   SBJSON *json = [[SBJSON new] autorelease];   NSArray *myArray = [json objectWithString:responseString error:&error];   [responseString release];      if (myArray == nil)     label.text = [NSString stringWithFormat:@"JSON parsing failed: %@", [error localizedDescription]];   else {     NSMutableString *text = [NSMutableString stringWithString:@"Array Data:n"];          for (int i = 0; i < [myArray count]; i++)       [text appendFormat:@"%@n", [myArray objectAtIndex:i]];     label.text = text;   } }
  • 33.
    Laurent Sansonetti ✦ Worked for Apple for 7 years on iLife and OS X ✦ Developed MacRuby ✦ Developed RubyCocoa ✦ Belgian
  • 34.
  • 35.
    So What Doesit Do? ✦ Ruby!!! (NO OBJECTIVE-C!)
  • 36.
    So What Doesit Do? ✦ Ruby!!! (NO OBJECTIVE-C!) ✦ Keep Your Editor (NO XCODE!)
  • 37.
    So What Doesit Do? ✦ Ruby!!! (NO OBJECTIVE-C!) ✦ Keep Your Editor (NO XCODE!) ✦ IRB
  • 38.
    So What Doesit Do? ✦ Ruby!!! (NO OBJECTIVE-C!) ✦ Keep Your Editor (NO XCODE!) ✦ IRB ✦ Terminal Based Workflow (Rake)
  • 39.
    So What Doesit Do? ✦ Ruby!!! (NO OBJECTIVE-C!) ✦ Keep Your Editor (NO XCODE!) ✦ IRB ✦ Terminal Based Workflow (Rake) ✦ Testing Framework (MacBacon)
  • 40.
    So What Doesit Do? ✦ Ruby!!! (NO OBJECTIVE-C!) ✦ Native Applications ✦ Keep Your Editor (NO XCODE!) ✦ IRB ✦ Terminal Based Workflow (Rake) ✦ Testing Framework (MacBacon)
  • 41.
    So What Doesit Do? ✦ Ruby!!! (NO OBJECTIVE-C!) ✦ Native Applications ✦ Keep Your Editor (NO XCODE!) ✦ App Store Approved ✦ IRB ✦ Terminal Based Workflow (Rake) ✦ Testing Framework (MacBacon)
  • 42.
  • 43.
    NSString *urlAsString =@"http://www.apple.com"; NSURL *url = [NSURL URLWithString:urlAsString]; NSURLRequest *urlRequest = [NSURLRequest requestWithURL:url]; NSOperationQueue *queue = [[NSOperationQueue alloc] init]; [NSURLConnection  sendAsynchronousRequest:urlRequest  queue:queue  completionHandler:^(NSURLResponse *response,                      NSData *data,                      NSError *error) {        if ([data length] >0 &&        error == nil){            /* Get the documents directory */      NSString *documentsDir =      [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,                                           NSUserDomainMask,                                           YES) objectAtIndex:0];            /* Append the file name to the documents directory */      NSString *filePath = [documentsDir                            stringByAppendingPathComponent:@"apple.html"];            /* Write the data to the file */      [data writeToFile:filePath             atomically:YES];            NSLog(@"Successfully saved the file to %@", filePath);          }    else if ([data length] == 0 &&             error == nil){      NSLog(@"Nothing was downloaded.");    }    else if (error != nil){      NSLog(@"Error happened = %@", error);    }      }];
  • 44.
    url = NSURL.URLWithString("http://www.apple.com") request= NSURLRequest.requestWithURL(url) queue = NSOperationQueue.alloc.init NSURLConnection.sendAsynchronousRequest(request,   queue: queue,   completionHandler: lambda do |response, data, error|     if(data.length > 0 && error.nil?)       doc_dir = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,                                                     NSUserDomainMask,                                                     true).first       #p doc_dir       file_path = doc_dir.stringByAppendingPathComponent("apple.html")       data.writeToFile(file_path, atomically: true)       p "Saved file to #{file_path}"     elsif( data.length == 0 && error.nil? )       p "Nothing was downloaded"     elsif(!error.nil?)       p "Error: #{error}"     end   end)
  • 45.
    But What AboutThose Weird Named Argument Methods?
  • 46.
    - (void)tableView:(UITableView *)tableViewdidSelectRowAtIndexPath:(NSIndexPath *)indexPath {   // do something } - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {   // do something } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {   // do something }
  • 47.
    def tableView(tableView, didSelectRowAtIndexPath:indexPath)   # do something end def tableView(tableView, numberOfRowsInSection: section)   # do something end def tableView(tableView, cellForRowAtIndexPath: indexPath)   # do something end
  • 48.
  • 49.
  • 50.
    HTTP data = {first_name:'Matt', last_name: 'Aimonetti'} BubbleWrap::HTTP.post("http://foo.bar.com/", {payload: data}) do |response| if response.ok? json = BubbleWrap::JSON.parse(response.body.to_str) p json['id'] elsif response.status_code.to_s =~ /40d/ App.alert("Login failed") else App.alert(response.error_message) end end
  • 51.
    App > App.documents_path # "~/iPhoneSimulator/5.0/Applications/EEC6454E-1816-451E-BB9A-EE18222E1A8F/Documents" > App.resources_path # "~/iPhone Simulator/5.0/Applications/EEC64-1816-451E-BB9A-EE18221A8F/testSuite_spec.app" > App.name # "testSuite" > App.identifier # "io.bubblewrap.testSuite" > App.alert("BubbleWrap is awesome!") # creates and shows an alert message. > App.run_after(0.5) { p "It's #{Time.now}" } # Runs the block after 0.5 seconds. > App.open_url("http://matt.aimonetti.net") # Opens the url using the device's browser. (accepts a string url or an instance of NSURL. > App::Persistence['channels'] # application specific persistence storage # ['NBC', 'ABC', 'Fox', 'CBS', 'PBS'] > App::Persistence['channels'] = ['TF1', 'France 2', 'France 3'] # ['TF1', 'France 2', 'France 3']
  • 52.
    Device > Device.iphone? # true >Device.ipad? # false > Device.front_camera? # true > Device.rear_camera? # true > Device.orientation # :portrait > Device.simulator? # true > Device.retina? # false > Device.screen.width # 320 > Device.screen.height # 480 > Device.screen.width_for_orientation(:landscape_left) # 480 > Device.screen.height_for_orientation(:landscape_left) # 320
  • 53.
    Camera # Uses thefront camera BW::Device.camera.front.picture(media_types: [:movie, :image]) do |result| image_view = UIImageView.alloc.initWithImage(result[:original_image]) end # Uses the rear camera BW::Device.camera.rear.picture(media_types: [:movie, :image]) do |result| image_view = UIImageView.alloc.initWithImage(result[:original_image]) end # Uses the photo library BW::Device.camera.any.picture(media_types: [:movie, :image]) do |result| image_view = UIImageView.alloc.initWithImage(result[:original_image]) end
  • 54.
    JSON BW::JSON.generate({'foo => 1,'bar' => [1,2,3], 'baz => 'awesome'}) => "{"foo":1,"bar":[1,2,3],"baz":"awesome"}" BW::JSON.parse "{"foo":1,"bar":[1,2,3],"baz":"awesome"}" => {"foo"=>1, "bar"=>[1, 2, 3], "baz"=>"awesome"}
  • 55.
    Media # Plays inyour custom frame local_file = NSURL.fileURLWithPath(File.join(NSBundle.mainBundle.resourcePath, 'test.mp3')) BW::Media.play(local_file) do |media_player| media_player.view.frame = [[10, 100], [100, 100]] self.view.addSubview media_player.view end # Plays in an independent modal controller BW::Media.play_modal("http://www.hrupin.com/wp-content/uploads/mp3/testsong_20_sec.mp3")
  • 56.
    Deferrables > d =EM::DefaultDeferrable.new => #<BubbleWrap::Reactor::DefaultDeferrable:0x6d859a0> > d.callback { |what| puts "Great #{what}!" } => [#<Proc:0x6d8a1e0>] > d.succeed "justice" Great justice! => nil
  • 57.
    Events > o =Class.new { include EM::Eventable }.new => #<#<Class:0x6dc1310>:0x6dc2ec0> > o.on(:november_5_1955) { puts "Ow!" } => [#<Proc:0x6dc6300>] > o.on(:november_5_1955) { puts "Flux capacitor!" } => [#<Proc:0x6dc6300>, #<Proc:0x6dc1ba0>] > o.trigger(:november_5_1955) Ow! Flux capacitor! => [nil, nil]
  • 58.
    More... ✦ Localization ✦ Location ✦ Color ✦ Gestures ✦ UUID Generator ✦ RSS Parser ✦ NSNotificationCenter ✦ Reactor ✦ Persistence ✦ Timers ✦ Observers ✦ String (underscore, camelize, etc...)
  • 59.
  • 60.
  • 61.
  • 62.
    The Pros ✦ Ruby!! ✦ IDE Agnostic
  • 63.
    The Pros ✦ Ruby!! ✦ IDE Agnostic ✦ “Wrapper” Gems
  • 64.
    The Pros ✦ Ruby!! ✦ IDE Agnostic ✦ “Wrapper” Gems ✦ Fast
  • 65.
    The Pros ✦ Ruby!! ✦ IDE Agnostic ✦ “Wrapper” Gems ✦ Fast ✦ IRB
  • 66.
    The Pros ✦ Ruby!! ✦ Easy to test ✦ IDE Agnostic ✦ “Wrapper” Gems ✦ Fast ✦ IRB
  • 67.
    The Pros ✦ Ruby!! ✦ Easy to test ✦ IDE Agnostic ✦ Growing community ✦ “Wrapper” Gems ✦ Fast ✦ IRB
  • 68.
    The Pros ✦ Ruby!! ✦ Easy to test ✦ IDE Agnostic ✦ Growing community ✦ “Wrapper” Gems ✦ Frequent updates ✦ Fast ✦ IRB
  • 69.
  • 70.
    The Cons ✦ Cost - $199
  • 71.
    The Cons ✦ Cost - $199 ✦ Existing tutorials in Objective-C
  • 72.
    The Cons ✦ Cost - $199 ✦ Existing tutorials in Objective-C ✦ Maintainability?
  • 73.
    The Cons ✦ Cost - $199 ✦ Existing tutorials in Objective-C ✦ Maintainability? ✦ No Debugger (Yet)
  • 74.
    The Cons ✦ Cost - $199 ✦ Existing tutorials in Objective-C ✦ Maintainability? ✦ No Debugger (Yet) ✦ Difficult to work with Interface Builder
  • 75.
  • 76.
    Resources ✦ http://www.rubymotion.com ✦ http://rubymotionapps.com/projects ✦ http://rubymotion-tutorial.com ✦ http://pragmaticstudio.com/screencasts/ rubymotion ✦ https://github.com/IconoclastLabs/ rubymotion_cookbook ✦ https://github.com/railsfactory/ rubymotion-cookbook ✦ https://twitter.com/RubyMotion ✦ http://rubymotionweekly.com