SlideShare ist ein Scribd-Unternehmen logo
1 von 133
Downloaden Sie, um offline zu lesen
Workshop 
By Brian Sam-Bodden 
@bsbodden 
http://www.integrallis.com
About me 
• Life-long polyglot programmer 
• Rubyist since 2005 
• CEO & Founder of Integrallis Software, circa 2002 
• From David, Panama 
• Lives in Scottsdale, Arizona
getting started 
Setting up the stage
Pre-Requisites 
• Follow http://www.rubymotion.com/developer-center/guides/getting-started/ 
• 64-bit Mac running OS X Mavericks 
• Apple’s Xcode 
• Command Line Tools Package 
• Download RubyMotion Product Installer 
• Register as an iPhone developer to test on a physical device 
• RVM installed on your machine 
• Use your favorite code editor!
testing 1, 2, 3… 
Checking your RubyMotion Version… 
~/rm_workshop/code 
/> motion --version 
2.32 
! 
! 
! 
! 
! 
! 
!
THE MOTION COMMAND 
The motion command is our entry point in the world of RubyMotion: 
~/rm_workshop/code 
/> motion 
RubyMotion lets you develop native iOS and OS X applications using the awesome Ruby language. 
! 
Commands: 
! 
* account Access account details. 
* activate Activate software license. 
* changelog View the changelog. 
* create Create a new project. 
* device-console Print iOS device logs 
* ri Display API reference. 
* support Create a support ticket. 
* update Update the software. 
! 
Options: 
! 
--version Show the version of RubyMotion 
--no-ansi Show output without ANSI codes 
--verbose Show more debugging information 
--help Show help banner of specified command
FUNDAMENTALS 
Exploring RubyMotion
hello world 
Let’s use the motion create command to get started: 
~/rm_workshop/code 
/> motion create hello 
Create hello 
Create hello/.gitignore 
Create hello/app/app_delegate.rb 
Create hello/Gemfile 
Create hello/Rakefile 
Create hello/resources/Default-568h@2x.png 
Create hello/spec/main_spec.rb 
!
RM PROJECT 
• Gemfile: Your project’s Gem dependencies (RM supported gems) 
• Rakefile: The entry point to build, launch, release and test your App 
• app/app_delegate.rb: Glues your custom code (your application) 
into the RubyMotion framework 
• spec/main_spec.rb: A sample test that verifies that your App has at 
least one window 
• Miscellaneous: A .gitignore to avoid checking build artifacts and a 
default splash screen PNG image
RM BUILD SYSTEM 
Let’s CD to the hello and run: rake -T 
~/rm_workshop/code/hello 
/> rake -T 
rake clean # Clear local build objects 
rake config # Show project config 
rake default # Build the project, then run the simulator 
rake device # Deploy on the device 
rake spec # Same as 'spec:simulator' 
rake spec:device # Run the test/spec suite on the device 
rake spec:simulator # Run the test/spec suite on the simulator 
... 
! 
The default task (which runs if we simply type rake), builds the project 
(in the build directory) and runs the simulator
iOS overview 
• iOS is the operating system that runs on iPhone, iPod Touch, and iPad 
devices 
• Apple provides the iOS SDK, which provides the tools and interfaces 
needed to interact with iOS' layered architecture 
• iOS provides a set of packages called Frameworks, which are dynamic 
shared libraries and resources that can be linked to your App
Cocoa Touch 
Core Services 
Core OS 
UIKit 
Hardware 
Core Animation 
Foundation 
Core Data 
Media Layer 
Core Graphics 
Open GL ES 
Applications
The APP DELEGATE 
class AppDelegate 
def application(application, didFinishLaunchingWithOptions:launchOptions) 
true 
end 
end 
• UIKit manages the app’s core behavior (event loop and interaction w/OS) 
• UIKit uses subclassing, delegation and callbacks to allow you to modify 
default behaviors and add your own 
• UIApplication object dispatches events to your code in the app delegate 
• AppDelegate is responsible for handling state transitions and app events
BUILD AND LAUNCH 
To build and launch simple type: rake 
~/rm_workshop/code/hello 
/> rake 
Build ./build/iPhoneSimulator-7.1-Development 
Compile ./app/app_delegate.rb 
Create ./build/iPhoneSimulator-7.1-Development/hello.app 
Link ./build/iPhoneSimulator-7.1-Development/hello.app/hello 
Create ./build/iPhoneSimulator-7.1-Development/hello.app/PkgInfo 
Create ./build/iPhoneSimulator-7.1-Development/hello.app/ 
Info.plist 
Copy ./resources/Default-568h@2x.png 
Create ./build/iPhoneSimulator-7.1-Development/hello.dSYM 
Simulate ./build/iPhoneSimulator-7.1-Development/hello.app 
(main)> 
Our empty application should be launched in the simulator…
BUILD AND LAUNCH 
The empty app doesn’t have any views yet. All we get is a blank screen…
BUILD AND LAUNCH 
Let’s simulate pressing the device ‘Home’ button. 
From the iOS Simulator menu select: Hardware | Home 
Our application is installed 
on the simulator 
Along with other “core” 
iOS apps
Learning in the repL 
Read Evaluate Print Loop
INTERACTING WITH YOUR APP 
After the application launched, we were left with a prompt on the console; the REPL! 
~/rm_workshop/code/hello 
(main)> self 
=> main 
(main)> alert = UIAlertView.new 
=> #<UIAlertView:0x90d39e0> 
(main)> alert.title = 'RubyMotion' 
=> "RubyMotion" 
(main)> alert.message = 'RubyMotion is in da house' 
=> "RubyMotion is in da house" 
(main)> alert.show 
=> #<UIAlertView:0x90d39e0> 
(main)> 
Let’s follow the interaction shown above 
The top level is the 
main object Many RM classes are counterpart/ 
wrappers for their iOS peers 
Let’s construct a UIAlertView 
which is an iOS class in UIKit
INTERACTING WITH YOUR APP 
The UIAlertView has a few properties, like title and 
message, and several methods, including show: 
We can learn more about UIAlertView in Apple’s developer library at: 
http://developer.apple.com/library/ios/#documentation/uikit/reference/UIAlertView_Class/UIAlertView/UIAlertView.html
INTERACTING WITH YOUR APP 
Let’s dismiss the previous alert and create a new one… 
~/rm_workshop/code/hello 
(main)> alert.dismiss 
=> #<UIAlertView:0x90d39e0> 
(main)> alert = UIAlertView.new 
=> #<UIAlertView:0x8ddcef0> 
(main)> alert.addButtonWithTitle 'Kaboom!' 
=> 0 
(main)> alert.message = 'Foo Bar' 
=> "Foo Bar" 
(main)> alert.show 
=> #<UIAlertView:0x8ddcef0> 
(main)> 
This time we’ll use the addButtonWithTitle method
INTERACTING WITH YOUR APP 
Clicking the button 
dismisses the alert 
We can learn more about UIAlertView#addButtonWithTitle method at: 
https://developer.apple.com/library/ios/documentation/uikit/reference/UIAlertView_Class/UIAlertView/UIAlertView.html#//apple_ref/doc/uid/TP40006802-CH3-SW6
WHEN TO USE THE REPL 
• The REPL gives us a playground to experiment and learn about the 
RubyMotion/iOS API 
• When doing TDD, the REPL is where I try my raw ideas before 
committing them to code 
• RubyMotion REPL is unique to the iOS development ecosystem and 
one of the best ways to speed up your development process
FROM THE REPL to your App 
class AppDelegate 
def application(application, didFinishLaunchingWithOptions:launchOptions) 
alert = UIAlertView.new 
alert.title = 'RubyMotion' 
alert.message = 'RubyMotion is that da house!' 
alert.addButtonWithTitle 'Kaboom!' 
! 
alert.show 
! 
true 
end 
end 
app/app_delegate.rb 
• We can apply what we learned on the REPL to our application 
• Modify your AppDelegate as show above and run the rake command again
The application method 
• In the application method, we create objects that inherit from UIView (as 
UIAlertView does) 
• UIView's define an interface for managing the content of a rectangular area 
• At runtime the app is represented by a UIApplication object which 
“delegates” to your custom code in the AppDelegate class 
• A delegate is a class that typically implements one or more protocols 
• Nearly all UI classes have a delegate that you can use to receive callbacks from 
the framework
that method looks weird! 
def application(application, didFinishLaunchingWithOptions:launchOptions) 
... 
end 
• You probably noticed the signature of application method 
• The second parameter is camel-cased and it has a right colon smack in the 
middle 
• The camel-casing is just a direct port of the Objective-C parameter names 
• RubyMotion is mostly based on Ruby 1.9. In the upcoming Ruby 2.0, we get 
named parameters which use the : syntax, aligning perfectly with 
Objective-C parameters
FROM THE REPL to your App 
• Let’s make the variable alert an instance variable 
• Modify your AppDelegate as show above and run the rake command again 
class AppDelegate 
def application(application, didFinishLaunchingWithOptions:launchOptions) 
@alert = UIAlertView.new 
@alert.title = 'RubyMotion' 
@alert.message = 'RubyMotion is that da house!' 
@alert.addButtonWithTitle 'Kaboom!' 
! 
@alert.show 
! 
true 
end 
end 
app/app_delegate.rb
…and back to the repL 
We can also interact with the running application elements: 
~/rm_workshop/code/hello 
(main) app = UIApplication.sharedApplication 
=> #<UIApplication:0x901ab90> 
(main)> delegate = app.delegate 
=> #<AppDelegate:0x8e2d940 @alert=#<UIAlertView:0x8e33ac0>> 
(main)> alert = delegate.instance_variable_get('@alert') 
=> #<UIAlertView:0x8e33ac0> 
(main) alert.message = 'Chunky Bacon is the best!' 
=> "Chunky Bacon is the best!" 
(main)> 
! 
! 
We can access the singleton application instance via the sharedApplication 
class method, which gets us access to the delegate and the instance variable via 
instance_variable_get.
TDD with Bacon 
Test-Driven Development in RubyMotion
TEST-driven Development 
• TDD creates a tight loop of development that cognitively engages us 
• TDD gives us lightweight rigor by making development, goal-oriented 
with a clear goal setting, goal reaching and improvement stages 
• The stages of TDD are commonly known as the 
Red-Green-Refactor loop
TEST-driven Development 
RED 
REFACTOR GREEN 
Clean up & improve without adding functionality Write the minimal code to pass the test 
Eliminate Redundancy 
Write a failing test for new functionality
TDD with BACON 
• The Ruby Community has embrace the practice of Test-Driven 
Development (TDD) 
• RubyMotion comes bundled with MacBacon, a Mac specific version 
of Bacon which is a small clone of RSpec 
• In this section we’ll embark on building the Okonawa application, a 
simple ToDo list manager for iOS
okonawa 
Okonawa (行わ) which translates roughly to “Done” will be a simple To Do app: 
~/rm_workshop/code 
/> motion create okonawa 
Create okonawa 
Create okonawa/.gitignore 
Create okonawa/app/app_delegate.rb 
Create okonawa/Gemfile 
Create okonawa/Rakefile 
Create okonawa/resources/Default-568h@2x.png 
Create okonawa/spec/main_spec.rb 
! 
Let’s start by creating a new RM application!
RM SPECS 
• The file spec/main_spec.rb, contains a simple Bacon test that verifies 
that your app has at least one window 
• In the before block we access the sharedApplication singleton in order to 
verify the size of the windows property 
describe "Application 'okonawa'" do 
before do 
@app = UIApplication.sharedApplication 
end 
! 
it "has one window" do 
@app.windows.size.should == 1 
end 
end 
spec/main_spec.rb
kicking off the TDD LOOP 
Change directories into the newly created application directory and type rake spec: 
~/rm_workshop/code/okonawa 
Application 'okonawa' 
- has one window [FAILED - 0.==(1) failed] 
! 
Bacon::Error: 0.==(1) failed 
spec.rb:700:in `satisfy:': Application 'okonawa' - has one window 
spec.rb:714:in `method_missing:' 
spec.rb:316:in `block in run_spec_block' 
spec.rb:440:in `execute_block' 
spec.rb:316:in `run_spec_block' 
spec.rb:331:in `run' 
! 
1 specifications (1 requirements), 1 failures, 0 errors 
Starting with Failure! A Good Thing
getting to green 
• We’ll make minimal modifications to app/app_delegate.rb in order to 
pass our failing spec 
• We’ll add a window (UIWindow) which is a manager for other views your 
app displays on the device screen 
• An iOS application has only one window (unless your app can use an 
external display). 
class AppDelegate 
def application(application, didFinishLaunchingWithOptions:launchOptions) 
@window = UIWindow.alloc.initWithFrame(UIScreen.mainScreen.bounds) 
@window.makeKeyAndVisible 
end 
end 
app/app_delegate.rb
alloc? 
@window = UIWindow.alloc.initWithFrame(UIScreen.mainScreen.bounds) 
• The alloc method is a class method of NSObject that returns a new 
instance of the receiving class 
• To complete the initialization process you must call one of it’s initializers 
• Objective-C provides designated initializers and secondary initializers 
• An initializer (init and friends) is coupled with alloc in the same line of 
code 
• We pass a CGRect (the size of the mainScreen) to the initWithFrame
what about ruby’s new? 
• NSObject provides a new method which allocates and calls init 
• When we call .new on an Objective-C class it is the equivalent 
of .alloc.init 
• Since initWithFrame is the designated initializer for UIWindow, calling init 
will call it (passing as a param the constant CGRectZero - a zero rectangle) 
• We could change the line above to (which is not exactly the same) but 
would make the spec pass: 
@window = UIWindow.alloc.init 
# or 
@window = UIWindow.new
getting to green 
Let’s run rake spec again: 
~/rm_workshop/code/okonawa 
Application 'okonawa' 
- has one window 
! 
1 specifications (1 requirements), 0 failures, 0 errors 
! 
! 
! 
! 
! 
! 
! 
We’ve reached the GREEN state. Yay!
STORY #1 
• We have passing set of tests and a very uninteresting application :-( 
• Let’s kick the TDD cycle into full gear by implementing a simple story: 
“As a User I should be able to see a 
list of all To-Dos”
• We’ll craft a very generic test following the formula we used to access the 
delegate from the REPL 
• In a before block, we’ll grab the application, the delegate, and something I’m 
calling table, which is yet to be created 
• Our first test will just test that this table exists in spec/todos_spec.rb 
describe "Todos View" do 
before do 
@app = UIApplication.sharedApplication 
@delegate = @app.delegate 
@table = @delegate.instance_variable_get("@table") 
end 
! 
it 'should exist' do 
@table.should.not == nil 
end 
end 
spec/todos_spec.rb
confirm failure 
Let’s run the tests again to get back to our “Red” state: 
~/rm_workshop/code/okonawa 
Application 'okonawa' 
- has one window 
! 
Todos View 
- should exist [FAILED - not nil.==(nil) failed] 
! 
Bacon::Error: not nil.==(nil) failed 
spec.rb:700:in `satisfy:': Todos View - should exist 
spec.rb:714:in `method_missing:' 
spec.rb:316:in `block in run_spec_block' 
spec.rb:440:in `execute_block' 
spec.rb:316:in `run_spec_block' 
spec.rb:331:in `run' 
! 
2 specifications (2 requirements), 1 failures, 0 errors 
Alright. Let’s go slay this dragon!
REPL Detective Work 
• To make our application pass this story we need to go into design mode. 
We’ll need a view that can display a list of things 
• Amongst the descendants of UIView there is a class called UITableView 
• how do we add this UITableView to our application’s window? Inspecting 
the methods of UIWindow we see that there is a method called addSubview 
~/rm_workshop/code/okonawa 
(main)> UIWindow.instance_methods.sort.keep_if { |m| m =~ /S*viewS*/ } 
=> 
[:"addSubview:", :autoresizesSubviews, :"bringSubviewToFront:", :deliversTouchesForGestu 
resToSuperview, :"didAddSubview:", :didMoveToSuperview, :"exchangeSubviewAtIndex:withSub 
viewAtIndex:", :"insertSubview:above:", :"insertSubview:aboveSubview:", :"insertSubview: 
atIndex:", :"insertSubview:below:", :"insertSubview:belowSubview:", :layoutSubviews, :"m 
ovedFromSuperview:", :"movedToSuperview:", :removeFromSuperview, :"resizeSubviewsWithOld 
Size:", :"resizeWithOldSuperviewSize:", :"sendSubviewToBack:", :"setAutoresizesSubviews: 
", :"setClipsSubviews:", :"setDeliversTouchesForGesturesToSuperview:", :"setSkipsSubview 
Enumeration:", :skipsSubviewEnumeration, :subviews, :superview, :viewDidMoveToSuperview, 
:viewForBaselineLayout, :viewPrintFormatter, :viewTraversalMark, :"viewWillMoveToSupervi 
ew:", :"viewWithTag:", :"willMoveToSuperview:", :"willRemoveSubview:"] 
(main)> 
!
using UITableView 
• Let’s expand our AppDelegate by creating the instance variable @table as a 
UITableView sized to the screen’s bounds: 
class AppDelegate 
def application(application, didFinishLaunchingWithOptions:launchOptions) 
@window = UIWindow.alloc.initWithFrame(UIScreen.mainScreen.bounds) 
@window.makeKeyAndVisible 
! 
@table = UITableView.alloc.initWithFrame(UIScreen.mainScreen.bounds) 
@window.addSubview(@table) 
end 
end 
app/app_delegate.rb
An Empty UITableView 
• Run rake to see what our app looks like so far:
and back to green 
Let’s run rake spec again: 
~/rm_workshop/code/okonawa 
Application 'okonawa' 
- has one window 
! 
Todos View 
- should exist 
! 
2 specifications (2 requirements), 0 failures, 0 errors 
! 
! 
! 
! 
Not a very robust test, but it’s allowing us to 
move forward in a controlled fashion
displaying some TODOs 
• Looking at the reference for UITableView there is a method 
visibleCells that returns the table cells that are visible in the receiver 
• The visibleCells method returns an array containing UITableViewCell 
objects 
• Let’s check the visibleCells method on the REPL by getting to our 
@table UITableView 
~/rm_workshop/code/okonawa 
(main)> (nil)? app = UIApplication.sharedApplication 
=> #<UIApplication:0x8d8b6b0> 
(main)> delegate = app.delegate 
=> #<AppDelegate:0x8fae550 @window=#<UIWindow:0x8da87d0> @table=#<UITableView: 
0x9b7ae00>> 
(main)> table = delegate.instance_variable_get("@table") 
=> #<UITableView:0x9b7ae00> 
(main)> table.visibleCells 
=> [] 
(main)> 
!!
displaying some TODOs 
• With that information on hand, let’s write a spec: 
it 'displays the given ToDos' do 
@table.visibleCells.should.not.be.empty 
end 
~/rm_workshop/code/okonawa 
Todos View 
- should exist 
- displays the given ToDos [FAILED - not [].empty?() failed] 
! 
Bacon::Error: not [].empty?() failed 
spec.rb:700:in `satisfy:': Todos View - displays the given ToDos 
spec.rb:714:in `method_missing:' 
spec.rb:316:in `block in run_spec_block' 
spec.rb:440:in `execute_block' 
spec.rb:316:in `run_spec_block' 
spec.rb:331:in `run' 
! 
3 specifications (3 requirements), 1 failures, 0 errors 
spec/todos_spec.rb
displaying some TODOs 
• So how are we going to pass our test? It seems that we might want to get 
some UITableViewCell objects in our table 
• If we inspect the UITableView reference we learn that a UITableView 
gets its data from a UITableViewDataSource which is set via the 
dataSource attribute 
• The UITableViewDataSource protocol is adopted by an object that 
mediates the application’s data model for a UITableView object
displaying some TODOs 
• To create a class that can serve as a UITableViewDataSource we must 
provide two method implementations: 
(Integer) tableView(tableView, numberOfRowsInSection:section) 
• Tells the table how many rows of data we have. 
(UITableViewCell) tableView(tableView, cellForRowAtIndexPath:indexPath) 
• Returns a UITableViewCell for a given row index
UITableViewDataSource 
• We will implement the two required methods in the contract, and serve our 
data from a simple array @data 
• Let’s create a TodosDataSource class in app/todos_data_source.rb: 
class TodosDataSource 
! 
attr_writer :data 
! 
def tableView(tableView, numberOfRowsInSection:section) 
@data.size 
end 
! 
def tableView(tableView, cellForRowAtIndexPath:indexPath) 
cell = UITableViewCell.alloc.initWithStyle(UITableViewCellStyleDefault, 
reuseIdentifier:nil) 
cell.textLabel.text = @data[indexPath.row] 
cell 
end 
end app/todos_data_source.rb
UITableViewDataSource 
• Let’s modify the AppDelegate to use the TodosDataSource by simply 
setting its data attribute with an array of strings: 
def application(application, didFinishLaunchingWithOptions:launchOptions) 
@window = UIWindow.alloc.initWithFrame(UIScreen.mainScreen.bounds) 
@window.makeKeyAndVisible 
! 
@table = UITableView.alloc.initWithFrame(UIScreen.mainScreen.bounds) 
! 
todos = %w(Milk Orange Juice Apples Bananas Broccoli Carrots Beef 
Chicken Enchiladas Hot Dogs Butter Bread Pasta Rice) 
! 
todos.map! { |thing| "Buy #{thing}"} 
! 
@data_source = TodosDataSource.new 
@data_source.data = todos 
@table.dataSource = @data_source 
! 
@window.addSubview(@table) 
end app/app_delegate.rb
displaying some TODOs 
• Run rake to see the UITableView populated with data from our 
UITableViewDataSource:
and back to green 
Let’s run rake spec again: 
~/rm_workshop/code/okonawa 
Application 'okonawa' 
- has one window 
! 
Todos View 
- should exist 
- displays the given ToDos 
! 
3 specifications (3 requirements), 0 failures, 0 errors 
! 
! 
!
UITableViewDataSource 
• Let’s complement the previous test with a check for contents of as row: 
it 'displays the correct label for a given ToDo' do 
first_cell = @table.visibleCells.first 
first_cell.textLabel.text.should == 'Buy Milk' 
end 
~/rm_workshop/code/okonawa 
Application 'okonawa' 
- has one window 
! 
Todos View 
- should exist 
- displays the given ToDos 
- displays the correct label for a given ToDo 
! 
4 specifications (4 requirements), 0 failures, 0 errors 
! 
! 
spec/todos_spec.rb
Time to Refactor 
• We’ve done a few red-green short loops; now let’s do a full red-green-refactor 
• You’ve probably noticed that when we launched the console we got a 
warning message that read: 
Application windows are expected to have a root view 
controller at the end of application launch 
• iOS has a pretty robust implementation of the MVC model-view-controller 
pattern 
• The message above hints that we should have delegated the initial display 
of the view to a “view controller”
• The first custom object created at launch time is the app delegate, which 
handles any events that are not handled by by the UIApplication. The app 
delegate is the entry point into the custom code that “controls” the app, the 
entry point into the “C” of M-V-C 
Data Objects Document 
Model 
UIApplication Application Delegate UI Window 
View Controllers Views and UI Objects 
Event 
Loop 
Controller View
View Controllers 
• View controllers in RubyMotion are classes that extend 
UIViewController 
• Using views directly in the AppDelegate is typically frowned upon 
• Let’s make a directory for our controllers under the app directory. In there 
we’ll create the the TodosController in todos_controller.rb
TODOs Controller 
• Controller including the data source contracts previously provided: 
class TodosController < UIViewController 
attr_writer :data 
! 
def viewDidLoad 
super 
self.title = 'Okonawa' 
@table = UITableView.alloc.initWithFrame(self.view.bounds) 
@table.dataSource = self 
self.view.addSubview(@table) 
! 
@data = %w(Milk Orange Juice Apples Bananas Broccoli Carrots Beef Chicken 
app/controllers/todos_controller.rb 
Enchiladas Hot Dogs Butter Bread Pasta Rice).map { |thing| "Buy #{thing}" } 
end 
! 
def tableView(tableView, numberOfRowsInSection: section) 
@data.size 
end 
! 
def tableView(tableView, cellForRowAtIndexPath: indexPath) 
cell = UITableViewCell.alloc.initWithStyle(UITableViewCellStyleDefault, reuseIdentifier:nil) 
cell.textLabel.text = @data[indexPath.row] 
cell 
end 
end
TODOs Controller 
• We’ll also need to refactor the AppDelegate to use the controller: 
class AppDelegate 
def application(application, didFinishLaunchingWithOptions:launchOptions) 
@window = UIWindow.alloc.initWithFrame(UIScreen.mainScreen.bounds) 
! 
@todos_controller = TodosController.alloc 
.initWithNibName(nil, bundle:nil) 
! 
@window.rootViewController = 
UINavigationController.alloc 
.initWithRootViewController(@todos_controller) 
! 
@window.makeKeyAndVisible 
! 
app/app_delegate.rb 
true 
end 
end 
• We accomplish this by setting the window root view controller to an 
instance of our TodosController
TODOs Controller 
• If we run the tests: boom! We get one failure and two errors: 
~/rm_workshop/code/okonawa 
Application 'Todo' 
- has one window 
ToDos View 
- should exist@Table is ==> 
[FAILED] 
- displays the given ToDos@Table is ==> 
[ERROR: NoMethodError] 
- displays the correct label for a give ToDo@Table is ==> 
[ERROR: NoMethodError] 
Bacon::Error: not nil.==(nil) failed 
Boo, our refactoring broke our tests!
Test Refactoring 
• The @table variable is nil. There is no @table in the delegate anymore! 
• RubyMotion provides the ability to declare the context of our tests 
• Let’s refactor the test with the class method tests, which gives us access 
to the controller variable 
describe "Todos Controller" do 
tests TodosController 
! 
before do 
spec/todos_controller.rb 
@table = controller.instance_variable_get("@table") 
end 
... 
• Let’s also rename the file from todos_spec.rb to todos_controller.rb
TODOs Controller 
• Run rake to reveal the Todos list backed by the Todos Controller:
M in MVC 
Working with Data in iOS
data in IOS 
Isolated applications are mostly non-existent. 
Most mobile apps keep their data in an 
external service and keep a local user data 
cache that needs to be synchronized from time 
to time
data in IOS 
There are many mechanisms by which to persist data locally in an iOS 
application, including but not limited to: 
• NSUserDefaults: Is a key-value local storage, capable of storing both objects and primitive data 
types 
• NSCache: A cache that stores key-value pairs. NSCache automatically evicts objects in order to 
free up space in memory as needed. 
• Archives: Serialize an object graph into an “architecture independent stream of bytes”. Archives 
are available in sequential and keyed archives backed by NSArchiver and NSKeyArchiver 
respectively. 
• Core Data: The 'official' persistence framework that can serializes an object graph, provide object 
life-cycle management, relationships, lazy loading, validation, change tracking, undo support, 
schema migrations and queries.
break out the gems! 
• Since RubyMotion is a dialect of Ruby that is statically compiled, most 
regular Ruby gems won't work right out of the gate 
• iOS typically provides an appropriate counter-part to a pure Ruby gem that 
has been wrapped in “RubyMotion goodness” 
• Just like regular Ruby apps, RM apps use Bundler 
• The big difference is that the require method is only allowed inside your 
project’s Rakefile 
• Gems required in the Rakefile are compiled into the target executable in 
alphabetical order. The generated Rakefile will pull all Gemfile dependencies 
automatically
break out the gems! 
For our ToDo App we'll be using a few libraries that will allow us to follow 'The 
Ruby Way' of development. Let’s add the following gems to our Gemfile: 
• MotionModel: Simple Model and Validation Mixins for RubyMotion 
• Formotion: A simple DSL to create iOS Forms 
source 'https://rubygems.org' 
! 
gem 'rake' 
gem 'motion_model' 
gem 'formotion' 
Gemfile
break out the gems! 
Let’s also isolate our dependencies from other projects in the system by adding 
a .ruby-version and a .ruby-gemset files at the root so that RVM and Bundler 
can manage our project: 
1.9.3 
baruco-okonawa 
.ruby-version 
.ruby-gemset
break out the gems! 
Now simply CD out and back into the project and type: bundle 
~/rm_workshop/code/okonawa 
/>bundle 
Resolving dependencies... 
Using rake 10.3.2 
Installing bubble-wrap-http 1.7.1 
Installing bubble-wrap 1.7.1 
Installing motion-require 0.2.0 
Installing formotion 1.8 
Installing motion-support 0.2.6 
Installing motion_model 0.5.4 
Using bundler 1.6.3 
Your bundle is complete! 
Use `bundle show [gemname]` to see where a bundled gem is installed.
Motion Model 
• MotionModel provides Ruby Objects with light persistence and 
validation abilities, similar to ActiveRecord 
• MotionModel's default persistence strategy relies on NSCoder to 
serialize data. It then uses NSKeyedArchiver to create a NSData 
representation 
• MotionModel is more like DataMapper than ActiveRecord, allowing 
you to declare a model’s columns directly in the Ruby class
the todo model 
• We'll start to TDD our models at the lowest level with a simple spec 
(todo_model_spec.rb) to test that the Todo model class exists: 
describe "Todo Model" do 
it "exists" do 
Object.const_defined?('Todo').should.be.true 
end 
end 
spec/todo_model_spec.rb
the todo model 
• Running rake spec will kick start or model development TDD loop: 
~/rm_workshop/code/okonawa 
Todo Model 
- exists [FAILED - false.true?() failed] 
! 
... 
! 
Bacon::Error: false.true?() failed 
spec.rb:700:in `satisfy:': Todo Model - exists 
spec.rb:714:in `method_missing:' 
spec.rb:316:in `block in run_spec_block' 
spec.rb:440:in `execute_block' 
spec.rb:316:in `run_spec_block' 
spec.rb:331:in `run
The Todo Model 
• Let’s create an app/models folder to house our new Todo model (todo.rb) 
class Todo 
end 
~/rm_workshop/code/okonawa 
Application 'okonawa' 
- has one window 
! 
Todo Model 
- exists 
! 
Todos Controller 
- should exist 
- displays the given ToDos 
- displays the correct label for a given ToDo 
! 
5 specifications (5 requirements), 0 failures, 0 errors 
app/models/todo.rb
the todo model 
Let’s write a spec to define what we expect from our Todo model: 
• Add a before block and instantiate a Todo 
• A Todo model should know its name, description, due_date, and 
whether it is done or not 
before do 
@todo = Todo.new 
end 
... 
! 
it 'has a name, description, a due date and whether is done or not' do 
@todo.should.respond_to :name 
@todo.should.respond_to :description 
@todo.should.respond_to :due_date 
@todo.should.respond_to :done 
end 
spec/todo_model_spec.rb
the todo model 
• Running rake spec again: 
~/rm_workshop/code/okonawa 
Todo Model 
- exists 
- has a name, description, a due date and whether is done or not [FAILED - 
#<Todo:0xa6c8a90>.respond_to?(:name) failed] 
... 
Bacon::Error: #<Todo:0xa6c8a90>.respond_to?(:name) failed 
spec.rb:700:in `satisfy:': Todo Model - has a name, description, a due date and 
whether is done or not 
spec.rb:714:in `method_missing:' 
spec.rb:316:in `block in run_spec_block' 
spec.rb:440:in `execute_block' 
spec.rb:316:in `run_spec_block' 
spec.rb:331:in `run' 
! 
6 specifications (6 requirements), 1 failures, 0 errors
motion model 
To pass the spec we’ll have to: 
• Mix in the main MotionModel functionality contained in 
MotionModel::Model 
• Use the default persistence adapter provided by the module 
MotionModel::ArrayModelAdapter 
• Define the 4 columns using the columns method
class Todo 
include MotionModel::Model 
include MotionModel::ArrayModelAdapter 
! 
columns :name => :string, 
:details => :string, 
:due_date => {:type => :date, 
:formotion => {:picker_type => :date_time}}, 
:done => {:type => :boolean, :default => false, 
:formotion => {:type => :switch}} 
end 
Motion Model 
• The Todo model enhanced with MotionModel: 
app/models/todo.rb
motion Model 
• Running rake spec again to confirm the newly added functionality: 
~/rm_workshop/code/okonawa 
Application 'okonawa' 
- has one window 
! 
Todo Model 
- exists 
- has a name, description, a due date and whether is done or not 
! 
Todos Controller 
- should exist 
- displays the given ToDos 
- displays the correct label for a given ToDo 
! 
6 specifications (9 requirements), 0 failures, 0 errors
validations w/ motion Model 
• Let’s specify that a Todo must have a name to be valid: 
it 'is invalid without a name' do 
@todo.name = nil 
@todo.should.not.be.valid 
end 
~/rm_workshop/code/okonawa 
spec/todo_model_spec.rb 
Todo Model 
- exists 
- has a name, description, a due date and whether is done or not 
- is invalid without a name [ERROR: NoMethodError - undefined method `valid?' for 
Todo#3:0xa7b8b50:Todo] 
! 
... 
NoMethodError: undefined method `valid?' for Todo#3:0xa7b8b50:Todo 
model.rb:863:in `method_missing:': Todo Model - is invalid without a name 
spec.rb:697:in `satisfy:' 
spec.rb:714:in `method_missing:' 
spec.rb:316:in `block in run_spec_block' 
spec.rb:440:in `execute_block' 
spec.rb:316:in `run_spec_block' 
spec.rb:331:in `run'
validations w/ motion Model 
• Including the MotionModel::Validatable mixin to use the validates 
method: 
class Todo 
include MotionModel::Model 
include MotionModel::ArrayModelAdapter 
include MotionModel::Validatable 
! 
! 
columns :name => :string, 
:details => :string, 
:due_date => {:type => :date, 
:formotion => {:picker_type => :date_time}}, 
:done => {:type => :boolean, :default => false, 
:formotion => {:type => :switch}} 
! 
validates :name, :presence => true 
end 
app/models/todo.rb
motion Model 
• Running rake spec again to confirm that the Todo model validates the 
presence of its name: 
~/rm_workshop/code/okonawa 
Application 'okonawa' 
- has one window 
! 
Todo Model 
- exists 
- has a name, description, a due date and whether is done or not 
- is invalid without a name 
! 
Todos Controller 
- should exist 
- displays the given ToDos 
- displays the correct label for a given ToDo 
! 
7 specifications (10 requirements), 0 failures, 0 errors
a couple more validations 
• Let's round out the Todo model development with a couple of 
specifications: “A Todo is not done by default” and “A Todo knows if its 
overdue” 
before do 
@now = NSDate.new 
@todo = Todo.new :name => "Buy Milk", 
:description => "We need some Milk", 
:due_date => @now 
end 
! 
it 'is not done by default' do 
@todo.done.should.not.be.true 
end 
! 
it 'knows if its overdue' do 
@todo.should.be.overdue 
end 
spec/todo_model_spec.rb
a couple more validations 
• Now we have to implement the overdue? method: 
~/rm_workshop/code/okonawa 
Todo Model 
... 
- knows if its overdue [ERROR: NoMethodError - undefined method `overdue?' for 
Todo#5:0xa69ab50:Todo] 
... 
! 
NoMethodError: undefined method `overdue?' for Todo#5:0xa69ab50:Todo 
model.rb:863:in `method_missing:': Todo Model - knows if its overdue 
spec.rb:697:in `satisfy:' 
spec.rb:714:in `method_missing:' 
spec.rb:316:in `block in run_spec_block' 
spec.rb:440:in `execute_block' 
spec.rb:316:in `run_spec_block' 
spec.rb:331:in `run' 
! 
9 specifications (11 requirements), 0 failures, 1 errors
a couple more validations 
• Add the overdue? method to the Todo model and provide an implementation: 
def overdue? 
# TODO: Implement me! 
end 
spec/todo_model_spec.rb
integrating the model 
• The next step is to refactor the TodosController controller to 
make use of the new model 
• Our refactoring will involve replacing the @data string array with a 
collection of Todo model instances 
• Also, since we are using a full-fledge model we’ll have to extract the 
property we want to use to display on the table
integrating the model 
• Replace the @data array contents with a call to Todo.all 
• Use the name attribute to be used in the cell label in cellForRowAtIndexPath
integrating the model 
• Refactor the controller spec to clean up the DB and create a new Todo 
using the create method
integrating the model 
• Refactor the seeding of the DB to a separate seed method 
• Refactor the AppDelegate to seed the DB in non-test mode
WORKING WITH FORMS 
Displaying and Editing our Models
FORMOTION 
https://github.com/clayallsopp/formotion 
• FormMotion “Making iOS Forms insanely great with RubyMotion" 
• Provides a builder DSL to quickly create iOS Forms 
• Provides custom controllers to display the forms 
• Integrates with MotionModel!
STORY #2 
• We now have a list of Todos but we also need to see a Todo’s details 
• Our next story will concentrate on the Todo’s details view: 
“As a User I should be able to see a 
To-Do’s details”
TODO controller 
• We want a form displaying the Todo's details. 
• That functionality will be the responsibility of the (yet to be created) 
TodoController 
• Let's start with the spec below: 
describe "Todo Controller" do 
it 'exists' do 
Object.const_defined?('TodoController').should.be.true 
end 
end 
spec/todo_controller_spec.rb
TODO controller 
• Let’s use Formotion to implement our model-driven form: 
class TodoController < Formotion::FormController 
end 
app/controllers/todo_controller.rb 
~/rm_workshop/code/okonawa 
Todo Controller 
- exists 
! 
... 
! 
10 specifications (13 requirements), 0 failures, 0 errors 
! 
! 
!
TODO controller 
• Let's add a spec to test that our controller can indeed display a Todo model 
• The spec does require some prior knowledge of how to access the soon to 
be created rows of our form 
it 'displays a Todo's details' do 
@name_row.value.should.equal 'Buy Milk' 
@details_row.value.should.equal 'We need some Milk' 
@due_date_row.object.date_value.hour.should.equal @now.hour 
@due_date_row.object.date_value.min.should.equal @now.min 
@done_row.value.should.equal false 
end 
spec/todo_controller_spec.rb
• Although we have a failure, it’s not very telling (a sign we might be testing 
to close to the implementation): 
~/rm_workshop/code/okonawa 
Todo Controller 
- exists 
- displays a Todo's details [ERROR: NoMethodError - undefined method `value' for nil:NilClass] 
… 
! 
NoMethodError: undefined method `value' for nil:NilClass 
spec.rb:316:in `block in run_spec_block': Todo Controller - displays a Todo's details 
spec.rb:440:in `execute_block' 
spec.rb:316:in `run_spec_block' 
spec.rb:331:in `run' 
! 
11 specifications (13 requirements), 0 failures, 1 errors 
!!!!!! 
TODO controller
FORM-AWARE MODELS 
• By adding the MotionModel::Formotion Mixin allow the ability to create 
a FormMotion::Form from an instance of Todo 
• E.g. Formotion::Form.new(todo.to_formotion('Edit your ToDo')) 
class Todo 
include MotionModel::Model 
include MotionModel::ArrayModelAdapter 
include MotionModel::Validatable 
include MotionModel::Formotion 
! 
... 
app/models/todo.rb
TODO controller 
• Let’s expand the TodoController with the ability to initialize itself with an 
instance of a FormMotion::Form (which we’ll build with our form-aware 
model): 
class TodoController < Formotion::FormController 
attr_accessor :todo 
attr_accessor :form 
! 
def initialize(todo) 
self.form = Formotion::Form.new(todo.to_formotion('Edit your ToDo')) 
self.initWithForm(self.form) 
self.todo = todo 
end 
end 
app/controllers/todo_controller.rb
TODO controller spec 
• Finally, we’ll need to refactor the spec to create a Todo model, initialize a 
controller instance with it and make that available to the spec 
describe "Todo Controller" do 
tests TodoController 
! 
before do 
@now = NSDate.new 
@todo = Todo.create :name => "Buy Milk", 
:details => "We need some Milk", 
:due_date => @now 
@controller = TodoController.new(@todo) 
! 
@form = @controller.instance_variable_get("@form") 
@name_row = @form.sections[0].rows[0] 
@details_row = @form.sections[0].rows[1] 
@due_date_row = @form.sections[0].rows[2] 
@done_row = @form.sections[0].rows[3] 
end 
! 
def controller 
@controller 
end 
... 
spec/todo_controller_spec.rb
TODO controller 
• A subclass of UIViewController, UITableViewController is designed 
to host and manage a UITableView 
• By replacing the base class we don’t have to manually wire the 
UITableView, set the datasource and add the view: 
class TodosController < UITableViewController 
attr_writer :data 
! 
def viewDidLoad 
super 
self.title = 'Okonawa' 
# @table = UITableView.alloc.initWithFrame(self.view.bounds) 
# @table.dataSource = self 
# self.view.addSubview(@table) 
! 
@data = Todo.all 
end 
... 
app/controllers/todo_controller.rb
TODO controller 
• Let’s add the tableView#didSelectRowAtIndexPath event handler 
• We’ll deselect the row the user just selected (clean up), grab the index of 
the selection (via indexPath param), find the Todo in our Array, create the 
TodoController and use the navigationController to push the new 
view: 
def tableView(tableView, didSelectRowAtIndexPath: indexPath) 
tableView.deselectRowAtIndexPath(indexPath, animated: true) 
todo = @data[indexPath.row] 
todo_controller = TodoController.new(todo) 
self.navigationController.pushViewController(todo_controller, animated: true) 
end 
app/controllers/todo_controller.rb
TODO controller spec 
• If you run the specs the Todos Controller spec will fail since there is no 
longer an instance variable @table in the controller 
• Instead we can access the tableView property of the UIViewController 
describe "Todos Controller" do 
tests TodosController 
! 
before do 
Todo.delete_all 
@now = NSDate.new 
@todo = Todo.create(:name => 'Buy Milk', 
:description => 'Get some 1% to rid yourself of the muffin top', 
:due_date => @now) 
#@table = controller.instance_variable_get("@table") 
@table = controller.tableView 
end 
spec/todo_controller_spec.rb
Integrated controllers
editing / saving 
• Now, let's add the ability to save any modifications made to a Todo 
• This section is going to be “Lab Style”. I’ll provide a failing spec for you to 
pass 
• Feel free to work in groups! 
it 'saves changes made to a Todo' do 
@name_row.object.row.value = 'Buy 1% Milk' 
controller.save 
! 
saved_todo = Todo.find(@todo.id) 
! 
saved_todo.name.should.equal 'Buy 1% Milk' 
end 
spec/todo_controller_spec.rb
editing / saving 
• Now, on to implement the save method! 
~/rm_workshop/code/okonawa 
Todo Controller 
- exists 
- saves changes made to a Todo [ERROR: NoMethodError - undefined method 
`save' for #<TodoController:0xd3e2d90>] 
... 
! 
NoMethodError: undefined method `save' for #<TodoController:0xd3e2d90> 
spec.rb:316:in `block in run_spec_block': Todo Controller - saves changes 
made to a Todo 
spec.rb:440:in `execute_block' 
spec.rb:316:in `run_spec_block' 
spec.rb:331:in `run' 
! 
11 specifications (13 requirements), 0 failures, 1 errors
editing / saving - hints 
• Implement the controller save method: 
• Find how to retrieve the data from the form (what methods does @form 
support?) 
• Find how to update a Todo model the form data (what methods does 
@todo support?) 
• Consult the Formotion and MotionModel documentation if necessary
save button 
• It doesn’t help our users that we have a working save method in the 
controller if they can't use it 
• We need to add a “Save” button to the TodoController view: 
def viewDidLoad 
super 
saveButton = UIBarButtonItem.alloc.initWithTitle("Save", 
style: UIBarButtonItemStyleBordered, 
target: self, action: 'save') 
self.navigationItem.rightBarButtonItem = saveButton 
end 
app/controllers/todo_controller.rb
save button 
• Launching the app we can see that the changed values are persisted when 
we tap the ‘Save’ button, but they are not reflected on the Todos list 
• Model Motion supports notifications that are issued on object save, update, 
and delete. We can use the NSNotificationCenter default instance to 
register an observer, which will invoke the todoChanged method 
def viewDidLoad 
super 
self.title = 'Okonawa' 
! 
@data = Todo.all 
! 
NSNotificationCenter.defaultCenter.addObserver(self, selector: 'todoChanged:', 
name: 'MotionModelDataDidChangeNotification', 
object: nil) unless RUBYMOTION_ENV == 'test' 
end 
app/controllers/todos_controller.rb
save button 
• The todoChanged method received the notification object which has an 
action property under userInfo 
• On ‘update’ we will retrieve the passed Todo, find the row for the it (hacky), 
create a NSIndexPath and tell the cell at that path to refresh itself: 
def todoChanged(notification) 
case notification.userInfo[:action] 
when 'add' 
when 'update' 
todo = notification.object 
row = todo.id - 1 
path = NSIndexPath.indexPathForRow(row, inSection:0) 
tableView.reloadRowsAtIndexPaths([path], withRowAnimation:UITableViewRowAnimationAutomatic) 
when 'delete' 
end 
end app/controllers/todos_controller.rb
save button 
• The “Save” button in action:
COCOAPODS 
Working with Non-Gem Dependencies
cocoapods 
• Until recently there was only one way to maintain 3rd-party dependencies: 
the vendoring procedure. 
• The procedure entails downloading, unzipping/un-tarring and manually 
copying dependencies (upgrades also following the same manual 
procedure) 
• Luckily CocoaPods liberates us from this 1990’s dependency management 
hell! 
• CocoaPods manages library dependencies for your Xcode/RubyMotion 
projects. The dependencies for your projects are specified in a single text 
file called a Podfile
cocoapods 
• To use CocoaPods within a RubyMotion application you use the motion-cocoapods 
gem located at https://github.com/HipByte/motion-cocoapods 
• It can be installed with Bundler like any other Ruby Gem. Let’s add it to our 
project’s Gemfile and bundle the application: 
source 'https://rubygems.org' 
! 
gem 'rake' 
gem 'motion_model' 
gem 'formotion' 
gem 'motion-cocoapods' 
Gemfile
pixate freestyle 
Style your iOS app with CSS
PIXATE FREESTYLE 
• Pixate Freestyle is a native iOS (and Android) library that styles native 
controls with CSS 
• Replace many complicated lines of Objective-C with a few lines of CSS 
• Pixate Freestyle allows you to add IDs, Class’es, and inline styles to your 
native components, and style them with CSS 
• Pixate also offers themes (CSS stylesheets offering a based style for most 
iOS components) http://pixate.github.io/pixate-freestyle-ios/themes/
INSTALLING W/ COCOAPODS 
• In order to add the Pixate Freestyle library to our RubyMotion project we 
first need to find out if the library is maintained under CocoaPods 
• The easiest way to do this to search directly on the CocoaPods site (paste 
http://cocoapods.org/?q=pixatef in your browser):
INSTALLING W/ COCOAPODS 
• Using motion-cocoapods we can declare our Pods directly in the Rakefile: 
Motion::Project::App.setup do |app| 
# Use `rake config' to see complete project settings. 
app.name = 'okonawa' 
! 
app.pods do 
pod 'PixateFreestyle', '~> 2.1' 
end 
end 
Rakefile
INSTALLING W/ COCOAPODS 
• CocoaPods configures a master repository locally (to avoid duplication) 
• After bundling the application, run the pod setup command: 
~/rm_workshop/code/okonawa 
/> pod setup 
Setting up CocoaPods master repo 
Setup completed (read-only access) 
!!!!!!!!!!
INSTALLING W/ COCOAPODS 
• The next step is to install the dependencies into our RubyMotion project. 
• Run motion-pods provided Rake task: rake pod:install: 
~/rm_workshop/code/okonawa 
/> rake pod:install 
Updating spec repo `master` 
Current branch master is up to date. 
Analyzing dependencies 
Downloading dependencies 
Installing PixateFreestyle (2.1.4) 
Generating Pods project 
!!!!!!
INSTALLING W/ COCOAPODS 
• Installing the pods create a Pods folder under your project’s vendor folder 
• Since we do not want to check those artifacts into our Repo, add the following 
lines to your .gitignore file 
vendor/Pods/ 
vendor/Pods/.build/ 
vendor/Pods/build-iPhoneOS/ 
vendor/Pods/build-iPhoneSimulator/ 
vendor/Podfile.lock 
.gitignore
motion-pixatefreestyle 
• Our next step is to bring in the Ruby bridge to our Pixate Freestyle pod, the 
motion-pixatefreestyle Gem: 
source 'https://rubygems.org' 
! 
gem 'rake' 
gem 'motion_model' 
gem 'formotion' 
gem 'motion-cocoapods' 
gem 'motion-pixatefreestyle' 
Gemfile
motion-pixatefreestyle 
• The motion-pixatefreestyle Gem adds a couple of Rake tasks 
• The init and sass tasks: 
~/rm_workshop/code/okonawa 
/> rake -T | grep pixate 
rake pixatefreestyle:init # Create initial stylesheet files 
rake pixatefreestyle:sass # Compile SASS/SCSS file 
! 
! 
! 
! 
! 
! 
! 
!
motion-pixatefreestyle 
• Running rake pixatefreestyle:init will create our sass/scss 
style file and the ‘compiled’ default.css under the resources folder: 
~/rm_workshop/code/okonawa 
/>rake pixatefreestyle:init 
! 
Create sass/default.scss 
Create resources/default.css 
! 
! 
! 
!
Using A THEME 
• Freestyle provides a theme (a set of SASS files) to quickly style an entire 
iOS app 
• Themes are available under the Pixate Freestyle repo, currently only the 
pixate-blue is available 
• To copy the theme clone the repo: git clone https://github.com/Pixate/ 
pixate-freestyle-ios.git OR download just the them from pixate-blue 
• Copy the contents of the file to your /sass folder
• Running rake pixatefreestyle:sass with the them SASS files in 
place will ‘compile’ a new default.css under the resources folder: 
~/rm_workshop/code/okonawa 
/>rake pixatefreestyle:sass 
! 
Compile sass/default.scss 
! 
! 
! 
! 
Using A THEME
Using A THEME 
• To work with Pixate Freestyle, we add CSS classes and ids to the app 
elements 
• To illustrate the power of CSS-based styling. Let’s add a CSS class to the cells 
in our UITableView (in UITableViewController in 
todos_controller.rb) 
def tableView(tableView, cellForRowAtIndexPath: indexPath) 
cell = UITableViewCell.alloc.initWithStyle(UITableViewCellStyleDefault, 
reuseIdentifier:nil) 
cell.textLabel.text = @data[indexPath.row].name 
cell.styleClass = 'table-cell' 
cell 
end 
app/controllers/todos_controller.rb
Using A THEME 
• Our newly styled Todo list:
Using A THEME 
• Styling the Formotion artifacts proved to be a little more involved since they 
are not directly exposed 
• After a few hours of GooglingTM I found that I needed to monkey-patch the 
Formotion::Form class as shown below: 
module Formotion 
class Form < Formotion::Base 
def tableView(tableView, willDisplayCell: cell, forRowAtIndexPath: indexPath) 
cell.styleClass = 'table-cell' 
cell.updateStyles 
end 
end 
end app/formotion_pixate.rb
Using A THEME 
• The styled Todo Controller:
NEXT STEPS 
Continuing your RubyMotion Learning…
CONTINUING YOUR LEARNING 
• I’ve created a series of articles that chronicle the development of the Okonawa 
application and I will continue enhancing the series in 2014 and 2015 
• http://integrallis.com/2013/03/tdd_in_ios_w_ruby_motion_part_i 
• http://integrallis.com/2013/04/tdd_in_ios_w_ruby_motion_part_ii 
• http://integrallis.com/2014/06/ios-development-in-ruby-with-rubymotion-part-iii 
• We also offer a 3 day RubyMotion course! http://integrallis.com/courses/rubymotion
https://github.com/integrallis/baruco-okonawa
http://spkr8.com/t/36071 
Please rate this workshop!
Thanks http://integrallis.com

Weitere ähnliche Inhalte

Was ist angesagt?

Java applet basics
Java applet basicsJava applet basics
Java applet basics
Sunil Pandey
 

Was ist angesagt? (20)

Java applet basics
Java applet basicsJava applet basics
Java applet basics
 
applet using java
applet using javaapplet using java
applet using java
 
Class notes(week 10) on applet programming
Class notes(week 10) on applet programmingClass notes(week 10) on applet programming
Class notes(week 10) on applet programming
 
Using API platform to build ticketing system (translations, time zones, ...) ...
Using API platform to build ticketing system (translations, time zones, ...) ...Using API platform to build ticketing system (translations, time zones, ...) ...
Using API platform to build ticketing system (translations, time zones, ...) ...
 
PhoneGap: Accessing Device Capabilities
PhoneGap: Accessing Device CapabilitiesPhoneGap: Accessing Device Capabilities
PhoneGap: Accessing Device Capabilities
 
ParisJS #10 : RequireJS
ParisJS #10 : RequireJSParisJS #10 : RequireJS
ParisJS #10 : RequireJS
 
Applet progming
Applet progmingApplet progming
Applet progming
 
Building an angular application -1 ( API: Golang, Database: Postgres) v1.0
Building an angular application -1 ( API: Golang, Database: Postgres) v1.0Building an angular application -1 ( API: Golang, Database: Postgres) v1.0
Building an angular application -1 ( API: Golang, Database: Postgres) v1.0
 
Using API Platform to build ticketing system #symfonycon
Using API Platform to build ticketing system #symfonyconUsing API Platform to build ticketing system #symfonycon
Using API Platform to build ticketing system #symfonycon
 
[ApacheCon 2016] Advanced Apache Cordova
[ApacheCon 2016] Advanced Apache Cordova[ApacheCon 2016] Advanced Apache Cordova
[ApacheCon 2016] Advanced Apache Cordova
 
Java Applet
Java AppletJava Applet
Java Applet
 
REST easy with API Platform
REST easy with API PlatformREST easy with API Platform
REST easy with API Platform
 
Applet programming in java
Applet programming in javaApplet programming in java
Applet programming in java
 
Building maintainable app
Building maintainable appBuilding maintainable app
Building maintainable app
 
Titanium Studio [Updated - 18/12/2011]
Titanium Studio [Updated - 18/12/2011]Titanium Studio [Updated - 18/12/2011]
Titanium Studio [Updated - 18/12/2011]
 
The Adapter Pattern in PHP
The Adapter Pattern in PHPThe Adapter Pattern in PHP
The Adapter Pattern in PHP
 
Java applet - java
Java applet - javaJava applet - java
Java applet - java
 
Apache Cordova In Action
Apache Cordova In ActionApache Cordova In Action
Apache Cordova In Action
 
React Ecosystem
React EcosystemReact Ecosystem
React Ecosystem
 
Appium understanding document
Appium understanding documentAppium understanding document
Appium understanding document
 

Ähnlich wie Baruco 2014 - Rubymotion Workshop

BEST PRACTICES PER LA SCRITTURA DI APPLICAZIONI TITANIUM APPCELERATOR - Aless...
BEST PRACTICES PER LA SCRITTURA DI APPLICAZIONI TITANIUM APPCELERATOR - Aless...BEST PRACTICES PER LA SCRITTURA DI APPLICAZIONI TITANIUM APPCELERATOR - Aless...
BEST PRACTICES PER LA SCRITTURA DI APPLICAZIONI TITANIUM APPCELERATOR - Aless...
Whymca
 

Ähnlich wie Baruco 2014 - Rubymotion Workshop (20)

Codename one
Codename oneCodename one
Codename one
 
BEST PRACTICES PER LA SCRITTURA DI APPLICAZIONI TITANIUM APPCELERATOR - Aless...
BEST PRACTICES PER LA SCRITTURA DI APPLICAZIONI TITANIUM APPCELERATOR - Aless...BEST PRACTICES PER LA SCRITTURA DI APPLICAZIONI TITANIUM APPCELERATOR - Aless...
BEST PRACTICES PER LA SCRITTURA DI APPLICAZIONI TITANIUM APPCELERATOR - Aless...
 
Lecture 1 Introduction to React Native.pptx
Lecture 1 Introduction to React Native.pptxLecture 1 Introduction to React Native.pptx
Lecture 1 Introduction to React Native.pptx
 
Intro to Rack
Intro to RackIntro to Rack
Intro to Rack
 
React Native +Redux + ES6 (Updated)
React Native +Redux + ES6 (Updated)React Native +Redux + ES6 (Updated)
React Native +Redux + ES6 (Updated)
 
Android Workshop
Android WorkshopAndroid Workshop
Android Workshop
 
Building Mobile Friendly APIs in Rails
Building Mobile Friendly APIs in RailsBuilding Mobile Friendly APIs in Rails
Building Mobile Friendly APIs in Rails
 
Dev streams2
Dev streams2Dev streams2
Dev streams2
 
Connecting with the enterprise - The how and why of connecting to Enterprise ...
Connecting with the enterprise - The how and why of connecting to Enterprise ...Connecting with the enterprise - The how and why of connecting to Enterprise ...
Connecting with the enterprise - The how and why of connecting to Enterprise ...
 
TorqueBox
TorqueBoxTorqueBox
TorqueBox
 
Developing Native Mobile Apps Using JavaScript, ApacheCon NA 2014
Developing Native Mobile Apps Using JavaScript, ApacheCon NA 2014Developing Native Mobile Apps Using JavaScript, ApacheCon NA 2014
Developing Native Mobile Apps Using JavaScript, ApacheCon NA 2014
 
Nike pop up habitat
Nike pop up   habitatNike pop up   habitat
Nike pop up habitat
 
Titanium appcelerator best practices
Titanium appcelerator best practicesTitanium appcelerator best practices
Titanium appcelerator best practices
 
How to Build & Develop Responsive Open Learning Environments with the ROLE SDK
How to Build & Develop Responsive Open Learning Environments with the ROLE SDKHow to Build & Develop Responsive Open Learning Environments with the ROLE SDK
How to Build & Develop Responsive Open Learning Environments with the ROLE SDK
 
RoR guide_p1
RoR guide_p1RoR guide_p1
RoR guide_p1
 
Building native Android applications with Mirah and Pindah
Building native Android applications with Mirah and PindahBuilding native Android applications with Mirah and Pindah
Building native Android applications with Mirah and Pindah
 
Minimum Viable Docker: our journey towards orchestration
Minimum Viable Docker: our journey towards orchestrationMinimum Viable Docker: our journey towards orchestration
Minimum Viable Docker: our journey towards orchestration
 
Plug yourself in and your app will never be the same (1 hr edition)
Plug yourself in and your app will never be the same (1 hr edition)Plug yourself in and your app will never be the same (1 hr edition)
Plug yourself in and your app will never be the same (1 hr edition)
 
1 app 2 developers 3 servers
1 app 2 developers 3 servers1 app 2 developers 3 servers
1 app 2 developers 3 servers
 
End-to-end web-testing in ruby ecosystem
End-to-end web-testing in ruby ecosystemEnd-to-end web-testing in ruby ecosystem
End-to-end web-testing in ruby ecosystem
 

Mehr von Brian Sam-Bodden

Mehr von Brian Sam-Bodden (10)

Server-Side Push: Comet, Web Sockets come of age (OSCON 2013)
Server-Side Push: Comet, Web Sockets come of age (OSCON 2013)Server-Side Push: Comet, Web Sockets come of age (OSCON 2013)
Server-Side Push: Comet, Web Sockets come of age (OSCON 2013)
 
Ruby Metaprogramming - OSCON 2008
Ruby Metaprogramming - OSCON 2008Ruby Metaprogramming - OSCON 2008
Ruby Metaprogramming - OSCON 2008
 
RailsConf 2013: RubyMotion
RailsConf 2013: RubyMotionRailsConf 2013: RubyMotion
RailsConf 2013: RubyMotion
 
Rspec and Capybara Intro Tutorial at RailsConf 2013
Rspec and Capybara Intro Tutorial at RailsConf 2013Rspec and Capybara Intro Tutorial at RailsConf 2013
Rspec and Capybara Intro Tutorial at RailsConf 2013
 
Road to mobile w/ Sinatra, jQuery Mobile, Spine.js and Mustache
Road to mobile w/ Sinatra, jQuery Mobile, Spine.js and MustacheRoad to mobile w/ Sinatra, jQuery Mobile, Spine.js and Mustache
Road to mobile w/ Sinatra, jQuery Mobile, Spine.js and Mustache
 
Trellis Framework At RubyWebConf
Trellis Framework At RubyWebConfTrellis Framework At RubyWebConf
Trellis Framework At RubyWebConf
 
Integrallis groovy-cloud
Integrallis groovy-cloudIntegrallis groovy-cloud
Integrallis groovy-cloud
 
Ferret
FerretFerret
Ferret
 
Bitter Java, Sweeten with JRuby
Bitter Java, Sweeten with JRubyBitter Java, Sweeten with JRuby
Bitter Java, Sweeten with JRuby
 
Ruby Metaprogramming 08
Ruby Metaprogramming 08Ruby Metaprogramming 08
Ruby Metaprogramming 08
 

Kürzlich hochgeladen

Large-scale Logging Made Easy: Meetup at Deutsche Bank 2024
Large-scale Logging Made Easy: Meetup at Deutsche Bank 2024Large-scale Logging Made Easy: Meetup at Deutsche Bank 2024
Large-scale Logging Made Easy: Meetup at Deutsche Bank 2024
VictoriaMetrics
 
%+27788225528 love spells in Boston Psychic Readings, Attraction spells,Bring...
%+27788225528 love spells in Boston Psychic Readings, Attraction spells,Bring...%+27788225528 love spells in Boston Psychic Readings, Attraction spells,Bring...
%+27788225528 love spells in Boston Psychic Readings, Attraction spells,Bring...
masabamasaba
 
%+27788225528 love spells in Huntington Beach Psychic Readings, Attraction sp...
%+27788225528 love spells in Huntington Beach Psychic Readings, Attraction sp...%+27788225528 love spells in Huntington Beach Psychic Readings, Attraction sp...
%+27788225528 love spells in Huntington Beach Psychic Readings, Attraction sp...
masabamasaba
 
%+27788225528 love spells in Atlanta Psychic Readings, Attraction spells,Brin...
%+27788225528 love spells in Atlanta Psychic Readings, Attraction spells,Brin...%+27788225528 love spells in Atlanta Psychic Readings, Attraction spells,Brin...
%+27788225528 love spells in Atlanta Psychic Readings, Attraction spells,Brin...
masabamasaba
 
Abortion Pill Prices Tembisa [(+27832195400*)] 🏥 Women's Abortion Clinic in T...
Abortion Pill Prices Tembisa [(+27832195400*)] 🏥 Women's Abortion Clinic in T...Abortion Pill Prices Tembisa [(+27832195400*)] 🏥 Women's Abortion Clinic in T...
Abortion Pill Prices Tembisa [(+27832195400*)] 🏥 Women's Abortion Clinic in T...
Medical / Health Care (+971588192166) Mifepristone and Misoprostol tablets 200mg
 
AI Mastery 201: Elevating Your Workflow with Advanced LLM Techniques
AI Mastery 201: Elevating Your Workflow with Advanced LLM TechniquesAI Mastery 201: Elevating Your Workflow with Advanced LLM Techniques
AI Mastery 201: Elevating Your Workflow with Advanced LLM Techniques
VictorSzoltysek
 
%+27788225528 love spells in Colorado Springs Psychic Readings, Attraction sp...
%+27788225528 love spells in Colorado Springs Psychic Readings, Attraction sp...%+27788225528 love spells in Colorado Springs Psychic Readings, Attraction sp...
%+27788225528 love spells in Colorado Springs Psychic Readings, Attraction sp...
masabamasaba
 
%+27788225528 love spells in Knoxville Psychic Readings, Attraction spells,Br...
%+27788225528 love spells in Knoxville Psychic Readings, Attraction spells,Br...%+27788225528 love spells in Knoxville Psychic Readings, Attraction spells,Br...
%+27788225528 love spells in Knoxville Psychic Readings, Attraction spells,Br...
masabamasaba
 

Kürzlich hochgeladen (20)

WSO2Con2024 - From Code To Cloud: Fast Track Your Cloud Native Journey with C...
WSO2Con2024 - From Code To Cloud: Fast Track Your Cloud Native Journey with C...WSO2Con2024 - From Code To Cloud: Fast Track Your Cloud Native Journey with C...
WSO2Con2024 - From Code To Cloud: Fast Track Your Cloud Native Journey with C...
 
Large-scale Logging Made Easy: Meetup at Deutsche Bank 2024
Large-scale Logging Made Easy: Meetup at Deutsche Bank 2024Large-scale Logging Made Easy: Meetup at Deutsche Bank 2024
Large-scale Logging Made Easy: Meetup at Deutsche Bank 2024
 
%in kempton park+277-882-255-28 abortion pills for sale in kempton park
%in kempton park+277-882-255-28 abortion pills for sale in kempton park %in kempton park+277-882-255-28 abortion pills for sale in kempton park
%in kempton park+277-882-255-28 abortion pills for sale in kempton park
 
%in Soweto+277-882-255-28 abortion pills for sale in soweto
%in Soweto+277-882-255-28 abortion pills for sale in soweto%in Soweto+277-882-255-28 abortion pills for sale in soweto
%in Soweto+277-882-255-28 abortion pills for sale in soweto
 
%+27788225528 love spells in Boston Psychic Readings, Attraction spells,Bring...
%+27788225528 love spells in Boston Psychic Readings, Attraction spells,Bring...%+27788225528 love spells in Boston Psychic Readings, Attraction spells,Bring...
%+27788225528 love spells in Boston Psychic Readings, Attraction spells,Bring...
 
call girls in Vaishali (Ghaziabad) 🔝 >༒8448380779 🔝 genuine Escort Service 🔝✔️✔️
call girls in Vaishali (Ghaziabad) 🔝 >༒8448380779 🔝 genuine Escort Service 🔝✔️✔️call girls in Vaishali (Ghaziabad) 🔝 >༒8448380779 🔝 genuine Escort Service 🔝✔️✔️
call girls in Vaishali (Ghaziabad) 🔝 >༒8448380779 🔝 genuine Escort Service 🔝✔️✔️
 
Architecture decision records - How not to get lost in the past
Architecture decision records - How not to get lost in the pastArchitecture decision records - How not to get lost in the past
Architecture decision records - How not to get lost in the past
 
%+27788225528 love spells in Huntington Beach Psychic Readings, Attraction sp...
%+27788225528 love spells in Huntington Beach Psychic Readings, Attraction sp...%+27788225528 love spells in Huntington Beach Psychic Readings, Attraction sp...
%+27788225528 love spells in Huntington Beach Psychic Readings, Attraction sp...
 
%+27788225528 love spells in Atlanta Psychic Readings, Attraction spells,Brin...
%+27788225528 love spells in Atlanta Psychic Readings, Attraction spells,Brin...%+27788225528 love spells in Atlanta Psychic Readings, Attraction spells,Brin...
%+27788225528 love spells in Atlanta Psychic Readings, Attraction spells,Brin...
 
W01_panagenda_Navigating-the-Future-with-The-Hitchhikers-Guide-to-Notes-and-D...
W01_panagenda_Navigating-the-Future-with-The-Hitchhikers-Guide-to-Notes-and-D...W01_panagenda_Navigating-the-Future-with-The-Hitchhikers-Guide-to-Notes-and-D...
W01_panagenda_Navigating-the-Future-with-The-Hitchhikers-Guide-to-Notes-and-D...
 
Abortion Pill Prices Tembisa [(+27832195400*)] 🏥 Women's Abortion Clinic in T...
Abortion Pill Prices Tembisa [(+27832195400*)] 🏥 Women's Abortion Clinic in T...Abortion Pill Prices Tembisa [(+27832195400*)] 🏥 Women's Abortion Clinic in T...
Abortion Pill Prices Tembisa [(+27832195400*)] 🏥 Women's Abortion Clinic in T...
 
Define the academic and professional writing..pdf
Define the academic and professional writing..pdfDefine the academic and professional writing..pdf
Define the academic and professional writing..pdf
 
%in Stilfontein+277-882-255-28 abortion pills for sale in Stilfontein
%in Stilfontein+277-882-255-28 abortion pills for sale in Stilfontein%in Stilfontein+277-882-255-28 abortion pills for sale in Stilfontein
%in Stilfontein+277-882-255-28 abortion pills for sale in Stilfontein
 
8257 interfacing 2 in microprocessor for btech students
8257 interfacing 2 in microprocessor for btech students8257 interfacing 2 in microprocessor for btech students
8257 interfacing 2 in microprocessor for btech students
 
AI Mastery 201: Elevating Your Workflow with Advanced LLM Techniques
AI Mastery 201: Elevating Your Workflow with Advanced LLM TechniquesAI Mastery 201: Elevating Your Workflow with Advanced LLM Techniques
AI Mastery 201: Elevating Your Workflow with Advanced LLM Techniques
 
Crypto Cloud Review - How To Earn Up To $500 Per DAY Of Bitcoin 100% On AutoP...
Crypto Cloud Review - How To Earn Up To $500 Per DAY Of Bitcoin 100% On AutoP...Crypto Cloud Review - How To Earn Up To $500 Per DAY Of Bitcoin 100% On AutoP...
Crypto Cloud Review - How To Earn Up To $500 Per DAY Of Bitcoin 100% On AutoP...
 
%+27788225528 love spells in Colorado Springs Psychic Readings, Attraction sp...
%+27788225528 love spells in Colorado Springs Psychic Readings, Attraction sp...%+27788225528 love spells in Colorado Springs Psychic Readings, Attraction sp...
%+27788225528 love spells in Colorado Springs Psychic Readings, Attraction sp...
 
%+27788225528 love spells in Knoxville Psychic Readings, Attraction spells,Br...
%+27788225528 love spells in Knoxville Psychic Readings, Attraction spells,Br...%+27788225528 love spells in Knoxville Psychic Readings, Attraction spells,Br...
%+27788225528 love spells in Knoxville Psychic Readings, Attraction spells,Br...
 
Shapes for Sharing between Graph Data Spaces - and Epistemic Querying of RDF-...
Shapes for Sharing between Graph Data Spaces - and Epistemic Querying of RDF-...Shapes for Sharing between Graph Data Spaces - and Epistemic Querying of RDF-...
Shapes for Sharing between Graph Data Spaces - and Epistemic Querying of RDF-...
 
%in Hazyview+277-882-255-28 abortion pills for sale in Hazyview
%in Hazyview+277-882-255-28 abortion pills for sale in Hazyview%in Hazyview+277-882-255-28 abortion pills for sale in Hazyview
%in Hazyview+277-882-255-28 abortion pills for sale in Hazyview
 

Baruco 2014 - Rubymotion Workshop

  • 1. Workshop By Brian Sam-Bodden @bsbodden http://www.integrallis.com
  • 2. About me • Life-long polyglot programmer • Rubyist since 2005 • CEO & Founder of Integrallis Software, circa 2002 • From David, Panama • Lives in Scottsdale, Arizona
  • 3. getting started Setting up the stage
  • 4. Pre-Requisites • Follow http://www.rubymotion.com/developer-center/guides/getting-started/ • 64-bit Mac running OS X Mavericks • Apple’s Xcode • Command Line Tools Package • Download RubyMotion Product Installer • Register as an iPhone developer to test on a physical device • RVM installed on your machine • Use your favorite code editor!
  • 5. testing 1, 2, 3… Checking your RubyMotion Version… ~/rm_workshop/code /> motion --version 2.32 ! ! ! ! ! ! !
  • 6. THE MOTION COMMAND The motion command is our entry point in the world of RubyMotion: ~/rm_workshop/code /> motion RubyMotion lets you develop native iOS and OS X applications using the awesome Ruby language. ! Commands: ! * account Access account details. * activate Activate software license. * changelog View the changelog. * create Create a new project. * device-console Print iOS device logs * ri Display API reference. * support Create a support ticket. * update Update the software. ! Options: ! --version Show the version of RubyMotion --no-ansi Show output without ANSI codes --verbose Show more debugging information --help Show help banner of specified command
  • 8. hello world Let’s use the motion create command to get started: ~/rm_workshop/code /> motion create hello Create hello Create hello/.gitignore Create hello/app/app_delegate.rb Create hello/Gemfile Create hello/Rakefile Create hello/resources/Default-568h@2x.png Create hello/spec/main_spec.rb !
  • 9. RM PROJECT • Gemfile: Your project’s Gem dependencies (RM supported gems) • Rakefile: The entry point to build, launch, release and test your App • app/app_delegate.rb: Glues your custom code (your application) into the RubyMotion framework • spec/main_spec.rb: A sample test that verifies that your App has at least one window • Miscellaneous: A .gitignore to avoid checking build artifacts and a default splash screen PNG image
  • 10. RM BUILD SYSTEM Let’s CD to the hello and run: rake -T ~/rm_workshop/code/hello /> rake -T rake clean # Clear local build objects rake config # Show project config rake default # Build the project, then run the simulator rake device # Deploy on the device rake spec # Same as 'spec:simulator' rake spec:device # Run the test/spec suite on the device rake spec:simulator # Run the test/spec suite on the simulator ... ! The default task (which runs if we simply type rake), builds the project (in the build directory) and runs the simulator
  • 11. iOS overview • iOS is the operating system that runs on iPhone, iPod Touch, and iPad devices • Apple provides the iOS SDK, which provides the tools and interfaces needed to interact with iOS' layered architecture • iOS provides a set of packages called Frameworks, which are dynamic shared libraries and resources that can be linked to your App
  • 12. Cocoa Touch Core Services Core OS UIKit Hardware Core Animation Foundation Core Data Media Layer Core Graphics Open GL ES Applications
  • 13. The APP DELEGATE class AppDelegate def application(application, didFinishLaunchingWithOptions:launchOptions) true end end • UIKit manages the app’s core behavior (event loop and interaction w/OS) • UIKit uses subclassing, delegation and callbacks to allow you to modify default behaviors and add your own • UIApplication object dispatches events to your code in the app delegate • AppDelegate is responsible for handling state transitions and app events
  • 14. BUILD AND LAUNCH To build and launch simple type: rake ~/rm_workshop/code/hello /> rake Build ./build/iPhoneSimulator-7.1-Development Compile ./app/app_delegate.rb Create ./build/iPhoneSimulator-7.1-Development/hello.app Link ./build/iPhoneSimulator-7.1-Development/hello.app/hello Create ./build/iPhoneSimulator-7.1-Development/hello.app/PkgInfo Create ./build/iPhoneSimulator-7.1-Development/hello.app/ Info.plist Copy ./resources/Default-568h@2x.png Create ./build/iPhoneSimulator-7.1-Development/hello.dSYM Simulate ./build/iPhoneSimulator-7.1-Development/hello.app (main)> Our empty application should be launched in the simulator…
  • 15. BUILD AND LAUNCH The empty app doesn’t have any views yet. All we get is a blank screen…
  • 16. BUILD AND LAUNCH Let’s simulate pressing the device ‘Home’ button. From the iOS Simulator menu select: Hardware | Home Our application is installed on the simulator Along with other “core” iOS apps
  • 17. Learning in the repL Read Evaluate Print Loop
  • 18. INTERACTING WITH YOUR APP After the application launched, we were left with a prompt on the console; the REPL! ~/rm_workshop/code/hello (main)> self => main (main)> alert = UIAlertView.new => #<UIAlertView:0x90d39e0> (main)> alert.title = 'RubyMotion' => "RubyMotion" (main)> alert.message = 'RubyMotion is in da house' => "RubyMotion is in da house" (main)> alert.show => #<UIAlertView:0x90d39e0> (main)> Let’s follow the interaction shown above The top level is the main object Many RM classes are counterpart/ wrappers for their iOS peers Let’s construct a UIAlertView which is an iOS class in UIKit
  • 19. INTERACTING WITH YOUR APP The UIAlertView has a few properties, like title and message, and several methods, including show: We can learn more about UIAlertView in Apple’s developer library at: http://developer.apple.com/library/ios/#documentation/uikit/reference/UIAlertView_Class/UIAlertView/UIAlertView.html
  • 20. INTERACTING WITH YOUR APP Let’s dismiss the previous alert and create a new one… ~/rm_workshop/code/hello (main)> alert.dismiss => #<UIAlertView:0x90d39e0> (main)> alert = UIAlertView.new => #<UIAlertView:0x8ddcef0> (main)> alert.addButtonWithTitle 'Kaboom!' => 0 (main)> alert.message = 'Foo Bar' => "Foo Bar" (main)> alert.show => #<UIAlertView:0x8ddcef0> (main)> This time we’ll use the addButtonWithTitle method
  • 21. INTERACTING WITH YOUR APP Clicking the button dismisses the alert We can learn more about UIAlertView#addButtonWithTitle method at: https://developer.apple.com/library/ios/documentation/uikit/reference/UIAlertView_Class/UIAlertView/UIAlertView.html#//apple_ref/doc/uid/TP40006802-CH3-SW6
  • 22. WHEN TO USE THE REPL • The REPL gives us a playground to experiment and learn about the RubyMotion/iOS API • When doing TDD, the REPL is where I try my raw ideas before committing them to code • RubyMotion REPL is unique to the iOS development ecosystem and one of the best ways to speed up your development process
  • 23. FROM THE REPL to your App class AppDelegate def application(application, didFinishLaunchingWithOptions:launchOptions) alert = UIAlertView.new alert.title = 'RubyMotion' alert.message = 'RubyMotion is that da house!' alert.addButtonWithTitle 'Kaboom!' ! alert.show ! true end end app/app_delegate.rb • We can apply what we learned on the REPL to our application • Modify your AppDelegate as show above and run the rake command again
  • 24. The application method • In the application method, we create objects that inherit from UIView (as UIAlertView does) • UIView's define an interface for managing the content of a rectangular area • At runtime the app is represented by a UIApplication object which “delegates” to your custom code in the AppDelegate class • A delegate is a class that typically implements one or more protocols • Nearly all UI classes have a delegate that you can use to receive callbacks from the framework
  • 25. that method looks weird! def application(application, didFinishLaunchingWithOptions:launchOptions) ... end • You probably noticed the signature of application method • The second parameter is camel-cased and it has a right colon smack in the middle • The camel-casing is just a direct port of the Objective-C parameter names • RubyMotion is mostly based on Ruby 1.9. In the upcoming Ruby 2.0, we get named parameters which use the : syntax, aligning perfectly with Objective-C parameters
  • 26. FROM THE REPL to your App • Let’s make the variable alert an instance variable • Modify your AppDelegate as show above and run the rake command again class AppDelegate def application(application, didFinishLaunchingWithOptions:launchOptions) @alert = UIAlertView.new @alert.title = 'RubyMotion' @alert.message = 'RubyMotion is that da house!' @alert.addButtonWithTitle 'Kaboom!' ! @alert.show ! true end end app/app_delegate.rb
  • 27. …and back to the repL We can also interact with the running application elements: ~/rm_workshop/code/hello (main) app = UIApplication.sharedApplication => #<UIApplication:0x901ab90> (main)> delegate = app.delegate => #<AppDelegate:0x8e2d940 @alert=#<UIAlertView:0x8e33ac0>> (main)> alert = delegate.instance_variable_get('@alert') => #<UIAlertView:0x8e33ac0> (main) alert.message = 'Chunky Bacon is the best!' => "Chunky Bacon is the best!" (main)> ! ! We can access the singleton application instance via the sharedApplication class method, which gets us access to the delegate and the instance variable via instance_variable_get.
  • 28. TDD with Bacon Test-Driven Development in RubyMotion
  • 29. TEST-driven Development • TDD creates a tight loop of development that cognitively engages us • TDD gives us lightweight rigor by making development, goal-oriented with a clear goal setting, goal reaching and improvement stages • The stages of TDD are commonly known as the Red-Green-Refactor loop
  • 30. TEST-driven Development RED REFACTOR GREEN Clean up & improve without adding functionality Write the minimal code to pass the test Eliminate Redundancy Write a failing test for new functionality
  • 31. TDD with BACON • The Ruby Community has embrace the practice of Test-Driven Development (TDD) • RubyMotion comes bundled with MacBacon, a Mac specific version of Bacon which is a small clone of RSpec • In this section we’ll embark on building the Okonawa application, a simple ToDo list manager for iOS
  • 32. okonawa Okonawa (行わ) which translates roughly to “Done” will be a simple To Do app: ~/rm_workshop/code /> motion create okonawa Create okonawa Create okonawa/.gitignore Create okonawa/app/app_delegate.rb Create okonawa/Gemfile Create okonawa/Rakefile Create okonawa/resources/Default-568h@2x.png Create okonawa/spec/main_spec.rb ! Let’s start by creating a new RM application!
  • 33. RM SPECS • The file spec/main_spec.rb, contains a simple Bacon test that verifies that your app has at least one window • In the before block we access the sharedApplication singleton in order to verify the size of the windows property describe "Application 'okonawa'" do before do @app = UIApplication.sharedApplication end ! it "has one window" do @app.windows.size.should == 1 end end spec/main_spec.rb
  • 34. kicking off the TDD LOOP Change directories into the newly created application directory and type rake spec: ~/rm_workshop/code/okonawa Application 'okonawa' - has one window [FAILED - 0.==(1) failed] ! Bacon::Error: 0.==(1) failed spec.rb:700:in `satisfy:': Application 'okonawa' - has one window spec.rb:714:in `method_missing:' spec.rb:316:in `block in run_spec_block' spec.rb:440:in `execute_block' spec.rb:316:in `run_spec_block' spec.rb:331:in `run' ! 1 specifications (1 requirements), 1 failures, 0 errors Starting with Failure! A Good Thing
  • 35. getting to green • We’ll make minimal modifications to app/app_delegate.rb in order to pass our failing spec • We’ll add a window (UIWindow) which is a manager for other views your app displays on the device screen • An iOS application has only one window (unless your app can use an external display). class AppDelegate def application(application, didFinishLaunchingWithOptions:launchOptions) @window = UIWindow.alloc.initWithFrame(UIScreen.mainScreen.bounds) @window.makeKeyAndVisible end end app/app_delegate.rb
  • 36. alloc? @window = UIWindow.alloc.initWithFrame(UIScreen.mainScreen.bounds) • The alloc method is a class method of NSObject that returns a new instance of the receiving class • To complete the initialization process you must call one of it’s initializers • Objective-C provides designated initializers and secondary initializers • An initializer (init and friends) is coupled with alloc in the same line of code • We pass a CGRect (the size of the mainScreen) to the initWithFrame
  • 37. what about ruby’s new? • NSObject provides a new method which allocates and calls init • When we call .new on an Objective-C class it is the equivalent of .alloc.init • Since initWithFrame is the designated initializer for UIWindow, calling init will call it (passing as a param the constant CGRectZero - a zero rectangle) • We could change the line above to (which is not exactly the same) but would make the spec pass: @window = UIWindow.alloc.init # or @window = UIWindow.new
  • 38. getting to green Let’s run rake spec again: ~/rm_workshop/code/okonawa Application 'okonawa' - has one window ! 1 specifications (1 requirements), 0 failures, 0 errors ! ! ! ! ! ! ! We’ve reached the GREEN state. Yay!
  • 39. STORY #1 • We have passing set of tests and a very uninteresting application :-( • Let’s kick the TDD cycle into full gear by implementing a simple story: “As a User I should be able to see a list of all To-Dos”
  • 40. • We’ll craft a very generic test following the formula we used to access the delegate from the REPL • In a before block, we’ll grab the application, the delegate, and something I’m calling table, which is yet to be created • Our first test will just test that this table exists in spec/todos_spec.rb describe "Todos View" do before do @app = UIApplication.sharedApplication @delegate = @app.delegate @table = @delegate.instance_variable_get("@table") end ! it 'should exist' do @table.should.not == nil end end spec/todos_spec.rb
  • 41. confirm failure Let’s run the tests again to get back to our “Red” state: ~/rm_workshop/code/okonawa Application 'okonawa' - has one window ! Todos View - should exist [FAILED - not nil.==(nil) failed] ! Bacon::Error: not nil.==(nil) failed spec.rb:700:in `satisfy:': Todos View - should exist spec.rb:714:in `method_missing:' spec.rb:316:in `block in run_spec_block' spec.rb:440:in `execute_block' spec.rb:316:in `run_spec_block' spec.rb:331:in `run' ! 2 specifications (2 requirements), 1 failures, 0 errors Alright. Let’s go slay this dragon!
  • 42. REPL Detective Work • To make our application pass this story we need to go into design mode. We’ll need a view that can display a list of things • Amongst the descendants of UIView there is a class called UITableView • how do we add this UITableView to our application’s window? Inspecting the methods of UIWindow we see that there is a method called addSubview ~/rm_workshop/code/okonawa (main)> UIWindow.instance_methods.sort.keep_if { |m| m =~ /S*viewS*/ } => [:"addSubview:", :autoresizesSubviews, :"bringSubviewToFront:", :deliversTouchesForGestu resToSuperview, :"didAddSubview:", :didMoveToSuperview, :"exchangeSubviewAtIndex:withSub viewAtIndex:", :"insertSubview:above:", :"insertSubview:aboveSubview:", :"insertSubview: atIndex:", :"insertSubview:below:", :"insertSubview:belowSubview:", :layoutSubviews, :"m ovedFromSuperview:", :"movedToSuperview:", :removeFromSuperview, :"resizeSubviewsWithOld Size:", :"resizeWithOldSuperviewSize:", :"sendSubviewToBack:", :"setAutoresizesSubviews: ", :"setClipsSubviews:", :"setDeliversTouchesForGesturesToSuperview:", :"setSkipsSubview Enumeration:", :skipsSubviewEnumeration, :subviews, :superview, :viewDidMoveToSuperview, :viewForBaselineLayout, :viewPrintFormatter, :viewTraversalMark, :"viewWillMoveToSupervi ew:", :"viewWithTag:", :"willMoveToSuperview:", :"willRemoveSubview:"] (main)> !
  • 43. using UITableView • Let’s expand our AppDelegate by creating the instance variable @table as a UITableView sized to the screen’s bounds: class AppDelegate def application(application, didFinishLaunchingWithOptions:launchOptions) @window = UIWindow.alloc.initWithFrame(UIScreen.mainScreen.bounds) @window.makeKeyAndVisible ! @table = UITableView.alloc.initWithFrame(UIScreen.mainScreen.bounds) @window.addSubview(@table) end end app/app_delegate.rb
  • 44. An Empty UITableView • Run rake to see what our app looks like so far:
  • 45. and back to green Let’s run rake spec again: ~/rm_workshop/code/okonawa Application 'okonawa' - has one window ! Todos View - should exist ! 2 specifications (2 requirements), 0 failures, 0 errors ! ! ! ! Not a very robust test, but it’s allowing us to move forward in a controlled fashion
  • 46. displaying some TODOs • Looking at the reference for UITableView there is a method visibleCells that returns the table cells that are visible in the receiver • The visibleCells method returns an array containing UITableViewCell objects • Let’s check the visibleCells method on the REPL by getting to our @table UITableView ~/rm_workshop/code/okonawa (main)> (nil)? app = UIApplication.sharedApplication => #<UIApplication:0x8d8b6b0> (main)> delegate = app.delegate => #<AppDelegate:0x8fae550 @window=#<UIWindow:0x8da87d0> @table=#<UITableView: 0x9b7ae00>> (main)> table = delegate.instance_variable_get("@table") => #<UITableView:0x9b7ae00> (main)> table.visibleCells => [] (main)> !!
  • 47. displaying some TODOs • With that information on hand, let’s write a spec: it 'displays the given ToDos' do @table.visibleCells.should.not.be.empty end ~/rm_workshop/code/okonawa Todos View - should exist - displays the given ToDos [FAILED - not [].empty?() failed] ! Bacon::Error: not [].empty?() failed spec.rb:700:in `satisfy:': Todos View - displays the given ToDos spec.rb:714:in `method_missing:' spec.rb:316:in `block in run_spec_block' spec.rb:440:in `execute_block' spec.rb:316:in `run_spec_block' spec.rb:331:in `run' ! 3 specifications (3 requirements), 1 failures, 0 errors spec/todos_spec.rb
  • 48. displaying some TODOs • So how are we going to pass our test? It seems that we might want to get some UITableViewCell objects in our table • If we inspect the UITableView reference we learn that a UITableView gets its data from a UITableViewDataSource which is set via the dataSource attribute • The UITableViewDataSource protocol is adopted by an object that mediates the application’s data model for a UITableView object
  • 49. displaying some TODOs • To create a class that can serve as a UITableViewDataSource we must provide two method implementations: (Integer) tableView(tableView, numberOfRowsInSection:section) • Tells the table how many rows of data we have. (UITableViewCell) tableView(tableView, cellForRowAtIndexPath:indexPath) • Returns a UITableViewCell for a given row index
  • 50. UITableViewDataSource • We will implement the two required methods in the contract, and serve our data from a simple array @data • Let’s create a TodosDataSource class in app/todos_data_source.rb: class TodosDataSource ! attr_writer :data ! def tableView(tableView, numberOfRowsInSection:section) @data.size end ! def tableView(tableView, cellForRowAtIndexPath:indexPath) cell = UITableViewCell.alloc.initWithStyle(UITableViewCellStyleDefault, reuseIdentifier:nil) cell.textLabel.text = @data[indexPath.row] cell end end app/todos_data_source.rb
  • 51. UITableViewDataSource • Let’s modify the AppDelegate to use the TodosDataSource by simply setting its data attribute with an array of strings: def application(application, didFinishLaunchingWithOptions:launchOptions) @window = UIWindow.alloc.initWithFrame(UIScreen.mainScreen.bounds) @window.makeKeyAndVisible ! @table = UITableView.alloc.initWithFrame(UIScreen.mainScreen.bounds) ! todos = %w(Milk Orange Juice Apples Bananas Broccoli Carrots Beef Chicken Enchiladas Hot Dogs Butter Bread Pasta Rice) ! todos.map! { |thing| "Buy #{thing}"} ! @data_source = TodosDataSource.new @data_source.data = todos @table.dataSource = @data_source ! @window.addSubview(@table) end app/app_delegate.rb
  • 52. displaying some TODOs • Run rake to see the UITableView populated with data from our UITableViewDataSource:
  • 53. and back to green Let’s run rake spec again: ~/rm_workshop/code/okonawa Application 'okonawa' - has one window ! Todos View - should exist - displays the given ToDos ! 3 specifications (3 requirements), 0 failures, 0 errors ! ! !
  • 54. UITableViewDataSource • Let’s complement the previous test with a check for contents of as row: it 'displays the correct label for a given ToDo' do first_cell = @table.visibleCells.first first_cell.textLabel.text.should == 'Buy Milk' end ~/rm_workshop/code/okonawa Application 'okonawa' - has one window ! Todos View - should exist - displays the given ToDos - displays the correct label for a given ToDo ! 4 specifications (4 requirements), 0 failures, 0 errors ! ! spec/todos_spec.rb
  • 55. Time to Refactor • We’ve done a few red-green short loops; now let’s do a full red-green-refactor • You’ve probably noticed that when we launched the console we got a warning message that read: Application windows are expected to have a root view controller at the end of application launch • iOS has a pretty robust implementation of the MVC model-view-controller pattern • The message above hints that we should have delegated the initial display of the view to a “view controller”
  • 56. • The first custom object created at launch time is the app delegate, which handles any events that are not handled by by the UIApplication. The app delegate is the entry point into the custom code that “controls” the app, the entry point into the “C” of M-V-C Data Objects Document Model UIApplication Application Delegate UI Window View Controllers Views and UI Objects Event Loop Controller View
  • 57. View Controllers • View controllers in RubyMotion are classes that extend UIViewController • Using views directly in the AppDelegate is typically frowned upon • Let’s make a directory for our controllers under the app directory. In there we’ll create the the TodosController in todos_controller.rb
  • 58. TODOs Controller • Controller including the data source contracts previously provided: class TodosController < UIViewController attr_writer :data ! def viewDidLoad super self.title = 'Okonawa' @table = UITableView.alloc.initWithFrame(self.view.bounds) @table.dataSource = self self.view.addSubview(@table) ! @data = %w(Milk Orange Juice Apples Bananas Broccoli Carrots Beef Chicken app/controllers/todos_controller.rb Enchiladas Hot Dogs Butter Bread Pasta Rice).map { |thing| "Buy #{thing}" } end ! def tableView(tableView, numberOfRowsInSection: section) @data.size end ! def tableView(tableView, cellForRowAtIndexPath: indexPath) cell = UITableViewCell.alloc.initWithStyle(UITableViewCellStyleDefault, reuseIdentifier:nil) cell.textLabel.text = @data[indexPath.row] cell end end
  • 59. TODOs Controller • We’ll also need to refactor the AppDelegate to use the controller: class AppDelegate def application(application, didFinishLaunchingWithOptions:launchOptions) @window = UIWindow.alloc.initWithFrame(UIScreen.mainScreen.bounds) ! @todos_controller = TodosController.alloc .initWithNibName(nil, bundle:nil) ! @window.rootViewController = UINavigationController.alloc .initWithRootViewController(@todos_controller) ! @window.makeKeyAndVisible ! app/app_delegate.rb true end end • We accomplish this by setting the window root view controller to an instance of our TodosController
  • 60. TODOs Controller • If we run the tests: boom! We get one failure and two errors: ~/rm_workshop/code/okonawa Application 'Todo' - has one window ToDos View - should exist@Table is ==> [FAILED] - displays the given ToDos@Table is ==> [ERROR: NoMethodError] - displays the correct label for a give ToDo@Table is ==> [ERROR: NoMethodError] Bacon::Error: not nil.==(nil) failed Boo, our refactoring broke our tests!
  • 61. Test Refactoring • The @table variable is nil. There is no @table in the delegate anymore! • RubyMotion provides the ability to declare the context of our tests • Let’s refactor the test with the class method tests, which gives us access to the controller variable describe "Todos Controller" do tests TodosController ! before do spec/todos_controller.rb @table = controller.instance_variable_get("@table") end ... • Let’s also rename the file from todos_spec.rb to todos_controller.rb
  • 62. TODOs Controller • Run rake to reveal the Todos list backed by the Todos Controller:
  • 63. M in MVC Working with Data in iOS
  • 64. data in IOS Isolated applications are mostly non-existent. Most mobile apps keep their data in an external service and keep a local user data cache that needs to be synchronized from time to time
  • 65. data in IOS There are many mechanisms by which to persist data locally in an iOS application, including but not limited to: • NSUserDefaults: Is a key-value local storage, capable of storing both objects and primitive data types • NSCache: A cache that stores key-value pairs. NSCache automatically evicts objects in order to free up space in memory as needed. • Archives: Serialize an object graph into an “architecture independent stream of bytes”. Archives are available in sequential and keyed archives backed by NSArchiver and NSKeyArchiver respectively. • Core Data: The 'official' persistence framework that can serializes an object graph, provide object life-cycle management, relationships, lazy loading, validation, change tracking, undo support, schema migrations and queries.
  • 66. break out the gems! • Since RubyMotion is a dialect of Ruby that is statically compiled, most regular Ruby gems won't work right out of the gate • iOS typically provides an appropriate counter-part to a pure Ruby gem that has been wrapped in “RubyMotion goodness” • Just like regular Ruby apps, RM apps use Bundler • The big difference is that the require method is only allowed inside your project’s Rakefile • Gems required in the Rakefile are compiled into the target executable in alphabetical order. The generated Rakefile will pull all Gemfile dependencies automatically
  • 67. break out the gems! For our ToDo App we'll be using a few libraries that will allow us to follow 'The Ruby Way' of development. Let’s add the following gems to our Gemfile: • MotionModel: Simple Model and Validation Mixins for RubyMotion • Formotion: A simple DSL to create iOS Forms source 'https://rubygems.org' ! gem 'rake' gem 'motion_model' gem 'formotion' Gemfile
  • 68. break out the gems! Let’s also isolate our dependencies from other projects in the system by adding a .ruby-version and a .ruby-gemset files at the root so that RVM and Bundler can manage our project: 1.9.3 baruco-okonawa .ruby-version .ruby-gemset
  • 69. break out the gems! Now simply CD out and back into the project and type: bundle ~/rm_workshop/code/okonawa />bundle Resolving dependencies... Using rake 10.3.2 Installing bubble-wrap-http 1.7.1 Installing bubble-wrap 1.7.1 Installing motion-require 0.2.0 Installing formotion 1.8 Installing motion-support 0.2.6 Installing motion_model 0.5.4 Using bundler 1.6.3 Your bundle is complete! Use `bundle show [gemname]` to see where a bundled gem is installed.
  • 70. Motion Model • MotionModel provides Ruby Objects with light persistence and validation abilities, similar to ActiveRecord • MotionModel's default persistence strategy relies on NSCoder to serialize data. It then uses NSKeyedArchiver to create a NSData representation • MotionModel is more like DataMapper than ActiveRecord, allowing you to declare a model’s columns directly in the Ruby class
  • 71. the todo model • We'll start to TDD our models at the lowest level with a simple spec (todo_model_spec.rb) to test that the Todo model class exists: describe "Todo Model" do it "exists" do Object.const_defined?('Todo').should.be.true end end spec/todo_model_spec.rb
  • 72. the todo model • Running rake spec will kick start or model development TDD loop: ~/rm_workshop/code/okonawa Todo Model - exists [FAILED - false.true?() failed] ! ... ! Bacon::Error: false.true?() failed spec.rb:700:in `satisfy:': Todo Model - exists spec.rb:714:in `method_missing:' spec.rb:316:in `block in run_spec_block' spec.rb:440:in `execute_block' spec.rb:316:in `run_spec_block' spec.rb:331:in `run
  • 73. The Todo Model • Let’s create an app/models folder to house our new Todo model (todo.rb) class Todo end ~/rm_workshop/code/okonawa Application 'okonawa' - has one window ! Todo Model - exists ! Todos Controller - should exist - displays the given ToDos - displays the correct label for a given ToDo ! 5 specifications (5 requirements), 0 failures, 0 errors app/models/todo.rb
  • 74. the todo model Let’s write a spec to define what we expect from our Todo model: • Add a before block and instantiate a Todo • A Todo model should know its name, description, due_date, and whether it is done or not before do @todo = Todo.new end ... ! it 'has a name, description, a due date and whether is done or not' do @todo.should.respond_to :name @todo.should.respond_to :description @todo.should.respond_to :due_date @todo.should.respond_to :done end spec/todo_model_spec.rb
  • 75. the todo model • Running rake spec again: ~/rm_workshop/code/okonawa Todo Model - exists - has a name, description, a due date and whether is done or not [FAILED - #<Todo:0xa6c8a90>.respond_to?(:name) failed] ... Bacon::Error: #<Todo:0xa6c8a90>.respond_to?(:name) failed spec.rb:700:in `satisfy:': Todo Model - has a name, description, a due date and whether is done or not spec.rb:714:in `method_missing:' spec.rb:316:in `block in run_spec_block' spec.rb:440:in `execute_block' spec.rb:316:in `run_spec_block' spec.rb:331:in `run' ! 6 specifications (6 requirements), 1 failures, 0 errors
  • 76. motion model To pass the spec we’ll have to: • Mix in the main MotionModel functionality contained in MotionModel::Model • Use the default persistence adapter provided by the module MotionModel::ArrayModelAdapter • Define the 4 columns using the columns method
  • 77. class Todo include MotionModel::Model include MotionModel::ArrayModelAdapter ! columns :name => :string, :details => :string, :due_date => {:type => :date, :formotion => {:picker_type => :date_time}}, :done => {:type => :boolean, :default => false, :formotion => {:type => :switch}} end Motion Model • The Todo model enhanced with MotionModel: app/models/todo.rb
  • 78. motion Model • Running rake spec again to confirm the newly added functionality: ~/rm_workshop/code/okonawa Application 'okonawa' - has one window ! Todo Model - exists - has a name, description, a due date and whether is done or not ! Todos Controller - should exist - displays the given ToDos - displays the correct label for a given ToDo ! 6 specifications (9 requirements), 0 failures, 0 errors
  • 79. validations w/ motion Model • Let’s specify that a Todo must have a name to be valid: it 'is invalid without a name' do @todo.name = nil @todo.should.not.be.valid end ~/rm_workshop/code/okonawa spec/todo_model_spec.rb Todo Model - exists - has a name, description, a due date and whether is done or not - is invalid without a name [ERROR: NoMethodError - undefined method `valid?' for Todo#3:0xa7b8b50:Todo] ! ... NoMethodError: undefined method `valid?' for Todo#3:0xa7b8b50:Todo model.rb:863:in `method_missing:': Todo Model - is invalid without a name spec.rb:697:in `satisfy:' spec.rb:714:in `method_missing:' spec.rb:316:in `block in run_spec_block' spec.rb:440:in `execute_block' spec.rb:316:in `run_spec_block' spec.rb:331:in `run'
  • 80. validations w/ motion Model • Including the MotionModel::Validatable mixin to use the validates method: class Todo include MotionModel::Model include MotionModel::ArrayModelAdapter include MotionModel::Validatable ! ! columns :name => :string, :details => :string, :due_date => {:type => :date, :formotion => {:picker_type => :date_time}}, :done => {:type => :boolean, :default => false, :formotion => {:type => :switch}} ! validates :name, :presence => true end app/models/todo.rb
  • 81. motion Model • Running rake spec again to confirm that the Todo model validates the presence of its name: ~/rm_workshop/code/okonawa Application 'okonawa' - has one window ! Todo Model - exists - has a name, description, a due date and whether is done or not - is invalid without a name ! Todos Controller - should exist - displays the given ToDos - displays the correct label for a given ToDo ! 7 specifications (10 requirements), 0 failures, 0 errors
  • 82. a couple more validations • Let's round out the Todo model development with a couple of specifications: “A Todo is not done by default” and “A Todo knows if its overdue” before do @now = NSDate.new @todo = Todo.new :name => "Buy Milk", :description => "We need some Milk", :due_date => @now end ! it 'is not done by default' do @todo.done.should.not.be.true end ! it 'knows if its overdue' do @todo.should.be.overdue end spec/todo_model_spec.rb
  • 83. a couple more validations • Now we have to implement the overdue? method: ~/rm_workshop/code/okonawa Todo Model ... - knows if its overdue [ERROR: NoMethodError - undefined method `overdue?' for Todo#5:0xa69ab50:Todo] ... ! NoMethodError: undefined method `overdue?' for Todo#5:0xa69ab50:Todo model.rb:863:in `method_missing:': Todo Model - knows if its overdue spec.rb:697:in `satisfy:' spec.rb:714:in `method_missing:' spec.rb:316:in `block in run_spec_block' spec.rb:440:in `execute_block' spec.rb:316:in `run_spec_block' spec.rb:331:in `run' ! 9 specifications (11 requirements), 0 failures, 1 errors
  • 84. a couple more validations • Add the overdue? method to the Todo model and provide an implementation: def overdue? # TODO: Implement me! end spec/todo_model_spec.rb
  • 85. integrating the model • The next step is to refactor the TodosController controller to make use of the new model • Our refactoring will involve replacing the @data string array with a collection of Todo model instances • Also, since we are using a full-fledge model we’ll have to extract the property we want to use to display on the table
  • 86. integrating the model • Replace the @data array contents with a call to Todo.all • Use the name attribute to be used in the cell label in cellForRowAtIndexPath
  • 87. integrating the model • Refactor the controller spec to clean up the DB and create a new Todo using the create method
  • 88. integrating the model • Refactor the seeding of the DB to a separate seed method • Refactor the AppDelegate to seed the DB in non-test mode
  • 89. WORKING WITH FORMS Displaying and Editing our Models
  • 90. FORMOTION https://github.com/clayallsopp/formotion • FormMotion “Making iOS Forms insanely great with RubyMotion" • Provides a builder DSL to quickly create iOS Forms • Provides custom controllers to display the forms • Integrates with MotionModel!
  • 91. STORY #2 • We now have a list of Todos but we also need to see a Todo’s details • Our next story will concentrate on the Todo’s details view: “As a User I should be able to see a To-Do’s details”
  • 92. TODO controller • We want a form displaying the Todo's details. • That functionality will be the responsibility of the (yet to be created) TodoController • Let's start with the spec below: describe "Todo Controller" do it 'exists' do Object.const_defined?('TodoController').should.be.true end end spec/todo_controller_spec.rb
  • 93. TODO controller • Let’s use Formotion to implement our model-driven form: class TodoController < Formotion::FormController end app/controllers/todo_controller.rb ~/rm_workshop/code/okonawa Todo Controller - exists ! ... ! 10 specifications (13 requirements), 0 failures, 0 errors ! ! !
  • 94. TODO controller • Let's add a spec to test that our controller can indeed display a Todo model • The spec does require some prior knowledge of how to access the soon to be created rows of our form it 'displays a Todo's details' do @name_row.value.should.equal 'Buy Milk' @details_row.value.should.equal 'We need some Milk' @due_date_row.object.date_value.hour.should.equal @now.hour @due_date_row.object.date_value.min.should.equal @now.min @done_row.value.should.equal false end spec/todo_controller_spec.rb
  • 95. • Although we have a failure, it’s not very telling (a sign we might be testing to close to the implementation): ~/rm_workshop/code/okonawa Todo Controller - exists - displays a Todo's details [ERROR: NoMethodError - undefined method `value' for nil:NilClass] … ! NoMethodError: undefined method `value' for nil:NilClass spec.rb:316:in `block in run_spec_block': Todo Controller - displays a Todo's details spec.rb:440:in `execute_block' spec.rb:316:in `run_spec_block' spec.rb:331:in `run' ! 11 specifications (13 requirements), 0 failures, 1 errors !!!!!! TODO controller
  • 96. FORM-AWARE MODELS • By adding the MotionModel::Formotion Mixin allow the ability to create a FormMotion::Form from an instance of Todo • E.g. Formotion::Form.new(todo.to_formotion('Edit your ToDo')) class Todo include MotionModel::Model include MotionModel::ArrayModelAdapter include MotionModel::Validatable include MotionModel::Formotion ! ... app/models/todo.rb
  • 97. TODO controller • Let’s expand the TodoController with the ability to initialize itself with an instance of a FormMotion::Form (which we’ll build with our form-aware model): class TodoController < Formotion::FormController attr_accessor :todo attr_accessor :form ! def initialize(todo) self.form = Formotion::Form.new(todo.to_formotion('Edit your ToDo')) self.initWithForm(self.form) self.todo = todo end end app/controllers/todo_controller.rb
  • 98. TODO controller spec • Finally, we’ll need to refactor the spec to create a Todo model, initialize a controller instance with it and make that available to the spec describe "Todo Controller" do tests TodoController ! before do @now = NSDate.new @todo = Todo.create :name => "Buy Milk", :details => "We need some Milk", :due_date => @now @controller = TodoController.new(@todo) ! @form = @controller.instance_variable_get("@form") @name_row = @form.sections[0].rows[0] @details_row = @form.sections[0].rows[1] @due_date_row = @form.sections[0].rows[2] @done_row = @form.sections[0].rows[3] end ! def controller @controller end ... spec/todo_controller_spec.rb
  • 99. TODO controller • A subclass of UIViewController, UITableViewController is designed to host and manage a UITableView • By replacing the base class we don’t have to manually wire the UITableView, set the datasource and add the view: class TodosController < UITableViewController attr_writer :data ! def viewDidLoad super self.title = 'Okonawa' # @table = UITableView.alloc.initWithFrame(self.view.bounds) # @table.dataSource = self # self.view.addSubview(@table) ! @data = Todo.all end ... app/controllers/todo_controller.rb
  • 100. TODO controller • Let’s add the tableView#didSelectRowAtIndexPath event handler • We’ll deselect the row the user just selected (clean up), grab the index of the selection (via indexPath param), find the Todo in our Array, create the TodoController and use the navigationController to push the new view: def tableView(tableView, didSelectRowAtIndexPath: indexPath) tableView.deselectRowAtIndexPath(indexPath, animated: true) todo = @data[indexPath.row] todo_controller = TodoController.new(todo) self.navigationController.pushViewController(todo_controller, animated: true) end app/controllers/todo_controller.rb
  • 101. TODO controller spec • If you run the specs the Todos Controller spec will fail since there is no longer an instance variable @table in the controller • Instead we can access the tableView property of the UIViewController describe "Todos Controller" do tests TodosController ! before do Todo.delete_all @now = NSDate.new @todo = Todo.create(:name => 'Buy Milk', :description => 'Get some 1% to rid yourself of the muffin top', :due_date => @now) #@table = controller.instance_variable_get("@table") @table = controller.tableView end spec/todo_controller_spec.rb
  • 103. editing / saving • Now, let's add the ability to save any modifications made to a Todo • This section is going to be “Lab Style”. I’ll provide a failing spec for you to pass • Feel free to work in groups! it 'saves changes made to a Todo' do @name_row.object.row.value = 'Buy 1% Milk' controller.save ! saved_todo = Todo.find(@todo.id) ! saved_todo.name.should.equal 'Buy 1% Milk' end spec/todo_controller_spec.rb
  • 104. editing / saving • Now, on to implement the save method! ~/rm_workshop/code/okonawa Todo Controller - exists - saves changes made to a Todo [ERROR: NoMethodError - undefined method `save' for #<TodoController:0xd3e2d90>] ... ! NoMethodError: undefined method `save' for #<TodoController:0xd3e2d90> spec.rb:316:in `block in run_spec_block': Todo Controller - saves changes made to a Todo spec.rb:440:in `execute_block' spec.rb:316:in `run_spec_block' spec.rb:331:in `run' ! 11 specifications (13 requirements), 0 failures, 1 errors
  • 105. editing / saving - hints • Implement the controller save method: • Find how to retrieve the data from the form (what methods does @form support?) • Find how to update a Todo model the form data (what methods does @todo support?) • Consult the Formotion and MotionModel documentation if necessary
  • 106. save button • It doesn’t help our users that we have a working save method in the controller if they can't use it • We need to add a “Save” button to the TodoController view: def viewDidLoad super saveButton = UIBarButtonItem.alloc.initWithTitle("Save", style: UIBarButtonItemStyleBordered, target: self, action: 'save') self.navigationItem.rightBarButtonItem = saveButton end app/controllers/todo_controller.rb
  • 107. save button • Launching the app we can see that the changed values are persisted when we tap the ‘Save’ button, but they are not reflected on the Todos list • Model Motion supports notifications that are issued on object save, update, and delete. We can use the NSNotificationCenter default instance to register an observer, which will invoke the todoChanged method def viewDidLoad super self.title = 'Okonawa' ! @data = Todo.all ! NSNotificationCenter.defaultCenter.addObserver(self, selector: 'todoChanged:', name: 'MotionModelDataDidChangeNotification', object: nil) unless RUBYMOTION_ENV == 'test' end app/controllers/todos_controller.rb
  • 108. save button • The todoChanged method received the notification object which has an action property under userInfo • On ‘update’ we will retrieve the passed Todo, find the row for the it (hacky), create a NSIndexPath and tell the cell at that path to refresh itself: def todoChanged(notification) case notification.userInfo[:action] when 'add' when 'update' todo = notification.object row = todo.id - 1 path = NSIndexPath.indexPathForRow(row, inSection:0) tableView.reloadRowsAtIndexPaths([path], withRowAnimation:UITableViewRowAnimationAutomatic) when 'delete' end end app/controllers/todos_controller.rb
  • 109. save button • The “Save” button in action:
  • 110. COCOAPODS Working with Non-Gem Dependencies
  • 111. cocoapods • Until recently there was only one way to maintain 3rd-party dependencies: the vendoring procedure. • The procedure entails downloading, unzipping/un-tarring and manually copying dependencies (upgrades also following the same manual procedure) • Luckily CocoaPods liberates us from this 1990’s dependency management hell! • CocoaPods manages library dependencies for your Xcode/RubyMotion projects. The dependencies for your projects are specified in a single text file called a Podfile
  • 112. cocoapods • To use CocoaPods within a RubyMotion application you use the motion-cocoapods gem located at https://github.com/HipByte/motion-cocoapods • It can be installed with Bundler like any other Ruby Gem. Let’s add it to our project’s Gemfile and bundle the application: source 'https://rubygems.org' ! gem 'rake' gem 'motion_model' gem 'formotion' gem 'motion-cocoapods' Gemfile
  • 113. pixate freestyle Style your iOS app with CSS
  • 114. PIXATE FREESTYLE • Pixate Freestyle is a native iOS (and Android) library that styles native controls with CSS • Replace many complicated lines of Objective-C with a few lines of CSS • Pixate Freestyle allows you to add IDs, Class’es, and inline styles to your native components, and style them with CSS • Pixate also offers themes (CSS stylesheets offering a based style for most iOS components) http://pixate.github.io/pixate-freestyle-ios/themes/
  • 115. INSTALLING W/ COCOAPODS • In order to add the Pixate Freestyle library to our RubyMotion project we first need to find out if the library is maintained under CocoaPods • The easiest way to do this to search directly on the CocoaPods site (paste http://cocoapods.org/?q=pixatef in your browser):
  • 116. INSTALLING W/ COCOAPODS • Using motion-cocoapods we can declare our Pods directly in the Rakefile: Motion::Project::App.setup do |app| # Use `rake config' to see complete project settings. app.name = 'okonawa' ! app.pods do pod 'PixateFreestyle', '~> 2.1' end end Rakefile
  • 117. INSTALLING W/ COCOAPODS • CocoaPods configures a master repository locally (to avoid duplication) • After bundling the application, run the pod setup command: ~/rm_workshop/code/okonawa /> pod setup Setting up CocoaPods master repo Setup completed (read-only access) !!!!!!!!!!
  • 118. INSTALLING W/ COCOAPODS • The next step is to install the dependencies into our RubyMotion project. • Run motion-pods provided Rake task: rake pod:install: ~/rm_workshop/code/okonawa /> rake pod:install Updating spec repo `master` Current branch master is up to date. Analyzing dependencies Downloading dependencies Installing PixateFreestyle (2.1.4) Generating Pods project !!!!!!
  • 119. INSTALLING W/ COCOAPODS • Installing the pods create a Pods folder under your project’s vendor folder • Since we do not want to check those artifacts into our Repo, add the following lines to your .gitignore file vendor/Pods/ vendor/Pods/.build/ vendor/Pods/build-iPhoneOS/ vendor/Pods/build-iPhoneSimulator/ vendor/Podfile.lock .gitignore
  • 120. motion-pixatefreestyle • Our next step is to bring in the Ruby bridge to our Pixate Freestyle pod, the motion-pixatefreestyle Gem: source 'https://rubygems.org' ! gem 'rake' gem 'motion_model' gem 'formotion' gem 'motion-cocoapods' gem 'motion-pixatefreestyle' Gemfile
  • 121. motion-pixatefreestyle • The motion-pixatefreestyle Gem adds a couple of Rake tasks • The init and sass tasks: ~/rm_workshop/code/okonawa /> rake -T | grep pixate rake pixatefreestyle:init # Create initial stylesheet files rake pixatefreestyle:sass # Compile SASS/SCSS file ! ! ! ! ! ! ! !
  • 122. motion-pixatefreestyle • Running rake pixatefreestyle:init will create our sass/scss style file and the ‘compiled’ default.css under the resources folder: ~/rm_workshop/code/okonawa />rake pixatefreestyle:init ! Create sass/default.scss Create resources/default.css ! ! ! !
  • 123. Using A THEME • Freestyle provides a theme (a set of SASS files) to quickly style an entire iOS app • Themes are available under the Pixate Freestyle repo, currently only the pixate-blue is available • To copy the theme clone the repo: git clone https://github.com/Pixate/ pixate-freestyle-ios.git OR download just the them from pixate-blue • Copy the contents of the file to your /sass folder
  • 124. • Running rake pixatefreestyle:sass with the them SASS files in place will ‘compile’ a new default.css under the resources folder: ~/rm_workshop/code/okonawa />rake pixatefreestyle:sass ! Compile sass/default.scss ! ! ! ! Using A THEME
  • 125. Using A THEME • To work with Pixate Freestyle, we add CSS classes and ids to the app elements • To illustrate the power of CSS-based styling. Let’s add a CSS class to the cells in our UITableView (in UITableViewController in todos_controller.rb) def tableView(tableView, cellForRowAtIndexPath: indexPath) cell = UITableViewCell.alloc.initWithStyle(UITableViewCellStyleDefault, reuseIdentifier:nil) cell.textLabel.text = @data[indexPath.row].name cell.styleClass = 'table-cell' cell end app/controllers/todos_controller.rb
  • 126. Using A THEME • Our newly styled Todo list:
  • 127. Using A THEME • Styling the Formotion artifacts proved to be a little more involved since they are not directly exposed • After a few hours of GooglingTM I found that I needed to monkey-patch the Formotion::Form class as shown below: module Formotion class Form < Formotion::Base def tableView(tableView, willDisplayCell: cell, forRowAtIndexPath: indexPath) cell.styleClass = 'table-cell' cell.updateStyles end end end app/formotion_pixate.rb
  • 128. Using A THEME • The styled Todo Controller:
  • 129. NEXT STEPS Continuing your RubyMotion Learning…
  • 130. CONTINUING YOUR LEARNING • I’ve created a series of articles that chronicle the development of the Okonawa application and I will continue enhancing the series in 2014 and 2015 • http://integrallis.com/2013/03/tdd_in_ios_w_ruby_motion_part_i • http://integrallis.com/2013/04/tdd_in_ios_w_ruby_motion_part_ii • http://integrallis.com/2014/06/ios-development-in-ruby-with-rubymotion-part-iii • We also offer a 3 day RubyMotion course! http://integrallis.com/courses/rubymotion