Losing reference to transitioningDelegate before presetentation presentation
Asked Answered
P

2

8

I had a working custom UIPresentationController prior to Xcode beta 2 and iOS 10 beta 2. I haven't changed any code, but the presentation is now being presented with the standard modal presentation.

There is a note in the Apple sample code for UIPresentationController that says:

For presentations which will use a custom presentation controller, it is possible for that presentation controller to also be the transitioningDelegate. This avoids introducing another object or implementing in the source view controller.

transitioningDelegate does not hold a strong reference to its destination object.To prevent presentationController from being released prior to calling -presentViewController:animated:completion: the NS_VALID_UNTIL_END_OF_SCOPE attribute is appended to the declaration.

I've checked the transitioningDelegate on the presented view controller before and after the presentation. Before it is my custom UIPresentationController, but after it is nil. My guess is that the reference is being released, but I cannot find an equivalent to NS_VALID_UNTIL_END_OF_SCOPE in Swift. EDIT: I've verified that transitioningDelegate is set up until just before the presentation, and then is nil when it is time to present.

My code in the presenting view controller:

@IBAction func buttonAction(_ sender: UIButton) {
    let secondViewController = storyboard!.instantiateViewController(withIdentifier: "NewViewController") as! NewViewController
    let presentationController = MyPresentationController(presentedViewController: secondViewController, presenting: self)
    presentationController.initialFrame = button.frame
    secondViewController.transitioningDelegate = presentationController

    // Move map
    let pixelsToMove: CGFloat = mapView.frame.height / 4
    let region = self.mapView.region

    self.mapView.setRegion(region, offsetBy: pixelsToMove, animated: true)

    // Delegate to NewViewController
    secondViewController.mapView = mapView
    mapView.delegate = secondViewController

    print(secondViewController.transitioningDelegate)
    UIView.animate(withDuration: 0.3, animations: {
        let tabBar = self.tabBarController!.tabBar
        tabBar.frame.origin.y += tabBar.frame.height

        self.present(secondViewController, animated: true, completion: nil)
    })
}

And my code in the UIPresentationController:

override init(presentedViewController: UIViewController, presenting presentingViewController: UIViewController?) {
    super.init(presentedViewController: presentedViewController, presenting: presentingViewController)
    presentedViewController.modalPresentationStyle = .custom
}
Pillbox answered 22/7, 2016 at 21:3 Comment(4)
try declaring presentationController before outside the method.Baikal
Tried it, but still having the same issue.Pillbox
try this self.presentedViewController.transitioningDelegateBaikal
Like I said, the transitioningDelegate is being set, but just before the transition occurs the reference is lost.Pillbox
P
2

The problem was in beta 2 the method signatures inUIViewControllerTransitioningDelegate changed, and so they were not being called in my code. I don't understand why, but once again everything works perfect without explicitly storing a strong reference to the presentation controller.

Pillbox answered 7/8, 2016 at 17:5 Comment(1)
That was my problem. The delegate protocol has all optional functions, so my functions with incorrect signatures were silently ignored. For more info, see: https://mcmap.net/q/331382/-xcode-8-swift-3-modal-presentation-transitioning-delegate-not-calledCaesar
E
5

The transitioningDelegate property is a weak var. See the docs here. This means that the retain count for presentationController is not increased when setting secondViewController.transitioningDelegate = presentationController. Since you are instantiating presentationController in that method and nothing else has a strong reference to that object, it's retain count will go to 0 and it will be nil as soon as control is returned from that function (right after print(secondViewController.transitioningDelegate), since UIView.animate(...) is asynchronous).

You will need something to keep a strong reference to presentationController throughout the presentation of the view controller. With something holding on strongly to the reference, it's retain count will not go below 1 until you specifically set that reference to nil. One solution would be to keep it as a property of the current class or a property of secondViewController.

Elwell answered 1/8, 2016 at 18:54 Comment(4)
I'll try this today. Why would it have worked in iOS 10 beta 1, and 9.3.x without creating a strong reference to it?Pillbox
Unfortunately, I'm not sure. I would've guessed this wouldn't have worked previously either.Elwell
I tried storing it with a strong reference in the presenting, presented and presentation controllers and the transitioningDelegate is still becoming nil as the presentation happensPillbox
I vote for this answer. I have to hold a strong reference of the transitioningdelegate in my presenting viewcontroller. It won't be released when the presented viewcontroller dismissed and the dismissing transitioning animation was played perfectlyDominique
P
2

The problem was in beta 2 the method signatures inUIViewControllerTransitioningDelegate changed, and so they were not being called in my code. I don't understand why, but once again everything works perfect without explicitly storing a strong reference to the presentation controller.

Pillbox answered 7/8, 2016 at 17:5 Comment(1)
That was my problem. The delegate protocol has all optional functions, so my functions with incorrect signatures were silently ignored. For more info, see: https://mcmap.net/q/331382/-xcode-8-swift-3-modal-presentation-transitioning-delegate-not-calledCaesar

© 2022 - 2024 — McMap. All rights reserved.