Views and Layouts

Now we are going to learn how to add actual UI elements to our screens. Almost all UI elements are based on plain view class - UIView, which is just a single rectangular view.

In plain Objective-C, we would create all UI right in UIViewControllers. It may be a good idea for simple screens, but if you have at least three views in it screen's code will increase dramatically, and will be harder to maintain in the future.

With Rubymotion, we can use Layouts. You can think of them as a list of UI elements that will be presented in a particular screen, with their positioning and styling options. By using layouts we separate views from controllers, and it helps us to make UI faster, with better code, and without a mess.

Here at Mova, we use motion-kit to build layouts in all of our projects, and we are going to learn it in this book as well. We have chosen it because it has cleaner syntax than, and it supports Constraints (we will talk about them later). There are also other similar gems that help build layouts such as Teacup or RMQ. Unfortunately, the first one is not supported anymore, because author started to work on motion-kit. RMQ is a really good library that can do almost anything. However, they do not support constraints yet.

motion-kit's main purpose is to create layouts. The developer specifies which UI elements screen will use, tell it where it should be placed, and how they should look like.

Another gem that we will use is a Sugarcube. This is some kind of a helper gem that helps us to write less code. Remember how we told the app to make some view yellow or red with UIColor.redColor? Sugarcube will allow us to use code like :red.uicolor or even 0xffffff.uicolor if we want to set the color with HEX code. With Sugarcube you can even replace some extremely long Objective-C commands like to this:

        UIApplication.sharedApplication.openURL(NSURL.URLWithString(url))
      
with this:
        url.nsurl.open
      
Cool, right?

So, let's add these two gem to our Gemfile:

        gem 'sugarcube', :require => 'sugarcube-classic'
        gem 'motion-kit'
      
You can see new :require symbol here. Sugarcube is a huge library, and we may not need all of it, so, for now, we are going to include only one its module. If you need mode, you can specify any additional modules as an array.

Now we need to do bundle install to install these two new dependencies, and we are ready to add some views to our app!

Do you remember what our app should do? It will be a small app that tracks user's mood daily. Several times per day app will show a modal screen, and will ask him how does he feel. Let's create this Mood Selector Screen: it will have one label, that asks user "How do you feel?", and 4 buttons: "Awesome", "Good", "Average", "Depressed".

First, we will need a new screen. Let's call it MoodSelectorScreen:

        class MoodSelectorScreen < PM::Screen
        end
      
And let's use MoodSelectorScreen modally instead of a red HomeScreen: in App Delegate:
        modal_screen = MoodSelectorScreen.new nav_bar: true,
          presentation_style: UIModalPresentationFormSheet
        screen.open_modal modal_screen
      

Now we can start building layout. Let's create a /app/layouts/mood_selector_layout.rb file, and start MoodSelectorLayoutCode with:

        class MoodSelectorLayout < MotionKit::Layout
        end
      
As you can find out in motion-kit docs, all layout classes are subclasses of MotionKit::Layout.

To add some UI, you will need to specify the class of an element and its name inside of a layout method. In our case, we need one label and four buttons:

        class MoodSelectorLayout < MotionKit::Layout
          def layout
            add UILabel, :select_mood_label
            add UIButton, :awesome
            add UIButton, :good
            add UIButton, :average
            add UIButton, :depressed
          end
        end
      
Element names can be set to whatever you want. However it is a good idea to keep those names helpful - it should remind of element's purpose. Here layout method creates a view hierarchy for us, and then motion-kit iterates over all added elements and tries to call style methods for each element. You can create those methods by adding _style to the end of element's name. For example:
        class MyScreenLayout < MotionKit::Layout
          def layout
            # create element with name :label
            add UILabel, :label
          end

          # define styles for element with name :label
          def label_style
            backgroundColor :white.color
          end
        end
      

Now we connect MoodSelectorScreen to MoodSelectorLayout. It should be done in Screen's load_view delegate method, which is called when the screen is going to load its main view. If we need to access screen's main view, we can do it later in on_load method. For example, we could change screen's color to white with a sugarcube's uicolor method:

        class MoodSelectorScreen < PM::Screen
          def load_view
            @layout = MoodSelectorLayout.new
            self.view = @layout.view
          end

          def on_load
            self.view.backgroundColor = 0xffffff.uicolor
          end
        end
      

Theoretically, elements will be on a screen, but you won't see them yet because size is not set at the moment. Now we just need to customize all our UI elements. We will start with a label. First we would need to set its text to "How do you feel?", and then specify where the label will be placed. In this example, it will be centered by the X coordinate, and label's top will be 80 points from the top of the screen.

        def select_mood_label_style
          text "How do you feel?"
          constraints do
            center_x.equals(:superview, :center_x)
            top.equals(:superview, :top).plus(80)
          end
        end
      

As you can see, element's style is defined in a method named ELELEMENT_NAME_style. In our case, styles of element named select_mood_label will be defined in a select_mood_label_style method. To know how label or any other view can be customized, you can check their official docs. For example UILabel or UIButton. You can see that UILabel has a text property, font, textColor, etc. To specify it developer needs to write property name and pass it some value. motion-kit also has one small interesting feature that helps us to keep our code more Ruby-like: properties like textColor (camelCase) can be converted to text_color (snake_case).

Constraints are something that we will talk about later. Those are made to help us place objects on a screen - you can specify where it's top, left, or any other border should be. You can specify whether the element should be centered, or not, whether it should compress with its container view, etc.

Let's now run the app. You can finally see our first label on the top of the screen:

Now we need to add some styles to those four buttons. As you can see on Apple's UIButton Docs, UIButtons have title instead of text. To set text color, we would have to use setTitleColor:forState: instead of usual fontColor. motion-kit can help us here too - we can use title_color instead of that long and scary setTitleColor:forState:. We are going to place buttons one below the other, and they should have light gray background color with black titles:

        class MoodSelectorLayout < MotionKit::Layout
          def layout
            add UILabel, :select_mood_label
            add UIButton, :awesome
            add UIButton, :good
            add UIButton, :average
            add UIButton, :bad
          end

          def select_mood_label_style
            text "How do you feel?"
            constraints do
              center_x.equals(:superview, :center_x)
              top.equals(:superview, :top).plus(80)
            end
          end

          def awesome_style
            title "Awesome"
            title_color 0x000000.uicolor
            background_color 0xfafafa.uicolor
            constraints do
              center_x.equals(:superview, :center_x)
              top.equals(:select_mood_label, :bottom).plus(50)
            end
          end

          def good_style
            title "Good"
            title_color 0x000000.uicolor
            background_color 0xfafafa.uicolor
            constraints do
              center_x.equals(:superview, :center_x)
              top.equals(:awesome, :bottom).plus(50)
            end
          end

          def average_style
            title "Average"
            title_color 0x000000.uicolor
            background_color 0xfafafa.uicolor
            constraints do
              center_x.equals(:superview, :center_x)
              top.equals(:good, :bottom).plus(50)
            end
          end

          def bad_style
            title "Bad"
            background_color 0xfafafa.uicolor
            title_color 0x000000.uicolor
            constraints do
              center_x.equals(:superview, :center_x)
              top.equals(:average, :bottom).plus(50)
            end
          end
        end
      

Now when you launch the app, you should see something like:

Not the prettiest app, but we will fix this later. Now, let's check our layout code - there are lots of duplicated strings because we have same styles for all buttons. Let's make a unified button style, and then use it with everywhere. It can be done by creating a random method that will have all shared properties, and then it can be called inside all elements with same styles:

        def shared_button_style
          title_color 0x000000.uicolor
          background_color 0xfafafa.uicolor
        end

        def awesome_style
          shared_button_style

          title "Awesome"
          constraints do
            center_x.equals(:superview, :center_x)
            top.equals(:select_mood_label, :bottom).plus(50)
          end
        end

        # ...
      

Summary

In Rubymotion, we can use Layouts to add UI to the screens. Layouts are simple lists of all elements with their styles. Elements hierarchy can be defined in layout method, and their styles can be defined in ELEMENT_NAME_style methods below. To figure out which properties can be configured on one particular element, you should browse its official documentation, e.g.: UILabel or UIButton.

Book Index | Next