Instruction 1

The Problem with View Identity

Back to the basics of SwiftUI, the framework uses view identity to tag a view for redrawing, should its data dependencies change. It’s important to understand this because a view might not behave like you expect it to because it’s being re-rendered or completely replaced by another view.

And that’s exactly what’s happening now. When you use regular if conditions to switch between two views, SwiftUI is essentially tracking two completely different views. That means that when the result of the conditional statement changes, the framework has to redraw everything, instead of just changing the values of the view.

if isRedColor {
  Text("RGB Picker")
    .foregroundStyle(.red)
} else {
  Text("RGB Picker")
    .foregroundStyle(.blue)
}

This code seems harmless at first, but instead of SwiftUI just changing the color of the text when isRedColor changes, it actually has to create a new Text view with the color blue. It redraws the entire view because both views have a different internal identity.

That’s not the case when you’re using the ternary operator. SwiftUI keeps the identity of the view and only changes the color.

Text("RGB Picker")
  .foregroundStyle(isRedColor ? .red : .blue)

Right now, that’s what’s happening with RGB Picker. When the orientation changes, and both horizontal and vertical size classes change, SwiftUI has to redraw the entire container of the view to change between VStack and HStack.

Instead, you want the layout of the container to change, and the sub views to be rearranged to match the new layout. That’s where AnyLayout comes into play.

Using AnyLayout to Switch Between Different Layouts

AnyLayout is a type that conforms to the new Layout protocol. You can use this type to change the arrangement of the subviews in the container view, without destroying the underlying views.

AnyLayout can change its layout during runtime, moving its subviews to match the new layout, without destroying and recreating them. This is good because the view keeps their view identity but moves to the new place they should appear.

let layout = landscapeIsCompact ? AnyLayout(VStackLayout()) : AnyLayout(HStackLayout())
layout {
  Text("Hello")
  Text("World")
}

In this code, landscapeIsCompact is used to dynamically change the arrangement of the two texts. The container, layout, uses the result of the ternary operation and arrange its subviews accordingly. When landscapeIsCompact is true, the container will behave like a VStack. But when the the value is false, the container will behave like a HStack.

You can use VStackLayout and HStackLayout to achieve the same layout of VStack and HStack, but you can also use your own layout types as long as they conform to the Layout protocol.

The New Layout Protocol

iOS 16 introduced the new Layout protocol. This protocol allows you to create types that build more complex custom container layouts. While most UI can be achieved with a combination of native SwiftUI views, like VStack and HStack, you might find yourself needing to implement something that is completely new and different from what you see elsewhere.

So the SwiftUI team created this protocol to allow developers to have more control over the layout system, and enable them to build a view that arranges its subviews exactly the way you need.

To create a new custom layout, your type has to conform to Layout and implement sizeThatFits(proposal:subviews:cache:) and placeSubviews(in:proposal:subviews:cache:).

The first method receives a proposed size. This is the proposed space the parent view has available. Next, the subviews of the layout, and finally a cache. You use those parameters to calculate the size of your layout. In other words, you calculate how much space your layout needs with all the subviews and the proposed available space from its parent view.

The second method receives the bounds of the container, where the subviews have to be placed in. The proposed size the container calculated on top of the proposed size of its parent. The subviews to be placed in the layout. Here, you use the bounds and the proposed size to actually place the subviews to appear on the layout.

These are just the two required methods of Layout. You can also implement other optional methods for handling cache, defining horizontal and vertical alignment, spacing and axis of the subviews.

This protocol is very powerful, and it’s what drives AnyLayout and allows developers to create complex layouts that adapt and animate it’s subviews.

You’re going to use AnyLayout to switch between a vertical and horizontal layout next.

See forum comments
Download course materials from Github
Previous: Introduction Next: Demo 1