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
# ...
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.