How to avoid clipping when animating transform in viewController transitionAnimation - ios

I have a problem with a custom animation viewController transition.
I have a collectionView (fromView) and when a cell is selected, it grows to fullSize (toView).
It's working great except for the bottom cells, they are cropped when animating to center.
I tried to change the frame of the containerView to get extra space to aboid the crop but it's messing up the positions of the viewcontrollers in it.
This is the relevant code for the transition and a sketch to spot the issue.
Many thanks.
Actual behaviour:
Expected:
// originFrame is define by the selected cell in fromView
var originFrame = CGRect.zero
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
guard let toView = transitionContext.view(forKey: .to),
let fromView = transitionContext.view(forKey: .from)
else { return }
let finalFrame = toView.frame
let growFactor = finalFrame.width / originFrame.width
let centerDiffX = fromView.center.x-originFrame.midX
let centerDiffY = fromView.center.y-originFrame.midY
// Animate the transition.
UIView.animate(
withDuration: duration,
delay:0.0,
animations: {
let scaleTransform = CGAffineTransform(scaleX: growFactor, y: growFactor)
fromView.transform = CGAffineTransform(translationX: deltaX, y: deltaY).concatenating(scaleTransform)
...

Make a snapshot of the collectionview do the trick ;)

Related

How to use completion block for UIView.animate()?

I'm working on a project to learn animations and am having trouble using the completion block for the UIView.animate(withDuration:) func. MY animation is a shoebox that falls from the top of the screen, lands on a pedestal, then opens. Once the box opens I want a UIImageView to come out of the box, grow to full size of the screen and then segue to the next page, but the completion handler that the code for segueing is called before my animation completes and the UIImageView doesn't appear at all.
Here's my code:
override func viewDidAppear(_ animated: Bool) {
UIView.animate(withDuration: 1.5, delay: 0.0, options: .curveEaseInOut,
animations: {
//Opening the box
self.shoeBoxImage.shoeBox.animationImages = self.boxOpeningAnimation
self.shoeBoxImage.shoeBox.animationDuration = 1.5
self.shoeBoxImage.shoeBox.animationRepeatCount = 1
self.shoeBoxImage.shoeBox.contentMode = .scaleAspectFill
self.shoeBoxImage.shoeBox.startAnimating()
//set to the final image
self.shoeBoxImage.shoeBox.image = UIImage(named: "frame13")
},completion: {_ in
let nextPage = UIImageView()
nextPage.frame = CGRect(origin: self.shoeBoxImage.center, size: CGSize(width: 0.0, height: 0.0))
nextPage.image = UIImage(named: "FirstLoadBackgroundImg.jpeg")
nextPage.autoresizesSubviews = true
self.view.addSubview(nextPage)
self.view.bringSubviewToFront(nextPage)
UIView.animate(withDuration: 5.0, animations: {
nextPage.transform = CGAffineTransform(scaleX: 428, y: 926)
})
self.performSegue(withIdentifier: "FinishedLoading", sender: self)
})
}
This is my first time working with animations and programatically creating views so if someone could explain how to make the completion block wait for the animation to complete. In order to make the UIImageView appear and animate then once it's full screen, segue to the next page it would be very much appreciated.
The size is 0, 0. Transforming zero by any scale is still zero. I would advise you to not use transform at all, but rather just set the final frame to be what you want.
E.g.,
let startFrame = CGRect(x: view.bounds.midX, y: view.bounds.midY, width: 0, height: 0)
let endFrame = view.bounds
let imageView = UIImageView(image: ...)
imageView.contentMode = .scaleAspectFill
view.addSubview(imageView)
imageView.frame = startFrame
UIView.animate(withDuration: 3, delay: 0, options: .curveEaseInOut) {
imageView.frame = endFrame
} completion: { _ in
// do something here
}
That yields:
By the way, the performSegue probably should be inside a completion closure of the inner animate call.

Custom UINavigationController Animation Unresponsive When Finished

I'm trying to make a slide animation for my navigation controller transitions. For instance, when I push a VC, the presenting and the presented VCs will transition just like a UIPageViewController transition.
Here is what I've coded so far:
guard let fromView = transitionContext.view(forKey: .from),
let toView = transitionContext.view(forKey: .to)
else { return }
let containerView = transitionContext.containerView
toView.frame = CGRect(x: -toViewFrameHorizationtalPosition,
y: 64,
width: UIScreen.main.bounds.width,
height: UIScreen.main.bounds.height)
containerView.addSubview(fromView)
containerView.addSubview(toView)
UIView.animate(withDuration: transitionDuration(using: transitionContext),
delay: 0,
options: .curveEaseInOut,
animations: {
containerView.frame.origin.x = self.toViewFrameHorizationtalPosition
}) { completed in
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
}
The transition animation is happening as I expected it to be but even though against various tries, once the animation executes the view becomes unresponsive; it does not recognise any touch events.
The navigation bar works fine but when I switch back to the initial view, that view is unresponsive as well.
I've read a lot on this and been struggling with this for a while now although I can't seem to spot the issue. Any ideas?
Alright then, I finally found the solution. Many many thanks to #DonMag for pointing me in the right direction with his great comment!
First off, as #DonMag has also mentioned, it was needless of me to add the fromView into the containerView as it's already present inside there.
Anyways, the problem with the presented view, toView, being unresponsive was that for the purposes of making the slide animation:
I was changing the x value of the toViews frame origin to position it right inside the container view,
and moving the container view again by changing it's x value of it's frame origin. I'm moving the container view because I want both the fromView and the toView to act one next to the other, i.e. just like how a UIPageViewController animates.
The problem was that I was not setting them back to their original values. Once I set toViews and containerViews frame origins back to their original values in the animation completion block, everything worked as expected.
Here is the revised animation code:
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
guard let toView = transitionContext.view(forKey: .to)
else { return }
toView.removeFromSuperview()
toView.frame = CGRect(x: -toViewFrameHorizationtalPosition,
y: 64,
width: UIScreen.main.bounds.width,
height: UIScreen.main.bounds.height)
let containerView = transitionContext.containerView
containerView.addSubview(toView)
UIView.animate(withDuration: transitionDuration(using: transitionContext),
delay: 0,
options: .curveEaseInOut,
animations: {
containerView.frame.origin.x = self.toViewFrameHorizationtalPosition
}) { completed in
containerView.frame.origin.x = 0
toView.frame.origin.x = 0
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
}
}
P.S. Again, many many thanks to #DonMag. Your insight has been really helpful.

slide transition using UIView.transition() method

I have and table view and when the user clicks a button I want it to reload it's data and change numerous other things. Sadly, my users have not been satisfied with the available options for the transitions/animations, and wanted a sliding transition. I couldn't find anything online to do this, however. Here is my code:
UIView.transition(with: view, duration: 0.5,options: .transitionCurlUp,animations:
{ () -> Void in
self.schedule.reloadData()
self.formatter.dateFormat = "yyyy-MM-dd"
let dayOfWeek = self.week[self.getDayOfWeek(self.currentDate)!-1]
var forReturn = dayOfWeek.getCourseNumber()
if forReturn>3{
forReturn = forReturn + 1
self.refreshButton.isHidden = false
self.noClasses.isHidden = true
}else{
self.refreshButton.isHidden = true
self.noClasses.isHidden = false
}
self.changeWeek()
let startDate = self.formatter.string(from: self.startDateBase)
if self.currentDate == startDate{
self.other1.text = "Today's Schedule"
}else{
self.formatter.dateFormat = "yyyy-MM-dd"
self.currentDate = self.formatter.string(from: self.date)
self.other1.text = dayNames[self.getDayOfWeek(self.currentDate)!-1] + " Schedule"
}
}, completion: nil
);
Thank you.
You can go old school on this:
take a snapshot of the view;
add it to the view hierarchy;
apply offsetting transforms to the snapshot view and the view, itself;
animate the restoration of the main view's transform back to .identity; and
removing the snapshot view when done with the animation.
Thus:
let snapshotView = snapshot()
view.addSubview(snapshotView)
snapshotView.transform = CGAffineTransform(translationX: -view.bounds.size.width, y: 0)
view.transform = CGAffineTransform(translationX: view.frame.size.width, y: 0)
// do whatever updates to the views you want here
tableView.reloadData()
UIView.animate(withDuration: 0.5, animations: {
self.view.transform = .identity
}, completion: { _ in
snapshotView.removeFromSuperview()
})
For the snapshot, I used:
func snapshot() -> UIView {
UIGraphicsBeginImageContextWithOptions(view.bounds.size, true, 0)
view.drawHierarchy(in: view.bounds, afterScreenUpdates: false)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
let imageView = UIImageView(image: image)
imageView.frame = view.bounds
return imageView
}
By the way, you can also use snapshotView(afterScreenupdates:), which is faster than drawHierarchy, but it doesn't work in 7+ simulator for some reason (see https://forums.developer.apple.com/thread/63438), so I'm not 100% comfortable with it. But if you wanted to do that, you'd do replace the shapshotView declaration with:
let snapshotView = view.snapshotView(afterScreenUpdates: false)!

TabBarController Transitions getting blank screen issue in iOS

In my application implemented TabBarController Transitions using reference code by Apple Objective-C link And Swift link. But when switch fast between two tabs some times I am getting blank screen, I tried many answers in Stack Overflow but no luck.
Please check below code for reference while doing TabBarController Transitions using Swift
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let fromViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from)!
let toViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to)!
let containerView = transitionContext.containerView
let fromView: UIView
let toView: UIView
// In iOS 8, the viewForKey: method was introduced to get views that the
// animator manipulates. This method should be preferred over accessing
// the view of the fromViewController/toViewController directly.
if #available(iOS 8.0, *) {
fromView = transitionContext.view(forKey: UITransitionContextViewKey.from)!
toView = transitionContext.view(forKey: UITransitionContextViewKey.to)!
} else {
fromView = fromViewController.view
toView = toViewController.view
}
let fromFrame = transitionContext.initialFrame(for: fromViewController)
let toFrame = transitionContext.finalFrame(for: toViewController)
// Based on the configured targetEdge, derive a normalized vector that will
// be used to offset the frame of the view controllers.
var offset: CGVector
if self.targetEdge == UIRectEdge.left {
offset = CGVector(dx: -1.0, dy: 0.0)
} else if self.targetEdge == .right {
offset = CGVector(dx: 1.0, dy: 0.0)
} else {
fatalError("targetEdge must be one of UIRectEdgeLeft, or UIRectEdgeRight.")
}
// The toView starts off-screen and slides in as the fromView slides out.
fromView.frame = fromFrame
toView.frame = toFrame.offsetBy(dx: toFrame.size.width * offset.dx * -1,
dy: toFrame.size.height * offset.dy * -1)
// We are responsible for adding the incoming view to the containerView.
containerView.addSubview(toView)
let transitionDuration = self.transitionDuration(using: transitionContext)
UIView.animate(withDuration: transitionDuration, animations: {
fromView.frame = fromFrame.offsetBy(dx: fromFrame.size.width * offset.dx,
dy: fromFrame.size.height * offset.dy)
toView.frame = toFrame
}, completion: {finshed in
let wasCancelled = transitionContext.transitionWasCancelled
// When we complete, tell the transition context
// passing along the BOOL that indicates whether the transition
// finished or not.
transitionContext.containerView.addSubview(toView)
transitionContext.completeTransition(!wasCancelled)
})
}
Below is the screen shot
It seems Like you have taken UINavigationController for your second tab. But somehow your connection from UINavigationController to your secondViewController is lost. Please check the image of a storyboard which may be in your scenario.
I also get the same as a blank screen and I fixed it by making the current view it the initial view controller.
check is Initial View Controller

Swift. Flip animation. FromView is not displayed

I've wrote this code to create a simple flip animation:
func frontView (view:UIView) ->UIView {
var frontView: UIView
frontView = UIView()
frontView.frame = view.frame
frontView.center = CGPoint(x: 0, y: 0)
return frontView
}
func backView (view:UIView) ->UIView {
var backView: UIView
backView = UIView()
backView.frame = view.frame
backView.backgroundColor = UIColor.redColor()
backView.center = CGPoint(x: view.frame.width/2, y: 0)
view.addSubview(backView)
return backView
}
func flipViewAnimation (viewToAnimate: UIView) {
var animationOption = self.animationOption
var duration = self.duration
UIView.transitionFromView(backView(viewToAnimate), toView: frontView(viewToAnimate), duration: duration, options: animationOption, completion: nil)
//
}
As a viewToAnimate I use views with labels and imageViews inside, which I've created in AutoLayout. The result I'm trying to achieve more or less should look like this. First I see views filled with color, then they flip and show the content inside (labels and ImageViews).
But it works in a different way. Views appear already with content (labels and ImageViews) then just flip and again show the same content.
I've created each view programmatically and it works very well for me now.

Resources