# 7 Essential Techniques for Crafting Adaptive Layouts in SwiftUI

Welcome to another exciting SwiftUI tutorial. Today, we're diving into the world of adaptive layouts. We'll explore how to leverage SwiftUI's powerful tools to create dynamic, flexible UIs that look stunning on any Apple device.

You'll learn to make your app layouts respond beautifully, whether they're displayed on an iPhone SE or the large screen of an iPad Pro.

*If you are new to SwiftUI check out our series on* [*SwiftUI Fundamentals*](https://namitgupta.com/understanding-the-fundamentals-of-swiftui-for-ios-development-pt1)*.*

## 1\. Embrace The Power of SwiftUI Stacks

SwiftUI provides us with three types of stack views: `HStack`, `VStack`, and `ZStack`. These allow us to stack views horizontally, vertically, and in depth order respectively. By using these stacks, we can construct layouts that automatically adjust to different screen sizes and orientations.

```swift
VStack {
    HStack {
        Text("Left")
        Spacer()
        Text("Right")
    }
    Spacer()
    ZStack {
        Text("Back")
        Text("Front")
    }
}
.padding()
```

In the code above, we've used `HStack` and `VStack` to create a layout that adjusts its content based on the available space. `Spacer()` pushes the adjacent views apart, filling up the available space.

## 2\. GeometryReader - Layout's Best Friend

The `GeometryReader` view in SwiftUI provides access to the size and coordinates of its parent view. This means that we can make our views responsive to the size of their parent view.

```swift
GeometryReader { geometry in
    VStack {
        Text("Hello, World!")
            .font(.system(size: geometry.size.width / 10)) // Adjust font size based on parent view's width
    }
}
```

In the code above, we're using `GeometryReader` to adjust the font size of our text based on the width of the parent view. This means the text will scale up for larger devices and scale down for smaller ones.

## 3\. Responding to Size Classes with Environment Property Wrapper

SwiftUI provides `horizontalSizeClass` and `verticalSizeClass` environment values that we can use to adjust our views according to the size class of the device.

```swift
@Environment(\.horizontalSizeClass) var horizontalSizeClass

var body: some View {
    Group {
        if horizontalSizeClass == .compact {
            // Layout for compact horizontal size class (e.g., iPhone in portrait)
            Text("Compact Size Class")
        } else {
            // Layout for regular horizontal size class (e.g., iPad or iPhone in landscape)
            Text("Regular Size Class")
        }
    }
}
```

In the example above, we're changing the displayed text based on the horizontal size class of the device.

## 4\. Device Specific Styling

The `UIDevice.current.userInterfaceIdiom` property returns a `UIUserInterfaceIdiom` enumeration value that determines the style of the user interface. It can have one of the following values:

* `.unspecified`
    
* `.phone` (for iPhones)
    
* `.pad` (for iPads)
    
* `.tv` (for tvOS)
    
* `.carPlay` (for CarPlay)
    
* `.mac` (for MacOS)
    

You can use this property to apply different styles or layout on different types of devices. Here's a simple example:

```swift
struct ContentView: View {
    var body: some View {
        Group {
            if UIDevice.current.userInterfaceIdiom == .phone {
                Text("Running on iPhone")
                    .font(.largeTitle)
                    .padding()
            } else if UIDevice.current.userInterfaceIdiom == .pad {
                Text("Running on iPad")
                    .font(.largeTitle)
                    .padding()
            } else {
                Text("Running on another device")
                    .font(.largeTitle)
                    .padding()
            }
        }
    }
}
```

In this example, we create a `ContentView` that displays a different text depending on whether it's running on an iPhone, iPad, or another device. The text is styled with a large font and padding.

Note that while this approach can be helpful for minor stylistic changes, SwiftUI encourages designing flexible interfaces that automatically adapt to their environment.

This means we should generally try to avoid hardcoding specific behaviors or layouts for different device types. Instead, aim to create a flexible, responsive design that looks good on all devices.

Use size classes, adaptive layout, and dynamic type to ensure your app works well in any environment.

## 5\. Harnessing the Power of Grids

Grids in SwiftUI are a powerful tool for creating adaptive layouts. They allow us to arrange views into a grid that can automatically adapt to different screen sizes and orientations.

With the introduction of iOS 14, SwiftUI provided two types of grids: `LazyVGrid` and `LazyHGrid`, where V stands for Vertical and H for Horizontal.

The key to creating adaptive grids in SwiftUI is the `GridItem` struct. You can create a `GridItem` with a fixed size, a flexible size, or an adaptive size that automatically adjusts to the available space. You can create a grid by passing an array of `GridItem` objects to the initializer of `LazyVGrid` or `LazyHGrid`.

Here's an example of how to create an adaptive grid that adjusts its layout based on the available space:

```swift
struct ContentView: View {
    var body: some View {
        ScrollView {
            let columns = [GridItem(.adaptive(minimum: 80))]
            LazyVGrid(columns: columns, spacing: 20) {
                ForEach(1...100, id: \.self) { index in
                    VStack {
                        Image(systemName: "photo")
                            .resizable()
                            .aspectRatio(contentMode: .fit)
                        Text("Image \(index)")
                    }
                    .frame(minWidth: 0, maxWidth: .infinity)
                }
            }
            .padding(.horizontal)
        }
    }
}
```

In this example, the `LazyVGrid` creates a vertical grid with adaptive columns. The `GridItem(.adaptive(minimum: 80))` creates a grid item with an adaptive size that adjusts to the available space, with a minimum size of 80 points.

When there's more space available, SwiftUI fits more columns into the grid. When there's less space, SwiftUI reduces the number of columns. The `ForEach` loop creates a series of image views that fill the grid. The `.frame(minWidth: 0, maxWidth: .infinity)` modifier ensures that the image views expand to fill their grid cells.

This creates a flexible grid layout that adapts to different screen sizes and orientations, providing a great user experience on any device.

## 6\. Creating a Custom AdaptiveView

While the tools provided by SwiftUI are powerful, there might be situations where we need more granular control over our layouts. In such cases, we can create our own `AdaptiveView` that responds to specific conditions.

```swift
struct AdaptiveView<Content: View>: View {
    var threshold: CGFloat
    let content: () -> Content

    init(threshold: CGFloat, @ViewBuilder content: @escaping () -> Content) {
        self.threshold = threshold
        self.content = content
    }

    var body: some View {
        GeometryReader { geometry in
            if geometry.size.width > self.threshold {
                HStack {
                    self.content()
                }
            } else {
                VStack {
                    self.content()
                }
            }
        }
    }
}
```

In the example above, `AdaptiveView` checks the available width and compares it with a threshold. Based on this, it decides whether to layout its content in a horizontal or vertical stack.

### Testing Your Adaptive Layouts

Once you've implemented your adaptive layouts, it's crucial to test them on various devices and screen sizes. This ensures that your layouts look great and function as expected across a wide range of conditions. With our custom `AdaptiveView`, it's easy to test different thresholds, layouts, and devices.

```swift
struct ContentView: View {
    var body: some View {
        AdaptiveView(threshold: 500) {
            Text("Hello, Adaptive Layout!")
        }
    }
}
```

In the example above, the `AdaptiveView` will use a horizontal stack layout if the available width is greater than 500 points, and a vertical stack layout otherwise.

## 7\. Adjusting Interface Based on Device Orientation

SwiftUI doesn't have a built-in way to detect device orientation changes, but we can create one using a custom modifier by responding to the `UIDevice.orientationDidChangeNotification` notification. Here's how you can do it:

```swift
// Our custom view modifier to track rotation and
// call our action
struct DeviceRotationViewModifier: ViewModifier {
    let action: (UIDeviceOrientation) -> Void

    func body(content: Content) -> some View {
        content
            .onAppear()
            .onReceive(NotificationCenter.default.publisher(for: UIDevice.orientationDidChangeNotification)) { _ in
                action(UIDevice.current.orientation)
            }
    }
}

// A View wrapper to make the modifier easier to use
extension View {
    func onRotate(perform action: @escaping (UIDeviceOrientation) -> Void) -> some View {
        self.modifier(DeviceRotationViewModifier(action: action))
    }
}

// An example view to demonstrate the solution
struct ContentView: View {
    @State private var orientation = UIDeviceOrientation.unknown

    var body: some View {
        Group {
            if orientation.isPortrait {
                Text("Portrait")
            } else if orientation.isLandscape {
                Text("Landscape")
            } else if orientation.isFlat {
                Text("Flat")
            } else {
                Text("Unknown")
            }
        }
        .onRotate { newOrientation in
            orientation = newOrientation
        }
    }
}
```

This example demonstrates how to change the text displayed based on the device's orientation.

By keeping these principles in mind and testing thoroughly, we can create dynamic, adaptive layouts that work seamlessly across all Apple devices.

I hope you enjoyed this article, and if you have any questions, comments, or feedback, then feel free to comment here or reach out via [**Twitter**](https://twitter.com/iamnamitg).

Thanks for reading!
