How to automatically switch between HStack and VStack based on size class
How to automatically switch between HStack and VStack based on size class 관련
Updated for Xcode 15
SwiftUI lets us monitor the current size class to decide how things should be laid out, for example switching from a HStack
when space is plentiful to a VStack
when space is restricted.
With a little thinking, we can write a new AdaptiveStack
view that automatically switches between horizontal and vertical layouts for us. This makes creating great layouts on iPad simpler, because our layouts will automatically adjust to split view and slipover scenarios.
Here's how it looks:
struct AdaptiveStack<Content: View>: View {
@Environment(\.horizontalSizeClass) var sizeClass
let horizontalAlignment: HorizontalAlignment
let verticalAlignment: VerticalAlignment
let spacing: CGFloat?
let content: () -> Content
init(horizontalAlignment: HorizontalAlignment = .center, verticalAlignment: VerticalAlignment = .center, spacing: CGFloat? = nil, @ViewBuilder content: @escaping () -> Content) {
self.horizontalAlignment = horizontalAlignment
self.verticalAlignment = verticalAlignment
self.spacing = spacing
self.content = content
}
var body: some View {
Group {
if sizeClass == .compact {
VStack(alignment: horizontalAlignment, spacing: spacing, content: content)
} else {
HStack(alignment: verticalAlignment, spacing: spacing, content: content)
}
}
}
}
struct ContentView: View {
var body: some View {
AdaptiveStack {
Text("Horizontal when there's lots of space")
Text("but")
Text("Vertical when space is restricted")
}
}
}
To try it out, run the app in an iPad simulator, then try different sizes of split view – you'll see ContentView
automatically switch to a VStack
when space runs low.
Now to explain how the custom view works:
- It monitors the
horizontalSizeClass
environment key, so that it will be updated every time that size class changes. - We've given it parameters to store horizontal and vertical alignment individually, so you can control exactly how your layout should adapt.
- There's an optional
CGFloat
for spacing, because that's whatVStack
andHStack
work with. If you wanted even more control you could addhorizontalSpacing
andverticalSpacing
properties. - The
content
property is a function that accepts no parameters and returns some sort of content, which is the view builder end users will rely on to create their layouts. - Our initializer stashes them all away for later.
- Inside the
body
property we can read the horizontal size class, then wrap a call tocontent()
in either aVStack
orHStack
.
And that's it! The actual code isn't as hard you might imagine, but it gives us some really helpful flexibility.