SwiftUI LazyHStack: a closer look

DevTechie Inc
Jun 17, 2022


Photo by Ross Joyner on Unsplash

LazyHStack is a container view used to layout child views in horizontal order, just like HStack with one difference, LazyHStack is lazy 🦥 😃.

Lazy mean only views that are visible on screen will be rendered. As user scrolls through the views, more views will be rendered only when they are about to become visible to the screen. This concept is very much close to what you might already know about use of reusable cells in UITableViews.

LazyHStack is more efficient than HStack as it doesn’t load all its child view in memory all at once but makes them available as they are needed.

Let’s start with a simple example:

struct LazyHStackExample: View {
    
    let dateFormatter: DateFormatter = {
            let formatter = DateFormatter()
            formatter.dateFormat = "HH:mm:ss z"
            return formatter
        }()
    
    var body: some View {
        ScrollView(.horizontal, showsIndicators: false) {
            LazyHStack {
                ForEach(0...20, id: \.self) { idx in
                    VStack {
                        Image(systemName: "\(idx).square")
                            
                        Text(Date(), formatter: dateFormatter)
                    }
                    .padding()
                    .background(Capsule().fill(Color.orange))
                    .foregroundColor(Color.white)
                }
            }.font(.system(size: 20))
        }
    }
}
Notice: timestamp for each capsule is different, this means views in LazyHStack are created and rendered only when they were about to become visible on screen.
LazyHStack takes all the available vertical space in the parent container, whereas HStack which only takes the space needed to display its child views.

struct LazyHStackExample: View {
    var body: some View {
        VStack {
            LazyHStack {
                Image(systemName: "person")
                Image(systemName: "person.fill")
                Image(systemName: "person.circle.fill")
                Image(systemName: "person.circle")
            }
            .padding()
            .border(Color.orange)
            
            HStack {
                Image(systemName: "person")
                Image(systemName: "person.fill")
                Image(systemName: "person.circle.fill")
                Image(systemName: "person.circle")
            }
            .padding()
            .border(Color.pink)
        }
    }
}
LazyHStack can align its child views with VerticalAlignment and supports bottom, center, firstTextBaseline, lastTextBaseline and top values.

struct LazyHStackExample: View {
    var body: some View {
        VStack {
            LazyHStack(alignment: .bottom) {
                Image(systemName: "person")
                Image(systemName: "person.fill")
                Image(systemName: "person.circle.fill")
                Image(systemName: "person.circle")
            }
            .padding()
            .border(Color.orange)
            
            LazyHStack(alignment: .center) {
                Image(systemName: "person")
                Image(systemName: "person.fill")
                Image(systemName: "person.circle.fill")
                Image(systemName: "person.circle")
            }
            .padding()
            .border(Color.orange)
            
            LazyHStack {
                LazyHStack(alignment: .top) {
                    Image(systemName: "person")
                    Image(systemName: "person.fill")
                    Image(systemName: "person.circle.fill")
                    Image(systemName: "person.circle")
                }
            }
            
            .padding()
            .border(Color.orange)
        }
    }
}
Spacing parameter in LazyHStack works exactly same as, it adds space between child views in LazyHStack.

struct LazyHStackExample: View {
    @State private var spacing: CGFloat = 0
    var body: some View {
        VStack {
            LazyHStack(alignment: .center, spacing: spacing) {
                Image(systemName: "person")
                Image(systemName: "person.fill")
                Image(systemName: "person.circle.fill")
                Image(systemName: "person.circle")
            }
            .padding()
            .border(Color.orange)
            .onAppear {
                withAnimation(Animation.spring()) {
                    spacing = 50
                }
            }
        }
    }
}
Sections in LazyHStack
Let’s add sections inside the LazyHStack. We will add a header inside our sections called SwiftUI and UIKit to represent courses from DevTechie on SwiftUI and UIKit vice versa. Then we will add ForEach inside the sections as shown below.

struct LazyHStackExample: View {
    var body: some View {
        ScrollView(.horizontal) {
            LazyHStack {
                Section(header: 
                            Text("SwiftUI").padding()
                            .background(.blue, in: Capsule())
                ) {
                    ForEach(0...10, id: \.self) { idx in 
                        Text("SwiftUI Course #\(idx)")
                            .padding()
                            .background(.orange, in: Capsule())
                    }
                }
                Section(header: 
                            Text("UIKit").padding()
                            .background(.blue, in: Capsule())
                ) {
                    ForEach(20...30, id: \.self) { idx in 
                        Text("UIKit Course #\(idx)")
                            .padding()
                            .background(.orange, in: Capsule())
                    }
                }
            }
        }
    }
}
Sections inside LazyHStack gives us another cool feature called pinnedViews. Here is how it works:

struct LazyHStackExample: View {
    var body: some View {
        ScrollView(.horizontal) {
            LazyHStack(pinnedViews: [.sectionHeaders]) {
                Section(header: 
                            Text("SwiftUI").padding()
                            .background(.blue, in: Capsule())
                ) {
                    ForEach(0...10, id: \.self) { idx in 
                        Text("SwiftUI Course #\(idx)")
                            .padding()
                            .background(.orange, in: Capsule())
                    }
                }
                Section(header: 
                            Text("UIKit").padding()
                            .background(.blue, in: Capsule())
                ) {
                    ForEach(20...30, id: \.self) { idx in 
                        Text("UIKit Course #\(idx)")
                            .padding()
                            .background(.orange, in: Capsule())
                    }
                }
            }
        }
    }
}
Similar to the header you can also add footer to your sections and pin them as well as shown below:

struct LazyHStackExample: View {
    var body: some View {
        ScrollView(.horizontal) {
            LazyHStack(pinnedViews: [.sectionHeaders, .sectionFooters]) {
                Section(header: 
                            Text("SwiftUI").padding()
                            .background(.blue, in: Capsule()),
                        footer: 
                            Text("End of section")
                            .padding()
                            .background(.gray, in: Capsule())
                ) {
                    ForEach(0...10, id: \.self) { idx in 
                        Text("SwiftUI Course #\(idx)")
                            .padding()
                            .background(.orange, in: Capsule())
                    }
                }
                Section(header: 
                            Text("UIKit").padding()
                            .background(.blue, in: Capsule()),
                        footer: 
                            Text("End of section")
                            .padding()
                            .background(.gray, in: Capsule())
                ) {
                    ForEach(20...30, id: \.self) { idx in 
                        Text("UIKit Course #\(idx)")
                            .padding()
                            .background(.orange, in: Capsule())
                    }
                }
            }
        }
    }
}
With that we have reached the end of this article. Thank you once again for reading. Don't forget to subscribe to our weekly newsletter at https://www.devtechie.com