I want to make an animation like iOS home screen folders, and I have to know the frame of the final position of the "folder":
I'm using a custom transition and here is the code of the animation file:
class FolderAnimationController: NSObject, UIViewControllerAnimatedTransitioning {
let duration = 5.0
var presenting = true
func transitionDuration(_ transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return duration
}
func animateTransition(_ transitionContext: UIViewControllerContextTransitioning) {
let containerView = transitionContext.containerView()
let toViewC = transitionContext.viewController(forKey: UITransitionContextToViewControllerKey)! as! ProjectViewController
let fromViewC = transitionContext.viewController(forKey: UITransitionContextFromViewControllerKey)!as! ViewController
let cell : FolderCollectionViewCell = fromViewC.folderCollectionView.cellForItem(at: (fromViewC.folderCollectionView.indexPathsForSelectedItems()?.first!)!) as! FolderCollectionViewCell
let cellSnapshot: UIView = cell.snapshotView(afterScreenUpdates: false)!
let cellFrame = containerView.convert(cell.frame, from: cell.superview)
cellSnapshot.frame = cellFrame
cell.isHidden = true
toViewC.view.frame = transitionContext.finalFrame(for: toViewC)
toViewC.view.alpha = 0
containerView.addSubview(toViewC.view)
containerView.addSubview(cellSnapshot)
UIView.animate(withDuration: duration, animations: {
toViewC.view.alpha = 1.0
let finalFrame = containerView.convert(toViewC.containerView.frame, from: toViewC.view)
cellSnapshot.frame = finalFrame
}) { (_) in
cellSnapshot.removeFromSuperview()
transitionContext.completeTransition(true)
}
}
}
All works correctly, except let finalFrame = containerView.convert(toViewC.containerView.frame, from: toViewC.view) that set finalFrame values (is a CGRect variable) to (origin = (x = 0, y = 0), size = (width = 0, height = 0)).
I've followed this tutorial, writing my code in Swift
So, how can I access the property correctly?
The frame is the zero rect because at the point you are accessing it, the view's layout pass has not occurred. After you set the toVC.view frame, add this line:
toVC.view.layoutIfNeeded()
I've written about the use of snapshots in view controller transitions here if you're interested.
Related
I am trying to achieve 3. Material design - container transform effect with custom transitioning in iOS. Below is the code for the presentation part.
class CustomTransition: NSObject {
var duration: TimeInterval = 5
}
extension CustomTransition:UIViewControllerAnimatedTransitioning {
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return duration
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
guard let toViewController = transitionContext.viewController(forKey: .to),
let fromViewController = transitionContext.viewController(forKey: .from) as? ViewController else {
return
}
let finalFrame = transitionContext.finalFrame(for: toViewController)
toViewController.view.frame = fromViewController.menuButton.frame
toViewController.view.layer.cornerRadius = fromViewController.menuButton.frame.size.width / 2
toViewController.view.clipsToBounds = true
toViewController.view.alpha = 0
let archive = NSKeyedArchiver.archivedData(withRootObject: fromViewController.menuButton!)
let menuButtonCopy = NSKeyedUnarchiver.unarchiveObject(with: archive) as! UIButton
menuButtonCopy.layer.cornerRadius = menuButtonCopy.frame.size.width / 2
transitionContext.containerView.addSubview(menuButtonCopy)
transitionContext.containerView.addSubview(toViewController.view)
UIView.animate(withDuration: transitionDuration(using: transitionContext), animations: {
toViewController.view.frame = finalFrame
toViewController.view.alpha = 1
menuButtonCopy.alpha = 0
menuButtonCopy.frame = finalFrame
}, completion: { _ in
transitionContext.completeTransition(true)
menuButtonCopy.removeFromSuperview()
})
}
}
Here is the result
Actually, I want to align the '+' button in the centre of the container as long as it animates to the full screen as seen in the design. What is the I am missing here? Why is the '+' seen arising from bottom centre?
So here is a class for Slide in transition which adds a ViewController with animation from left to right and it works flawlessly I want a transition from bottom to top.
import UIKit
class SlideInTransition: NSObject, UIViewControllerAnimatedTransitioning {
var isPresenting = false
let dimmingView = UIView()
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return 0.3
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
guard let toViewController = transitionContext.viewController(forKey: .to),
let fromViewController = transitionContext.viewController(forKey: .from) else { return }
let containerView = transitionContext.containerView
let finalWidth = toViewController.view.bounds.width * 0.8
let finalHeight = toViewController.view.bounds.height
if isPresenting {
// Add dimming view
dimmingView.backgroundColor = .black
dimmingView.alpha = 0.0
containerView.addSubview(dimmingView)
dimmingView.frame = containerView.bounds
// Add menu view controller to container
containerView.addSubview(toViewController.view)
// Init frame off the screen
toViewController.view.frame = CGRect(x: -finalWidth, y: 0, width: finalWidth, height: finalHeight)
}
// Move on screen
let transform = {
self.dimmingView.alpha = 0.5
toViewController.view.transform = CGAffineTransform(translationX: finalWidth, y: 0)
}
// Move back off screen
let identity = {
self.dimmingView.alpha = 0.0
fromViewController.view.transform = .identity
}
// Animation of the transition
let duration = transitionDuration(using: transitionContext)
let isCancelled = transitionContext.transitionWasCancelled
UIView.animate(withDuration: duration, animations: {
self.isPresenting ? transform() : identity()
}) { (_) in
transitionContext.completeTransition(!isCancelled)
}
}
}
To be honest I copied this code from somewhere a while ago and I don't have a source of it.
I'm fairly new to iOS so any help would be appreciated.
Try this,
class SlideInTransition: NSObject, UIViewControllerAnimatedTransitioning {
var isPresenting = false
let dimmingView = UIView()
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return 0.3
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
guard let toViewController = transitionContext.viewController(forKey: .to),
let fromViewController = transitionContext.viewController(forKey: .from) else { return }
let containerView = transitionContext.containerView
let finalWidth = toViewController.view.bounds.width
let finalHeight = toViewController.view.bounds.height * 0.8
if isPresenting {
// Add dimming view
dimmingView.backgroundColor = .black
dimmingView.alpha = 0.0
containerView.addSubview(dimmingView)
dimmingView.frame = containerView.bounds
// Add menu view controller to container
containerView.addSubview(toViewController.view)
// Init frame off the screen
toViewController.view.frame = CGRect(x: 0, y: finalHeight, width: finalWidth, height: finalHeight)
}
// Move on screen
let transform = {
self.dimmingView.alpha = 0.5
toViewController.view.transform = CGAffineTransform(translationX: 0, y: -finalHeight)
}
// Move back off screen
let identity = {
self.dimmingView.alpha = 0.0
fromViewController.view.transform = .identity
}
// Animation of the transition
let duration = transitionDuration(using: transitionContext)
let isCancelled = transitionContext.transitionWasCancelled
UIView.animate(withDuration: duration, animations: {
self.isPresenting ? transform() : identity()
}) { (_) in
transitionContext.completeTransition(!isCancelled)
}
}
}
I'm trying to create a custom ViewController transition.
I'm basically trying to replicate Apple's animation when launching an app, the zoom in starts from the application's position.
I have a collection view, and when the user taps on a cell, I get the cell's point (which does work), and I'm trying to set the animation starting point to be that point.
And while I can see that I get the point the zoom in effect always starts from the center of the screen. This is what I have as the custom transition:
enum ZoomTransitionMode: Int {
case present, dismiss, pop
}
class ZoomTransition: NSObject{
var zoom = UIView()
var originFrame = CGRect.zero
var startingPoint: CGPoint!
var zoomColor = UIColor.appWhite
var animationDuration = 0.3
var transitionMode: ZoomTransitionMode = .present
}
extension ZoomTransition: UIViewControllerAnimatedTransitioning {
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return animationDuration
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let containerView = transitionContext.containerView
switch transitionMode {
case .present:
if let presentedView = transitionContext.view(forKey: UITransitionContextViewKey.to) {
print(startingPoint)
zoom = UIView()
zoom.frame = CGRect(origin: startingPoint, size: CGSize(width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height))
zoom.layer.cornerRadius = 20
zoom.backgroundColor = zoomColor
zoom.transform = CGAffineTransform(scaleX: 0.001, y: 0.001)
containerView.addSubview(zoom)
presentedView.transform = CGAffineTransform(scaleX: 0.001, y: 0.001)
presentedView.frame = UIScreen.main.bounds
presentedView.alpha = 0
presentedView.backgroundColor = .systemGreen
containerView.addSubview(presentedView)
containerView.bringSubviewToFront(presentedView)
UIView.animate(withDuration: 10, animations: {
self.zoom.transform = CGAffineTransform.identity
presentedView.transform = CGAffineTransform.identity
presentedView.alpha = 1
}) { (didSuccessed) in
transitionContext.completeTransition(didSuccessed)
}
}
default:
return
}
}
I also tried setting the zoom's and the presentedView center as the starting point, but still, the animation starts from the center of the screen instead of the starting point.
I have this CollectionView with small images and a UIViewController with single image on the center of a screen with full-screen width.
When user taps on small image it should scale to take full screen width.
There's a custom transition animation between those two.
let previewVC = PreviewTutorialViewController(image: image!, imageFrame: frame, text: data!.text, imageView: cell.toDoImageView)
previewVC.modalPresentationStyle = .overCurrentContext
previewVC.transitioningDelegate = previewVC
self.present(previewVC, animated: true, completion: nil)
AnimationController
class AnimationController: NSObject, UIViewControllerAnimatedTransitioning {
var duration = 10.4
var isPresenting: Bool
init(forTransitionType type: TransitionType) {
self.isPresenting = type == .presenting
super.init()
}
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return self.duration
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let containerView = transitionContext.containerView
let fromVC = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from)!
let toVC = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to)!
var imageRectAfter : CGRect = .zero
var imageRectInitial : CGRect = .zero
var previewTutorialVC : PreviewTutorialViewController?
if self.isPresenting {
containerView.addSubview(toVC.view)
containerView.layoutIfNeeded()
previewTutorialVC = (toVC as! PreviewTutorialViewController)
imageRectInitial = previewTutorialVC!.initialRect
imageRectAfter = previewTutorialVC!.tutorialImageView.frame
previewTutorialVC?.initialImageView?.alpha = 0
print("$$$ animating image view frame : PRESENTING ", imageRectInitial, " to ", imageRectAfter)
} else {
previewTutorialVC = (fromVC as! PreviewTutorialViewController)
imageRectAfter = previewTutorialVC!.initialRect
imageRectInitial = previewTutorialVC!.tutorialImageView.frame
print("$$$ animating image view frame : DISSMISSING ", imageRectInitial, " to ", imageRectAfter)
//let frame2 = previewTutorialVC!.tutorialImageView.convert(imageRectInitial, to: containerView)
//imageRectInitial = CGRect(x: 0, y: 430, width: 414, height: 155)
//print("$$$ converted :", frame2)
print("$$$ ", containerView.bounds.size.width, previewTutorialVC?.view.bounds.size.width)
}
previewTutorialVC?.tutorialImageView.transform = .identity
previewTutorialVC?.tutorialImageView.frame = imageRectInitial
previewTutorialVC?.containerView.alpha = isPresenting == true ? 0 : 1
previewTutorialVC?.closeButton.alpha = self.isPresenting == true ? 0 : 1
//previewTutorialVC?.textContainerView.transform = isPresenting == true ? CGAffineTransform(translationX: 0, y: previewTutorialVC?.textContainerView.frame.size.height ?? 0) : .identity
UIView.animate(withDuration: duration, delay: 0, options: [.curveEaseOut], animations: {
previewTutorialVC?.tutorialImageView.frame = imageRectAfter
previewTutorialVC?.containerView.alpha = self.isPresenting == true ? 1 : 0
previewTutorialVC?.closeButton.alpha = self.isPresenting == true ? 1 : 0
// previewTutorialVC?.textContainerView.transform = self.isPresenting == true ? .identity : CGAffineTransform(translationX: 0, y: previewTutorialVC?.textContainerView.frame.size.height ?? 0)
}) { (_) in
if self.isPresenting == false { previewTutorialVC?.initialImageView?.alpha = 1 }
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
}
}
}
Console log:
$$$ animating image view frame : PRESENTING (15.0, 461.5, 384.0, 143.5) to (0.0, 370.5, 414.0, 155.0)
$$$ animating image view frame : DISSMISSING (0.0, 370.5, 414.0, 155.0) to (15.0, 461.5, 384.0, 143.5)
It used to work fine, but since iOS 13 i've noticed dismiss animation starts with way too wide image than it should be, although frame prints correct values.. also dismiss animation ends with image being a bit too high. Present animation's working fine
previewTutorialVC?.tutorialImageView.translatesAutoresizingMaskIntoConstraints = true
this inside dismiss block fixed the issue. As mentioned here
I am trying to make a simple animated transitioning when pushing a UIViewController, but it seems I am missing something.
I animate a snapshot of a subview from the fromViewController to the toViewController. I am animating snapshot’s frame, but the snapshot is invisible for the whole duration of the animation.
Here is a simple code example. I am trying to animate a single UILabel from the first controller to the second. I specifically want to animate a snapshot taken from the toViewConroller and not from the fromViewController.
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let fromVC = transitionContext.viewController(forKey: .from) as! ViewController
let toVC = transitionContext.viewController(forKey: .to) as! SecondViewController
let container = transitionContext.containerView
toVC.view.frame = fromVC.view.frame
container.addSubview(toVC.view)
toVC.view.layoutIfNeeded()
let animatedFromView = fromVC.label!
let animatedToView = toVC.label!
let initialFrame = container.convert(animatedFromView.frame,
from: animatedFromView.superview)
let finalFame = container.convert(animatedToView.frame,
to: animatedToView.superview)
let snapshot = animatedToView.snapshotView(afterScreenUpdates: true)!
snapshot.frame = initialFrame
container.addSubview(snapshot)
animatedFromView.alpha = 0
animatedToView.alpha = 0
UIView.animate(withDuration: 2,
animations: {
snapshot.frame = finalFame
}) { (_) in
snapshot.removeFromSuperview()
fromVC.label.alpha = 1
toVC.label.alpha = 1
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
}
}
I guess that the snapshot is hidden, because setting the animatedToView’s alpha to 0, however I am not sure how to achieve that animation without setting that.
I tried your code its working fine.I changed a few things like initial frame hardcoded it so i can see the effect and also from viewController alpha.
::::::for presenting view controller
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let fromVC = transitionContext.viewController(forKey: .from) as! ViewController
let toVC = transitionContext.viewController(forKey: .to) as! SecondViewController
let container = transitionContext.containerView
toVC.view.frame = fromVC.view.frame
container.addSubview(toVC.view)
toVC.view.layoutIfNeeded()
let animatedFromView = fromVC.view!
let animatedToView = toVC.view!
let initialFrame = container.convert(CGRect(x: 0, y: 200, width: 100, height: 100),
from: animatedFromView.superview)
let finalFame = container.convert(animatedToView.frame,
to: animatedToView.superview)
let snapshot = animatedToView.snapshotView(afterScreenUpdates: true)!
snapshot.frame = initialFrame
container.addSubview(snapshot)
animatedFromView.alpha = 1
animatedToView.alpha = 0
UIView.animate(withDuration: 2,
animations: {
snapshot.frame = finalFame
}) { (_) in
snapshot.removeFromSuperview()
fromVC.view.alpha = 1
toVC.view.alpha = 1
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
}
}
:::::::::::::::::
Use while you are pushing view controller
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
guard let fromView = transitionContext.view(forKey: UITransitionContextViewKey.from) else { return }
guard let toView = transitionContext.view(forKey: UITransitionContextViewKey.to) else { return }
let container = transitionContext.containerView
container.insertSubview(toView, belowSubview: fromView)
let animatedFromView = fromView
let animatedToView = toView
let initialFrame = container.convert(CGRect(x: 0, y: 200, width: 100, height: 100),
from: animatedFromView.superview)
let finalFame = container.convert(animatedToView.frame,
to: animatedToView.superview)
let snapshot = animatedToView.snapshotView(afterScreenUpdates: true)!
snapshot.frame = initialFrame
container.addSubview(snapshot)
animatedFromView.alpha = 1
animatedToView.alpha = 1
UIView.animate(withDuration: 2,
animations: {
snapshot.frame = finalFame
}) { (_) in
snapshot.removeFromSuperview()
//fromVC.view.alpha = 1
//toVC.view.alpha = 1
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
}
}