Skip to content

Instantly share code, notes, and snippets.

@simonbs
Last active September 25, 2023 14:49
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save simonbs/37b748321a155274f338107d9837abdc to your computer and use it in GitHub Desktop.
Save simonbs/37b748321a155274f338107d9837abdc to your computer and use it in GitHub Desktop.
It seems that -childForStatusBarStyle: isn’t called on a UIViewController that is presented from a SwiftUI view using UIViewControllerRepresentable. Or am I doing something wrong? I came up with this *ugly* workaround that swizzles -childForStatusBarStyle: to return an associated object when present and uses the default implementation as fallback.
// We'll store a UIViewController as an associated object and don't want to store a strong reference to it.
private final class WeakBoxedValue<T: AnyObject>: NSObject {
private(set) weak var value: T?
init(_ value: T?) {
self.value = value
}
}
// Use associated objects to a UIViewController that should determine the status bar appearance.
private var forcedChildForStatusBarStyleKey: Void?
extension UIViewController {
var sbs_forcedChildForStatusBarStyle: UIViewController? {
get {
let boxedValue = objc_getAssociatedObject(self, &forcedChildForStatusBarStyleKey) as? WeakBoxedValue<UIViewController>
return boxedValue?.value
}
set {
if let newValue = newValue {
objc_setAssociatedObject(self, &forcedChildForStatusBarStyleKey, WeakBoxedValue(newValue), .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
} else {
objc_setAssociatedObject(self, &forcedChildForStatusBarStyleKey, nil, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
}
}
// Swizzle UIViewController to use our sbs_forcedChildForStatusBarStyle when available.
extension UIViewController {
static let classInit: Void = {
if let originalMethod = class_getInstanceMethod(UIViewController.self, #selector(getter: childForStatusBarStyle)),
let swizzledMethod = class_getInstanceMethod(UIViewController.self, #selector(getter: sbs_childForStatusBarStyle)) {
method_exchangeImplementations(originalMethod, swizzledMethod)
}
}()
@objc var sbs_childForStatusBarStyle: UIViewController? {
return sbs_forcedChildForStatusBarStyle ?? self.sbs_childForStatusBarStyle
}
}
// This view is presented modally from another SwiftUI view using fullScreenCover(isPresented:)
struct DemoView: UIViewControllerRepresentable {
func makeUIViewController(context: Context) -> some UIViewController {
return DemoViewController()
}
func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {}
}
// This is the view controller being presented modally. Sets itself as being the child
// that determine the status bar appearance on its parent UIHostingController.
final class DemoViewController: UIViewController {
override func willMove(toParent parent: UIViewController?) {
super.willMove(toParent: parent)
// When the UIViewController is presented modally from a SwiftUI, it will be
// wrapped in a PresentationHostingController<AnyView>. We detect that and
// set the UIViewController to determine the status bar appearance. This is not good 😬
if let parent = parent, String(describing: type(of: parent)).hasPrefix("PresentationHostingController") {
parent.modalPresentationCapturesStatusBarAppearance = true
parent.sbs_forcedChildForStatusBarStyle = self
parent.setNeedsStatusBarAppearanceUpdate()
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment