We can see that we have a base view that can have subviews to present all the visual components to the user. To arrange those views according to the visual design we require, we need to position and size each view.
Positioning & Sizing
The position of each subview is relative to its superview. To manage positioning and sizing we need to learn a few elements:
Size is a composition of 2 components: length and width.
Point represents coordinates: x and y.
Frame is a composition of the Origin and Size. Origin is a point that refers to the left top corner of a subview relative to the left top corner of its superview.
Bounds is also a composition the Origin and Size. Origin of bounds is relative to the frame of the same view.
To describe those in code we need to know the following data types:
CGSize describes a size.
CGPoint describes a point - Origin.
CGRect describes a rectangle - Frame and Bounds.
Those new types are structures.
What are structures?
Frame and bounds are properties of a view:
Origin and size are properties of both: frame and bounds:
Origin is a point, and a point has 2 properties to represent a position: x, y. And width and height are properties of size:
The end properties - x, y, width and height are of CGFloat that data type that is very correlated with Float and Double.
Variables for size and position
Frame, Bounds, Size and Origin are of structure data type and to create those we use the following syntax:
CGPoint(x: CGFloat, y: CGFloat)CGPoint(x: Double, y: Double)CGPoint(x: Int, y: Int)CGSize(width: CGFloat, height: CGFloat)CGSize(width: Double, height: Double)CGSize(width: Int, height: Int)CGRect(origin: CGPoint, size: CGSize)CGRect(x: CGFloat, y: CGFloat, width: CGFloat, height: CGFloat)CGRect(x: Double, y: Double, width: Double, height: Double)CGRect(x: Int, y: Int, width: Int, height: Int)
Other types in the example above are already familiar to you: Double and Int.
Let's look at some of the variable examples for the new structures:
// declare variableslet origin = CGPoint(x: 20.0, y: 10.0)let size = CGSize(width: 50, height: 50)let frame = CGRect(origin: origin, size: size)let anotherFrame = CGRect(x: 20.0, y: 10.0, width: 50.0, height: 50.0)// access propertiesprint(origin.x) // 20.0print(size.width) // 50.0print(frame.origin.y) // 10.0print(anotherFrame.size.height) // 50.0
Notice that we have multiple initializers for these structures. They allow for the use of different datatypes, specifically: CGFloat, Double and Int.
Regardless of the initializer we choose, the resulting properties (x, y, width and height) are always of CGFloat type, they are converted during the assigning process:
let double = 10.0let x = CGFloat(double)
Let's illustrate all these properties and their relativity:
View A in the above illustration is the containing view for a subview B. Frame origin of view B is relative to view A. And bounds origin of view B is relative to itself (view B).
Let's review our example of views hierarchy from previous chapter:
And now let's shift some of the views around:
This illustration presents 2 new cases:
Views extended beyond bounds layout
Beyond bounds layout is when either a view has negative origin relative to its containing view, or a view is larger than its superview:
Parts of a view that extend beyond the bounds of a containing view may or may not be visible. It depends on whether the containing view has a restriction to not display subviews that lay beyond its own bounds. It's done using the property
view.clipsToBounds = true/false
Overlapping is when one subview is positioned within an area taken by another subview of the same superview:
In case of overlapping, a part of a view that is covered by another view may or may not be visible depending on the level of transparency of a covering view.
View A-2 is not transparent and it's making part of the A-1 view invisible. On the other hand, view A-4 is semi-transparent so that makes view A-3 still visible on the screen.
We can nest multiple views with different layouts, including the full screen ones that would appear as completely different screens. That may become hard to manage within the one ViewController. Here's how it may look:
To address this we can create multiple base views, also called screens. Those views will then contain their own nested subviews in multiple layers. We will present different screens to the user depending on the user flow of an application. Those screens are created in the Storyboard by dragging new VewContoller objects from the Object Library:
Like with multiple trees in a garden, we can visit one or another as we please.
Views' layout is described using frame and bounds properties.
Both frame and bounds are represented by a composition of origins and size.
Frame origin is relative to its superview and bounds origin is relative to its frame.
The origin property is described with 2 properties that represent coordinates: x and y.
The size property is described with 2 properties that represent size: width and height.
Frame and Bounds are represented by a structure CGRect.
Origin is represented by a structure CGPoint.
Size is represented by a structure CGSize.
Structures are named types, they are value types similar to classes but do not support inheritance.
Multiple view controllers are used to generate multiple screens in the app to implement a required UI flow.