We’ve just launched Take5 Feedback and its mobile app. This is the fourth mobile app that The Frontier Group has launched, that I’ve worked on.

Previously, we had been using a combination of PhoneGap and SpineJS. This helped us get into the iOS market while still using tools we were familiar with as web developers. After releasing a few apps using PhoneGap and SpineJS, we found that we were consistently spending time during app development fixing things like scrolling, tap events propagating to other views and transition animations. This coupled with the less-than-fantastic debugging tools made us feel that perhaps we needed to re-evaluate our technology choices.

This time we decided to kick it up a notch by moving to RubyMotion. (Bam!).

For those who haven’t heard about RubyMotion, it’s a library that lets you write native iOS apps in Ruby which is then compiled down into Objective-C. As most of us at The Frontier Group are Ruby developers, this felt like a technology well worth exploring. After working with it for a few months, I think it’s the way to go for us moving forward. Now, let’s talk a little about RubyMotion.

Off the bat, one of the big advantages of RubyMotion is that you’re writing native apps in Ruby and not in Objective-C. I’ve found that Objective-C is not an intuitive language and certainly nowhere near as readable as Ruby (especially when method chaining looks like array declaration shudders). One thing RubyMotion has ported over from Objective-C that I do like is the named parameter which makes method declaration/invocation much more readable. For example, let’s say I have a method that generates a receipt and takes a the date of the purchase and the amount in cents as parameters.

In regular Ruby it would be declared something like this:

def generate_receipt date, amount
  # Do something...
end

Now this method could conceivably be called like this:

square = generate_receipt('3/3/2013', 100)

In RubyMotion, however, using named parameters the same declaration/invocation looks like this:

def generateReceiptForDate date, andAmount: amount
  # Do something...
end
square = generateReceiptForDate('3/3/2013', andAmount: amount)

As you can see, it gives the parameters passed in more meaning during the method invocation. Now most Rubyists won’t call methods with “magic numbers/strings” and you can achieve the same thing in < Ruby 2.0 by accepting a hash but I think this is a nice little addition.

Next I’d like to discuss generating and organizing views. MVC in iOS is a little different from MVC in Ruby on Rails, in that views are a little more tightly coupled to the controller. View elements are ultimately defined and positioned in the controller. Those elements will rely on methods which should be defined in the controller as they deal with event handling and navigation. Let’s take a simple example. I want a screen to have a table and when I tap a row I want to navigate to another screen.

class ExampleController < UIViewController

  REUSE_IDENTIFIER = 'example'

  TABLE_FRAME = CGRectMake(0,0,300,400)
  TABLE_ROW_HEIGHT = 30

  def viewDidLoad
    @data = [1,2,3,4]
    table = UITableView.alloc.initWithFrame(TABLE_FRAME)
    table.delegate = self
    table.dataSource = self
    table.rowHeight = TABLE_ROW_HEIGHT
    self.addSubview table
  end

  def tableView(tableView, cellForRowAtIndexPath: indexPath)
    cell = tableView.dequeueReusableCellWithIdentifier(@reuseIdentifier)
    cell ||= UITableCell.alloc.initWithStyle(UITableViewCellStyleDefault,
    reuseIdentifier: REUSE_IDENTIFIER)
    cell.textLabel.text = @data[indexPath.row]
    cell
  end

  def tableView(tableView, didSelectRowAtIndexPath:indexPath)
    tableView.deselectRowAtIndexPath(indexPath, animated: true)
    self.navigationController.pushViewController(AnotherController.new, animated: true)
  end

  def tableView(tableView, numberOfRowsInSection: section)
    @data.count
  end
end

As you can see we’ve got a mix of view level logic (positioning the table on the screen and defining what a row will look like) and controller level logic (setting the data source and the navigational behavior). This makes views and controllers coupled a little tighter than you might be used to. This can lead to extremely fat controllers when you have many elements on the screen each with its own position, look, feel and event handlers. A pattern I found that has worked for me is to have a subclass of a UIView that is responsible for drawing all the elements on a given screen. Let look at an example.

Let’s start by creating our own UIView where we define our table and its look and feel.

class ExampleView < UIView

  TABLE_FRAME = CGRectMake(0,0,300,400)
  TABLE_ROW_HEIGHT = 30

  attr_accessible :table

  def initWithFrame frame
    super
    setupTable
    self
  end

  private

  def setupTable
    @table = UITableView.alloc.initWithFrame(TABLE_FRAME)
    @table.rowHeight = TABLE_ROW_HEIGHT
    self.addSubview @table
  end

end

class ExampleController < UIViewController

  REUSE_IDENTIFIER = 'example'

  def viewDidLoad
    @data = [1,2,3,4]
    self.view = ExampleView.alloc.initWithFrame(view.frame)
    self.view.table.delegate = self
    self.view.table.dataSource = self
  end

  def tableView(tableView, cellForRowAtIndexPath: indexPath)
    cell = tableView.dequeueReusableCellWithIdentifier(@reuseIdentifier)
    cell ||= UITableCell.alloc.initWithStyle(UITableViewCellStyleDefault,
    reuseIdentifier: REUSE_IDENTIFIER)
    cell.textLabel.text = @data[indexPath.row]
    cell
  end

  def tableView(tableView, didSelectRowAtIndexPath:indexPath)
    tableView.deselectRowAtIndexPath(indexPath, animated: true)
    self.navigationController.pushViewController(AnotherController.new, animated: true)
  end

  def tableView(tableView, numberOfRowsInSection: section)
    @data.count
  end

end

Ok so now the responsibility for drawing the table (and any other element) is localized to the ExampleView class and the data manipulation and navigation logic is in the controller which is looking better. We can neaten this up further by making the view responsible for assigning delegates.

Let’s start by creating a new parent view that stores an object which will be the delegate for all elements within the view. In this example the delegate is the ExampleController.

class UIViewWithDelegate < UIView

  attr_accessor :controllerDelegate

  def initWithFrame(frame, andDelegate: delegate)
    self.initWithFrame(frame)
    self.controllerDelegate = delegate
    self
  end

end

Now that we have this class our ExampleView class can inherit from it, allowing the ExampleView to set the delegates for any elements inside it.

class ExampleView < UIViewWithDelegate

  TABLE_FRAME = CGRectMake(0,0,300,400)
  TABLE_ROW_HEIGHT = 30

  attr_accessible :table

  def initWithFrame(frame, andDelegate: delegate)
    super
    setupTable
    self
  end

  private

  def setupTable
    table = UITableView.alloc.initWithFrame(TABLE_FRAME)
    table.delegate = self.controllerDelegate
    table.dataSource = self.controllerDelegate
    self.addSubview table
  end

end

class ExampleController < UIViewController

  REUSE_IDENTIFIER = 'example'

  def viewDidLoad
    @data = [1,2,3,4]
    self.view = ExampleView.alloc.initWithFrame(view.frame)
  end

  def tableView(tableView, cellForRowAtIndexPath: indexPath)
    cell = tableView.dequeueReusableCellWithIdentifier(@reuseIdentifier)
    cell ||= UITableCell.alloc.initWithStyle(UITableViewCellStyleDefault,
    reuseIdentifier: REUSE_IDENTIFIER)
    cell.textLabel.text = @data[indexPath.row]
    cell
  end

  def tableView(tableView, didSelectRowAtIndexPath:indexPath)
    tableView.deselectRowAtIndexPath(indexPath, animated: true)
    self.navigationController.pushViewController(AnotherController.new, animated: true)
  end

  def tableView(tableView, numberOfRowsInSection: section)
    @data.count
  end

end

This has separated the view and controller logic out a little more and our controller is a little thinner. This becomes more useful as the number of elements on your screen increases. The one drawback I found was that my views were ending up with walls of constants defining positions, fonts, etc. Creating subclasses for individual elements that include the position/look/feel, would solve the problem, however you’ll end up with a lot of singleton classes.

Now a little on automated testing. Automated testing is generally a big part of any of the applications we develop. This time however, I found the experience to be so much more costly than testing in a Rails world. When I started developing the app I was aiming for the level of automated coverage we have in our Rails environments. As I attempted to achieve this, I kept hitting road blocks with either the testing framework not behaving as expected, or the mocking libraries placing a significant impact on the code being tested. After some discussion, we decided that due to the simplicity of this app we could achieve an acceptable level of quality and coverage by manual user testing. From this experience though we now know areas that can be improved and are currently developing our skills and tools to ensure that as our apps increase in complexity, we are able to ensure our code quality.

I could write so much more about RubyMotion and the different technical challenges I faced during the development process, but I really just wanted to give my overall impressions. I think it’s a good choice for any Rubyist wanting to venture in iOS development. In combination with the Apple developer docs, which are fantastic, it lets you learn the iOS framework while programming in a language that you’re comfortable with. It’s a well maintained library with the RubyMotion team making constant improvements. My only real criticisms are with the testing and debugging tools not feeling as flexible and as powerful as I’m used to in the Rails environment, but like I’ve said, these tools are being continually developed and improved, and have no doubt these tools will get better with time.