Animation alongside dismissal animation of UISheetPresentationController

Viewed 223

I use the Apple's new UISheetPresentationController and want to update the layout of the underlying view controller when the sheet is presented and dismissed.

When the user starts to pull down the sheet, func presentationControllerWillDismiss(_ presentationController: UIPresentationController) of UISheetPresentationControllerDelegate is called and I start my animation like so

presentationController.presentedViewController.transitionCoordinator?.animate(
    alongsideTransition: { _ in
        // my animations
    },
    completion: nil
)

This works perfectly when the interaction is not too fast. If it is fast, it seems like the animation gets out of sync:

enter image description here

I also added a handler which logs when the animation is cancelled:

transitionCoordinator.notifyWhenInteractionChanges() {
  print("cancelled: \(context.isCancelled)")
}

And it looks like the animation is cancelled but the func presentationControllerWillDismiss is not called again. When the the gesture is not too fast, the animation is also cancelled but afterwards presentationControllerWillDismiss is called.

Do you think it is a bug that the animation is cancelled but presentationControllerWillDismiss is not called again? And does someone maybe know a simple workaround?

Minimal reproducible Example:

import UIKit

class ViewController: UIViewController {
    
    lazy var openSheetButton: UIButton = {
        let button = UIButton()
        button.translatesAutoresizingMaskIntoConstraints = false
        button.setTitle("Open Sheet", for: .normal)
        button.backgroundColor = .systemGreen
        button.addTarget(
            self,
            action: #selector(didTapOpenSheetButton(_:)),
            for: .touchDown
        )
        return button
    }()
    
    let orangeView: UIView = {
        let view = UIView()
        view.translatesAutoresizingMaskIntoConstraints = false
        view.backgroundColor = .systemOrange
        return view
    }()
    
    var bottomConstraint: NSLayoutConstraint?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        positionElements()
    }
    
    func positionElements() {
        
        // Orange view
        
        view.addSubview(orangeView)
        orangeView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 16).isActive = true
        orangeView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 16).isActive = true
        orangeView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -16).isActive = true
        
        bottomConstraint = orangeView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -200)
        bottomConstraint?.isActive = true
        
        // Open Sheet Button
        
        orangeView.addSubview(openSheetButton)
        openSheetButton.centerXAnchor.constraint(equalTo: orangeView.centerXAnchor).isActive = true
        openSheetButton.topAnchor.constraint(equalTo: orangeView.topAnchor, constant: 8).isActive = true
    }
    
    @objc
    func didTapOpenSheetButton(_ sender: Any) {
        
        // Create modal view controller
        
        let viewController = UIViewController()
        viewController.view.backgroundColor = .systemGreen
        guard let sheet = viewController.sheetPresentationController else { return }
        
        // Configure sheet
        
        sheet.detents = [.medium()]
        sheet.prefersGrabberVisible = true
        sheet.delegate = self
        
        // Present modal view controller
        
        present(viewController, animated: true)
        
        // Animate something alondside transition (e.g. bottom constraint of orange container)
        
        transitionCoordinator?.animate(
            alongsideTransition: { _ in
                self.bottomConstraint?.constant = -UIScreen.main.bounds.height / 2
                self.view.layoutIfNeeded()
            },
            completion: nil
        )
    }
}

extension ViewController: UISheetPresentationControllerDelegate {
    
    func presentationControllerWillDismiss(_ presentationController: UIPresentationController) {
        
        print("presentationControllerWillDismiss called")
        
        // Animate something alongside dismissal transition
        
        presentationController.presentedViewController.transitionCoordinator?.animate(
            alongsideTransition: { _ in
                self.bottomConstraint?.constant = -UIScreen.main.bounds.height / 2
                self.view.layoutIfNeeded()
                self.bottomConstraint?.constant = -200
                self.view.layoutIfNeeded()
            },
            completion: nil
        )
        
        // Print when transition was cancelled
        
        presentationController.presentedViewController.transitionCoordinator?.notifyWhenInteractionChanges() { context in
            
            print("notifyWhenInteractionChanges: \(context.isCancelled)")
        }
    }
}
0 Answers
Related