SwiftUI Environment When Presenting Views

It’s easy to miss but the way SwiftUI presented views inherit the environment has changed from iOS 13, iOS 14 and iOS 15.

Presenting a SwiftUI View

To experiment I have an app that has a minimum deployment target of iOS 13.6. Apple didn’t add the App life cycle to SwiftUI until iOS 14 so I’m still using the older App and Scene Delegates.

I create and show my SwiftUI content view using a UIHostingController from the scene delegate:

let rootView = ContentView()
window.rootViewController = UIHostingController(rootView: rootView)

My content view has a single button which triggers the presentation of a sheet view:

struct ContentView: View {
  @State private var isShowingSheet = false
    
  var body: some View {
    Button("Present View") {
      isShowingSheet = true
    }
    .sheet(isPresented: $isShowingSheet) {
      SheetView()
    }
  }
}

Finally the SheetView has some text and a button to dismiss the sheet:

struct SheetView: View {
  @Environment(\.presentationMode) private var presentationMode

  var body: some View {
    VStack(spacing: 16) {
      Text("Hello World!")
        .font(.headline)
      Text("This is a presented sheet.")
        .font(.body)
      Button("Done") {
        presentationMode.wrappedValue.dismiss()
      }
    }
    .padding()
  }    
}

SwiftUI Environment Inheritance

We can experiment with the SwiftUI environment by setting the foreground color on our root content view:

let rootView = ContentView()
  .foregroundColor(.purple)

The question is what foreground color does our presented sheet view inherit?

iOS 15 - Environment Is Inherited

When we run this on iOS 15 the presented sheet inherits the environment from the presenting content view. The button in our content view and the text and button in the presented sheet view inherit the purple foreground color:

Root view and presented sheet view. Both have purple text.

iOS 14 - Environment Is Unpredictable

Repeating the experiment on iOS 14 shows some strange behaviour. On initial presentation the share sheet seems to have inherited the environment from the content view. The text appears in purple.

The problem happens if you start to dismiss the presented sheet by dragging the view downwards. At that point the text switches back to the default text color:

iPhone 11 device views showing presented sheet first in purple then with black text as the sheet is dragged downwards.

iOS 13 - Environment Is Not Inherited

The result for iOS 13 is more predictable. The presented sheet never inherits the foreground color from the environment of the content view:

Root view with purple text and presented sheet view with default black text and blue tinted button.

What Does It Mean?

If you’re presenting SwiftUI views and you need to support back to iOS 14 or iOS 13 you should be explicit about setting the environment of any presented views:

var body: some View {
  Button("Present View") {
    isShowingSheet = true
  }
  .sheet(isPresented: $isShowingSheet) {
    SheetView()
    .foregroundColor(.purple)
  }
}

It’s a pain, but until I can require iOS 15 I find it’s the only way to get consistent results on iOS 14 and iOS 13.

Black Friday Sale!

It’s that time of year again. I recently updated Modern Auto Layout for Xcode 13 and iOS 15. If you’re still struggling with Auto Layout grab it for 20% off. Sale ends 5th December!