--- title: View hierarchy and constraints tags: uikit --- # View hierarchy A UI in UIKit consists of a tree of `UIView`s, with a `UIRootView` at the root of the tree. The `UIRootView` manages all of the views, their state, their positioning and sizing, and any input to the UI. A simple `UIView` does not provide much functionality, other than providing grouping for other views. A `UIView`'s frame is defined by its top left corner (the `X1` and `Y1` properties) and bottom right corner (the `X2` and `Y2` properties). The values of `X2` and `Y2` are automatically calculated when setting the `Width` and `Height` properties, respectively. The system will not allow a view to have those properties set up in a way where either `Width` or `Height` would end up being negative. Additionally, a view exposes helper properties `TopLeft`, `TopRight`, `BottomLeft` and `BottomRight`. All of the coordinates are relative to the view's superview (or in other words parent, or in other words the view containing it). Converting a point between two different views can be achieved via the `UIViews.ConvertPointBetweenViews` utility method, as long as both views are in the same hierarchy. ## Traversing upstream `UIView` exposes the `UIView? Superview` property to access its superview. Additionally, `UIRootView? Root` property is also exposed to quickly access the root of the view hierarchy. Both of those properties will return `null` if the view does not have a parent or its hierarchy does not have a `UIRootView` root, respectively. `IEnumerable<UIView> UIViews.GetViewHierarchy` provides a quick way of traversing all views upstream. `UIView? UIViews.GetCommonSuperview` finds the first common view in the hierarchy between two views. ## Traversing downstream `UIView` exposes the `IReadOnlyList<UIView> Subviews` property, allowing access to the views it directly contains. `IEnumerable<UIView> UIViews.VisitAllViews` provides a quick way of traversing all views downstream. ## Modifying the hierarchy To add a `UIView` to another `UIView`, use `UIView.AddSubview`. If you need to control the order of the subviews (for example to control the rendering order), use `UIView.InsertSubview` instead. To remove a `UIView` from its superview, use `UIView.RemoveFromSuperview`. To change the ordering of views already in hierarchy, use `UIView.BringSubviewToFront`, `UIView.SendSubviewToBack`, `UIView.PutSubviewAbove` and `UIView.PutSubviewBelow`. # `IConstrainable` `IConstrainable` is a base type for things which have properties (called **anchors**; see below) for which you can create constraints. An `IConstrainable` does not have a view hierarchy of its own and it has to belong to a `UIView`. `UIView` is one of the `IConstrainable` types (it belongs to itself). Some views may also expose additional `IConstrainable` types -- one such example would be `UIScrollView`'s `ContentFrame`. # Constraints In a usual simple UI system, you would position views by setting their coordinates and sizes directly. In UIKit, this should be avoided in most cases, and constraints should be used instead. A **constraint** defines a relationship between two anchors (or sometimes just one). For example: `B.Top == A.Top + 16`. Such a constraint would make the `B` view's top anchor to be pinned to the `A` view's top anchor, but offset by 16 points (pixels). It's important to note that those constraints are *not* assignments -- they're actually linear equations. Whenever any layout change happens, this equation will be solved and both `A` and `B` views' properties will be set accordingly. This also means the order of constraints in a valid layout does not matter. The anchors available on all `UIView`s are: `LeftAnchor`, `RightAnchor`, `TopAnchor`, `BottomAnchor`, `WidthAnchor`, `HeightAnchor`, `CenterXAnchor` and `CenterYAnchor`. |Horizontal|Vertical|Centering| |-|-|-| |![](https://i.imgur.com/vz4WPCO.png)|![](https://i.imgur.com/1MQN6CX.png)|![](https://i.imgur.com/mYhs0Ot.png)| ## Anatomy of a constraint As mentioned above, constraints are actually linear equations, in the form of $y = ax + b$. For the above example, we have $a = 1; b = 16$. We didn't explicitly specify the value of $a$, but it defaults to $1$ ($b$ defaults to $0$). In UIKit, the $a$ variable is called the **multiplier**, while $b$ is called the **constant**. Additionally, constraints can be *inequalities*. Imagine you have a view that you want to stay in width between 40 and 200 points. To achieve this, you can use two inequality constraints: * `MyView.Width >= 40` * `MyView.Width <= 200` The above is also an example of a constraint using only one anchor in the equation. These constraints will make sure the view will never be shorter than 40 points or longer than 200 points, *but* it does not specify what width it will have exactly, so if those are the only constraints constraining the width of the view, the width would be [ambiguous](#Ambiguous-layouts). Under the hood, inequality constraints are also what allows the system to enforce the `Width` and `Height` properties to never be negative. ## Unsatisfiable layouts An unsatisfiable layout is a layout for which the constraint solver could not find a solution in which all required constraints are satisfied (often due to overconstraining). In case of such a layout, the constraints which could not be satisfied will be broken (ignored), allowing the code to proceed. Additionally, the `UIRootView`'s `UnsatifiableConstraintEvent` will be fired. It may be tempting to ignore these problems (especially if it seems like the views are layed out okay), but any change to the view hierarchy or UIKit itself could alter the set of broken constraints, suddenly producing an obviously broken layout. ## Ambiguous layouts An ambiguous layout is a layout for which there are multiple possible solutions and it is not clear which one should be used. The two main causes are: * Underconstraining -- the layout requires some additional constraints. * Conflicting non-required constraints with the same priority. UIKit currently ***does not*** detect layout ambiguity. Ambiguous layouts may look completely broken, but also completely right, depending on what the constraint solver decides to do. If you suspect you may have an ambiguous layout problem, it is recommended to fix it - even if the layout looks as expected. Any change to the view hierarchy or UIKit itself could result in a wildly different solution, suddenly producing an obviously broken layout. ## Creating nonambiguous, satisfiable layouts :::info This section is mostly a copy of [Apple's own documentation](https://developer.apple.com/library/archive/documentation/UserExperience/Conceptual/AutolayoutPG/AnatomyofaConstraint.html#//apple_ref/doc/uid/TP40010853-CH9-SW1), because honestly, I could never do a better job than they did here. ::: When using UIKit, the goal is to provide a series of equations that have one and only one possible solution. Ambiguous layouts have more than one possible solution. Unsatisfiable layouts don't have valid solutions. In general, the constraints must define both the size and the position of each view. Assuming the superview's size is already set (for example, the `StardewRootView`), a nonambiguous, satisfiable layout requires two constraints per view per dimension (not counting the superview). However, you have a wide range of options when it comes to choosing which constraints you want to use. For example, the following three layouts all produce nonambiguous, satisfiable layouts (only the horizontal constraints are shown): ![](https://developer.apple.com/library/archive/documentation/UserExperience/Conceptual/AutolayoutPG/Art/constraint_examples_2x.png) * The first layout constrains the view's left edge relative to its superview's left edge. It also gives the view a fixed width. The position of the right edge can then be calculated based on the superview's size and the other constraints. * The second layout constrains the view's left edge relative to its superview's left edge. It also constrains the view's right edge relative to the superview's right edge. The view's width can then be calculated based on the superview's size and the other constraints. * The third layout constrains the view's left edge relative to its superview's left edge. It also center aligns the view and superview. Both the width and right edge's position can then be calculated based on the superview's size and the other constraints. Notice that each layout has one view and two horizontal constraints. In each case, the constraints fully define both the width and the horizontal position of the view. That means all of the layouts produce a nonambiguous, satisfiable layout along the horizontal axis. However, these layouts are not equally useful. Consider what happens when the superview's width changes. In the first layout, the view's width does not change. Most of the time, this is not what you want. In fact, as a general rule, you should avoid assigning constant sizes to views. UIKit is designed to create layouts that dynamically adapt to their environment. Whenever you give a view a fixed size, you are short circuiting that ability. It may not be obvious, but the second and third layouts produce identical behaviors: they both maintain a fixed margin between the view and its superview as the superview's width changes. However, they are not necessarily equal. In general, the second example is easier to understand, but the third example may be more useful, especially when you are center aligning a number of items. As always, choose the best approach for your particular layout. ## Constraint priorities Up until now, all constraints were strictly required. In reality, a lot of the constraints are not always required or wanted to be satisfied. This is where priorities come in. Each constraint in UIKit can have a **priority** value specified, between 0 and 1000. A constraint with a priority of 1000 is required; anything below it will be only satisfied if it can be done while satisfying all constraints with higher priority. UIKit defines some default priorities in `UILayoutConstraintPriority`: `Required` (1000), `High` (750), `Medium` (500) and `Low` (250). Additionally, there is an `OptimalCalculations` (50) priority, but it generally should not be used for declaring constraints between views -- see the [optimal size calculations section](#Optimal-size-calculations). ## Intrinsic view size An **intrinsic size** is a size, that is defined by the view's content, unrelated to any subviews and constraints. For example, consider a `UILabel`, which is a component that displays some text. The size of the text on screen is the value of the intrinsic size of the `UILabel`. This does not necessarily mean the `UILabel` will be displayed at this size, however. It is just its preferred size. The `IntrinsicWidth` and `IntrinsicHeight` properties are automatically transformed into (non-required) inequality constraints. Because they are non-required, additional constraints can be added which override those values. To create those inequality constraints, `IntrinsicWidth` and `IntrinsicHeight` are used together with the `HorizontalContentHuggingPriority`, `VerticalContentHuggingPriority`, `HorizontalCompressionResistancePriority` and `VerticalCompressionResistancePriority`. **Content hugging** priority defines how much the view's contents want to stick to the edges of the view (restrict growing). **Compression resistance** priority defines how much the view's contents want to keep being at least their preferred size (restrict shrinking). By default, the content hugging priorities are `Low`, while compression resistance priorities are `High`, but this can vary from `UIView` to `UIView` (`UILabel` overrides content hugging with a value of 251 -- a bit higher than `Low`) and can also be set manually. `Required` priorities should generally be avoided for those four properties to avoid any potential unsatisfiable layout problems. ### Content hugging and compression resistance For this exercise we will completely ignore vertical positioning. This is our layout: ![](https://i.imgur.com/1x9AWf4.png) `UILabel`s declare their `IntrinsicWidth` automatically. This may seem like a simple layout to recreate with these constraints: * `LabelA.Left == Root.Left` * `LabelB.Left == LabelA.Right` * `LabelB.Right == Root.Right` The reality is -- it's often not that simple. #### Growing, content hugging If the root view is constrained to be larger than the labels' width sum, the labels would also have to be resized in some way. But in what way? * `LabelA` should be made larger. ![](https://i.imgur.com/F4YswBj.png) * `LabelB` should be made larger. ![](https://i.imgur.com/aztcNi5.png) * Both labels should be made larger. ![](https://i.imgur.com/fhoAo7w.png) All three options are valid here, but [it is not obvious which one should be used](#Ambiguous-layouts). This is why content hugging priorities exist. * Setting `LabelA`'s `HorizontalContentHuggingPriority` lower than `LabelB`'s will make `LabelA` grow. * Setting `LabelB`'s `HorizontalContentHuggingPriority` lower than `LabelA`'s will make `LabelB` grow. * If both priorities are equal, it is not clear which label should be grown. To help the constraint solver in this case, you can add an additional constraint `LabelA.Width == LabelB.Width`. #### Shrinking, compression resistance If the root view is constrained to be smaller than the labels' width sum, the labels would also have to be resized in some way (possibly truncating the text displayed). But in what way? * `LabelA` should be made smaller. ![](https://i.imgur.com/TDDgaq1.png) * `LabelB` should be made smaller. ![](https://i.imgur.com/nd76XqI.png) * Both labels should be made smaller. ![](https://i.imgur.com/MhLIKmE.png) All three options are valid here, but [it is not obvious which one should be used](#Ambiguous-layouts). This is why compression resistance priorities exist. * Setting `LabelA`'s `HorizontalCompressionResistancePriority` lower than `LabelB`'s will make `LabelA` shrink. * Setting `LabelB`'s `HorizontalCompressionResistancePriority` lower than `LabelA`'s will make `LabelB` shrink. * If both priorities are equal, it is not clear which label should be shrunk. To help the constraint solver in this case, you can add an additional constraint `LabelA.Width == LabelB.Width`. ## Building constraints The `UILayoutConstraint` type represents a constraint. You can use its constructor to create new constraints. This is not recommended, however. The preferred way of creating constraints is to call any methods of the `MakeConstraint*` family, found as extension methods for the `IUIAnchor` type: * `MakeConstraint` to create a constant constraint -- only applicable to `WidthAnchor` and `HeightAnchor`. * `MakeConstraintTo(IUIAnchor, ...)` to create a standard constraint to another anchor. * `MakeConstraintTo(IConstrainable, ...)` to create a constraint to the same anchor on another `IConstrainable`. * `MakeConstraintToOpposite(IConstrainable, ...)` to create a constraint to the opposite anchor on another `IConstrainable`. An opposite anchor is one that is found on the opposite side of the `IConstrainable`. `Left` anchor's opposite is the `Right` anchor, `Top` anchor's opposite is the `Bottom` anchor, and vice versa. The `Width`, `Height`, `CenterX` and `CenterY` anchors do not have an opposite and these methods will not accept those. More extension methods can be found on the `IConstrainable` type: * `MakeAspectRatioConstraint` to create an aspect ratio constraint. * `MakeHorizontalEdgeConstraintsTo` to create left and right constraints to another `IConstrainable` at once. * `MakeVerticalEdgeConstraintsTo` to create top and bottom constraints to another `IConstrainable` at once. * `MakeEdgeConstraintsTo` to create left, right, top and bottom constraints to another `IConstrainable` at once. * `MakeCenterConstraintsTo` to create horizontal and vertical centering constraints to another `IConstrainable` at once. Even more extension methods can be found on the `UIView` type: * `MakeConstraintToSuperview` to create a standard constraint to the same anchor of the view's superview. * `MakeConstraintToSuperviewOpposite` to create a standard constraint to the opposite anchor of the view's superview. * `MakeHorizontalEdgeConstraintsToSuperview` to create left and right constraints to the view's superview at once. * `MakeVerticalEdgeConstraintsToSuperview` to create top and bottom constraints to the view's superview at once. * `MakeEdgeConstraintsToSuperview` to create left, right, top and bottom constraints to the view's superview at once. * `MakeCenterConstraintsToSuperview` to create horizontal and vertical centering constraints to the view's superview at once. :::warning Note that a constraint has to be activated before it's taken into consideration when solving a layout. To do so, use `IUILayoutConstraint.Activate()`. You can deactivate a constraint with `IUILayoutConstraint.Deactivate()`. Additionally, there are built-in `Activate()` and `Deactivate()` extension methods for `IEnumerable<IUILayoutConstraint>`. ::: ## Accessing constraints To access all of the constraints applying to a specific view or its related frames, use the `IReadOnlySet<IUILayoutConstraint> UIView.Constraints` property. ## Optimal size calculations Sometimes it may be beneficial to calculate an optimal size for a `UIView`, taking into account all of its current constraints. For example, consider a `UILabel`. If the label is constrained to be shorter than the text it is supposed to render, it has to break that text into multiple lines. To do so, it first needs to know how big is the space it can use. It asks the layout to calculate this size, then that value is used to break any longer lines into multiple ones. This is achieved by the `UIView.GetOptimalSize` method. The method allows specifying a `UIOptimalSideLength` for each axis, a priority to use for that length and whether the view's current intrinsic length in that axis should be ignored. Allowed `UIOptimalSideLength` values are: * `Compressed` -- the smallest possible value will be calculated. * `Expanded` -- the largest possible value will be calculated. * `OfLength(x)` -- a constant (provided) value will be used. The default priority (if not provided) is `OptimalCalculations` (50). --- <div style="float: left;"> <a href="https://hackmd.io/@Shockah/Skkq-TZMq"><i class="fa fa-chevron-left"></i> Getting started</a> </div> <div style="float: right;"> <a href="https://hackmd.io/@Shockah/BJy0g6-Gq">Building layouts in code <i class="fa fa-chevron-right"></i></a> </div>