Building Custom Views with SwiftUI
Description: Learn how to build custom views and controls in SwiftUI with advanced composition, layout, graphics, and animation. See a demo of a high performance, animatable control and watch it made step by step in code. Gain a deeper understanding of the layout system of SwiftUI.
View
always has the size of its body- The
rootView
has the dimensions of the device minus the safe area insets. - The top layer of any view with a body is always what we call layout neutral. Because its view bounds are defined by the bounds of its body, regardless of what’s above.
Layout Process
Three steps:
- The root view offers our view a proposed size (the safe layout area)
- The view answers with the size it would like
- The root view, know with its child view size knowledge, put the child somewhere (by default at the center of its size, both vertically and horizontally)
In SwiftUI there's no way to force a size on the view's child: the parent has to respect that choice.
In short:
- Parent proposes a size for child
- Child chooses its own size
- Parent places child in parent’s coordinate space
- (Bonus) SwiftUI rounds coordinates to the nearest pixel
Views sizes
Since every view controls its own size, when we build a view, we get to decide how and when it resizes.
The bounds of a Text
view never stretch beyond the height and width of its displayed lines.
When we need to fight for space, especially in stack views, we can raise the layout priority by setting the view layoutPriority
(0
by default).
Custom Alignment
Need of an alignment between views of different stacks?
We can achieve so by creating an extension of VerticalAlignment
(or any other alignment), and then use that as our .alignmentGuide
in the interested views.
Graphics
- SwiftUI has some built in shapes (
Circle
for example) that conform to theShape
protocol and have their own modifiers, since all shapes are effectively rendered asView
s, modifiers of shapes can be applied to views and vice versa.
Styling
is used to convert shapes into views, available predefined shapes are:
- We can create custom shapes by conforming to the
Shape
protocol and implementing the functionpath(in:)
that draws a path representing the desired shape.
- By implementing the
animatableData
property in our custom shapes, we can tell SwiftUI how this shape can be interpolated for animations.
- When a view appears/disappears, the default transition animation is to fade-in/fade-out. We can customize these transitions by creating a custom
ViewModifier
and specifying how the view should change between the end states of the animation, then including thatViewModifier
in anasymmetric
transition.
- SwiftUI
ViewModifier
s areView
s defined as a function of some other view. This can be clearly seen when creating a customViewModifier
where we will have to implement abody
function, which takes a genericContent
as a parameter (which is constrained to conform toView
) and returnssome View
.
- By default, SwiftUI renders all views natively as either a
UIView
or anNSView
(based on the platform it's rendering for), and that's typically what we want for normal controls likeButton
s andTextField
s. However, if we need better rendering performance for many views, then we can flatten our views inside adrawingGroup
, which is a special way of rendering graphics, where our view hierarchy is flatten into a singleUIView
orNSView
, which renders all our views using Metal.