📜 ⬆️ ⬇️

Developing iOS Ruby Applications

Recently, RubyMotion has become an increasingly popular tool for developing for iOS. After a close acquaintance with him, it becomes clear why Ruby is a much more attractive language for this purpose than Objective-C.


Introduction


Is it Ruby or not?

RubyMotion is a ruby language iOS development toolkit . It consists of a runtime environment that implements Ruby functionality within iOS. Although the scope of this ruby ​​code is different from CRuby , the implementation is based on the specifications of Ruby 1.9.

Knowledge of Ruby does not guarantee that you can write iOS applications, but it definitely will not be superfluous. To work with RubyMotion, it’s much more important to be familiar with the Objective-C and Foundation Framework APIs. In essence, RubyMotion "codifies" Objective-C.

Project work

To work with RubyMotion, it is not necessary to use XCode, you can use your favorite editor. Together with RubyMotion comes a console tool ( motion ) that helps you create and customize a folder with a project.
')
It also has a set of rake tasks that will help you build a project and run it in the iOS simulator. It can recognize resources, such as images or pfile, as well as connect and use .xib, .storyboard and .xcdatamodeld files.

Where to begin?

If you can’t wait to start work, you will have to buy a license ( at the moment it costs $ 199, the website also shows the amount in rubles according to the curved exchange rate ), since This is a proprietary software. After that, there is no harm in going through the Getting Started Guide on the RubyMotion site.

An introductory screencast from the Motion Casts site will also be useful. And there is a 50-minute screencast from Pragmatic Studio, which will tell you how to create a simple application. This tutorial is also good.

Simple application


We figured out what RubyMotion is and how it can be useful, so let's work on our first application.

Since I could not find any ready more or less complex example, I offer you my own crafts. Sources are attached .

Our application

Let's pretend that we are doing the applique for the conference. For this, I will use the schedule MagmaRails 2012 . The amount of information displayed is limited to the speakers and their speeches, divided by day.

Our interface

All resources on the RubyMotion developer site show how to build an interface using code, there are even some DSLs specially created for building user interfaces for iOS. But instead, we will use Xcode Storyboard, which will help us build a model for navigating the application.

Storyboards are tied to Xcode, and I previously promised that we will be able to develop applications without the need for it, but in this case we will use it simply because Storyboards are a very convenient visualization tool.

Preparing a project

The first step we will take is to create a new XCode project - it is assumed that you will use XCode 4.5.x for this - based on the Master-Detail Application, so give it a name, and also make sure that iPhone is selected in the Devices field. The checkboxes “Use StoryBoard” and “Use Core Data” are checked.



Now let's create a project in RubyMotion and call it “conference”:

 $ motion create conference 

Inside the structure of our RubyMotion project, there is a resources folder, into which you need to copy the MainStoryboard.storyboard file, which was created by XCode. Copy the file and then remove the link to it from XCode.



Since we still want to be able to edit this file in XCode, you need to drag it from our daddy to the XCode interface. We will be asked how exactly we want to do this. Make sure that the Copy items into destination group's folder (if needed) unchecked, and then click Finish. So we created a symlink to our file, and all changes inside Xcode will be automatically reflected in the project.



Now let's configure the Bundler:

 $ bundle init 

Edit the gemfile as follows:

 source :rubygems gem 'xcodeproj', '~> 0.3.0' gem 'ib' gem 'rake' 

Run the Bundler:

 $ bundle 

The xmesproj and ib gems will help connect the Ruby Outlets with the Xcode Storyboards, which we will use later.

Now we’ll correct Rakefile and add, after the line, require "motion/project" code that connects the Bundler to the project:

 require 'rubygems' require 'ib' require 'bundler' Bundler.require 

Find app_delegate.rb and open it in your favorite editor. Inside it, the application method is declared, which is the entry point of our application. Now we tell RubyMotion to download the MainStoryboard.storyboard :

 def application(application, didFinishLaunchingWithOptions:launchOptions) @window = UIWindow.alloc.initWithFrame(UIScreen.mainScreen.bounds) @storyboard ||= UIStoryboard.storyboardWithName('MainStoryboard', bundle:NSBundle.mainBundle) @window.rootViewController = @storyboard.instantiateInitialViewController @window.rootViewController.wantsFullScreenLayout = true @window.makeKeyAndVisible true end 

We initialize the screen frame, load the Storyboard, set the main Storyboard controller as our rootViewController and make our screen visible.

To test, run:

 $ rake 

This command will compile our project in the iOS application, load it into the simulator and launch it. If everything went well, you will see an empty grid. To close an application, type quit in the console.



Create a Storyboard

Going back to Xcode, let's open the MainStoryboard.storyboard file.



Select Master View Controller and double click on the title to change it to MagmaRails . Now select the Table View and change the value of the Content property from Dynamic to Static . The label should now have three rows of cells. You need to select the first one and make two copies of it. Change the title of each of them - double-click - to "Day One", "Day Two", "Day Three", "Speakers" and "Venue". Click on the transition connecting the Master View Controller and the Detail View Controller to remove it.



We need another View Controller to display conferences by day, so let's drag it from the Objects Library directly to the canvas. Drag the table view to this new controller, and the table-view cell in the table view.

Using identity inspector, rename the controller to TalksViewController . In the attribute inspector, change the identifier of our new cell to Talk, and from the identity inspector, change the class of the cell to TalkViewCell. Also, make sure that our cell style is custom and change its height to 115.

Add in the cell 3 labels and a picture that will display the information we need. Once done, drag the cursor while holding the ctrl key from the Day One cell on the Master View Controller to the new TalksViewController to create the transition. As the type of transition you need to select push , and as the name - DayOne . Do the same for cells with the second and third day.



Core Data models and application seed

Before we continue, let's create the Core Data models as well as the basic information sid.

When we created the project in Xcode, the Use Core Data option was checked. This means that together with our project an .xcdatamodeld file was created. Find this file in Xcode and right click on it. Navigate to it using Show File in Finder, move it to the resources folder of our application and remove the link to the file in Xcode, then drag it back, but from our folder.

In Xcode, open Core Data Models and add two new entities, Talk and Presenter. For Talk, add the attributes as shown in the image and verify with the Data Model Inspector that the Class is called Talk. Repeat the same for Presenter.




Save the file. Now we are ready to create Models inside RubyMotion.

Let's connect two gems. The first, motion-cocoapods , will allow us to manage dependencies through CocoaPods , the dependency manager for Objective-C. Yes, this means that even if we write code in Ruby, we can still freely use Objective-C libraries.

The library we will use is called MagicalRecord . It will allow us to work easily with Core Data.

The second gem is called motion_support . It provides us with functionality similar to ActionSupport in Ruby on Rails (declensions and some kernel extensions), but with its own specifics. Add these gems to the Gemfile and run the Bundler.

 gem 'motion-cocoapods' gem 'motion_support' 

To make both hemes available, open your Rackfile and add two lines to it immediately below the mention of rubygems.

 require 'motion-cocoapods' require 'motion_support/all' 

We also need to tell RubyMotion that we want to use the Core Data framework and connect the MagicalRecord from cocoapods. For these purposes, we will force RubyMotion to load files into app/lib before the rest, so let's fix app.files.

Add to the end of the Motion :: Project :: App.setup block:

 app.frameworks += %w(CoreData) app.files.unshift Dir.glob(File.join(app.project_dir, 'app/lib/**/*.rb')) app.pods do pod 'MagicalRecord' end 

After these changes, you need to install motion-cocoapods:

 $ pod setup $ rake UPDATE=1 

These two lines will configure cocoapods and install the Objective-C libraries we specified in the Rakefile.

After all this, you can start using Core Data.

You need to create a class for each model that we described in the Core Data digram. Create a model directory inside the app, and in it the Talk.rb and Presenter.rb files:

 class Talk < NSManagedObject end class Presenter < NSManagedObject end 

In order for our models to behave like Core Data entities, you need to declare a couple of methods. It is important to note that we do not need to declare any properties for storing data and relationships in the model. These methods were declared for us during the configuration of the Core Data chart.

Our models will need methods to fill themselves with data from hashes, as well as a couple of finders. For this we need MagicalRecord .

Also, inside the app_delegate.rb file app_delegate.rb we will need a method for filling the database for the first time. This method will be called when the application starts. Seed data is stored in a plist file, which is transformed into a hash, and then used to populate our models and save them to the database.

 def seedDatabase MagicalRecord.setupCoreDataStackWithStoreNamed('database.sqlite') if Talk.allTalks.size == 0 #https://github.com/Bodacious/PListReadWrite PListRW.copyPlistFileFromBundle(:seed) seed = PListRW.plistObject(:seed, Hash) presenters = [] seed['presenters'].each do |presenter_attrs| presenters << Presenter.createWithHash(presenter_attrs) end seed['talks'].each do |talk_attrs| talk = Talk.createWithHash(talk_attrs) presenter = presenters.select{|p| p.presenterId == talk.presenterId }.first talk.presenter = presenter talk.save presenter.addTalk(talk) presenter.save end end end 

We work on controllers

Earlier we linked three MasterViewController and TalksViewController transitions, one for each conference day.

Since all 3 transitions point to the same controller, we need to use identifiers so that the TalksViewController understands which day is being discussed.

Create a masterviewcontroller.rb file inside the app/controllers and add the prepareForSegue method in it:

 class MasterViewController < UITableViewController def prepareForSegue(segue, sender: sender) case segue.identifier when 'DayOne', 'DayTwo', 'DayThree' segue.destinationViewController.setFilter segue.identifier end end end 

The destinationViewController property refers to the target controller, in our case TalksViewController , and it is assumed that we have a criterion by which we can filter the performances.

So let's create our TalksViewController . This controller will be the delegator of TableUIView, therefore we need to add two more methods:

 class TalksViewController < UIViewController attr_accessor :filter attr_accessor :dataSource def tableView(tv, numberOfRowsInSection:section) self.dataSource.count end def tableView(tv, cellForRowAtIndexPath:indexPath) @reuseIdentifier ||= 'TalkCell' cell = tv.dequeueReusableCellWithIdentifier(@reuseIdentifier) || begin TalkCell.alloc.initWithStyle(UITableViewCellStyleDefault, reuseIdentifier:@reuseIdentifier) end talk = self.dataSource[indexPath.row] # We will come back to this a bit later end def viewDidLoad day = case filter when 'DayOne' then 1 when 'DayTwo' then 2 when 'DayThree' then 3 end self.dataSource = Talk.talksByDay(day) end end 

In the viewDidLoad method, based on the value of the filter passed to prepareSegue , we select suitable reports from the database and save them in the dataSource property.

The tableView(tv, numberOfRowsInSection:section) method tableView(tv, numberOfRowsInSection:section) needs to know how many rows we want to display in the table, and we simply know this information from the datasource .

tableView(tv, cellForRowAtIndexPath:indexPath) somewhat more confusing, this method is called every time a row is displayed on the screen, and we need to pass a cell object here with all the relevant data. If we have dozens or hundreds of records, this can lead to unwanted memory waste. Therefore, the framework stores a pool of previously instantiated cells, and instead of creating a new cell each time, it takes the old one from the pool and reuses it. Due to this, the number of objects in the memory is always small. It uses cells in accordance with the identifier, and if not available at the moment, it creates a new one.

In the case of our Storyboard, we created a custom cell for information about the report, and also added TalkCell as an identifier that has several labels and a picture. So let's create a new TalkViewCell class inside /app/cells/talkviewcell.rb :

 class TalkViewCell < UITableViewCell end 

The next step is to create IB Outlets that will allow us to connect our code with Storyboard labels and pictures, in which gem ib will help us:

 class TalkViewCell < UITableViewCell extend IB outlet :talk, UILabel outlet :speaker, UILabel outlet :day, UILabel outlet :picture, UIImageView end 

Once our outlets are in place, they need to be connected at Storyboard. If you have Xcode open, close it and run the following command (provided by ib):

 $ rake ib:open 

This will open a fake ib Xcode project, which you can immediately close. Open Storyboard in Xcode and select TalkViewCell, then click on the connections inspector. You should see all the outlets we announce, connected to each other.

Drag the outlet onto the canvas by pulling the circle to the right of its name and save the Storyboard.



Open the TalkViewCell class again and add the following method that will be used to set the contents of the cell:

 def setupTalk(talk) self.talk.text = talk.title self.speaker.text = talk.presenter.name self.picture.image = UIImage.imageNamed(talk.presenter.picture) self.day.text = "Day #{talk.day}, #{talk.time}" end 

Now go back to TalksViewController, and replace the comment "# We will come back to this a bit later" at the bottom of the tableView(tv, cellForRowAtIndexPath:indexPath) with the following code:

 cell.setupTalk(talk) cell 

Run the simulator:

 rake 

It should turn out as in the screenshot.



When you click on Day Two, you will receive a list of all performances for that day:



Finally


You are reading these lines, which means that you have managed to build an iPhone application. It uses:


If you want to play with the code from this post, then welcome to the repository .

RubyMotion makes iOS development a pleasure.

Additional Information




From translator


If you started to go through the tutorial and found any inaccuracies or errors, I will be grateful if you write me about this in PM.

RubyMotion is really gaining momentum, although many people with whom I spoke are skeptical about it. Yes, it is expensive, but damp. But if you use Ruby in your daily development, you should understand the speed with which projects are usually developed thanks to the community.

I bought myself a license, and in the near future I am going to sort out the question somewhat deeper. From myself I can add a number of interesting links:



Enjoy the development, make good applications and see you soon. Thanks for attention!

Source: https://habr.com/ru/post/166047/


All Articles