Frames and Layout Constraints

Now we are going to talk about different ways to set element's size and its position on the screen. But before we start we need to add a table view to use it as an example in this chapter.

Since our app will save user's mood for each period of time, we will need to show it somewhere. It can be a chart, a calendar or a table view that will show user's mood in the past. For now, we will start with a table view.

Let's create a new screen, MoodHistoryScreen. It will have only a fullscreen table view inside. Later it will show data taken from a real database, but for now, we are going to use some fake pre-set data. We will also use this screen as our first screen, instead of a HomeScreen. Let's get started with /app/layouts/mood_history_layout.rb. This layout will have only one table view, but it's size and position will be set with frames:

        class MoodHistoryLayout < MotionKit::Layout
          def layout
            add UITableView, :table
          end

          def table_style
            frame x: 0, y: 0, w: '100%', h: '100%'
          end
        end
      
frame method here sets origin and size of an element. Here we say that want the table to start from the top left corner, and that element should be full-screen (width and height take 100% of its container).

Now let's create the screen in a /app/screens/mood_history_screen.rb file that will use MoodHistoryLayout:

        class MoodHistoryScreen < PM::Screen
          title "Mood History"

          def load_view
            @layout = MoodHistoryLayout.new
            self.view = @layout.view
          end

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

To show this MoodHistoryScreen we need to update App Delegate. We are going to remove modal screen for now, and will replace HomeScreen with MoodHistoryScreen:

        class AppDelegate < PM::Delegate
          def on_load(app, options={})
            screen = MoodHistoryScreen.new
            open screen
          end
        end
      

Let's launch the app now. The result will be an empty full-screen table. You can try to rotate the Simulator with CMD + Left or CMD + Right

Stop! When Simulator rotated to a landscape mode, something went wrong. The table takes only half of the screen, which is not what user expects. So what is the problem?

This is a good time to talk about frames and constraints. Frames set the origin point and the size of a table. These values do not update automatically. We wrote that we want our table to be as wide as device's screen, and iOS will always keep this width. We can only update it manually, and in this case, we would need to find a delegate method that would be called each time when a device is rotated, and then change the frame of the view.

Layout Constraints (or AutoLayout) are different. You tell your app: "I want the right border of the table to be always equal to the right side of the screen". And iOS will always keep it in mind. Has device rotated? Table's size will be updated. If you wanted some element to be always in the center of some other element, iOS will always keep it there, and will update position automatically, even when you will change the size of a container.

Layout Constraints were introduced only in iOS 6.0, so not all developers started to use it right away. Even at this moment, when iOS 9.1 is available, lots of apps still use frames instead of Constraints.

Layout Constraints are also one of the main reasons why we use motion-kit.

Let's rewrite our layout now, to use constraints:

        class MoodHistoryLayout < MotionKit::Layout
          def layout
            add UITableView, :table
          end

          def table_style
            constraints do
              width.equals(:superview)
              height.equals(:superview)
            end
          end
        end
      
Even Layout Constraints code is beautiful! By the way, you could also use next code:
        constraints do
          x 0
          y 0
          right.equals(:superview, :right)
          bottom.equals(:superview, :bottom)
        end
      
or this:
        constraints do
          right.equals(:superview, :right)
          left.equals(:superview, :left)
          bottom.equals(:superview, :bottom)
          top.equals(:superview, :top)
        end
      
If you try to rotate the phone now, you will see that everything works correctly, and Layout Constraints resize the table automatically:

Now when we have some screens of the app ready, it is a good idea to make a new snapshot and save it with git:

        git add .
        git commit -m "Table View added"
        
      

Summary

In this chapter we learned that view frames are static - they do not update its values according to outside changes. To make views resize accordingly, we need to use AutoLayout (or Layout Constraints). They are set of simple rules that view will always follow, even if a device is rotated or superview's size has changed.

You can read more on AutoLayout at Apple Docs.

Book Index | Next