如何通过自定义转换在当前上下文中呈现视图控制器?

时间:2017-08-30 17:18:43

标签: ios swift cocoa-touch uikit

我遇到了视图控制器包含问题,并希望在当前上下文中呈现视图控制器"自定义演示/动画。

我有一个根视图控制器,它有两个子视图控制器,可以作为子项添加到根目录中。当这些子视图控制器呈现视图控制器时,我希望该呈现是在当前上下文中,以便当从视图中移除所呈现的子项时,并且取消分配所呈现的模式也将被移除。此外,如果孩子A呈现一个视图控制器,我希望孩子B提供ViewController'在过去的情境中,财产是零的。即使A仍在呈现,也会呈现。

当我将所呈现的视图控制器的modalPresentationStyle设置为overCurrentContext时,以及子视图控制器将definesPresentationContext设置为true时,一切都按预期工作。

如果我将modalPresentationStyle设置为custom并覆盖shouldPresentInFullscreen在我的自定义演示文稿控制器中返回false,则无法正常工作。

以下是一个说明问题的示例:

import UIKit

final class ProgressController: UIViewController {
    private lazy var activityIndicatorView = UIActivityIndicatorView(activityIndicatorStyle: .white)
    private lazy var progressTransitioningDelegate = ProgressTransitioningDelegate()

    // MARK: Lifecycle

    override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
        super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)

        setup()
    }

    required public init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)

        setup()
    }

    override public func viewDidLoad() {
        super.viewDidLoad()

        view.backgroundColor = UIColor(white: 0, alpha: 0)

        view.addSubview(activityIndicatorView)
        activityIndicatorView.startAnimating()
    }

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

    override public func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()

        activityIndicatorView.center = CGPoint(x: view.bounds.width/2, y: view.bounds.height/2)
    }

    // MARK: Private

    private func setup() {
        modalPresentationStyle = .custom
        modalTransitionStyle = .crossDissolve
        transitioningDelegate = progressTransitioningDelegate
    }
}

final class ProgressTransitioningDelegate: NSObject, UIViewControllerTransitioningDelegate {
    func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? {
        return DimBackgroundPresentationController(presentedViewController: presented, presenting: source)
    }
}

final class DimBackgroundPresentationController: UIPresentationController {
    lazy var overlayView: UIView = {
        let v = UIView()
        v.backgroundColor = UIColor(white: 0, alpha: 0.5)
        return v
    }()

    override var shouldPresentInFullscreen: Bool {
        return false
    }

    override func presentationTransitionWillBegin() {
        super.presentationTransitionWillBegin()

        overlayView.alpha = 0
        containerView!.addSubview(overlayView)
        containerView!.addSubview(presentedView!)

        if let coordinator = presentedViewController.transitionCoordinator {
            coordinator.animate(alongsideTransition: { _ in
                self.overlayView.alpha = 1
            }, completion: nil)
        }
    }

    override func containerViewDidLayoutSubviews() {
        super.containerViewDidLayoutSubviews()

        overlayView.frame = presentingViewController.view.bounds
    }

    override func dismissalTransitionWillBegin() {
        super.dismissalTransitionWillBegin()

        let coordinator = presentedViewController.transitionCoordinator
        coordinator?.animate(alongsideTransition: { _ in
            self.overlayView.alpha = 0
        }, completion: nil)
    }
}

class ViewControllerA: UIViewController {
    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)

        definesPresentationContext = true

        let vc = ProgressController()
        self.present(vc, animated: true) {
        }
    }
}

class ViewController: UIViewController {

    let container = UIScrollView()

    override func viewDidLoad() {
        super.viewDidLoad()

        container.frame = view.bounds
        view.addSubview(container)

        let lhs = ViewControllerA()
        lhs.view.backgroundColor = .red

        let rhs = UIViewController()
        rhs.view.backgroundColor = .blue

        addChildViewController(lhs)
        lhs.view.frame = CGRect(x: 0, y: 0, width: view.bounds.width, height: view.bounds.height)
        container.addSubview(lhs.view)
        lhs.didMove(toParentViewController: self)

        addChildViewController(rhs)
        rhs.view.frame = CGRect(x: view.bounds.width, y: 0, width: view.bounds.width, height: view.bounds.height)
        container.addSubview(rhs.view)
        rhs.didMove(toParentViewController: self)


        container.contentSize = CGSize(width: view.bounds.width * 2, height: view.bounds.height)
    }

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
//        let rect = CGRect(x: floor(view.bounds.width/2.0), y: 0, width: view.bounds.width, height: view.bounds.height)
//        container.scrollRectToVisible(rect, animated: true)
    }
}
  1. 这将显示一个带有滚动视图的ViewController,其中包含两个子视图控制器
  2. 左侧的红色视图控制器将显示应呈现当前上下文的进度视图控制器。
  3. 如果视图控制器被正确呈现并且#34;在当前上下文中#34;那么你就可以滚动滚动视图,如果你选中了" presentsViewController"蓝色右侧视图控制器的属性然后它应该是零。这些都不是真的。
  4. 如果您将setup()上的ProgressController功能更改为:

    private func setup() {
        modalPresentationStyle = .overCurrentContext
        modalTransitionStyle = .crossDissolve
        transitioningDelegate = progressTransitioningDelegate
    }
    

    演示文稿/视图层次结构将按预期运行,但不会使用自定义演示文稿。

    Apple shouldPresentInFullscreen的文档似乎表明这应该有效:

      

    此方法的默认实现返回true,表示   演示文稿涵盖整个屏幕。你可以覆盖它   方法并返回false以强制演示文稿仅显示在   当前的背景。

         

    如果覆盖此方法,请不要调用super。

    我在iOS 10的Xcode 8和iOS 11的Xcode 9上进行了测试,上述代码在任何一种情况下都无法正常工作。

3 个答案:

答案 0 :(得分:3)

I'm going to guess that you've found a bug. The docs say that this can turn this into a watch presentation, but it does nothing. (In addition to your test and my test, I found a few online complaints about this, leading me to think that that's the case.)

The conclusion is that you cannot get the default {default: {…}, __esModule: true} default : beforeCreate : [ƒ] beforeDestroy : [ƒ] created : ƒ created() data : ƒ data() name : "lists" render : ƒ () staticRenderFns : [] watch : {$route: ƒ} _Ctor : {0: ƒ} __file : "../my-app/src/components/Lists.vue" __proto__ : Object behavior (where the runtime consults the source view controller and up the hierarchy looking for shouldPresentInFullscreen) if you use presentation style of currentContext.

I suggest filing a bug with Apple.

答案 1 :(得分:1)

我也认为shouldPresentInFullscreen基本上什么也没做。如果你问我一个错误。

要解决此问题,您应该覆盖frameOfPresentedViewInContainerView并返回调整后的框架。

然后,覆盖containerViewWillLayoutSubviews并设置self.presentedViewController.view.frame = self.frameOfPresentedViewInContainerView。在这里,您还应该设置您可能拥有的任何背景或铬视图的框架。

答案 2 :(得分:0)

最近我自己也遇到了同样的问题。您是对的,UIKit文档似乎在误导UIPresentationController.shouldPresentInFullscreen的行为。但是,在尝试使用此属性时,我发现将其设置为NO时,会将UITransitionView添加到窗口中,但是将其设置为YES时,则会将其添加到最近显示的UIViewController的UITransitionView中。这不包括其子项UIViewControllers,它们的definePresentationContext属性返回YES。

但是,如果在带有definePresentationContext == YES的UIViewController上呈现具有UIModalPresentationStyleOverCurrentContext的UIViewController,则UIKit会在呈现视图控制器上方的层次结构中插入呈现容器视图。如果在使用UIModalPresentationStyleCustom和UIPresentationController呈现的UIViewController呈现,其UIPresentInFullscreen属性返回NO,则呈现的UIViewController的呈现容器视图将插入到在子UIViewController的当前上下文中呈现的第一个UIViewController中。