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!
01. “AARRR” Theoretical Definition As the penetra...
Choice is greater than effort? No comment. I don’...
This article mainly introduces the reasons for be...
Produced by: Science Popularization China Author:...
This article only discusses accounts with landing...
Editor: Wang Shanshan and Yang Minghao...
Chris Red Pill Awakening Member 3.0 New Content R...
The Secret of Zhongtian Feng Shui PDF, written by...
The most popular industry now is the mobile phone...
In August 2010, Zhihu founder and CEO Zhou Yuan, ...
The "Personal Information Protection Law&quo...
When you eat biscuits, instant noodles, or fried ...
APP operation and promotion should be carried out...
Time flies, and a new year has arrived. The elect...
Recently, many friends have asked me: "What ...