Search code examples
iosapple-watchswiftuiwatchos

Device-specific Layout with SwiftUI on Apple Watch and iPhone


Sometimes, I need to make device-specific adjustments to layouts. For example, I may need to reduce spacing on an iPhone with a smaller screen or increase spacing on the largest screens. With UIKit (and even Interface Builder) it was easy to make layout exceptions for specific size classes. What is the best way to do conditional device-specific layouts with SwiftUI?

I've been scouring the SwiftUI documentation, and haven't found a way to access and use this type of information in layouts.

Below is an example for an Apple Watch app. Per Apple's design guidelines, I'm adding 8.5 points of padding to the left and right on the 40mm Series 4. However, the 44mm should have 9.5 points of padding, and any Apple Watch older than Series 4 should have no padding.

What is the best way to achieve this with SwiftUI?

struct ContentView : View {

    var body: some View {
        HStack {
            Text("Hello World")
        }.padding([.horizontal], 8.5)
    }
}

Solution

  • In general there are two methods you can use to achieve device specific layouts:

    1. Size classes via @Environment variables
    2. GeometryReader for more fine-grained control

    Unfortunately, UserInterfaceSizeClass only has .compact and .regular and is not available on watchOS.

    To use the environment:

    struct MyView: View {
        @Environment(\.horizontalSizeClass) var horizontalSizeClass: UserInterfaceSizeClass?
    }
    

    To use GeometryReader:

    var body -> some View {
        GeometryReader { proxy in
          if proxy.size.width > 324.0/2.0 { // 40mm watch resolution in points
            MyBigView()
          } else {
            MySmallView()
          }
        }
    }
    

    For reference, here are the watch resolutions:

    • 40mm: 394×324
    • 44mm: 448×368
    • 38mm: 340×272
    • 42mm: 390×312

    Divide by 2.0 to get their values in points instead of pixels.