How to present a view controller in the current context using a custom transition?

I am faced with the problem of restraining the view controller and want to present view controllers "in the current context" using a special presentation / animation.

I have a root view controller that has two child view controllers that can be added and removed as children at the root. When these child view controllers present the view controller, I want the presentation to be in the current context, so that when the child object that is displayed is removed from the view and its release will also be deleted. Also, if the child of A represents the presentation controller, I would expect the child B 'presentationViewController' property to be zero in the "over current context" view, even if A still represents.

Everything works as expected when I set the modalPresentationStyle my submitted view controller to overCurrentContext , and if the child view controllers definesPresentationContext set to true.

This does not work if I expect this to happen if I have modalPresentationStyle set to custom and overriding shouldPresentInFullscreen returns false in my custom presentation controller.

Here is an example illustrating the problem:

 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) } } 
  • A ViewController with a scroll list that contains two view child controllers will be displayed.
  • The red view controller on the left side will represent the progress view controller, which should be displayed in the current context.
  • If the presentation controller was correctly presented "according to the current context", you could scroll the scroll, and if you checked the "presentationViewController" property of the blue view controller on the right, then it should be zero. Neither one nor the other is true.

If you change the setup() function on the ProgressController to:

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

The presentation / presentation hierarchy will behave as expected, but the user presentation will not be used.

Apple docs for shouldPresentInFullscreen seem to indicate that this should work:

By default, this method returns true, indicating that the presentation spans the entire screen. You can cancel this method and return false to make the presentation appear only in the current context.

If you override this method, do not call super.

I tested this on Xcode 8 on iOS 10 and Xcode 9 on iOS 11, and the above code will not work properly anyway.

+5
source share
2 answers

I'm going to guess that you found a mistake. The docs say that shouldPresentInFullscreen can turn this into a currentContext presentation, but does nothing. (In addition to your test and my test, I found several online complaints about this, which made me think that this was the case.)

The conclusion is that you cannot get the default behavior of currentContext (where the runtime will consult the controller of the original presentation and up the hierarchy looking for definesPresentationContext ) if you use the presentation style .custom .

I suggest filing a bug with Apple.

+1
source

I also believe that shouldPresentInFullscreen practically nothing. Error if you ask me.

To fix this, you must override frameOfPresentedViewInContainerView and return the adjusted frame.

Then override containerViewWillLayoutSubviews and set self.presentedViewController.view.frame = self.frameOfPresentedViewInContainerView . Here you should also set frames for any background or chrome representations you may have.

0
source

Source: https://habr.com/ru/post/1271371/


All Articles