Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to hide the home indicator with SwiftUI?

Tags:

ios

swift

swiftui

What's the UIKit equivalent of the prefersHomeIndicatorAutoHidden property in SwiftUI?

like image 794
Scotow Avatar asked Jun 27 '19 16:06

Scotow


2 Answers

For SwiftUI with the new application life cycle

From SwiftUI 2.0 when using the new Application Life Cycle we need to create a new variable in our @main .app file with the wrapper:

@UIApplicationDelegateAdaptor(MyAppDelegate.self) var appDelegate

The main app file will look like this:

import SwiftUI

@main
struct MyApp: App {
    @UIApplicationDelegateAdaptor(MyAppDelegate.self) var appDelegate

    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

Then we create our UIApplicationDelegate class in a new file:

import UIKit

class MyAppDelegate: NSObject, UIApplicationDelegate {
    func application(
        _ application: UIApplication,
        configurationForConnecting connectingSceneSession: UISceneSession,
        options: UIScene.ConnectionOptions
    ) -> UISceneConfiguration {
        let config = UISceneConfiguration(name: "My Scene Delegate", sessionRole: connectingSceneSession.role)
        config.delegateClass = MySceneDelegate.self
        return config
    }
}

Above we passed the name of our SceneDelegate class as "MySceneDelegate", so lets create this class in a separate file:

class MySceneDelegate: UIResponder, UIWindowSceneDelegate {
    var window: UIWindow?
    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        if let windowScene = scene as? UIWindowScene {
            let window = UIWindow(windowScene: windowScene)
            let rootView = ContentView()
            let hostingController = HostingController(rootView: rootView)
            window.rootViewController = hostingController
            self.window = window
            window.makeKeyAndVisible()
        }
    }
}

The property prefersHomeIndicatorAutoHidden will have to be overridden in the HostingController class as usual as in the above solution by ShengChaLover:

class HostingController: UIHostingController<ContentView> {
    override var prefersHomeIndicatorAutoHidden: Bool {
        return true
    }
} 

Of course do not forget to replace contentView with the name of your view if different!

Kudos to Paul Hudson of Hacking with Swift and Kilo Loco for the hints!

like image 188
multitudes Avatar answered Oct 01 '22 05:10

multitudes


Since I could't find this in the default API either, I made it myself in a subclass of UIHostingController.

What I wanted:

var body: some View {
    Text("I hide my home indicator")
        .prefersHomeIndicatorAutoHidden(true)
}

Since the prefersHomeIndicatorAutoHidden is a property on UIViewController we can override that in UIHostingController but we need to get the prefersHomeIndicatorAutoHidden setting up the view hierarchy, from our view that we set it on to the rootView in UIHostingController.

The way that we do that in SwiftUI is PreferenceKeys. There is lots of good explanation on that online.

So what we need is a PreferenceKey to send the value up to the UIHostingController:

struct PrefersHomeIndicatorAutoHiddenPreferenceKey: PreferenceKey {
    typealias Value = Bool

    static var defaultValue: Value = false

    static func reduce(value: inout Value, nextValue: () -> Value) {
        value = nextValue() || value
    }
}

extension View {
    // Controls the application's preferred home indicator auto-hiding when this view is shown.
    func prefersHomeIndicatorAutoHidden(_ value: Bool) -> some View {
        preference(key: PrefersHomeIndicatorAutoHiddenPreferenceKey.self, value: value)
    }
}

Now if we add .prefersHomeIndicatorAutoHidden(true) on a View it sends the PrefersHomeIndicatorAutoHiddenPreferenceKey up the view hierarchy. To catch that in the hosting controller I made a subclass that wraps the rootView to listen to the preference change, then update the UIViewController.prefersHomeIndicatorAutoHidden:

// Not sure if it's bad that I cast to AnyView but I don't know how to do this with generics
class PreferenceUIHostingController: UIHostingController<AnyView> {
    init<V: View>(wrappedView: V) {
        let box = Box()
        super.init(rootView: AnyView(wrappedView
            .onPreferenceChange(PrefersHomeIndicatorAutoHiddenPreferenceKey.self) {
                box.value?._prefersHomeIndicatorAutoHidden = $0
            }
        ))
        box.value = self
    }

    @objc required dynamic init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }

    private class Box {
        weak var value: PreferenceUIHostingController?
        init() {}
    }

    // MARK: Prefers Home Indicator Auto Hidden

    private var _prefersHomeIndicatorAutoHidden = false {
        didSet { setNeedsUpdateOfHomeIndicatorAutoHidden() }
    }
    override var prefersHomeIndicatorAutoHidden: Bool {
        _prefersHomeIndicatorAutoHidden
    }
}

Full example that doesn't expose the PreferenceKey type and has preferredScreenEdgesDeferringSystemGestures too on git: https://gist.github.com/Amzd/01e1f69ecbc4c82c8586dcd292b1d30d

like image 42
Casper Zandbergen Avatar answered Oct 01 '22 04:10

Casper Zandbergen