UI elements

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:
First is a usual label, and then the button, text field, slider, switch, and empty toolbar in the bottom. You may notice that we missed some elements - UISegmentedControl and UIImageView. We will talk about them later.

Label

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.

UIImageView

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:
Does not look good, isn't it? To make it look better, we need to specify content_mode for our image view:
        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:
Image Views are usually used to show an image, so there is not much we can talk about. If you want to see what UIImageView can do, please visit Apple Docs.

Buttons

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.

UITextField

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.

UISlider

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:
As usual, UISlider Class Reference on Apple Docs

UISwitch

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

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:
Let's change segmented control's color each time user presses on its button:
        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

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:
UIToolbar Class Reference can be found on Apple Docs

UIAlertController

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"
      

Summary

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.

Book Index | Next