Last active
September 25, 2023 14:49
-
-
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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