如何使用SwiftUI隐藏Home指示器?

holgip5t  于 2023-10-15  发布在  Swift
关注(0)|答案(7)|浏览(193)

SwiftUI中prefersHomeIndicatorAutoHidden属性的UIKit等价物是什么?

hgb9j2n6

hgb9j2n61#

因为我在默认的API中也找不到这个,所以我自己在UIHostingController的子类中创建了它。
我想要的:

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

由于prefersHomeIndicatorAutoHidden是UIViewController上的一个属性,我们可以在UIHostingController中覆盖它,但是我们需要从我们的视图中获取prefersHomeIndicatorAutoHidden来设置视图层次结构,我们将其设置为UIHostingController中的rootView。
我们在SwiftUI中实现这一点的方式是PreferenceKeys。网上有很多很好的解释。
因此,我们需要一个PreferenceKey来将值发送到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)
    }
}

现在,如果我们在View上添加.prefersHomeIndicatorAutoHidden(true),它会将DataHomeIndicatorAutoHiddenPreferenceKey发送到视图层次结构中。为了在主机控制器中捕获这一点,我做了一个子类来 Package rootView来监听首选项的变化,然后更新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
    }
}

完整的例子,没有暴露PreferenceKey类型,并且在git上也有preferredScreenEdgesDeferringSystemGestureshttps://gist.github.com/Amzd/01e1f69ecbc4c82c8586dcd292b1d30d

iugsix8n

iugsix8n2#

iOS 16

您可以使用.persistentSystemOverlays并传入.hidden来隐藏所有自动放置在UI上的 * 非 transient 系统视图 *

Text("Goodbye home indicator, the multitask indicator on iPad, and more.")
    .persistentSystemOverlays(.hidden)
s5a0g9ez

s5a0g9ez3#

针对SwiftUI新的应用生命周期

从SwiftUI 2.0开始,当使用新的应用程序生命周期时,我们需要在我们的@main .app文件中创建一个新的变量,并使用 Package 器:
@UIApplicationDelegateAdaptor(MyAppDelegate.self) var appDelegate
主应用程序文件看起来像这样:

import SwiftUI

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

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

然后我们在一个新文件中创建我们的UIApplicationDelegate类:

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
    }
}

上面我们将SceneDelegate类的名称传递为“MySceneDelegate”,所以让我们在一个单独的文件中创建这个类:

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()
        }
    }
}

属性prefersHomeIndicatorAutoHidden必须在HostingController类中被重写,就像ShengChaLover在上面的解决方案中一样:

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

当然,如果不同的话,不要忘记用你的视图的名称替换contentView!
称赞保罗哈德逊的黑客与斯威夫特和基洛疯子的提示!

cgyqldqp

cgyqldqp4#

我发现的唯一一个100%有效的解决方案是在所有UIViewController中混合示例属性'prefersHomeIndicatorAutoHidden',这样它总是返回true。
在NSObject上创建一个扩展,用于混合示例方法/属性

//NSObject+Swizzle.swift

extension NSObject {
    class func swizzle(origSelector: Selector, withSelector: Selector, forClass: AnyClass) {
          let originalMethod = class_getInstanceMethod(forClass, origSelector)
          let swizzledMethod = class_getInstanceMethod(forClass, withSelector)
          method_exchangeImplementations(originalMethod!, swizzledMethod!)
     }
}

在UIViewController上创建扩展,这将交换所有视图控制器中的示例属性,并将其与我们创建的始终返回true的视图控制器中的示例属性交换

//UIViewController+HideHomeIndicator.swift

extension UIViewController {

   @objc var swizzle_prefersHomeIndicatorAutoHidden: Bool {
       return true
   }

   public class func swizzleHomeIndicatorProperty() {
       self.swizzle(origSelector:#selector(getter: UIViewController.prefersHomeIndicatorAutoHidden),
                    withSelector:#selector(getter: UIViewController.swizzle_prefersHomeIndicatorAutoHidden),
                    forClass:UIViewController.self)
   }
}

然后在应用程序委托中调用swizzleHomeIndicatorProperty()函数

// AppDelegate.swift

class AppDelegate: UIResponder, UIApplicationDelegate {

 func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {

    //Override 'prefersHomeIndicatorAutoHidden' in all UIViewControllers
    UIViewController.swizzleHomeIndicatorProperty()

    return true
  }

}

如果使用SwiftUI,请使用UIApplicationDelegateAdaptor注册AppDelegate

//Application.swift

@main
struct Application: App {

   @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate

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

7bsow1i65#

我已经设法隐藏在我的单视图应用程序中的家庭指标使用的技术比卡斯珀Zandbergen提出的更简单。它的方式少'通用',我不确定首选项将传播下来的视图层次结构,但在我的情况下,这就足够了。
在SceneDelegate子类中,使用根视图类型作为泛型参数并覆盖 prefersHomeIndicatorAutoHidden 属性的UIHostingController。

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

在scene方法的例程中,创建一个自定义HostingController的示例,像往常一样传递根视图,并将该示例分配给窗口的rootViewController:

if let windowScene = scene as? UIWindowScene {
    let window = UIWindow(windowScene: windowScene)
    let rootView = YourRootView()
    let hostingController = HostingController(rootView: rootView)
    window.rootViewController = hostingController
    self.window = window
    window.makeKeyAndVisible()
}

更新:如果你需要将一个EnvironmentObject注入到根视图中,这个**将不起作用。

ozxc1zmp

ozxc1zmp6#

我的解决方案只适用于一个屏幕(UIHostingController)。这意味着您不需要在整个应用程序中替换UIHostingController并处理AppDelegate。因此,它不会影响将EnvironmentObject s注入ContentView。如果你只想有一个显示的屏幕与隐藏的主页指示器,你需要 Package 你的视图周围自定义UIHostingController和显示它。
这可以这样做(或者如果你想在运行时更改属性,你也可以像前面的答案一样使用PreferenceUIHostingController。但我想这需要更多的变通方法):

final class HomeIndicatorHideableHostingController: UIHostingController<AnyView> {
    init<V: View>(wrappedView: V) {
        super.init(rootView: AnyView(wrappedView))
    }

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

    override var prefersHomeIndicatorAutoHidden: Bool {
        return true
    }
}

然后,您必须以UIKit风格呈现您的HomeIndicatorHideableHostingController(在iOS 14上测试)。解决方案基于此:https://gist.github.com/fullc0de/3d68b6b871f20630b981c7b4d51c8373。如果你想让它适应iOS 13,请查看链接(topMost属性也在那里)。
您可以像fullScreenCover一样为它创建视图修改器:

public extension View {
    /// This is used for presenting any SwiftUI view in UIKit way.
    ///
    /// As it uses some tricky way to make the objective,
    /// could possibly happen some issues at every upgrade of iOS version.
    /// This way of presentation allows to present view in a custom `UIHostingController`
    func uiKitFullPresent<V: View>(isPresented: Binding<Bool>,
                               animated: Bool = true,
                               transitionStyle: UIModalTransitionStyle = .coverVertical,
                               presentStyle: UIModalPresentationStyle = .fullScreen,
                               content: @escaping (_ dismissHandler:
                                   @escaping (_ completion:
                                       @escaping () -> Void) -> Void) -> V) -> some View {
        modifier(FullScreenPresent(isPresented: isPresented,
                               animated: animated,
                               transitionStyle: transitionStyle,
                               presentStyle: presentStyle,
                               contentView: content))
    }
}

修改自己:

public struct FullScreenPresent<V: View>: ViewModifier {
    typealias ContentViewBlock = (_ dismissHandler: @escaping (_ completion: @escaping () -> Void) -> Void) -> V

    @Binding var isPresented: Bool

    let animated: Bool
    var transitionStyle: UIModalTransitionStyle = .coverVertical
    var presentStyle: UIModalPresentationStyle = .fullScreen
    let contentView: ContentViewBlock

    private weak var transitioningDelegate: UIViewControllerTransitioningDelegate?

    init(isPresented: Binding<Bool>,
         animated: Bool,
         transitionStyle: UIModalTransitionStyle,
         presentStyle: UIModalPresentationStyle,
         contentView: @escaping ContentViewBlock) {
        _isPresented = isPresented
        self.animated = animated
        self.transitionStyle = transitionStyle
        self.presentStyle = presentStyle
        self.contentView = contentView
    }

    @ViewBuilder
    public func body(content: Content) -> some View {
        content
            .onChange(of: isPresented) { _ in
                if isPresented {
                    DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(100)) {
                        let topMost = UIViewController.topMost
                        let rootView = contentView { [weak topMost] completion in
                            topMost?.dismiss(animated: animated) {
                                completion()
                                isPresented = false
                            }
                        }
                        let hostingVC = HomeIndicatorHideableHostingController(wrappedView: rootView)

                        if let customTransitioning = transitioningDelegate {
                            hostingVC.modalPresentationStyle = .custom
                            hostingVC.transitioningDelegate = customTransitioning
                        } else {
                            hostingVC.modalPresentationStyle = presentStyle
                            if presentStyle == .overFullScreen {
                                hostingVC.view.backgroundColor = .clear
                            }
                            hostingVC.modalTransitionStyle = transitionStyle
                        }

                        topMost?.present(hostingVC, animated: animated, completion: nil)
                    }
                }
            }
    }
}

然后你这样使用它:

struct ContentView: View {
    @State var modalPresented: Bool = false

    var body: some View {
        Button(action: {
            modalPresented = true
        }) {
            Text("First view")
        }
        .uiKitFullPresent(isPresented: $modalPresented) { closeHandler in
            SomeModalView(close: closeHandler)
        }
    }
}

struct SomeModalView: View {
    var close: (@escaping () -> Void) -> Void

    var body: some View {
        Button(action: {
            close({
                // Do something when dismiss animation finished
            })
        }) {
            Text("Tap to go back")
        }
    }
}
wztqucjr

wztqucjr7#

纯Swift 5和SwiftUI

您可以简单地将视图的背景设置为具有prefersHomeIndicatorAutoHiddentrue的UIHostingController的视图。

用法:

ContentView()
    .prefersHomeIndicatorAutoHidden()

定义:

import SwiftUI

extension View {
    func prefersHomeIndicatorAutoHidden() -> some View {
        background(HiddenHomeIndicatorHostingController())
    }
}

struct HiddenHomeIndicatorHostingController: UIViewControllerRepresentable {
    typealias UIViewControllerType = UIViewController

    func makeUIViewController(context: Context) -> UIViewController {
        HiddenHomeIndicatorViewController(rootView: AnyView(EmptyView()))
    }

    func updateUIViewController(_ uiViewController: UIViewController, context: Context) {}
}

final class HiddenHomeIndicatorViewController: UIHostingController<AnyView> {
    override var prefersHomeIndicatorAutoHidden: Bool {
        return true
    }
}

相关问题