I am using two UIHostingController
s 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()
}
}