Reputation: 2467
I created a simple custom transition animation for UIViewController
with UIViewControllerAnimatedTransitioning
. Everything is working, but when target view presents, content starts appear from bottom right corner and when view is disappear, content disappear from left top corner. I attached gif with example. This is my code:
CustomViewAnimator.swift
class CustomViewAnimator: NSObject, UIViewControllerAnimatedTransitioning {
private let interval = 0.5
var forward = false
var originFrame: CGRect = .zero
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return interval
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let containerView = transitionContext.containerView
guard let toView = transitionContext.view(forKey: .to) else {return}
guard let fromView = transitionContext.view(forKey: .from) else {return}
let destinationView = forward ? toView : fromView
destinationView.alpha = forward ? 0 : 1
forward ? containerView.addSubview(toView) : containerView.insertSubview(toView, belowSubview: fromView)
forward ? (destinationView.frame = originFrame) : (destinationView.frame = fromView.frame)
UIView.animate(withDuration: interval, animations: {
self.forward ? (destinationView.frame = fromView.frame) : (destinationView.frame = self.originFrame)
destinationView.alpha = self.forward ? 1 : 0
}) { (finished) in
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
}
}
}
MainViewController.self
class MainViewController: UIViewController {
private var button: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
setup()
}
@objc private func openView() {
let viewController = TargetViewController()
viewController.transitioningDelegate = self
navigationController?.pushViewController(viewController, animated: true)
}
}
extension MainViewController: UINavigationControllerDelegate {
func navigationController(_ navigationController: UINavigationController, animationControllerFor operation: UINavigationController.Operation, from fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
let animator = CustomViewAnimator()
animator.originFrame = button.frame
animator.forward = (operation == .push)
return animator
}
}
extension MainViewController: UIViewControllerTransitioningDelegate {
func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
let animator = CustomViewAnimator()
animator.forward = true
return animator
}
func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
let animator = CustomViewAnimator()
animator.forward = false
return animator
}
}
TargetViewController.swift
class TargetViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
setup()
}
}
extension TargetViewController {
fileprivate func setup() {
view.backgroundColor = .orange
navigationItem.title = "Target view"
let label: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.text = "Hello from target view :)"
label.textColor = .white
label.font = UIFont.boldSystemFont(ofSize: 18)
return label
}()
view.addSubview(label)
label.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
label.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
}
}
Can anybody say how I can fix this issue, so label(or any other content) in TargetViewController stayed fixed on their places?
Upvotes: 3
Views: 199
Reputation: 11529
I agree with the discussion from other answers. My suggestion is instead of animating the frame of UIView, you may animate the mask of the destination View as following. It should be the effect of what the oper really wants.
The bold part is the difference from the original post.
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let containerView = transitionContext.containerView
guard let toView = transitionContext.view(forKey: .to) else {return}
guard let fromView = transitionContext.view(forKey: .from) else {return}
let destinationView = forward ? toView : fromView
destinationView.alpha = forward ? 0 : 1
forward ? containerView.addSubview(toView) : containerView.insertSubview(toView, belowSubview: fromView)
forward ? (destinationView.frame = toView.frame) : (destinationView.frame = fromView.frame)
UIView.animate(withDuration: interval, animations: {
destinationView.alpha = self.forward ? 1 : 0
}) { (finished) in
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
}
}
Upvotes: 1
Reputation: 3410
Basically your problem is because of mixing Auto Layout and frames. You position subviews (button and label) in ViewController
s' views with constraints but for animation you use frames updates. So as I understand correctly how layout works your label constraints are not active during transition or at least are not used by layout engine to calculate position of label.
So, the best advice is to avoid frame animation in transition and try to create appropriate constraints for .from
and .to
views and animate constraints' constants. It would take more code, but you can be sure that you don't mix two different strategies for layout.
Easier solution is to tell containerView
to calculate constraints and to position elements appropriately during animation by simply add containerView.layoutIfNeeded()
to animations block. So it would be:
UIView.animate(withDuration: interval, animations: {
self.forward ? (destinationView.frame = fromView.frame) : (destinationView.frame = self.originFrame)
destinationView.alpha = self.forward ? 1 : 0
containerView.layoutIfNeeded()
}) { (finished) in
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
}
But that only makes things work and doesn't solve the main problem with simultaneous usage of two layout strategies.
Upvotes: 1