SwiftUI中prefersHomeIndicatorAutoHidden
属性的UIKit等效于什么?
答案 0 :(得分:2)
我设法使用比Casper Zandbergen提出的技术更简单的技术将Home Indicator隐藏在单视图应用程序中。这样的方式不太“泛型”,我不确定首选项是否会沿视图层次结构传播,但就我而言,这就足够了。
在您的SceneDelegate子类中,UIHostingController以您的根视图类型作为通用参数,并覆盖 prefersHomeIndicatorAutoHidden 属性。
class HostingController: UIHostingController<YourRootView> {
override var prefersHomeIndicatorAutoHidden: Bool {
return true
}
}
在场景方法的例程中,创建一个自定义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()
}
答案 1 :(得分:2)
我的解决方案仅适用于一个屏幕 (UIHostingController
)。这意味着您不需要在整个应用程序中替换 UIHostingController
并处理 AppDelegate
。因此,它不会影响将您的 EnvironmentObject
注入 ContentView
。如果您只想显示一个带有可隐藏主页指示器的显示屏幕,您需要将视图围绕自定义 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 风格(在 iOS 14 上测试)。解决方案基于此:https://gist.github.com/fullc0de/3d68b6b871f20630b981c7b4d51c8373。如果您想使其适应 iOS 13,请查看链接(此处还可以找到 HomeIndicatorHideableHostingController
属性)。
您可以像 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)
}
}
}
}
}
答案 2 :(得分:1)
由于我也无法在默认API中找到它,所以我自己在UIHostingController的子类中创建了它。
我想要的是:
var body: some View {
Text("I hide my home indicator")
.prefersHomeIndicatorAutoHidden(true)
}
由于prefersHomeIndicatorAutoHidden
是UIViewController的属性,因此我们可以在UIHostingController中覆盖它,但是我们需要从视图中将prefersHomeIndicatorAutoHidden
设置为rootView,将其设置为rootView UIHostingController。
在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)
}
}
现在,如果我们在视图上添加.prefersHomeIndicatorAutoHidden(true)
,它将向视图层次结构发送PrefersHomeIndicatorAutoHiddenPreferenceKey。为了在托管控制器中了解到这一点,我制作了一个子类,该子类包装了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上也有preferredScreenEdgesDeferringSystemGestures
的完整示例:https://gist.github.com/Amzd/01e1f69ecbc4c82c8586dcd292b1d30d
答案 3 :(得分:0)
在SwiftUI 2.0中,我们需要使用包装器在@main .app文件中创建一个新变量:
@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
在class HostingController: UIHostingController<ContentView> {
override var prefersHomeIndicatorAutoHidden: Bool {
return true
}
}
类中进行覆盖:
const Generator = require('yeoman-generator');
module.exports = class extends Generator {
// The name `constructor` is important here
constructor(args, opts) {
// Calling the super constructor is important so our generator is correctly set up
super(args, opts);
// Next, add your custom code
this.argument("appname", {type: String, required: false});
}
async prompting(){
const answers = await this.prompt([
{
type: "input",
name: "name",
message: "Your project name",
default: this.appname
},
{
type: "confirm",
name: "cool",
message: "Would you like to enable the Cool Feature?"
},
{
type: "list",
name: "features",
message: "What do you want to make today?",
choices: [
"Some tests",
"Other tests, why not!",
"Another test?",
"Yes."
]
},
{
type: "checkbox",
name: "more",
message: "Anything more?",
choices: [
{
name: "I'm a bit bored",
value: "bored",
checked: true
},
{
name: "I'm busy doing something else.",
value: "smthelse",
checked: false
}
]
}
]);
this.log("app name", answers.name);
this.log("cool feature", answers.cool);
this.log("feature", answers.features);
this.log("more", answers.more.join(" + "))
}
};
当然不要忘了用不同的视图名称替换contentView!
对Swift和Kilo Loco的黑客入侵的Paul Hudson表示感谢!