In this chapter, we are going to talk about main
iOS elelemnts available by default. We are already familiar with
UILabel
,
UIButton
,
UITextField
,
but there are of course much more of them.
As usual we need to add one new cell to the
HomeScreen
:
def set_table_data
@data = [
{
cells: [
{
title: "Collection Views",
}, {
title: "Map Views",
}, {
title: "Web View",
}, {
title: "UI Elements",
}
]
}
]
end
# ... other HomeScreen code ...
def tableView(table, didSelectRowAtIndexPath: index)
if index.row == 0
open CollectionScreen.new
elsif index.row == 1
open MapScreen.new
elsif index.row == 2
url = 'http://google.com'.nsurl
open WebScreen.alloc.initWithURL url, entersReaderIfAvailable: false
elsif index.row == 3
open ElementScreen.new
end
end
Let's create an
ElementScreen
in the Tutorial app. For now, it will do nothing:
class ElementScreen < PM::Screen
title "Collection"
def load_view
@layout = ElementScreenLayout.new
self.view = @layout.view
end
def on_load
self.view.backgroundColor = 0xffffff.uicolor
end
end
The layout will be very simple.
We will show every element centered on X axis, each
element below the other. And Toolbar at the bottom of the screen:
class ElementScreenLayout < MotionKit::Layout
def layout
add UILabel, :label
add UIButton, :button
add UITextField, :textfield
add UISlider, :slider
add UISwitch, :switch
add UISegmentedControl, :tabs
add UIImageView, :image
add UIToolbar, :toolbar
end
def label_style
text "Label!"
constraints do
top.equals(:superview, :top).plus(80)
center_x.equals(:superview, :center_x)
end
end
def button_style
title "Button"
title_color :blue.uicolor
constraints do
top.equals(:label, :bottom).plus(20)
center_x.equals(:superview, :center_x)
end
end
def textfield_style
placeholder "Some text..."
background_color 0xfafafa.uicolor
constraints do
top.equals(:button, :bottom).plus(20)
center_x.equals(:superview, :center_x)
width(200)
end
end
def slider_style
minimum_value 0
maximum_value 100
value 50
constraints do
top.equals(:textfield, :bottom).plus(20)
center_x.equals(:superview, :center_x)
width(200)
end
end
def switch_style
constraints do
top.equals(:slider, :bottom).plus(20)
center_x.equals(:superview, :center_x)
end
end
def tabs_style
constraints do
top.equals(:switch, :bottom).plus(20)
center_x.equals(:superview, :center_x)
end
end
def image_style
constraints do
top.equals(:tabs, :bottom).plus(20)
center_x.equals(:superview, :center_x)
width(200)
height(100)
end
end
def toolbar_style
constraints do
bottom.equals(:superview, :bottom)
left.equals(:superview, :left)
right.equals(:superview, :right)
height(44)
end
end
end
If you launch the app, it will look like this:
UISegmentedControl
and
UIImageView
. We will talk about them later.
Labels are usually used as a static text. However, you can always
change its text anytime with a
label.text = "Some New Text"
. In this example, our
label will show the state of all other items. To read more
about UILabel, you can visit
Apple Docs.
We have added some element with
UIImageView
class to our layout, but we don't see it. This
is because we did not specify what image should be shown inside of
Image View. To do it, we need to pass
UIImage
to
UIImageView
's
image
method. With plain Objective-C-like syntax it would take some lines of
code:
image = UIImage.imageWithContentsOfFile "resources/images/image.png"
image_view.image = image
sugracube
allows us to create image easier: you just need
to call method
uiimage
on a string representation of an image path:
image_view.image = "resources/images/image.png".uiimage
Let's add it to our layout now. First, add some image to your
/resources/images
directory. I've added a picture of a chipmunk in three sizes: 1x, 2x, 3x:
/resources/images/chipmunk.png
,
/resources/images/chipmunk@2x.png
,
/resources/images/chipmunk@3x.png
.
Then in your
ElementScreenLayout
add
image
method with an image you want:
image "images/chipmunk.png".uiimage
No need to specify three different file names - iOS will figure out which
image to select for each device by itself. Now your app should look
like this:
content_mode UIViewContentModeScaleAspectFit
This string tells
UIImageView
that we want an image to fit inside the view with correct aspect ratio.
Now the app will work well:
UIImageView
can do, please visit
Apple Docs.
Buttons are more fun. You can also set its title, color and background color in layout or in a screen:
# set button in Layout:
title "Button"
title_color :blue.uicolor
background_color :white.uicolor
# or outside of layout:
button.setTitle "New", forState: UIControlStateNormal
button.setTitleColor :red.uicolor, forState: UIControlStateNormal
button.setBackgroundColor :black.uicolor, forState: UIControlStateNormal
Main button's purpose is to do some action when the user presses it.
With plain Objective-C-like syntax you would use next code:
def on_load
# other on_load code
button.addTarget self, action: 'some_method:' forControlEvents: UIControlEventTouchUpInside
# other on_load code
end
# and then you would need to add an action method:
def some_method(button)
# some action
end
With
sugarcube
you code would look like this:
button.on_tap do
# some action
end
However if you will try to launch the app now with
sugarcube
-way, the app will crash. This is because the
on_tap
method is not included in the
sugarcube-classic
that we required in Gemfile.
If you will visit
sugarcube's docs,
and will look for the
on_tap
method, you may find that it is included in
sugarcube-gestures
.
Let's add it to Tutorial and Superapp Gemfiles:
gem 'sugarcube', :require => [ 'sugarcube-classic', 'sugarcube-gestures' ]
Now the app will work. Let's make the button show a number of taps on it: by
default button will have a title "Tap me!". With each tap, we will change
its title to "Tapped X times!".
To do it, we will need an instance variable that will store a number of taps. Each time user taps on a button, we will increase this number and change button's title. Let's change initial button's title in layout:
class ElementScreenLayout < MotionKit::Layout
# layout code ...
def button_style
title "Tap Me!"
title_color :blue.uicolor
# other button styles
end
# other layout code ...
end
class ElementScreen < PM::Screen
# screen code ...
def on_load
# on_load code ...
@taps = 0
button = @layout.get(:button)
button.on_tap do
@taps += 1
button.setTitle "Tapped #{@taps} times", forState: UIControlStateNormal
end
end
end
If you launch the app and will tap on the button couple of times,
you will see something like this:
UIButton's
class reference can be found at
Apple Developer Website.
Text Field may be familiar to you; we did work with it in Maps Chapter.
By default Text Field is not that interesting - you can use
text
method to get or set text field's text, you can set placeholder on it
with a
placeholder
,
etc. To see more info on
UITextField
you can visit
Apple Docs
To customize text field behavior you can use its delegate methods. You can assign custom actions each time user presses Return Button, when text field's text changes, or when the user starts/stops editing.
Main parameters of
UISlider
are its
value
,
minimumValue
,
and
maximumValue
.
If you want to customize its appearance, you can use
its tint color parameters: for example
thumbTintColor
changes thumb's color, and
maximumTrackTintColor
will change the color of a slider on a right side of the thumb.
You can also fire some method each time slider's value changes. Let's
use slider to change opacity of our
UIImageView
with chipmunk:
@layout.get(:slider).addTarget self, action: 'slider_moved:', forControlEvents: UIControlEventValueChanged
# and later:
def slider_moved(slider)
# get new slider's value:
new_value = slider.value
@layout.get(:image).alpha = new_value * 0.01
end
Since all
UIView
's
have
alpha
property, we can use it on
UIImageView
which is a subclass of a
UIView
.
Alpha can be between 0 and 1, where 0 means that view is completely
transparent.
We get slider values from 1 to 100 and to translate it
to acceptable alpha values, we have to multiply it by 0.01 first.
Try to launch the app, and move the slider:
You can find lots of
UISwitch
in iOS Settings app. This element has only two states: on and off. To
check
UISwitch
current state, use
on
method, which will return a
Boolean
value -
true
or
false
.
Just like
UISlider
,
you can change
UISwitch
appearance easily, and to add a method that will be fired each time
user changes switch state.
@layout.get(:switch).addTarget self, selector: 'switch_flipped:', forControlEvents: UIControlEventValueChanged
# somewhere later:
def switch_flipped(switch)
# get new switch value:
new_value = switch.value
# your code ...
end
UISwitch
Class Reference can be found
here
UISegmentedControl usually looks like
a bar with multiple buttons, where only one button can be selected.
It is currently invisible because we have to set
it buttons first. We could do
it when we were initialising this element, but initialization has been done
for us by
motion-kit
.
Since it does not know which buttons we want to add, we have to do
it ourself in the
ElementScreen
.
I suggest doing it in
on_load
method:
def on_load
self.view.backgroundColor = 0xffffff.uicolor
tabs = @layout.get(:tabs)
["Red", "Green", "Blue"].each_with_index do |button_title, index|
tabs.insertSegmentWithTitle button_title, atIndex: index, animated: false
end
end
We iterated over the array of button names, and each time we were adding
new segment (button) to the
UISegmentedControl
with the
insertSegmentWithTitle title, atIndex: index, animated: animated
method. We passed it button title, the index where the button should be added,
and whether it should be added with animation or not. Since we don't
want a user to see how we create an element, we want it to be created
without any animation.
Now your app should look like this:
def on_load
# other on_load code
tabs.addTarget self, action: 'tab_changed:', forControlEvents: UIControlEventValueChanged
# other on_load code
end
# and then you would need to add an action method:
def tab_changed(tabs_view)
# use next to get index of a selected button:
selected_tab_index = tabs_view.selectedSegmentIndex
if selected_tab_index == 0
tabs_view.tintColor = :red.uicolor
elsif selected_tab_index == 1
tabs_view.tintColor = :green.uicolor
elsif selected_tab_index == 2
tabs_view.tintColor = :blue.uicolor
end
end
You can check some more info on
UISegmentedControl
at
Apple Docs.
UIToolbar
is a good place for some additional group of buttons. You can
add buttons to
UIToolbar
just like you would do it for a Navigation Controller:
create
UIBarButtons
first, and then add it to the toolbar. As always, Objective-C style
will take more lines to create a button and assign some action to it:
button = UIBarButtonItem.alloc.initWithTitle "Button",
style: UIBarButtonItemStylePlain,
target: self,
action: "button_tapped:"
We don't have to write all this code with
sugarcube
.
To create three buttons in a toolbar, one in the center, two on each side
of the toolbar, we would need next code:
def on_load
# on_load code ...
toolbar = @layout.get(:toolbar)
left = UIBarButtonItem.titled('Left') { left_tapped }
right = UIBarButtonItem.titled('Right') { right_tapped }
center = UIBarButtonItem.titled('Center') { center_tapped }
space = UIBarButtonItem.flexiblespace
toolbar.setItems [left, space, center, space, right], animated: false
# rest of on_load code ...
end
# and action methods:
def left_tapped
NSLog "Left Button tapped"
end
def center_tapped
NSLog "Center Button tapped"
end
def right_tapped
NSLog "Right Button tapped"
end
Each time you press a button, it will show a message in your console:
In iOS9, all possible alert views (UIActionSheet, UIAlertView) were
replaced by
UIAlertController
.
It can be used for all kinds of dialogs, alerts, and notifications.
To call it in a
sugarcube
way, you need to use next code:
UIAlertController.alert(self, 'Hey!',
message: "Here is the message.",
buttons: ['Cancel', 'OK!', '???']) do |button|
# button is the title of a tapped button
end
Let's use our "Center" button from
UIToolbar
to show alert controller. We just need to replace
center_tapped
method code with
UIAlertController
's
code:
class ElementScreen < PM::Screen
# screen code ...
def center_tapped
UIAlertController.alert(self, 'Hey!',
message: "Here is the message.",
buttons: ['Cancel', 'OK!', '???']) do |button|
NSLog "Tapped #{button} button"
end
end
# rest of screen code ...
end
I think it is a good idea to make a commit now:
git add .
git commit -m "Elements Screen added"
In this chapter, we have reviewed main UI elements. Of course, there are more of them, but this should be enough for beginning, and to understand how views work in iOS.
All views here are subclasses of
UIView
. Therefore, they all have a lot in common: you
can change an alpha channel on all of them, you can access their
layer
to change drawing options, etc.
Try to play with these elements, or try to add new ones. For example
change tint colors on some of them, add delegate methods for a text field,
and try to replace text in
UIBarButtonItem
with an image. When you feel you are ready, let's move on
to the next chapter.