I am using a custom UINavigationController with a custom pop animation and an interactive transition. The custom navigation controller works well for standard UIKit view controllers. However, when I push a UIHostingController that hosts a SwiftUI view, the SwiftUI view becomes unresponsive to tap gestures if the interactive transition is cancelled. The issue seems to occur specifically after the interactive transition is started and then cancelled.
This is interactive transition code
class CustomPopAnimator: NSObject, UIViewControllerAnimatedTransitioning {
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return 0.3
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let containerView = transitionContext.containerView
guard let fromView = transitionContext.view(forKey: .from),
let toView = transitionContext.view(forKey: .to) else {
return
}
containerView.insertSubview(toView, belowSubview: fromView)
let screenWidth = UIScreen.main.bounds.width
toView.transform = CGAffineTransform(translationX: -screenWidth / 3, y: 0)
UIView.animate(withDuration: transitionDuration(using: transitionContext), animations: {
fromView.transform = CGAffineTransform(translationX: screenWidth, y: 0)
toView.transform = .identity
}) { finished in
fromView.transform = .identity
toView.transform = .identity
fromView.isUserInteractionEnabled = true
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
}
}
}
class CustomInteractiveTransition: UIPercentDrivenInteractiveTransition {
var hasStarted = false
var shouldFinish = false
override func cancel() {
super.cancel()
reset()
}
override func finish() {
super.finish()
reset()
}
private func reset() {
hasStarted = false
shouldFinish = false
}
}
@objc
class CustomNavigationController: UINavigationController, UINavigationControllerDelegate {
private let customAnimator = CustomPopAnimator()
private let customInteractiveTransition = CustomInteractiveTransition()
override func viewDidLoad() {
super.viewDidLoad()
delegate = self
setupGesture()
}
private func setupGesture() {
let edgeSwipeGesture = UIScreenEdgePanGestureRecognizer(target: self, action: #selector(handleEdgeSwipe(_:)))
edgeSwipeGesture.edges = .left
view.addGestureRecognizer(edgeSwipeGesture)
}
@objc private func handleEdgeSwipe(_ gesture: UIScreenEdgePanGestureRecognizer) {
let translation = gesture.translation(in: view)
let progress = translation.x / view.bounds.width
switch gesture.state {
case .began:
customInteractiveTransition.hasStarted = true
popViewController(animated: true)
case .changed:
customInteractiveTransition.shouldFinish = progress > 0.5
customInteractiveTransition.update(progress)
case .ended:
customInteractiveTransition.hasStarted = false
customInteractiveTransition.shouldFinish ? customInteractiveTransition.finish() : customInteractiveTransition.cancel()
case .cancelled:
customInteractiveTransition.hasStarted = false
customInteractiveTransition.cancel()
default:
break
}
}
func navigationController(_ navigationController: UINavigationController, animationControllerFor operation: UINavigationController.Operation, from fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return operation == .pop ? customAnimator : nil
}
func navigationController(_ navigationController: UINavigationController, interactionControllerFor animationController: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
return customInteractiveTransition.hasStarted ? customInteractiveTransition : nil
}
func navigationController(_ navigationController: UINavigationController, didShow viewController: UIViewController, animated: Bool) {
if let hostingController = viewController as? AIMAHostingController<SwiftUIView> {
DispatchQueue.main.async {
hostingController.view.setNeedsLayout()
hostingController.view.layoutIfNeeded()
}
}
}
}
SwiftUI code
struct SwiftUIView: View {
var body: some View {
VStack {
Spacer()
Text("Hello, World!")
.id("123")
.background(Color.green)
.padding()
.onTapGesture {
print("===onclick")
}
Spacer()
}
}
}
let hostingController = UIHostingController(rootView: SwiftUIView())
navigationController?.pushViewController(hostingController, animated: true)