Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

iOS Swift: UIViewControllerAnimatedTransitioning end frame in wrong position

I have a Swift project, learning a weather API and also trying to get a better handle on the AnimatedTransitions. I have a UITableView using a custom UITableViewCell with images and text. Tapping a cell in the tableView transitions to a new UIViewController as a Show (push), with the whole thing embedded in a UINavigationController.

When the transition is invoked, the image from the cell is supposed to move to the final location of the UIImageView on the destination viewController. However, what it does is move past that point to the far side of the screen before the transition completes and the view changes, making the image appear to snap back to the center of the view.

screenshots

I have read a lot of tutorials trying to fix this and have read a lot of StackOverflow but have failed to figure it out. Can someone point out to me what I have missed, please? I'm going crazy, here.

The segue that invokes the transition, in the original ViewController:

func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
  self.performSegueWithIdentifier("SHOW_DETAIL", sender: self)
}

override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
  if segue.identifier == "SHOW_DETAIL" {
    let detailVC = segue.destinationViewController as DetailViewController
    let indexPathForForecast = self.tableView.indexPathForSelectedRow() as NSIndexPath!
    let detailForecast = self.forecasts?[indexPathForForecast.row]
    let cell = self.tableView.cellForRowAtIndexPath(indexPathForForecast) as WeatherCell
    let image = cell.forecastImage.image
    detailVC.forecastForDetail = detailForecast
    detailVC.forecastDetailImage = image
  }
}

func navigationController(navigationController: UINavigationController, animationControllerForOperation operation: UINavigationControllerOperation, fromViewController fromVC: UIViewController, toViewController toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
  if fromVC == self && toVC.isKindOfClass(DetailViewController) {
    let transitionVC = AnimateToDetailVCController()
    return transitionVC
  } else {
    return nil
  }
}

And here's the animateTransition code from the UIViewControllerAnimatedTransitioning object (EDIT: solution code edited into code block, thanks @jrturton!)

func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
  let fromViewController = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey) as ViewController
  let toViewController = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey) as DetailViewController

  let containerView = transitionContext.containerView()
  let duration = self.transitionDuration(transitionContext)

  let selectedRow = fromViewController.tableView.indexPathForSelectedRow()
  let cell = fromViewController.tableView.cellForRowAtIndexPath(selectedRow!) as WeatherCell
  let weatherSnapshot = cell.forecastImage.snapshotViewAfterScreenUpdates(false)
  weatherSnapshot.frame = containerView.convertRect(cell.forecastImage.frame, fromView: fromViewController.tableView.cellForRowAtIndexPath(selectedRow!)?.superview)
  cell.forecastImage.hidden = true

  toViewController.view.frame = transitionContext.finalFrameForViewController(toViewController)
  toViewController.view.alpha = 0
  toViewController.detailImageView.hidden = true

  containerView.addSubview(toViewController.view)
  containerView.addSubview(weatherSnapshot)

  var toFrame = toViewController.locationIs

  UIView.animateWithDuration(duration, animations: { () -> Void in
    // EDIT: This solved the issue, thanks JRTurton!
    toViewController.view.setNeedsLayout() // Solution: This is where it was needed
    toViewController.view.layoutIfNeeded() //  Solution: This is where it was needed

    toViewController.view.alpha = 1.0
    var endRect = containerView.convertRect(toViewController.detailImageView.frame, fromView: toViewController.view)
    weatherSnapshot.frame = endRect

  }) { (finished) -> Void in
    toViewController.detailImageView.hidden = false
    cell.forecastImage.hidden = false
    weatherSnapshot.removeFromSuperview()

    transitionContext.completeTransition(true)
  }

}
like image 313
Nate Birkholz Avatar asked Nov 30 '22 18:11

Nate Birkholz


1 Answers

It's hard to say, but here's my guess: you're using size classes, designing at the any/any size, and the image in your to view controller is still centered in respect of that when you get its frame to use for your animation, making it too far to the right. Once the transition is complete, a layout pass happens and it gets corrected.

To fix, after you set the frame of the to view controller, force a layout pass:

toViewController.view.setNeedsLayout()
toViewController.view.layoutIfNeeded()

Before making the above change, you can first confirm if this is the issue by checking the image view's frame before the animation.

like image 200
jrturton Avatar answered Dec 09 '22 17:12

jrturton