// 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
}
}
//Application.swift
@main
struct Application: App {
@UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
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()
}
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")
}
}
}
7条答案
按热度按时间hgb9j2n61#
因为我在默认的API中也找不到这个,所以我自己在UIHostingController的子类中创建了它。
我想要的:
由于
prefersHomeIndicatorAutoHidden
是UIViewController上的一个属性,我们可以在UIHostingController中覆盖它,但是我们需要从我们的视图中获取prefersHomeIndicatorAutoHidden
来设置视图层次结构,我们将其设置为UIHostingController中的rootView。我们在SwiftUI中实现这一点的方式是PreferenceKeys。网上有很多很好的解释。
因此,我们需要一个PreferenceKey来将值发送到UIHostingController:
现在,如果我们在View上添加
.prefersHomeIndicatorAutoHidden(true)
,它会将DataHomeIndicatorAutoHiddenPreferenceKey发送到视图层次结构中。为了在主机控制器中捕获这一点,我做了一个子类来 Package rootView来监听首选项的变化,然后更新UIViewController.prefersHomeIndicatorAutoHidden
:完整的例子,没有暴露PreferenceKey类型,并且在git上也有
preferredScreenEdgesDeferringSystemGestures
:https://gist.github.com/Amzd/01e1f69ecbc4c82c8586dcd292b1d30diugsix8n2#
iOS 16
您可以使用
.persistentSystemOverlays
并传入.hidden
来隐藏所有自动放置在UI上的 * 非 transient 系统视图 *s5a0g9ez3#
针对SwiftUI新的应用生命周期
从SwiftUI 2.0开始,当使用新的应用程序生命周期时,我们需要在我们的@main .app文件中创建一个新的变量,并使用 Package 器:
@UIApplicationDelegateAdaptor(MyAppDelegate.self) var appDelegate
主应用程序文件看起来像这样:
然后我们在一个新文件中创建我们的UIApplicationDelegate类:
上面我们将SceneDelegate类的名称传递为“MySceneDelegate”,所以让我们在一个单独的文件中创建这个类:
属性
prefersHomeIndicatorAutoHidden
必须在HostingController
类中被重写,就像ShengChaLover在上面的解决方案中一样:当然,如果不同的话,不要忘记用你的视图的名称替换contentView!
称赞保罗哈德逊的黑客与斯威夫特和基洛疯子的提示!
cgyqldqp4#
我发现的唯一一个100%有效的解决方案是在所有UIViewController中混合示例属性'prefersHomeIndicatorAutoHidden',这样它总是返回true。
在NSObject上创建一个扩展,用于混合示例方法/属性
在UIViewController上创建扩展,这将交换所有视图控制器中的示例属性,并将其与我们创建的始终返回true的视图控制器中的示例属性交换
然后在应用程序委托中调用swizzleHomeIndicatorProperty()函数
如果使用SwiftUI,请使用UIApplicationDelegateAdaptor注册AppDelegate
7bsow1i65#
我已经设法隐藏在我的单视图应用程序中的家庭指标使用的技术比卡斯珀Zandbergen提出的更简单。它的方式少'通用',我不确定首选项将传播下来的视图层次结构,但在我的情况下,这就足够了。
在SceneDelegate子类中,使用根视图类型作为泛型参数并覆盖 prefersHomeIndicatorAutoHidden 属性的UIHostingController。
在scene方法的例程中,创建一个自定义HostingController的示例,像往常一样传递根视图,并将该示例分配给窗口的rootViewController:
更新:如果你需要将一个EnvironmentObject注入到根视图中,这个**将不起作用。
ozxc1zmp6#
我的解决方案只适用于一个屏幕(
UIHostingController
)。这意味着您不需要在整个应用程序中替换UIHostingController
并处理AppDelegate
。因此,它不会影响将EnvironmentObject
s注入ContentView
。如果你只想有一个显示的屏幕与隐藏的主页指示器,你需要 Package 你的视图周围自定义UIHostingController
和显示它。这可以这样做(或者如果你想在运行时更改属性,你也可以像前面的答案一样使用
PreferenceUIHostingController
。但我想这需要更多的变通方法):然后,您必须以UIKit风格呈现您的
HomeIndicatorHideableHostingController
(在iOS 14上测试)。解决方案基于此:https://gist.github.com/fullc0de/3d68b6b871f20630b981c7b4d51c8373。如果你想让它适应iOS 13,请查看链接(topMost
属性也在那里)。您可以像
fullScreenCover
一样为它创建视图修改器:修改自己:
然后你这样使用它:
wztqucjr7#
纯Swift 5和SwiftUI
您可以简单地将视图的背景设置为具有
prefersHomeIndicatorAutoHidden
到true
的UIHostingController的视图。用法:
定义: