AVPlayServiceController在iOS10的弹出窗口中搞乱了底层的模态视图控制器

AVPlayServiceController在iOS10的弹出窗口中搞乱了底层的模态视图控制器,ios,uiviewcontroller,uiinterfaceorientation,avplayerviewcontroller,Ios,Uiviewcontroller,Uiinterfaceorientation,Avplayerviewcontroller,我有一个应用程序(支持的界面方向-仅纵向),其中包含下一层次的模式显示视图控制器: A -> B -> AVP 其中A是位于选项卡栏控制器中的视图控制器,选项卡栏控制器依次是窗口的根 B是一个相当简单的视图控制器,带有按钮、图像和标签,但显示为弹出窗口: // ... presentation method in A let B = // create B B.modalPresentationStyle = .popover B.preferredContentSize = CG

我有一个应用程序(支持的界面方向-仅纵向),其中包含下一层次的模式显示视图控制器:

A -> B -> AVP
其中A是位于选项卡栏控制器中的视图控制器,选项卡栏控制器依次是窗口的根

B是一个相当简单的视图控制器,带有按钮、图像和标签,但显示为弹出窗口:

// ... presentation method in A
let B = // create B
B.modalPresentationStyle = .popover
B.preferredContentSize = CGSize(width: 300, height: 400)
B.isModalInPopover = true
if let BPopover = B.popoverPresentationController {
    BPopover.delegate = self
    BPopover.permittedArrowDirections = []
    let window = // grab current window
    BPopover.sourceView = window
    BPopover.sourceRect = window.bounds
    BPopover.passthroughViews = nil
}
self.tabBarController?.present(B, animated: true, completion: nil)
AVP是由B:

// This method is in B.
@IBAction func playVideoButtonPressed(_ sender: Any) {
    if let videoURL = self.videoURL {
        let videoPlayer = AVPlayer(url: videoURL)
        let videoVC = AVPlayerViewController()
        videoVC.player = videoPlayer
        self.present(videoVC, animated: true, completion: nil)
    }
}
在iOS 10.0上,如果执行以下步骤,我会遇到问题:

  • 目前的AVP
  • 旋转设备,使AVP以水平方向旋转视频
  • 我在水平方向上解除AVP(使用其系统提供的控件)
  • 当我回来时,我的视图控制器B被弄乱了-移动到窗口顶部,它的大小变小了(内部也被弄乱了,但我猜内部被弄乱了,这是我的自动布局约束的结果)

    这似乎不会发生在iOS 11上

    我能做些什么来修理它吗

    编辑:按要求截图(出于隐私原因隐藏了tabbar):

    其他信息:

    我还截获了一个代理回调以获取更多信息:

    func popoverPresentationController(_ popoverPresentationController: UIPopoverPresentationController,
                                       willRepositionPopoverTo rect: UnsafeMutablePointer<CGRect>,
                                       in view: AutoreleasingUnsafeMutablePointer<UIView>) {
        print("willRepositionPopoverTo")
        print(popoverPresentationController)
        print(rect.pointee)
        print(view.pointee)
    }
    
    func-popoverPresentationController(\uPopOverpresentationController:UIPopoverPresentationController,
    WillPopOverto-rect:UnsafemeutablePointer,
    视图中:AutoreleasingUnsafeMutablePointer){
    打印(“Will Popoverto”)
    打印(popoverPresentationController)
    打印(矩形指针)
    打印(view.pointee)
    }
    

    它将
    视图
    大小打印为
    (w:568;h:320)
    ,因此,当我旋转AVP控制器时,它似乎会改变我的应用程序的窗口方向,从而调整弹出窗口的大小。虽然它不会尝试重新调整大小:(在我关闭AVP后。

    根据描述,该应用程序仅设计为纵向模式。在视频模式下旋转设备,会导致您观察到的行为。解决方法是在关闭视频后覆盖方向。尝试添加

    let value = UIInterfaceOrientation.portrait.rawValue
    UIDevice.current.setValue(value, forKey: "orientation") 
    

    查看将出现的功能。这将解决这些随机行为的问题。

    我已成功复制了您的问题,因此您可以放心,知道这不仅仅是您。我还花了大量时间尝试使用各种“黑客”修复iOS 10行为。我最初没有成功。此外,尽管iOS 11解决了popover的定位问题,但它也引入了标签栏大小(图片)的错误。因此,我的解决方案也需要解决这个问题

    :

    更新的解决方法 在重新讨论这个问题时,我重新考虑了一个我原先排除的解决方案。结果表明,
    avplayervewcontroller
    只会影响其父
    UIWindow
    实例的方向。在iOS中使用额外的
    UIWindow
    s并不是一个常用的解决方案,但在这里效果很好

    解决方案是使用shim
    rootViewController
    创建一个清晰的
    UIWindow
    ,其唯一目的是在它出现的第二次时消除自身动画。此解决方案在iOS 10和iOS 11上同样有效。它不会改变用户体验(除了修复错误)

    步骤:

  • 创建香草
    ui窗口
    ,并使其背景清晰
  • 将窗口的rootVC设置为
    ShimVC
  • 使窗口键可见(因为一切都很清楚,用户什么也看不见)
  • 从窗口的rootVC中显示
    avplayervewcontroller
    (这样您就可以获得动画,就像其他方式一样)
  • 当视频播放器关闭时,shim view控制器将自动关闭。当窗口的rootVC关闭时,该窗口将被删除,原始主窗口将重新设置为关键帧
  • 下面是一个gif,显示了它的工作原理:

    最初提出的解决办法 我将提出一个解决方案。这是我以前在一些应用程序中见过的行为。基本上,不用从popover中演示视频播放器,而是使用AVC切换到B。可能还有其他解决方案需要稍微偏离常用的UIKit功能。(例如,可能实现您自己的演示控制器以实现popover,或者将popover实现为子视图控制器,以便您可以直接从a演示AVC

    在下面的解决方案中,
    A->B
    然后当用户按下播放器B时,通知A它需要显示AVC。这样你就得到了
    A->AVC
    。当AVC被解除时,我出现了B,所以你回到了
    A->B
    。总体来说,iOS 10或iOS 11上都没有UI问题,但是你的用户将需要等待额外的几分之一秒。如果时间紧迫,您可以禁用(或尝试缩短?)动画,但总体上感觉非常流畅和自然

    此外,我建议提交一份关于标签栏大小问题的雷达文件

    以下是我的解决方案:

    class A: UIViewController, UIViewControllerTransitioningDelegate {
    
        var popover: B?
    
        override func viewDidAppear(_ animated: Bool) {
            super.viewDidAppear(animated)
    
            if let popover = popover {
                if let ppc = popover.popoverPresentationController {
                    ppc.delegate = self
                    ppc.permittedArrowDirections = []
                    let window = view.window!
                    ppc.sourceView = window
                    ppc.sourceRect = window.frame
                    ppc.passthroughViews = nil
                }
    
                present(popover, animated: true, completion: nil)
            }
        }
    
        @IBAction func popover(_ sender: UIButton) {
    
            let b = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "B") as! B
            b.modalPresentationStyle = .popover
            b.preferredContentSize = CGSize(width: 300, height: 400)
            b.isModalInPopover = true
            b.delegate = self
    
            if let ppc = b.popoverPresentationController {
                ppc.delegate = self
                ppc.permittedArrowDirections = []
                let window = view.window!
                ppc.sourceView = window
                ppc.sourceRect = window.frame
                ppc.passthroughViews = nil
            }
    
            self.popover = b
    
            present(b, animated: true, completion: nil)
        }
    }
    
    extension A: UIPopoverPresentationControllerDelegate {
    
        func adaptivePresentationStyle(for controller: UIPresentationController) -> UIModalPresentationStyle {
            return .none
        }
    }
    
    extension A: BDelegate {
    
        func dismissB() {
            popover?.dismiss(animated: true)
            popover = nil
        }
    
        func showAVPlayerViewController(_ vc: AVPlayerViewController) {
    
            popover?.dismiss(animated: true) {
                // Dispatch async allows it to come up in landscape if the phone is already rotated
                DispatchQueue.main.async {
                    self.present(vc, animated: true)
                }
            }
        }
    }
    
    protocol BDelegate: class {
    
        func showAVPlayerViewController(_ vc: AVPlayerViewController)
        func dismissB()
    }
    
    class B: UIViewController {
    
        weak var delegate: BDelegate?
    
        let videoURL: URL? = Bundle.main.url(forResource: "ImAfraidWeNeedToUseMath", withExtension: "m4v")
    
        @IBAction func playVideo(_ sender: UIButton) {
    
            if let videoURL = self.videoURL {
                let videoPlayer = AVPlayer(url: videoURL)
                let videoVC = AVPlayerViewController()
                videoVC.player = videoPlayer
                delegate?.showAVPlayerViewController(videoVC)
            }
        }
    
        @IBAction func done(_ sender: UIButton) {
            delegate?.dismissB()
        }
    }
    

    不幸的是,这并不能解决我的问题。我将它添加到我的弹出式视图控制器
    viewwillbeen
    方法中。您可以发布一些截图吗?@ArunB在视频播放器前后添加了截图。@iur您正在使用吗?(它应该不在Info.plist中,或者设置为YES。)我最近在使用
    avplayervewcontroller
    时遇到了问题,这是因为它被禁用了。@allenhumpries它不在我的Info.plist中。这不完全是修复方法,但我会接受它作为答案。我最终编写了自己的全屏视频播放器视图控制器,它允许旋转(取决于设备方向,因此即使接口方向仅为纵向也有效)并在iOS10上使用。@iur Th
    class A: UIViewController, UIViewControllerTransitioningDelegate {
    
        var popover: B?
    
        override func viewDidAppear(_ animated: Bool) {
            super.viewDidAppear(animated)
    
            if let popover = popover {
                if let ppc = popover.popoverPresentationController {
                    ppc.delegate = self
                    ppc.permittedArrowDirections = []
                    let window = view.window!
                    ppc.sourceView = window
                    ppc.sourceRect = window.frame
                    ppc.passthroughViews = nil
                }
    
                present(popover, animated: true, completion: nil)
            }
        }
    
        @IBAction func popover(_ sender: UIButton) {
    
            let b = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "B") as! B
            b.modalPresentationStyle = .popover
            b.preferredContentSize = CGSize(width: 300, height: 400)
            b.isModalInPopover = true
            b.delegate = self
    
            if let ppc = b.popoverPresentationController {
                ppc.delegate = self
                ppc.permittedArrowDirections = []
                let window = view.window!
                ppc.sourceView = window
                ppc.sourceRect = window.frame
                ppc.passthroughViews = nil
            }
    
            self.popover = b
    
            present(b, animated: true, completion: nil)
        }
    }
    
    extension A: UIPopoverPresentationControllerDelegate {
    
        func adaptivePresentationStyle(for controller: UIPresentationController) -> UIModalPresentationStyle {
            return .none
        }
    }
    
    extension A: BDelegate {
    
        func dismissB() {
            popover?.dismiss(animated: true)
            popover = nil
        }
    
        func showAVPlayerViewController(_ vc: AVPlayerViewController) {
    
            popover?.dismiss(animated: true) {
                // Dispatch async allows it to come up in landscape if the phone is already rotated
                DispatchQueue.main.async {
                    self.present(vc, animated: true)
                }
            }
        }
    }
    
    protocol BDelegate: class {
    
        func showAVPlayerViewController(_ vc: AVPlayerViewController)
        func dismissB()
    }
    
    class B: UIViewController {
    
        weak var delegate: BDelegate?
    
        let videoURL: URL? = Bundle.main.url(forResource: "ImAfraidWeNeedToUseMath", withExtension: "m4v")
    
        @IBAction func playVideo(_ sender: UIButton) {
    
            if let videoURL = self.videoURL {
                let videoPlayer = AVPlayer(url: videoURL)
                let videoVC = AVPlayerViewController()
                videoVC.player = videoPlayer
                delegate?.showAVPlayerViewController(videoVC)
            }
        }
    
        @IBAction func done(_ sender: UIButton) {
            delegate?.dismissB()
        }
    }