Layouts
Layout
is a protocol
in SwiftUI used to create layouts for user interfaces. Layouts specify how to arrange the views they contain. It’s possible to create custom and conditional layouts. There are also built-in layouts that can be used when creating conditional layouts.
Custom Layouts
Custom layouts can be used when the built-in layouts and views don’t meet the layout needs of an application.
Defining Custom Layouts
When creating a custom layout, the methods .sizeThatFits()
and .placeSubviews()
are required to conform to the Layout
protocol
:
struct myLayout: Layout {
func sizeThatFits(
// Parameters: proposal, subviews, and cache
) -> CGSize {
// Custom code to calculate and return the size of the layout container.
}
func placeSubviews(
// Parameters: in, proposal, subviews, and cache
) {
// Custom code for arranging the subviews.
}
}
It’s also possible to add additional behavior for alignment and spacing by adding optional methods and type properties associated with the Layout
protocol
.
Using Custom Layouts
Custom layouts can be used in the same way as other view containers such as HStack. Include views in braces after the layout name:
myLayout {
// Views go here.
}
Custom Layout Example
The code below is for a custom layout called GalaxyLayout
that places its views in a spiral:
struct GalaxyLayout: Layout {func sizeThatFits(proposal: ProposedViewSize,subviews: Subviews,cache: inout Void) -> CGSize {// Use as much space as is available.proposal.replacingUnspecifiedDimensions()}func placeSubviews(in bounds: CGRect,proposal: ProposedViewSize,subviews: Subviews,cache: inout Void) {// Set the radius to fit within the available space.let radius = min(bounds.size.width, bounds.size.height) / 2.0// Calculate the angle for each view, depending on the total number of views.let angle = Angle.degrees(360.0 / Double(subviews.count)).radians// Place each view, rotating around the center of the space, with a decreasing radius to create a spiral effect.for (index, subview) in subviews.enumerated() {var place = CGPoint(x: 0, y: -radius*CGFloat(Float(index))/10).applying(CGAffineTransform(rotationAngle: angle * Double(index)))place.x += bounds.midXplace.y += bounds.midYsubview.place(at: place, anchor: .center, proposal: .unspecified)}}}
In the above example, the .sizeThatFits()
method specifies to use all the space available. The .placeSubviews()
method uses some math to place each view along a spiral shape.
Note: In SwiftUI, containers are limited to 10 subviews.
Below, an instance of GalaxyLayout
is being used to create a mini galaxy using text and emoji:
var body: some View {GalaxyLayout {Text(".")Text(".")Text("*")Text("*")Text("✨")Text("✨")Text("✨")Text("✨")Text("✨")Text("🌟")}.foregroundColor(.white).background(.black)}
This will display:
Conditional Layouts
Conditional layouts can change dynamically. If the space available gets smaller, for example, it’s possible to automatically switch to a more concise layout.
The syntax below shows how to create a conditional layout:
// Declare a variable that returns a specific layout.
// If some condition is true, some layout is returned, otherwise another layout is returned.
let MyLayout = myCondition ? AnyLayout(SomeLayout) : AnyLayout(AnotherLayout)
MyLayout {
// Subviews go here.
}
The layouts in the conditional statement need to be wrapped with AnyLayout()
and can be built-in or custom. Then, the following instance of MyLayout
will display its containing views using the relevant layout.
Below are some of the built-in layouts that conditional layouts can use:
GridLayout()
arranges subviews in a grid.HStackLayout()
arranges subviews horizontally.VStackLayout()
arranges subviews vertically.ZStackLayout()
overlays subviews.
Conditional Layout Example
In the example below, the variable layout
is set to use either a vertical VStackLayout()
or horizontal HStackLayout()
, depending on whether the variable stackBricks
is true
or false
. A toggle switch is bound to stackBricks
so it can be toggled on and off, with an animation to smoothen the layout transitions. Then a ForEach
loop is used to create green rectangles within an instance of the conditional layout, layout
:
@State private var stackBricks = falsevar body: some View {let layout = stackBricks ? AnyLayout(VStackLayout()) : AnyLayout(HStackLayout())VStack {Toggle("Stack Bricks", isOn: $stackBricks.animation()).font(.title2)Spacer()layout {ForEach(0..<6) { i inRoundedRectangle(cornerRadius: 5).fill(.green).frame(width:50, height:50)}}}.padding()}
When stackBricks
is toggled on and off, the layout changes between horizontal and vertical, with the bricks stacking and unstacking, displaying the following:
Contribute to Docs
- Learn more about how to get involved.
- Edit this page on GitHub to fix an error or make an improvement.
- Submit feedback to let us know how we can improve Docs.
Learn SwiftUI on Codecademy
- Skill path
Build iOS Apps with SwiftUI
Learn how to build iOS applications with Swift and SwiftUI and publish them to Apples' App Store.Includes 7 CoursesWith CertificateBeginner Friendly13 hours - Free course
Learn Swift
A powerful programming language developed by Apple for iOS, macOS, and more.Beginner Friendly12 hours