-
-
Save Amzd/01e1f69ecbc4c82c8586dcd292b1d30d to your computer and use it in GitHub Desktop.
| extension View { | |
| /// Controls the application's preferred home indicator auto-hiding when this view is shown. | |
| func prefersHomeIndicatorAutoHidden(_ value: Bool) -> some View { | |
| preference(key: PreferenceUIHostingController.PrefersHomeIndicatorAutoHiddenPreferenceKey.self, value: value) | |
| } | |
| /// Controls the application's preferred screen edges deferring system gestures when this view is shown. Default is UIRectEdgeNone. | |
| func edgesDeferringSystemGestures(_ edge: UIRectEdge) -> some View { | |
| preference(key: PreferenceUIHostingController.PreferredScreenEdgesDeferringSystemGesturesPreferenceKey.self, value: edge) | |
| } | |
| } | |
| class PreferenceUIHostingController: UIHostingController<AnyView> { | |
| init<V: View>(wrappedView: V) { | |
| weak var weakSelf: PreferenceUIHostingController? | |
| super.init(rootView: AnyView(wrappedView | |
| .onPreferenceChange(PrefersHomeIndicatorAutoHiddenPreferenceKey.self) { | |
| weakSelf?._prefersHomeIndicatorAutoHidden = $0 | |
| } | |
| .onPreferenceChange(PreferredScreenEdgesDeferringSystemGesturesPreferenceKey.self) { | |
| weakSelf?._preferredScreenEdgesDeferringSystemGestures = $0 | |
| } | |
| )) | |
| weakSelf = self | |
| } | |
| @objc required dynamic init?(coder aDecoder: NSCoder) { | |
| super.init(coder: aDecoder) | |
| } | |
| // MARK: Prefers Home Indicator Auto Hidden | |
| fileprivate struct PrefersHomeIndicatorAutoHiddenPreferenceKey: PreferenceKey { | |
| typealias Value = Bool | |
| static var defaultValue: Value = false | |
| static func reduce(value: inout Value, nextValue: () -> Value) { | |
| value = nextValue() || value | |
| } | |
| } | |
| private var _prefersHomeIndicatorAutoHidden = false { | |
| didSet { setNeedsUpdateOfHomeIndicatorAutoHidden() } | |
| } | |
| override var prefersHomeIndicatorAutoHidden: Bool { | |
| _prefersHomeIndicatorAutoHidden | |
| } | |
| // MARK: Preferred Screen Edges Deferring SystemGestures | |
| fileprivate struct PreferredScreenEdgesDeferringSystemGesturesPreferenceKey: PreferenceKey { | |
| typealias Value = UIRectEdge | |
| static var defaultValue: Value = [] | |
| static func reduce(value: inout Value, nextValue: () -> Value) { | |
| value.formUnion(nextValue()) | |
| } | |
| } | |
| private var _preferredScreenEdgesDeferringSystemGestures: UIRectEdge = [] { | |
| didSet { setNeedsUpdateOfScreenEdgesDeferringSystemGestures() } | |
| } | |
| override var preferredScreenEdgesDeferringSystemGestures: UIRectEdge { | |
| _preferredScreenEdgesDeferringSystemGestures | |
| } | |
| } |
| /// If you are unable to access window.rootViewController this is a method using swizzling | |
| struct PreferenceUIHostingControllerView<Wrapped: View>: UIViewControllerRepresentable { | |
| init(@ViewBuilder wrappedView: @escaping () -> Wrapped) { | |
| _ = UIViewController.preferenceSwizzling | |
| self.wrappedView = wrappedView | |
| } | |
| var wrappedView: () -> Wrapped | |
| func makeUIViewController(context: Context) -> PreferenceUIHostingController { | |
| PreferenceUIHostingController(wrappedView: wrappedView()) | |
| } | |
| func updateUIViewController(_ uiViewController: PreferenceUIHostingController, context: Context) {} | |
| } | |
| import SwizzleSwift // I have a fork of this for SPM (Amzd/SwizzleSwift) | |
| extension UIViewController { | |
| static var preferenceSwizzling: Void = { | |
| Swizzle(UIViewController.self) { | |
| #selector(getter: childForScreenEdgesDeferringSystemGestures) <-> #selector(childForScreenEdgesDeferringSystemGestures_Amzd) | |
| #selector(getter: childForHomeIndicatorAutoHidden) <-> #selector(childForHomeIndicatorAutoHidden_Amzd) | |
| } | |
| }() | |
| } | |
| extension UIViewController { | |
| @objc func childForScreenEdgesDeferringSystemGestures_Amzd() -> UIViewController? { | |
| if self is PreferenceUIHostingController { | |
| // dont continue searching | |
| return nil | |
| } else { | |
| return search() | |
| } | |
| } | |
| @objc func childForHomeIndicatorAutoHidden_Amzd() -> UIViewController? { | |
| if self is PreferenceUIHostingController { | |
| // dont continue searching | |
| return nil | |
| } else { | |
| return search() | |
| } | |
| } | |
| private func search() -> PreferenceUIHostingController? { | |
| if let result = children.compactMap({ $0 as? PreferenceUIHostingController }).first { | |
| return result | |
| } | |
| for child in children { | |
| if let result = child.search() { | |
| return result | |
| } | |
| } | |
| return nil | |
| } | |
| } |
Has anybody had any luck adapting this to the iOS 16 life cycle? This no longer works for iOS 16 and while .persistentSystemOverlays(.hidden) is available, this is more preferential.
@LePips Do you have any more info on why this doesn't work on iOS 16? is there a new api for deferring system gestures in UIKit? as it should just use that right? or does the swizzle no longer work?
I do not have an idea why they don't work, the swizzled methods just aren't called. Most specifically, I am looking at the home indicator but I strongly assume this would also apply to all other methods.
Here is a minimal example that works for iOS 15 but not iOS 16:
Example
The commented out appDelegate is explained below.
@main
struct PreferenceHostingDevApp: App {
// @UIApplicationDelegateAdaptor(MyAppDelegate.self)
// var appDelegate
var body: some Scene {
WindowGroup {
PreferenceUIHostingControllerView {
ContentView()
}
}
}
}
struct ContentView: View {
@State
private var showSecondView: Bool = false
var body: some View {
ZStack {
VStack {
Image(systemName: "globe")
.imageScale(.large)
.foregroundColor(.accentColor)
Text("Hello, world!")
Button {
showSecondView = true
} label: {
Text("Present")
}
}
}
.ignoresSafeArea()
.fullScreenCover(isPresented: $showSecondView) {
SecondView(showSecondView: $showSecondView)
}
}
}
struct SecondView: View {
@Binding
var showSecondView: Bool
var body: some View {
VStack {
Text("Hello There")
Button {
showSecondView = false
} label: {
Text("Dismiss")
}
}
.prefersHomeIndicatorAutoHidden(true)
}
}Other things attempted:
- setting the scene window root view controller (via the
appDelegate) - create another solution that uses a proxy
ObservableObjectwhich will manually call the update method on the view controller - wrapping
SecondViewin aPreferenceUIHostingControllerView
@LePips Hmm, is it possible that the preference key doesn’t forward from a presented view? Have you tried without the presented view?
If you mean trying to hide the home indicator on ContentView, that does work
I am most grateful for this code - thank you. I dim a view in my app if it is idle for a while and use my own .dimIfIdle() modifier along with your .prefersHomeIndicatorAutoHidden(timer.idle). 'timer' is my StateObject. I simply code the view that all this happens in with "PreferenceUIHostingControllerView { PanelView() }". This is wonderful because it let me move away from AppDelegate and SceneDelegate.