PrefaceSwiftUI’s various stacks are the most basic layout tool among many frameworks, allowing us to define groups of views that can be aligned horizontally, vertically, or overlapping views. When it comes to horizontal and vertical variants ( HStack and VStack ), we need to dynamically switch between the two. For example, let's say we are building an app that contains LoginActionsView , a class that lets the user select an action from a list when logging in: struct LoginActionsView : View {
Currently, our buttons are arranged vertically and fill the available space on the horizontal line (you can preview what the buttons will look like with the sample code above). While this looks good on an iPhone in portrait orientation, suppose we now want to make the UI flow horizontally in landscape mode. GeometryReaderCan GeometryReader achieve this? One way is to use GeometryReader to measure the current available space, and depending on whether the width is greater than its height, you can choose to use HStack or VStack to render the content. Although we can put this logic in LoginActionsView, we hope to reuse the code in the future, so we need to recreate a dedicated view as a separate component to implement the dynamic stack switching logic. To make the code more usable, we won’t hard-code the alignment or spacing used by the two stack variants. Instead, let’s parameterize these properties like SwiftUI does, and set the default values used by the framework — like this: struct DynamicStack < Content : View > : View { Since we made the new DynamicStack use the same API as HStack and VStack, we can now simply replace the previous VStack with the new custom instance in LoginActionsView: struct LoginActionsView : View { Excellent! However, as the code above shows, using a GeometeryReader to display dynamic switching has a rather obvious drawback, in that the geometry reader will always fill all available space both horizontally and vertically (to measure the actual space). In our case, the LoginActionsView is no longer just horizontally aligned, it can now also be moved to the top of the screen. While there are ways to solve these problems (such as using techniques like the one used in this Q&A to make multiple views have the same width and height), the real question is whether measuring available space is a good approach when we want to determine the orientation dynamically. An example using size classesInstead, let's use Apple's size class system to decide whether DynamicStack should use an HStack or VStack under the hood. This will not only preserve the same compact layout we had before introducing GeometeryReader, but will also allow DynamicStack to be built in a similar way to system components on all devices and orientations from the start. To see the current horizontal size, we need to use the SwiftUI environment system — by declaring an @Environment -tagged property (with a horizontalSizeClass key path) on the DynamicStack , which will let us switch to the current sizeClass value in the view content: struct DynamicStack < Content : View > : View { With the above in place, LoginActionsView will dynamically switch to a horizontal layout when rendering at regular sizes (such as landscape on a large iPhone, or either orientation on a full-screen iPad), while all other size configurations use a vertical layout. All of these still use a compact vertical layout, which doesn't use more space than is needed to render its content. Using layout protocolsWhile we’ve ended up with a pretty cool solution that works on all versions of iOS that support SwiftUI, let’s also explore some of the new layout tools introduced in iOS 16 (which is still in beta as part of Xcode 14 at the time of writing this post). One of those tools is the new Layout protocol, which lets us create complete custom layouts that integrate directly into SwiftUI’s layout system, while also giving us a smoother and more animated way to dynamically switch between layouts. This is all because it turns out that Layout is not just an API for us third-party developers, Apple also made SwiftUI's own layout containers use this new protocol. So, instead of using HStack and VStack directly as container views, it is better to use them as instances that conform to Layout and wrap them with the AnyLayout type — like this: private extension DynamicStack { The above is possible because both HStack and VStack conform to the new Layout protocol when their content type is EmptyView (which is the case when the content is empty). Let’s take a look at SwiftUI’s public interface. struct DynamicStack < Content : View > : View {
Now that we can resolve what layout to use by using the new currentLayout , let’s update the body implementation to simply call the AnyLayout returned from that property as a function — like this: struct DynamicStack < Content : View > : View {
So what’s the difference between our previous approach and the layout-based approach above? The key difference is that (besides the latter requiring iOS 16) switching layouts preserves the identity of the underlying view being rendered, whereas switching between HStack and VStack does not. This results in smoother animations, such as when switching device orientation, and we’re also likely to get a small performance boost when performing such changes (since SwiftUI always tries to perform best when its view hierarchy is static). Choose the right viewBut we are not done yet, because iOS 16 also gives us other interesting new layout tools, which may also be used to implement DynamicStack — a brand new view type, named ViewThatFits . As the name suggests, this new container will pick the best view based on the current context from the list of candidates we pass when initializing it. In our case, this means we can pass it both an HStack and a VStack and it will automatically switch between them on our behalf. struct DynamicStack < Content : View > : View { Note: In this case, it’s important that we place the HStack first, as a VStack may always fit, even in situations where we want the layout to be in landscape orientation (such as fullscreen mode for iPad). It’s also important to point out that the ViewThatFits-based technique above will always try an HStack , even when rendering the layout at a compact size, and will only choose a VStack-based layout if an HStack doesn’t fit. ConclusionThe above are four different ways to implement DynamicStack view, which can dynamically switch between HStack and VStack based on the current content. |
>>: Apple responds: iOS 16 paste pop-up must be fixed, or next week!
"One good copy is worth 100 sales experts.&q...
1. Introduction Have you ever met some designers ...
How much is the quote for customized fitness in Z...
When we are in the wild, we always feel the call ...
Competition is fierce in the new media marketing ...
The World Economic Forum has released its report,...
Over the past weekend, we monitored that some cha...
The users of search engines are people who search...
Q: Is the traffic of mini programs counted in WeC...
Recently, topics about "killing friends"...
During my time as Party B, I wrote hundreds of pr...
The absence of the iPhone 6 from the mainland was...
According to recent news, global consulting firm ...
I have been thinking, what is the essence of Inte...
The Chengdu Auto Show will open on August 31. Man...