Gaining Control of RubyMotion with UIViewControllers

Aug 2, 2014

In the previous tutorial, you learnt some of the basics about RubyMotion, like how to get something on screen, the basics of working with the REPL, and how RubyMotion method names are different from other Ruby implementations.

When we did this, we did everything in the AppDelegate class. If we continued to do this for an entire application, things would get messy very quickly. So in this tutorial I’ll introduce you to the basics of UIViewControllers, the C in MVC for iOS development with RubyMotion.

Core Frameworks

Before we start though, I want to quickly explain an important concept of iOS development, the frameworks we work with.

There is many frameworks that make up the Cocoa Touch frameworks, but two that you’ll be working with (and have already worked with) are Foundation, and UIKit.

The Foundation Framework

Foundation is well named for it’s purpose, because it’s the foundation of what we build everything on. It contains classes like NSString, NSDate, NSArray, etc. The core classes for working with any kind of data.

You’ve already been working with classes from this framework, because RubyMotion has a fantastic abstraction in it that gives us these classes as the Ruby versions, but with all the same functionality of Apple’s ones. They work interchangeably.

In RubyMotion, a Foundation NSString is a Ruby String, and a Ruby String is a Foundation NSString. Same for Arrays, Hashes (which relate to NSDictionary), and more.

Side note: The “NS” stands for NeXTSTEP, the predecessor of the operating system you’re working on right now.

This is great because it gives us not only our normal Ruby methods, but also all of the methods from the Foundation classes too.

The UIKit Framework

UIKit is a large collection of classes related to displaying things on the screen, such as UIView, UIWindow, UILabel, and UIViewController.

You’ll be working with classes from this framework almost constantly. We use it to display things, handle interaction and input, and much more.

Apple suggests constructing your applications using an MVC paradigm, though many other types of classes appear over time too. UIKit helps provide the classes we need to structure our applications properly. It gives us everything we need for the View and Controller parts of MVC.

Everything is Ruby

If you’re coming from a web background, you’ll quickly realise that there isn’t something like HTML or CSS in iOS development. Instead everything is Ruby!

I suggest you embrace it, it’s quite a powerful idea. Everything in our application is a stateful object, and our views instead of being a static thing which are rendered far away from our Ruby code on a computer we have no control over, they’re an object just like anything else in our Ruby code, that we react to and interact with in real time.

Cleaning up our code

With all of that out of the way, it’s time to clean up our code by introducing a controller into the mix.

You may have noticed in the REPL prompt when you start your application, it says something like:

Application windows are expected to have a root view controller at the end of application launch

This is iOS telling us that we’ve got some organising to do, to keep our code in the right place.

When an application finishes launching, it expects two things to be in place. First is that there should be a UIWindow set up and that it’s been made to be the key window. The second thing is that UIWindow should have a property set on it called rootViewController.

The root view controller of a window is the controller that will be used for this window at the start. This can change over the lifetime of the application, and we have ways of navigating between controllers, but for now, we’re just going to have one.

As the name of UIViewController implies, they are controllers of views. When the application is focusing on a specific view controller, it’s views will be added to the window.

Moving the code around

In your app_delegate.rb file, delete all the lines related to creating the UILabel and adding it to the window. Then we’re going to set the rootViewController property of our UIWindow.

We’re setting the rootViewController property to be a new instance of a MainController class that we’re going to create in a second.

When the application launches now (it will crash if you try to launch it right now because MainController doesn’t exist) it will use MainController to set up the view.

Creating our MainController

All controllers inherit from UIViewController, or a speciality subclass of it. So we’re going to create a new class that does that now. In your app directory, create a new file called main_controller.rb, and add the following to it.

The body of the viewDidLoad method is almost exactly the same as the code we had in our AppDelegate, except for one important difference, we’re adding the label to the view property of our controller, instead of to the window directly. This parent view is created for us when the controller is getting set up.

Like the plan was, our MainController inherits from UIViewController, which contains a large amount of functionality related to display things on screen and handling interaction.

The viewDidLoad method that we defined is one of the standard lifecycle methods that will be called by the application as things happen. There is many others we’ll cover over time such as viewDidAppear:animated: and loadView as well.

Getting interactive

Our application is pretty boring at the moment, it just sits there and says hello. Lets at least add some basic interaction.

Underneath where we create and set up our label, we’re going to create a button using the UIButton class from UIKit. This is a kind of UIControl that handles interaction.

If you run the application at the moment, this button doesn’t actually do anything, but there is already some code here for us to talk about, specifically the setting of the text on the button.

As an interactive view on the screen, a UIButton could be in multiple states, such as the normal/default state when it’s just doing it’s thing, or maybe the disabled state where all interaction is disabled.

By setting attributes like the title for the normal state, this becomes the default for all other states as well.

We’ve set a title for the disabled state as well though, so use hold down the Command key on your keyboard and select the button in your REPL, and then disable the button using this:

self.enabled=false

When you hit enter, you’ll see the text on the button change from “Press Me” to “Don’t Press Me” because we’ve gone from the normal state, to a disabled state.

Exit the app and REPL and lets continue.

Target/Action

When working with things like buttons in iOS, one of the ways we react to events is by using Target/Action. You can imagine this as when an event, such as being tapped happens, the button will shoot an arrow towards a target (usually our controller), and when that arrow hits, it will set off an action to occur (usually a method on our controller).

Lets set up our own Target/Action now on our button to toggle the text on our label.

If we read through this line “in english”, then we can read it as: add a target of self (our controller) that will have the button_pressed: (pay attention to the colon) method called on it, when the control (our button) has a finger lifted while it’s still on the button.

The reason we want this to happen on “touch up inside” instead of something like “tap”, is that it allows a user to cancel an action by dragging their finger off the button before they lift it, incase they tap it accidentally.

Now to explain that colon on the end of the action name, by looking at our new method button_pressed:.

The button_pressed: method takes an argument called sender, which is the button. The colon on the end of the method name says that the method we’re going to call has a parameter after this part of the method name.

If we only had button_pressed, without the colon, then that would be an entirely different method.

The code inside our button_pressed: method is fairly simple, we’re just checking if the current text on our label is “Hello World”, and if so setting it to be “Goodbye Friend”, otherwise we’re setting it to be “Hello World”, so when you run this now, you’ll get a toggling effect when you press the button.

Cleaning up our view code

It’s generally not good practice to include all of this view creation code in the controller. The controller should be in charge of controlling the view, and not creating all the bits of it.

So what we’re going to do is move all of our view set up code into our own UIView subclass. Before we do that though, we need to tell our controller how to load it by defining the loadView method. Add this to the top of your MainController class.

defloadViewself.view=MainView.newend

The loadView method gets called when things are getting ready to be displayed on the screen. Previously we were just using the default functionality provided by inheriting from UIViewController. Now we’re creating our root view for our controller, and assigning it ourselves.

Note that we’re using self.view = instead of just view = because in the second case we would be setting a local variable.

Now we have to delete all that view creation code from our viewDidLoad method except for the the line that sets up the target-action. We’re also going to be accessing our button through our view, instead of as a local variable.

That’s all the changes we need in our controller, so lets move on to creating our MainView class.

Creating the MainView

Start out by creating a new file in the app directory called main_view.rb. In here we’re going to create our MainView class which will inherit from another UIKit class called UIView, which is also the superclass for our UILabel, and in the ancestry chain of our UIButton.

Then we’ll move all of our old view code into here and make some small changes such as making the views accessible via attribute readers on our class.

The notable changes in our existing code that we moved over are that we’re using an instance variable for both our label and button now, so that our attr_readers at the top of this class work, and that we were about to remove calling addSubview on anything in particular, instead seeming it’s just a method on the view, we can just call it on it’s own in both places. Other than that, nothing really has changed.

We’ve got a new topic though for RubyMotion development, the two different kinds of initialisers that exist when we’re defining them. I’ve covered usage, but not definition.

Before I explain, I’ll point out that using new to create an instance of a class is just an shortcut for alloc.init when it comes to classes that inherit or are from the Cocoa Touch frameworks like Foundation or UIKit.

Two different kinds of initializers

When you’re creating a plain Ruby class that does not inherit from one of these Cocoa Touch framework classes, you can still define initialize like you would in normal Ruby implementations.

There is a big difference for classes that inherit from Cocoa Touch classes though (so basically that aren’t plain old ruby objects). In this case you can use initialize, instead alloc and then a specialised initializer needs to be called, even if that initializer is just init like we’re doing. Like I said too, new is just a shortcut for calling alloc.init.

The other difference with these Cocoa Touch initializers is that you should call super at the start, and return self at the end. This is just the way the Cocoa Touch classes work.

We’re clean!

With all of this done, we now have a basic working application that is well structured! We’ve also once again learnt an enormous amount within this tutorial. You’re well on your way now to being able to create real apps, with some of the core concepts in your bag of tricks.

Remember to keep an eye out for further tutorials, I’ll be posting as much as possible.

Last few things are that you can learn more and support me by subscribing the MotionInMotion weekly RubyMotion screencast, which is only $9AUD/month, or by purchasing my upcoming book, RubyMotion for Rails Developers, which will be covering these topics in more depth, and taking it much further than this. Part 1 is already out explaining a lot of the core concepts of iOS and OS X development in great depth.