UIViewController 通话状态栏问题

Posted

技术标签:

【中文标题】UIViewController 通话状态栏问题【英文标题】:UIViewController In-Call Status Bar Issue 【发布时间】:2017-09-21 13:45:29 【问题描述】:

问题:

在通话状态栏消失后,模态显示的视图控制器不会向后移动,顶部留下 20px 的空白/透明空间。


正常:没有问题


通话中:没有问题


通话消失后:

在顶部留下 20 像素高的空白/透明空间,显示下方的橙色视图。但是状态栏仍然存在于透明区域上。导航栏也为状态栏留出了空间,它的位置太低了 20px。


基于 ios 10 模态显示的视图控制器 自定义模态展示 主视图控制器后面是橙色的 不使用自动布局 当旋转到横向时,20px In-Call Bar 离开并仍然留下 20px 间隙。 我选择不以横向显示状态栏。 (即大多数股票应用)

我尝试听 App Delegates:

willChangeStatusBarFrame
didChangeStatusBarFrame

还查看基于控制器的通知:

UIApplicationWillChangeStatusBarFrame
UIApplicationDidChangeStatusBarFrame

当我为上述所有四种方法记录呈现视图的框架时,该框架始终位于 (y: 0) 原点。


更新

视图控制器自定义模态展示

    let storyboard = UIStoryboard(name: "StoryBoard1", bundle: nil)
    self.modalVC = storyboard.instantiateViewController(withIdentifier: "My Modal View Controller") as? MyModalViewController
    self.modalVC!.transitioningDelegate = self
    self.modalVC.modalPresentationStyle = .custom
    self.modalVC.modalPresentationCapturesStatusBarAppearance = true;
    self.present(self.modalVC!, animated: true, completion: nil)


    func animateTransition(using transitionContext: UIViewControllerContextTransitioning) 
        let containerView = transitionContext.containerView
        let fromViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from)
        let toViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to)
        toViewController!.view.transform = CGAffineTransform(scaleX: 0.001, y: 0.001)

        UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 0.8, initialSpringVelocity: 0.0, options: [.curveEaseOut], animations:  () -> Void in

            toViewController!.view.transform = CGAffineTransform.identity

        , completion:  (completed) -> Void in

           transitionContext.completeTransition(completed)

        )
 

【问题讨论】:

“自定义模态演示”是什么意思?您能否详细说明您是如何呈现模态 VC 的? 更新了 OP。在过渡动画中,我只是将其缩小并以 1:1 的比例制作动画。 你的模态VC通过设置这个属性modalPresentationCapturesStatusBarAppearance来控制状态栏的外观。您是否覆盖了模态 VC 的 preferredStatusBarStyleprefersStatusBarHidden 方法? modalPresentationCapturesStatusBarAppearance 如您所见为真。我也确实覆盖了preferredStatusBarStyle 和prefersStatusBarHidden 方法。状态栏在正确的时间正确显示。仅当 In-Call 消失时,视图才不会调整大小。 不幸的是,自己没有时间检查这种行为。但是这个问题很有趣,我很喜欢它:) 到目前为止运气好吗? 【参考方案1】:

3 天来我一直在寻找解决方案。我不喜欢这个解决方案,但没有找到更好的解决方法。

当 rootViewController 视图的高度比窗口高 20 点时,当我收到有关状态栏高度更新的通知时,我会手动设置正确的值。

将方法添加到 AppDelegate.swift

func application(_ application: UIApplication, didChangeStatusBarFrame oldStatusBarFrame: CGRect) 
        if let window = application.keyWindow 
            window.rootViewController?.view.frame = window.frame
        
    

之后它会按预期工作(即使在方向改变之后)。 希望它会对某人有所帮助,因为我在这方面花费了太多时间。

附:它会闪烁一点,但有效。

【讨论】:

这是唯一一个对我有用的简单解决方案,即使在 iOS12.1 上没有闪烁 willChangeStatusBarFrame 现在对我有用【参考方案2】:

我也遇到过这个问题,但是用了这个方法后,问题就消失了。

iOS 有它的默认方法willChangeStatusBarFrame 来处理状态栏。请把这个方法检查一下。

func application(_ application: UIApplication, willChangeStatusBarFrame newStatusBarFrame: CGRect) 
    UIView.animate(withDuration: 0.35, animations: () -> Void in
        let windowFrame: CGRect? = ((window?.rootViewController? as? UITabBarController)?.viewControllers[0] as? UINavigationController)?.view?.frame
        if newStatusBarFrame.size.height > 20 
            windowFrame?.origin?.y = newStatusBarFrame.size.height - 20
            // old status bar frame is 20
        
        else 
            windowFrame?.origin?.y = 0.0
        
        ((window?.rootViewController? as? UITabBarController)?.viewControllers[0] as? UINavigationController)?.view?.frame = windowFrame
    )

希望这篇文章对你有所帮助。 谢谢

【讨论】:

当我在我的 OP 中提到这个委托调用时,我尝试了这个。这会暂时修复它,直到您将视图旋转到不同的方向。由于我没有在横向中显示状态栏,因此这会破坏方向更改的布局。混乱..混乱.. 另外,仅移动原点是不够的,似乎。框架也加高了…… @Gizmodo 尝试编写一个条件来检查设备是处于纵向还是横向模式,如果处于横向模式,则状态栏中没有任何变化,否则按照方法进行更改。尝试一次。 好吧,在横向模式中,In-Call 消失后会留下 20px 的空白空间。这使它与肖像相同。在这两种情况下,当我检查顶视图控制器框架的 Y 原点时,它都是 0。我想知道这是否是一个错误。 但是你在横向模式下隐藏状态栏,试试 unhidden @Gizmodo【参考方案3】:

我在修改状态栏时遇到了同样的问题。 解决方案是向系统通知注册状态栏框架的更改,这将允许您更新布局并解决您可能遇到的任何布局问题。 我的解决方案应该对您完全一样:

在您的视图控制器中,在viewWillAppear 中订阅UIApplicationDidChangeStatusBarFrameNotification

    NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(myControllerName.handleFrameResize(_:)), name: UIApplicationDidChangeStatusBarFrameNotification, object: nil)

创建你的选择器方法

func handleFrameResize(notification: NSNotification) 
self.view.layoutIfNeeded() 

viewWillDisappear的通知中心移除你的控制器

    NSNotificationCenter.defaultCenter().removeObserver(self, name: UIApplicationDidChangeStatusBarFrameNotification, object: nil)

你还需要你的modal负责状态栏所以你应该设置

destVC.modalPresentationCapturesStatusBarAppearance = true 在呈现视图之前。

您可以在每个容易在状态栏上发生更改的控制器上实现此功能,或者您可以创建另一个类来为每个控制器执行此操作,例如将 self 传递给方法,保留更改布局的引用和有一种方法可以删除自我。你知道,为了重用代码。

【讨论】:

我在 OP 中提到我确实使用了 UIApplicationWillChangeStatusBarFrame、UIApplicationDidChangeStatusBarFrame。问题是,模式视图控制器的原点正在注销 (0, 0) 的原点,即使它的 20px 太低。 不确定您使用它完成了什么。将进行一些测试,我会尽快回复您以获取最新答案 @Gizmodo 在呈现之前将 modalPresentationCapturesStatusBarAppearance 添加到模态似乎可以解决问题。我的堆栈与您的堆栈相同,并且对我有用。我不知道它会对视图的移动做出什么反应,如果这不起作用,您应该考虑为此使用自动布局 看我上面的代码,我以为我在演示之前就在做? @Gizmodo 您是否尝试将您的模式嵌入到导航控制器中?【参考方案4】:

我认为这是 UIKit 中的一个错误。当状态栏恢复到正常大小时,包含使用自定义转换呈现的呈现控制器视图的containerView 似乎没有完全向后移动。 (您可以在关闭通话状态栏后查看视图层次结构)

为了解决这个问题,您可以在演示时提供自定义演示控制器。然后,如果您不需要呈现控制器的视图保留在视图层次结构中,您只需返回 true 以获得呈现控制器的 shouldRemovePresentersView 属性,就是这样。

func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? 
    return PresentationController(presentedViewController: presented, presenting: presenting)


class PresentationController: UIPresentationController 
    override var shouldRemovePresentersView: Bool 
        return true
    

或者如果您需要保留当前控制器的视图,您可以观察状态栏框架的变化并手动将containerView调整为与其父视图相同的大小

class PresentationController: UIPresentationController 
    override init(presentedViewController: UIViewController, presenting presentingViewController: UIViewController?) 
        super.init(presentedViewController: presentedViewController, presenting: presentingViewController)
        NotificationCenter.default.addObserver(self,
                                               selector: #selector(self.onStatusBarChanged),
                                               name: .UIApplicationWillChangeStatusBarFrame,
                                               object: nil)
    

    @objc func onStatusBarChanged(note: NSNotification) 
        //I can't find a way to ask the system for the values of these constants, maybe you can
        if UIApplication.shared.statusBarFrame.height <= 20,
            let superView = containerView?.superview 
            UIView.animate(withDuration: 0.4, animations: 
                self.containerView?.frame = superView.bounds
            )
        
    

【讨论】:

【参考方案5】:

我一直在寻找解决这个问题的方法。事实上,我发布了一个与此类似的新问题。这里:How To Avoid iOS Blue Location NavigationBar Messing Up My StatusBar?

相信我,这几天我一直在解决这个问题,但由于 iOS 的状态栏会随通话中、热点和位置的变化而变化,因此屏幕混乱真的很烦人。

我已经尝试实现 Modi 的答案,我将那段代码放在我的 AppDelegate 中并对其进行了一些修改,但没有运气。我相信 iOS 会自动执行此操作,因此您不必自己实现。

在我发现问题的罪魁祸首之前,我确实尝试了这个特定问题的所有解决方案。无需实现AppDelegate的方法willChangeStatusBar...或添加通知观察statusBar变化。

我还重做了我项目的一些流程,以编程方式制作了一些屏幕(我正在使用情节提要)。我做了一些实验,然后检查了我以前和其他当前的项目,为什么他们正确地进行了调整:)

底线是:我用 UITabBarController 以这种错误的方式显示我的主屏幕。

请始终注意modalPresentationStyle。由于 Noah 的评论,我有了查看我的代码的想法。

示例:

func presentDashboard() 
    if let tabBarController = R.storyboard.root.baseTabBarController() 
        tabBarController.selectedIndex = 1
        tabBarController.modalPresentationStyle = .fullScreen
        tabBarController.modalTransitionStyle = .crossDissolve

        self.baseTabBarController = tabBarController
        self.navigationController?.present(tabBarController, animated: true, completion: nil)
    

【讨论】:

【参考方案6】:

我用一行代码解决了这个问题

在目标 C 中

tabBar.autoresizingMask = (UIViewAutoResizingFlexibleWidth | UIViewAutoResizingFlexibleTopMargin);

在 Swift 中

self.tabBarController?.tabBar.autoresizingMask = 
  UIViewAutoresizing(rawValue: UIViewAutoresizing.RawValue(UInt8(UIViewAutoresizing.flexibleWidth.rawValue) | UInt8(UIViewAutoresizing.flexibleTopMargin.rawValue)))`

您只需要从顶部使 tabBar 的 autoresizingMask 变得灵活。

【讨论】:

这在 mytabbar 控制器或带有过渡的视图中对我不起作用【参考方案7】:

就我而言,我正在为我的 ViewController 使用自定义演示样式。 问题是Y位置没有计算好。 假设原始屏幕高度为 736p。 尝试打印view.frame.origin.yview.frame.height,你会发现高度是716p,y是20。 但显示高度为 736 - 20(通话状态栏额外高度) - 20(y 位置)。 这就是为什么我们的视图是从 ViewController 的底部切开的,以及为什么到顶部有 20p 的边距。 但是如果你回去看看导航控制器的帧值。 你会发现无论通话状态栏是否显示,y位置始终为0。

所以,我们要做的就是将 y 位置设置为零。

override func viewDidAppear(_ animated: Bool) 
    super.viewDidAppear(animated)

    let f = self.view.frame

    if f.origin.y != 0 
        self.view.frame = CGRect(x: f.origin.x, y: 0, width: f.width, height: f.height)
        self.view.layoutIfNeeded()
        self.view.updateConstraintsIfNeeded()
    

【讨论】:

【参考方案8】:

确保在容器视图被添加到容器视图之后,将您呈现的视图控制器视图的框架设置为容器视图的边界。这为我解决了这个问题。

containerView.addSubview(toViewController.view)
toViewController.view.frame = containerView.bounds

【讨论】:

以上是关于UIViewController 通话状态栏问题的主要内容,如果未能解决你的问题,请参考以下文章

通话时iOS状态栏高度不正确

使用自定义模式演示处理通话状态栏

通话状态栏更改时查看调整大小[重复]

jQuery - 检测 iPhone 通话状态栏

当用户在通话状态栏中时,导航栏布局出现异常

检测用户是不是有通话状态栏