BLACK FRIDAY: Save 50% on all my books and bundles! >>

How to create a custom layout using the Layout protocol

Paul Hudson    @twostraws   

Updated for Xcode 16.4

New in iOS 16

SwiftUI lets us create wholly custom layouts for our views using the Layout protocol, and our custom layouts can be used just like HStack, VStack, or any other built-in layout types.

Adopting the Layout protocol has just two requirements:

  • A method that returns how much space your layout wants for its subviews. This will be given a size proposal, which is how much space the parent view has available for it. This might be called multiple times so SwiftUI can see how flexible your container is.
  • Another method that actually places those subviews where you want them. This will be given the same size proposal as the first method, but will also be given a specific bounds to work with – this will be one of the sizes you requested in the original method.

You can also optionally make these methods cache their calculations if you’re doing something particularly slow, but I’ve yet to encounter a situation where this is needed.

Important: When you’re giving a size proposal, it might contain nil values for either or both of its width or height. As a result, it’s common to call replacingUnspecifiedDimensions() on the proposal so that any nil values are replaced with a nominal, non-nil value.

For example, we could implement a radial layout – a layout that places it views around a circle:

struct RadialLayout: Layout {
    func sizeThatFits(proposal: ProposedViewSize, subviews: Subviews, cache: inout Void) -> CGSize {
        // accept the full proposed space, replacing any nil values with a sensible default
        proposal.replacingUnspecifiedDimensions()
    }

    func placeSubviews(in bounds: CGRect, proposal: ProposedViewSize, subviews: Subviews, cache: inout Void) {
        // calculate the radius of our bounds
        let radius = min(bounds.size.width, bounds.size.height) / 2

        // figure out the angle between each subview on our circle
        let angle = Angle.degrees(360 / Double(subviews.count)).radians

        for (index, subview) in subviews.enumerated() {
            // ask this view for its ideal size
            let viewSize = subview.sizeThatFits(.unspecified)

            // calculate the X and Y position so this view lies inside our circle's edge
            let xPos = cos(angle * Double(index) - .pi / 2) * (radius - viewSize.width / 2)
            let yPos = sin(angle * Double(index) - .pi / 2) * (radius - viewSize.height / 2)

            // position this view relative to our centre, using its natural size ("unspecified")
            let point = CGPoint(x: bounds.midX + xPos, y: bounds.midY + yPos)
            subview.place(at: point, anchor: .center, proposal: .unspecified)
        }
    }
}

We can now use that just like any other layout type. For example, we could place an array of shapes around, using a stepper to control how many are shown:

struct ContentView: View {
    @State private var count = 16

    var body: some View {
        RadialLayout {
            ForEach(0..<count, id: \.self) { _ in
                Circle()
                    .frame(width: 32, height: 32)
            }
        }
        .safeAreaInset(edge: .bottom) {
            Stepper("Count: \(count)", value: $count.animation(), in: 0...36)
                .padding()
        }
    }
}

My book Pro SwiftUI goes into a lot more detail on custom layouts, including SwiftUI code for masonry layouts, equal width stacks, relative stacks, layout caches, custom animations, and more. Find out more here: https://www.hackingwithswift.com/store/pro-swiftui.

Save 50% in my Black Friday sale.

SAVE 50% All our books and bundles are half price for Black Friday, so you can take your Swift knowledge further for less! Get my all-new book Everything but the Code to make more money with apps, get the Swift Power Pack to build your iOS career faster, get the Swift Platform Pack to builds apps for macOS, watchOS, and beyond, or get the Swift Plus Pack to learn Swift Testing, design patterns, and more.

Save 50% on all our books and bundles!

Similar solutions…

BUY OUR BOOKS
Buy Everything but the Code Buy Pro Swift Buy Pro SwiftUI Buy Swift Design Patterns Buy Testing Swift Buy Hacking with iOS Buy Swift Interview Challenges Buy Swift on Sundays Volume One Buy Server-Side Swift Buy Hacking with watchOS Buy Hacking with tvOS Buy Hacking with macOS Buy Dive Into SpriteKit Buy Swift in Sixty Seconds Buy Objective-C for Swift Developers Buy Beyond Code

Was this page useful? Let us know!

Average rating: 4.1/5

 
Unknown user

You are not logged in

Log in or create account