Using LazyHStack and LazyVStack (iOS 14+)
SwiftUI 2.0 introduced the LazyHStack and LazyVStack components. These components are used in a similar way to regular HStack and VStack components but offer the advantage of lazy loading. Lazy components are loaded just before the item becomes visible on the device's screen during a device scroll, therefore reducing latency.
We will create an app that uses LazyHStack and LazyVStack and observe how it works.
Getting ready
Let's create a new SwiftUI app called LazyStacks:
- Open Xcode and click Create New Project.
- In the Choose template window, select iOS and then App.
- Click Next.
- Enter
LazyStacksin the Product Name field and select SwiftUI App from the Life Cycle field. - Click Next and select a location on your computer where the project should be stored.
How to do it…
We will implement a LazyHStack and LazyVStack view within a single SwiftUI view file by embedding both in a VStack component. The steps are as follows:
- Click on the
ContentView.swiftfile to view its content in Xcode's editor pane. - Let's create a
ListRowSwiftUI view that will have two properties: an ID and a type.ListRowshould also print a statement showing what item is currently being initialized:struct ListRow: View { let id: Int let type: String init(id: Int, type: String){ print("Loading \(type) item \(id)") self.id = id self.type = type } var body: some View { Text("\(type) \(id)").padding() } } - Replace the initial
Textview withVStack:VStack { } - Add a horizontal scroll view inside the
VStackcomponent and use a.frame()modifier to limit the view's height:ScrollView(.horizontal){ }.frame(height: 100, alignment: .center) - Add
LazyHStackinside the scroll view with aForEachview that iterates through the numbers1–10000and displays them using ourListRowview:LazyHStack { ForEach(1...10000, id:\.self){ item in ListRow(id: item, type: "Horizontal") } } - Add a second vertical scroll view to
VStackwith aLazyVStackstruct that loops through numbers1–10000:ScrollView { LazyVStack { ForEach(1...10000, id:\.self){ item in ListRow(id: item, type: "Vertical") } } } - Now, let's observe lazy loading in action. If the Xcode debug area is not visible, click on View | Debug Area | Show Debug Area:

Figure 2.10 – Show Debug Area
- Select a simulator to use for running the app. The app should look as follows:

Figure 2.11 – Selecting the simulator from Xcode
- Click the play button to run the code in the simulator:

Figure 2.12 – The LazyStacks app running on the simulator
- Scroll through the items in
LazyHStack(located at the top). Observe how theprintstatements appear in the debug area just before an item is displayed on the screen. Each item is initialized just before it is displayed. - Scroll through the items in
LazyVStack. Observe how theprintstatements appear in the debug area just before an item is displayed.
How it works…
We started this recipe by creating the ListRow view because we wanted to clearly demonstrate the advantage of lazy loading over the regular method where all items get loaded at once. The ListRow view has two properties: an ID and a string. We add a print statement to the init() function so that we can observe when each item gets initialized:
init(id: Int, type: String){
print("Loading \(type) item \(id)")
self.id = id
self.type = type
}
The ListRow view body presents a Text view with the ID and type parameters passed to it.
Moving up to the ContentView struct, we replace the initial Text view in the body variable with a VStack component. This allows us to implement both LazyHStack and LazyVStack within the same SwiftUI view.
We implement LazyHStack by first wrapping it in a scroll view, then using a ForEach struct to iterate over the range of values we want to display. For each of those values, a new ListRow view is initialized just before it becomes visible when the user scrolls down:
ScrollView {
LazyVStack {
ForEach(1...10000, id:\.self){ item in
ListRow(id: item, type: "Vertical")
}
}
}
Run the app using a device emulator to view the print statements before each item is initialized. Nothing will be printed if the app is run in live preview mode on Xcode.
There's more…
Try implementing the preceding app using a regular HStack or VStack component and observe the performance difference. The app will be significantly slower since all the rows are initialized at once.