Combining environment variables into one in SwiftUI

Viewed 53

My apps adapt layout based on horizontal size class and ContentSizeCategory. So, I typically have code like this:

  @Environment(\.horizontalSizeClass) var horizontalSizeClass
  @Environment(\.sizeCategory) var sizeCategory: ContentSizeCategory
  
  private var isHorCompactLayout: Bool {
    horizontalSizeClass == .compact || sizeCategory.isAccessibilityCategory
  }

which I use like this:

  var body: some Scene {
    if isHorCompactLayout {
       Text("CompactLayout()")
    } else {
       Text("NormalLayout()")
  }

I'd like to refactor the first chunk of code to avoid repeating it in all views where I adapt the layout. How can this be done?

I suppose I could create a new view, pass it the two views and render the correct one based on the result of isHorCompactLayout. But it would still be good to get the value of isHorCompactLayout when needed; for example, to adjust padding.

1 Answers

A possible approach is to have own environment value and set at the very root level of your content view, so all other subviews will have it update on changes at root level, like

struct ContentView: View {
      @Environment(\.horizontalSizeClass) var horizontalSizeClass
      @Environment(\.sizeCategory) var sizeCategory

    var body: some View {
        VStack {    // some top-most container or root view

            // ... all app hierarchy 

        }
        .environment(\.isHorCompactLayout, horizontalSizeClass == .compact || sizeCategory.isAccessibilityCategory)
    }
}

struct HorCompactLayoutKey: EnvironmentKey {
    static let defaultValue: Bool = false
}

extension EnvironmentValues {
    var isHorCompactLayout: Bool {
        get { self[HorCompactLayoutKey.self] }
        set { self[HorCompactLayoutKey.self] = newValue }
    }
}

Tested with Xcode 13.4

Related