The DRY principle states the following:
"Every piece of knowledge must have a single, unambiguous, authoritative representation within a system."
We are now approaching the end of this book. So far we have learned how to quickly make iOS applications with RubyMotion. To make this process even more rapid, RubyMotion lets us use special RubyMotion-flavored gems and wrappers. Gems and wrappers are Ruby programs that are wrapped into a self-contained format. These are generally open source projects, which other developers can use in their applications or can even contribute back to these projects. Fortunately, RubyMotion has a very enthusiastic community; within months of launching RubyMotion's tool chain, plenty of gems were introduced that implement many laborious tasks fairly quickly. In this chapter, we will learn how to augment our application by using RubyMotion-flavored gems. The following topics will be covered in this chapter:
Use of gems is based on the programming practice of Don't Repeat Yourself (DRY), which states that when some piece of code is ready to use and is available, why bother working on it again. The RubyMotion community may be very young right now, but it already has some amazing gems that make a lot of tiring tasks pretty easy. Some gems even target challenging functionalities in a very simple manner.
In this chapter we will cover the following RubyMotion-flavored gems:
motion-addressbook
Designing a UI for iOS apps is a tough job, especially for developers who have worked previously on easy-to-learn-and-implement web technologies. Teacup is a gem that will make your life really easy. Teacup augments your ability to quickly design and style the views of your RubyMotion application; you can easily create layouts while keeping your code DRY.
Let's create an application and learn how easy it is to use Teacup:
$motion create TeaCupMotion
We will be using Bundler (which is also a Ruby gem) to install all our gems. Bundler also helps us manage application dependencies, so that the exact version of the gems used are available for the application to run.
Let's add Bundler to our application:
Rakefile
with the following lines of code:$:.unshift("/Library/RubyMotion/lib") require 'motion/project' require 'bundler' Bundler.require
source "https://rubygems.org" gem "teacup"
So, in the future, if you want to add any new gem to your project, you can simply add it to this file.
bundle install
and we're good to go:bundle install
app_delegate.rb
file with the following code:class AppDelegate def application(application, didFinishLaunchingWithOptions:launchOptions) @window = UIWindow.alloc.initWithFrame(UIScreen.mainScreen.bounds) myNavController = RootController.alloc.init @window.rootViewController = UINavigationController.alloc.initWithRootViewController(myNavController) @window.rootViewController.wantsFullScreenLayout = true @window.makeKeyAndVisible true end end
In this code, we are only initializing an instance of RootController
, just like we do with every application. As you may remember, the controller is where all our application logic resides.
So far, in various chapters we have made RubyMotion iOS applications in a traditional way. Let's use Teacup in our application this time, and add styles by making use of its Cascading Style Sheets (CSS) type syntax.
Let's create a directory named style
and add a new file with the name of style.rb
in it. Add the following code to the style.rb
file in the style
folder:
Teacup::Stylesheet.new(:style) do style :your_layout, landscape: true style UILabel, textColor: UIColor.blueColor style :label, text: 'Awesome', backgroundColor: UIColor.whiteColor, top: 10, left: 100, width: 100, height: 20 end
Let's understand the preceding code:
style
.Teacup::Stylesheet.new(:style) do … end
This convention is provided by Teacup to create a new stylesheet.
style :your_layout, landscape: true
This will create a style named your_layout
and will enable the landscape rotation (otherwise, only portrait orientation is enabled).
UILabel
instances.style UILabel, textColor: UIColor.blueColor
The preceding line of code gives text color to all UILabel
instances that are defined inside the style. Since we apply a style to all the labels when using UILabel
, if we want to style a specific element, we have to add the following commands:
style :label, text: 'Awesome', backgroundColor: UIColor.whiteColor, top: 10, left: 100, width: 100, height: 20
Here, label
is like a class. This will do the styling for the label.
To understand this better, let's create a view. Perform the following steps to create a view:
root_view_controller.rb
and add the following code to it:class RootController < UIViewController stylesheet :style layout :your_layout do @label1 = subview(UILabel, :label) end def shouldAutorotateToInterfaceOrientation(orientation) autorotateToOrientation(orientation) end end
As we have created a new controller file, we must make the corresponding changes to the app_delegate.rb
file. Make these changes in your app_delegate.rb
file as shown in the previous chapters.
In the preceding code snippet, first we have given the stylesheet a name, which is done using stylesheet:style
, and then we have specified a layout named your_layout
and passed label : @label1 = subview(UILabel, :label)
to it.
$rake
The following is the output:
We can see the text Awesome appear on the simulator screen and it is styled as we have defined in the stylesheet.
We can also define different stylesheets for changing dimensions as we rotate the device, such as the landscape and portrait modes. Let's try this in our next example.
style.rb
file, with the following code:style :label, text: 'Awesome', backgroundColor: UIColor.whiteColor, top: 10, left: 100, width: 100, height: 20, landscape: { backgroundColor: UIColor.redColor, }
style.rb
file:style UITextField, # Defining styles based on view class instead textColor: UIColor.redColor style :field, left: 10, top: 10, width: 200, height: 30, landscape: { width: 360 # make it wide in landscape view } style :search, extends: :field, backgroundColor: UIColor.whiteColor, left: 20, top: 70, placeholder: 'Search Box' style :search_new, extends: :field, backgroundColor: UIColor.redColor, left: 20, top: 110, placeholder: 'Search Box'
Here we have created two text field boxes.
root_controller.rb
file.layout :your_layout do @label1 = subview(UILabel, :label) @search = subview(UITextField, :search) @one_more_search = subview(UITextField, :search_new) end
$rake
The following is the output:
With the preceding example, we can see how easy it is to design views with the Teacup gem; it has delivered a way to create interfaces programmatically with ease. We have shared a few of the features of this amazing gem; you can explore more at https://github.com/rubymotion/teacup.
BubbleWrap is a collection of very well-tested helpers and wrappers used to wrap Cocoa SDK code and provide more Ruby-like APIs for RubyMotion. It provides wrappers for a lot of iOS Cocoa SDK code, such as camera, notification center, HTTP, and many more.
We can do a lot of things very easily. For example, to perform a GET HTTP
request with BubbleWrap, we require the following simple code snippet:
BW::HTTP.get("https://twitter.com/rubymotion") do |response| p response.body.to_str end
In Chapter 6, Device Capability – Power Unleashed, we have learned about device capabilities—implementing camera functionalities in your app. We have written quite a lot of code there, but with BubbleWrap things get really simplified. We only require the following code snippet for using a camera in our application:
BW::Device.camera.front.picture(media_types: [:movie, :image]) do |result| image_view = UIImageView.alloc.initWithImage(result[:original_image]) end
BubbleWrap also provides a module named App
that can be used while running the application. To understand this, perform the following steps:
$motion create UseBubbleWrap
Rakefile
to include a Bundler that will help us install the BubbleWrap gem easily.require 'bundler' Bundler.require
Gemfile
to our project with the following code:source :rubygems gem 'bubble-wrap'
$bundle install
App
module on the console:$rake
App
module, run the following commands in REPL:(main)> App.name => "UseBubbleWrap" (main)> App.identifier => "com.yourcompany.UseBubbleWrap" (main)> App.documents_path => "/Users/abhishek/Library/Application Support/iPhone Simulator/6.1/Applications/3CF89A96-F390-4A7D-89B8-2F0E7B54A38A/Documents" (main)> App.resources_path => "/Users/abhishek/Library/Application Support/iPhone Simulator/6.1/Applications/3CF89A96-F390-4A7D-89B8-2F0E7B54A38A/UseBubbleWrap.app" (main)> App.frame => #<CGRect origin=#<CGPoint x=0.0 y=20.0> size=#<CGSize width=320.0 height=460.0>> (main)> App.states => {} (main)> App.shared => #<UIApplication:0x9530920> (main)> App.current_locale => #<__NSCFLocale:0x966a040> (main)> App.alert("This is nice!!") => #<UIAlertView:0xa8433f0> (main)> App.run_after(0.5) { p "It's #{Time.now}" } => #<__NSCFTimer:0x93760c0> (main)> "It's 2013-05-10 18:47:34 +0530"
Device
that provides many options related to the current device. Let's once again fire up REPL in our terminal and execute the following commands:$rake (main)> Device.iphone? => true (main)> Device.ipad? => false (main)> Device.front_camera? "This method (front_camera?) is DEPRECATED. Transition to using Device.camera.front?" => false (main)> Device.screen.width => 320.0 (main)> Device.screen.height => 480.0 (main)> Device.orientation => :portrait
There are tons of helpers that come with the BubbleWrap gem. It will be helpful for your project if you have a look at the BubbleWrap documentation at http://bubblewrap.io/.
In Chapter 6, Device Capability – Power Unleashed, we had discussed in detail how to use the Address Book technology for iOS devices. In this section, we will use a special gem for RubyMotion named motion-addressbook
that simplifies using the Address Book.
We will perform the following actions in this section:
motion-addressbook
gemLet's start by performing the following steps:
$motion create AddressBook_example
motion-addressbook
gem in the Gemfile
.source :rubygems gem 'bubble-wrap' gem 'motion-addressbook'
$bundle install
addressbook_controller.rb
in which we will add a button and three labels. With the button, we will access our address book and choose the desired contact. In the labels, we will display the data of the user, which we have copied from the address book. Add the following code in your addressbook_controller.rb
file:def viewDidLoad view.backgroundColor = UIColor.underPageBackgroundColor load_button load_labels end def load_button @phonebook_button = UIButton.buttonWithType(UIButtonTypeRoundedRect) @phonebook_button.frame = [[50, 20], [200, 50]] @phonebook_button.setTitle("Click from Contacts", forState:UIControlStateNormal) @phonebook_button.addTarget(self, action: :addressbook_access, forControlEvents:UIControlEventTouchUpInside) view.addSubview(@phonebook_button) end def load_labels @first_name = UILabel.new @first_name.text = 'First Name' @first_name.frame = [[100,100],[150,50]] @last_name = UILabel.new @last_name.text = 'Last Name' @last_name.frame = [[100,160],[150,50]] @organization = UILabel.new @organization.text = 'Organization' @organization.frame = [[100,220],[150,50]] view.addSubview(@first_name) view.addSubview(@last_name) view.addSubview(@organization) end
app_delegate.rb
file so that our delegate points to our address book controller:class AppDelegate def application(application, didFinishLaunchingWithOptions:launchOptions) @window = UIWindow.alloc.initWithFrame(UIScreen.mainScreen.bounds) @window.rootViewController = AddressbookController.alloc.init @window.makeKeyAndVisible true end end
$rake
The following is the output:
addressbook_access
. To access the Address Book, we need to use the AddressBook
picker that lets us open the device's Address Book in our application and pick data from it. With this method, we will be doing the same. Let's create this method in our addressbook_controller.rb
file and add the following code to it:def addressbook_access AddressBook.pick { |person| if person first_name = person.attributes[:first_name] last_name = person.attributes[:last_name] org = person.attributes[:organization] @first_name.text = first_name @last_name.text = last_name @organization.text = org else # write some cancel code end } end
$rake
The following is the output:
That's it, we are done. It's the same application we had created in Chapter 6, Device Capability – Power Unleashed, but with motion-addressbook
, we have substantially less code.
Let's understand what we have done here. The
motion-addressbook
gem gives us many options to easily use the device's Address Book. In the addressbook_access
method, we have used the AddressBook
picker by using AddressBook.pick
, which opens up the device's Address Book for us. Once we select any contact, we get a person
object that has a hash of all the attributes of the selected contact.
In our example, we have used the first_name
, last_name
, and organization
values from the selected person
object. However, the motion-addressbook
gem has many more options that make working with the Address Book framework faster and easier. A few of them are as follows:
AddressBook::Person.new #<AddressBook::Person:0xc360bc0 @address_book=nil @ab_person=nil @attributes={}>
AddressBook::Person.all [#<AddressBook::Person:0x9d78c80 @address_book=#<__NSCFType:0xc0db6d0> @ab_person=#<__NSCFType:0x9d77ea0> @attributes={:first_name=>"Abhishek", :last_name=>"Nalwaya", :organization=>"Fun Inc."}>, #<AddressBook::Person:0x78f0a20 @address_book=#<__NSCFType:0xc0db6d0> @ab_person=#<__NSCFType:0x9d78520> @attributes={:first_name=>"Akshat", :last_name=>"Paul", :organization=>"PacktPub"}>, #<AddressBook::Person:0x78a5eb0 @address_book=#<__NSCFType:0xc0db6d0> @ab_person=#<__NSCFType:0x9d788b0> @attributes={:first_name=>"Laurent", :last_name=>"Sansonetti", :organization=>"HipByte"}>, #<AddressBook::Person:0x78c06e0 @address_book=#<__NSCFType:0xc0db6d0> @ab_person=#<__NSCFType:0x9d78700> @attributes={:first_name=>"Manu", :last_name=>"Singhal", :organization=>"Ruby Inc"}>]
AddressBook::Person.find_all_by_organization('HipByte')
AddressBook::Person.where(:email => '[email protected]', :organization => 'Fun Inc')
AddressBook::Person.create(:first_name => 'Shi', :last_name => 'Foo', :email => [email protected]')
18.188.96.5