ios – Accessing SwiftUI subviews to animate in UiKit


I am using two UIHostingControllers with SwiftUI views, presenting one of them modally using .present(...).
In my SwiftUI view hierarchy of both of the rootViews of the UIHostingControllers there is one subview that I want to animate “into” each other, i.e. transition one frame over the other when dismissing the modal viewcontroller.


Option 1: In pure SwiftUI, this could easily be done using matchedGeometryEffect, however this does not work since they are not part of the same view, both have their own UIHostingController that they are part of.

Option 2: In pure UiKit, this could be done implementing a UIViewControllerAnimatedTransitioning and UIViewControllerTransitioningDelegate. However, in order to animate, one would need the UIViews and their frame and use something like UIView.animateKeyframes(...).

How can this be accomplished in above described case? In my understanding one cannot access the subviews of a SwiftUI view using something like view.subviews.... Could I implement some sort of modifier to add to the relevant SwiftUI subviews to expose them to UiKit? If so, how can this be done?

How else can one implement custom transitions / animations between views inside different UIHostingControllers?


UIHostingControllers code:

class MyViewController: UIViewController, ViewController {
    
    weak var viewModel: MyViewModel!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        view.backgroundColor = UIColor(named: "background")
    }
    
    func setup() {
        let myView = MyView(viewModel: self.viewModel)
        let vc = UIHostingController(rootView: myView)
        addChild(vc)
        vc.view.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(vc.view)
        vc.didMove(toParent: self)

        NSLayoutConstraint.activate([
            vc.view.widthAnchor.constraint(equalTo: view.widthAnchor),
            vc.view.heightAnchor.constraint(equalTo: view.heightAnchor),
            vc.view.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            vc.view.centerYAnchor.constraint(equalTo: view.centerYAnchor)
        ])
    }
}

In some other part of the code I call:

viewController.modalPresentationStyle = .fullScreen
viewController.modalTransitionStyle = .crossDissolve
navigationController.present(viewController, animated: true)

And the incomplete code for the custom transition:

class ShrinkDismissAnimationController: NSObject, UIViewControllerAnimatedTransitioning {
    
    func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
        return 0.6
    }
    
    func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
        guard let fromVC = transitionContext.viewController(forKey: .from),
              let toVC = transitionContext.viewController(forKey: .to),
              let fromVCStatusCapture = fromVC as? MyViewController,
              let imageView = fromVCStatusCapture.view.subviews.first(where: { $0 is Image }) // <- HERE! this doesn't work, how can I access a SwiftUI subview ???
        else {
            return
        }
        // ...
        UIView.animateKeyframes(
            withDuration: duration,
            delay: 0,
            options: .calculationModeCubic,
            animations: {
        // ...
        })
     }
}

class ShrinkDismissTransition: NSObject, UIViewControllerTransitioningDelegate {
    func animationController(forDismissed dismissed: UIViewController)
    -> UIViewControllerAnimatedTransitioning? {
        guard let _ = dismissed as? MyViewController else {
            return nil
        }
        return ShrinkDismissAnimationController()
    }
}

Latest articles

spot_imgspot_img

Related articles

Leave a reply

Please enter your comment!
Please enter your name here

spot_imgspot_img