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
LazyStacks
in 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.swift
file to view its content in Xcode's editor pane. - Let's create a
ListRow
SwiftUI view that will have two properties: an ID and a type.ListRow
should 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
Text
view withVStack
:VStack { }
- Add a horizontal scroll view inside the
VStack
component and use a.frame()
modifier to limit the view's height:ScrollView(.horizontal){ }.frame(height: 100, alignment: .center)
- Add
LazyHStack
inside the scroll view with aForEach
view that iterates through the numbers1
–10000
and displays them using ourListRow
view:LazyHStack { ForEach(1...10000, id:\.self){ item in ListRow(id: item, type: "Horizontal") } }
- Add a second vertical scroll view to
VStack
with aLazyVStack
struct 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:
- Select a simulator to use for running the app. The app should look as follows:
- Click the play button to run the code in the simulator:
- Scroll through the items in
LazyHStack
(located at the top). Observe how theprint
statements 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 theprint
statements 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.