SwiftKickMobile / SwiftMessages

A very flexible message bar for UIKit and SwiftUI.
MIT License
7.24k stars 741 forks source link

Safe area insets are not respected when setting overrideUserInterfaceStyle on a MessageView in iOS 17 #532

Open jmarr opened 8 months ago

jmarr commented 8 months ago

In my app, I'm setting overrideUserInterfaceStyle on my MessageView to force a dark appearance when the app is in light mode.

However, when doing this on iOS 17, the MessageView no longer respects the safe area of the view controller it's being presented in. The MessageView is presented correctly on iOS 16.

I've attached a sample project that reproduces the issue. Run the sample project on a simulator or device running iOS 17 that has a bottom safe area inset. Tap the Show Message button and you'll see the message overlaps the safe area, as shown in the screenshot below. If you comment out the line that sets overrideUserInterfaceStyle or present the message while in dark mode, the message will correctly appear above the safe area.

Screenshot showing message overlapping safe area

In stepping through the code, it looks like after calling container.layoutIfNeeded() on line 100 of TopBottomAnimation.swift, the frame of container is changed and no longer overlapping the safe area, so the safe area isn't taken into account by the call to adjustMargins.

Looking at the constraints, I don't see why container would no longer be filling its parent view, but I did notice that after the call to layoutIfNeeded, container.hasAmbigousLayout() returns true. I highly suspect this is an iOS bug.

FWIW, I did discover that container lays out correctly if I call container.superview.layoutIfNeeded() instead of container.layoutIfNeeded(), so that might be a potential fix / workaround.

I don't understand why this only occurs when setting overrideUserInterfaceStyle, but there is a comment for overrideUserInterfaceStyle in UIView.h that says "Starting in iOS 17, this also affects any nested view controllers and subviews of those view controllers.", so Apple did make changes to the behavior of that property in iOS 17.

For my own app, I'm now configuring the MessageView with the appearance I want without using overrideInterfaceStyle, but I wanted to file an issue here so you can potentially look into it further and so anyone else seeing this behavior can learn how to work around it.

wtmoose commented 8 months ago

@jmarr I appreciate the detailed writeup. I'm unlikely to look at this anytime soon given that it is an obscure problem and you have a workaround. Hopefully it will resolve itself in a future version of iOS.

jmarr commented 8 months ago

@wtmoose Thanks and yeah, that's totally understandable. I mostly wanted to post it here in case anyone else runs into the issue, since it took a while to discover thatoverrideUserInterfaceStyle was triggering it.

In our app the issue was much more noticeable because we have a container view with a bottom bar that sets additionalSafeAreaInsets on its children, so the message was appearing entirely behind the bar.

And yes, hopefully the underlying issue will be resolved in an iOS update, though I did verify it's still happening on the latest iOS 17.1 beta.

jmarr commented 8 months ago

I just discovered that this issue also occurs when showing a message from a view controller that's being presented.

  1. Build and run this project on a device with safe areas.
  2. Tap Show Message and note that the message respects the safe area.
  3. Tap Present View Controller.
  4. Tap Show Message and note that the message overlaps the safe area.

The issue doesn't occur if I change the presentationContext from .viewController(self) to .window(windowLevel: .normal)

In stepping through the code, after calling container.layoutIfNeeded() on line 100 of TopBottomAnimation.swift, the frame of container is changed and no longer overlapping the safe area, so the safe area isn't taken into account by the call to adjustMargins, just like when overrideUserInterfaceStyle was used.