Storyboard cutting
The steps that I did were:
Create the + and x buttons in the Controllers
Create simple outlets in the Controllers
Create an action in the Controller for the x (dismiss)-button
#IBAction func button_close_pressed(_ sender: Any) {
self.dismiss(animated: true, completion: nil)
}
Create a segue on the Storyboard from the + button to the targetViewController.
Create the transition class.
import UIKit
class CircularTransition: NSObject {
var circle = UIView()
var startingPoint = CGPoint.zero {
didSet {
circle.center = startingPoint
}
}
var circleColor = UIColor.red
var duration = 3.0
enum CircularTransitionMode: Int {
case present, dismiss, pop
}
var transitionMode: CircularTransitionMode = .present
}
extension CircularTransition: UIViewControllerAnimatedTransitioning {
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return duration
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let containerView = transitionContext.containerView
if transitionMode == .present {
if let presentedView = transitionContext.view(forKey: UITransitionContextViewKey.to) {
let viewCenter = presentedView.center
let viewSize = presentedView.frame.size
circle = UIView()
circle.frame = frameForCircle(withViewCenter: viewCenter, size: viewSize, startPoint: startingPoint)
circle.layer.cornerRadius = circle.frame.size.height / 2
circle.center = startingPoint
circle.backgroundColor = circleColor
circle.transform = CGAffineTransform(scaleX: 0.001, y: 0.001)
containerView.addSubview(circle)
presentedView.center = startingPoint
presentedView.transform = CGAffineTransform(scaleX: 0.001, y: 0.001)
presentedView.alpha = 0
containerView.addSubview(presentedView)
UIView.animate(withDuration: duration, animations: {
self.circle.transform = CGAffineTransform.identity
presentedView.transform = CGAffineTransform.identity
presentedView.alpha = 1
presentedView.center = viewCenter
}, completion: { (success: Bool) in
transitionContext.completeTransition(success)
})
}
} else {
let transitionModeKey = (transitionMode == .pop) ? UITransitionContextViewKey.to : UITransitionContextViewKey.from
if let returningView = transitionContext.view(forKey: transitionModeKey) {
let viewCenter = returningView.center
let viewSize = returningView.frame.size
circle.frame = frameForCircle(withViewCenter: viewCenter, size: viewSize, startPoint: startingPoint)
circle.layer.cornerRadius = circle.frame.size.height / 2
circle.center = startingPoint
UIView.animate(withDuration: duration, animations: {
self.circle.transform = CGAffineTransform(scaleX: 0.001, y: 0.001)
returningView.transform = CGAffineTransform(scaleX: 0.001, y: 0.001)
returningView.center = self.startingPoint
returningView.alpha = 0
if self.transitionMode == .pop {
containerView.insertSubview(returningView, belowSubview: returningView)
containerView.insertSubview(self.circle, belowSubview: returningView)
}
}, completion: { (success: Bool) in
returningView.center = viewCenter
returningView.removeFromSuperview()
self.circle.removeFromSuperview()
transitionContext.completeTransition(success)
})
}
}
}
func frameForCircle(withViewCenter viewCenter: CGPoint, size viewSize: CGSize, startPoint: CGPoint) -> CGRect {
let xLength = fmax(startPoint.x, viewSize.width - startPoint.x)
let yLength = fmax(startPoint.y, viewSize.height - startPoint.y)
let offsetVector = sqrt(xLength * xLength + yLength * yLength) * 2
let size = CGSize(width: offsetVector, height: offsetVector)
return CGRect(origin: CGPoint.zero, size: size)
}
}
In the source Controller I added ... to the class
UIViewControllerTransitioningDelegate
Then
let transition = CircularTransition()
And last but not least
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let eventVC = segue.destination as! EventViewController
eventVC.transitioningDelegate = self
eventVC.modalPresentationStyle = .custom
}
func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
transition.transitionMode = .present
transition.startingPoint = self.button_addEvent.center
transition.circleColor = self.button_addEvent.backgroundColor!
return transition
}
func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
transition.transitionMode = .dismiss
transition.startingPoint = self.button_addEvent.center
transition.circleColor = self.button_addEvent.backgroundColor!
return transition
}
Now, why doesn't that work? I only can think of that the problem comes with the Navigation Controller instead of a normal ViewController, because with a normal one it works fine. But I really have no idea hwo to change the code to match with the Navigation Controller (source Controller).
Kind regards and thank you!
You need to implement UINavigationControllerDelegate in your viewController
func navigationController(_ navigationController: UINavigationController, animationControllerFor operation: UINavigationControllerOperation, from fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
switch operation {
case .push:
transition.transitionMode = .present
transition.startingPoint = self.button_addEvent.center
transition.circleColor = self.button_addEvent.backgroundColor!
return transition
default:
transition.transitionMode = .dismiss
transition.startingPoint = self.button_addEvent.center
transition.circleColor = self.button_addEvent.backgroundColor!
return transition
}
}
Related
I am trying to zoom image view inside a UIView, but it zoom outside the UIView.
Condition : without using scrollview
here is my code :
extension UIImageView {
func enableZoom() {
let pinchGesture = UIPinchGestureRecognizer(target: self, action: #selector(startZooming(_:)))
isUserInteractionEnabled = true
addGestureRecognizer(pinchGesture)
}
#objc
private func startZooming(_ sender: UIPinchGestureRecognizer) {
let scaleResult = sender.view?.transform.scaledBy(x: sender.scale, y: sender.scale)
guard let scale = scaleResult, scale.a > 1, scale.d > 1 else { return }
sender.view?.transform = scale
sender.scale = 1
}
}
lazy var ImgView : UIImageView = {
let i = UIImageView()
i.enableZoom()
return i
}()
lazy var topView : UIView = {
let c = UIView()
return c
}()
in viewdidload
self.view.layout(self.ImgView).top(90).left().right().height(topViewHeight)
self.topView = self. ImgView
What if the UILabel is in class A and the didTapRightutton that will animate it is in class B?
the percentDiscountLabel is in RandomizeDealsCollectionViewCell. This should animate into fade appear if I tap didTapRightutton which is in a different VC called RandomizeDealsViewController
How do I call the function that is inside RandomizeDealsCollectionViewCell to animate the percentDiscountLabel? Is there other way to do this?
class RandomizeDealsCollectionViewCell: UICollectionViewCell {
#IBOutlet weak var percentDiscountLabel: UILabel!
func animatePercentDiscountLabel(deals: String) {
self.percentDiscountLabel.alpha = 0.6
self.percentDiscountLabel.isHidden = false
UIView.animate(withDuration: 0.6, delay: 0, options: .curveEaseInOut, animations: {
self.percentDiscountLabel.alpha = 1.0
}) { (isCompleted) in
}
percentDiscountLabel.text = deals
}
}
class RandomizeDealsViewController: UIViewController {
private var centeredCollectionViewFlowLayout: CenteredCollectionViewFlowLayout!
#IBAction func didTapRightButton(_ sender: Any) {
guard let indexCard = centeredCollectionViewFlowLayout.currentCenteredPage else { return }
if (indexCard > 0) {
centeredCollectionViewFlowLayout.scrollToPage(index: indexCard + 1, animated: true)
// should call the animation function here
}
}
}
If indexCard is indexPath of collectionViewCell which you want to animate,
You can call your cell like ->
in RandomizeDealsViewController
#IBAction func didTapRightButton(_ sender: Any) {
guard let indexCard = centeredCollectionViewFlowLayout.currentCenteredPage else { return }
if (indexCard > 0) {
centeredCollectionViewFlowLayout.scrollToPage(index: indexCard + 1, animated: true)
// should call the animation function here
let cell = collectionView!.cellForItemAtIndexPath(indexCard) as? RandomizeDealsCollectionViewCell
cell?.animatePercentDiscountLabel(deals: "deals")
}
}
Hi I want to give the PanGecture Swipe action for UIView on the top of UIViewControllers.for that one I write one common method in view controller extension but its make the entire viewcontroller is swipe.
I want to give the swipe action only for UIView please help to me any idea to change the following code to UIView Extension.
extension UIViewController{
#objc func pangectureRecognizerDismissoneScreen(_ sender: UIPanGestureRecognizer){
var initialTouchPoint: CGPoint = CGPoint(x: 0,y: 0)
let touchPoint = sender.location(in: self.view?.window)
if sender.state == UIGestureRecognizerState.began {
initialTouchPoint = touchPoint
} else if sender.state == UIGestureRecognizerState.changed {
if touchPoint.y - initialTouchPoint.y > 0 {
self.view.frame = CGRect(x: 0, y: touchPoint.y - initialTouchPoint.y, width: self.view.frame.size.width, height: self.view.frame.size.height)
}
} else if sender.state == UIGestureRecognizerState.ended || sender.state == UIGestureRecognizerState.cancelled {
if touchPoint.y - initialTouchPoint.y > 100 {
self.view.backgroundColor = UIColor.clear
self.dismiss(animated: true, completion: nil)
} else {
UIView.animate(withDuration: 0.3, animations: {
self.view.frame = CGRect(x: 0, y: 0, width: self.view.frame.size.width, height: self.view.frame.size.height)
})
}
}
}
}
You can add this UIView extension
import UIKit
import RxSwift
struct AssociatedKeys {
static var actionState: UInt8 = 0
}
typealias ActionTap = () -> Void
extension UIView {
var addAction: ActionTap? {
get {
guard let value = objc_getAssociatedObject(self, &AssociatedKeys.actionState) as? ActionTap else {
return nil
}
return value
}
set {
objc_setAssociatedObject(self, &AssociatedKeys.actionState, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
self.tapWithAnimation()
}
}
func tapWithAnimation() {
self.gestureRecognizers?.removeAll()
let longTap = UILongPressGestureRecognizer(target: self, action: #selector(viewLongTap(_:)))
longTap.minimumPressDuration = 0.035
longTap.delaysTouchesBegan = true
self.addGestureRecognizer(longTap)
}
#objc
func viewLongTap(_ gesture: UILongPressGestureRecognizer) {
if gesture.state != .ended {
animateView(alpha: 0.3)
return
} else if gesture.state == .ended {
let touchLocation = gesture.location(in: self)
if self.bounds.contains(touchLocation) {
animateView(alpha: 1)
addAction?()
return
}
}
animateView(alpha: 1)
}
fileprivate func animateView(alpha: CGFloat) {
UIView.transition(with: self, duration: 0.3,
options: .transitionCrossDissolve, animations: {
self.subviews.forEach { subView in
subView.alpha = alpha
}
})
}
}
Example:
myView.addAction = {
print("click")
}
Use this class extension.
//Gesture extention
extension UIGestureRecognizer {
#discardableResult convenience init(addToView targetView: UIView,
closure: #escaping () -> Void) {
self.init()
GestureTarget.add(gesture: self,
closure: closure,
toView: targetView)
}
}
fileprivate class GestureTarget: UIView {
class ClosureContainer {
weak var gesture: UIGestureRecognizer?
let closure: (() -> Void)
init(closure: #escaping () -> Void) {
self.closure = closure
}
}
var containers = [ClosureContainer]()
convenience init() {
self.init(frame: .zero)
isHidden = true
}
class func add(gesture: UIGestureRecognizer, closure: #escaping () -> Void,
toView targetView: UIView) {
let target: GestureTarget
if let existingTarget = existingTarget(inTargetView: targetView) {
target = existingTarget
} else {
target = GestureTarget()
targetView.addSubview(target)
}
let container = ClosureContainer(closure: closure)
container.gesture = gesture
target.containers.append(container)
gesture.addTarget(target, action: #selector(GestureTarget.target(gesture:)))
targetView.addGestureRecognizer(gesture)
}
class func existingTarget(inTargetView targetView: UIView) -> GestureTarget? {
for subview in targetView.subviews {
if let target = subview as? GestureTarget {
return target
}
}
return nil
}
func cleanUpContainers() {
containers = containers.filter({ $0.gesture != nil })
}
#objc func target(gesture: UIGestureRecognizer) {
cleanUpContainers()
for container in containers {
guard let containerGesture = container.gesture else {
continue
}
if gesture === containerGesture {
container.closure()
}
}
}
}
EDIT 1 :-
Use like this
UIGestureRecognizer.init(addToView: yourView, closure: {
// your code
})
Here is the code that I tried, but when the image has not loaded yet, nothing show up (an animated indicator should show up). What is the best practice of showing the activity indicator? I hooked into the ASNetworkImageNodeDelegate.
import AsyncDisplayKit
class WideImageFeedNode : ASCellNode, ASNetworkImageNodeDelegate {
var imageNode = ASNetworkImageNode()
var activityIndicator:UIActivityIndicatorView?
init(itemid:Int) {
super.init()
imageNode.backgroundColor = ASDisplayNodeDefaultPlaceholderColor()
let imgURL = URL(string:"http://...somelargeimage.jpg")
imageNode.url = imgURL
imageNode.delegate = self
self.addSubnode(imageNode)
self.automaticallyManagesSubnodes = true
}
override func layoutSpecThatFits(_ constrainedSize: ASSizeRange) -> ASLayoutSpec {
var finalStackArr:[ASLayoutElement] = [self.imageNode]
let finalSpec = ASStackLayoutSpec(direction: .vertical, spacing: 10.0, justifyContent: .start, alignItems: .start, children: finalStackArr)
return finalSpec
}
func imageNode(_ imageNode: ASNetworkImageNode, didLoad image: UIImage) {
if let activityIndicator = self.activityIndicator {
activityIndicator.removeFromSuperview()
self.activityIndicator = nil
}
self.setNeedsLayout()
}
// helper functions
func setupActivityIndicator(bounds:CGSize) -> UIActivityIndicatorView {
let activityIndicator = UIActivityIndicatorView(activityIndicatorStyle: .gray)
var refreshRect = activityIndicator.frame
refreshRect.origin = CGPoint(x: (bounds.width - activityIndicator.frame.size.width) / 2.0, y: (bounds.height - activityIndicator.frame.size.height) / 2.0)
activityIndicator.frame = refreshRect
return activityIndicator
}
func imageNodeDidStartFetchingData(_ imageNode: ASNetworkImageNode) {
self.activityIndicator = setupActivityIndicator(bounds: imageNode.style.preferredSize)
imageNode.view.addSubview(self.activityIndicator!)
}
func imageNode(_ imageNode: ASNetworkImageNode, didFailWithError error: Error) {
if let activityIndicator = self.activityIndicator {
activityIndicator.removeFromSuperview()
self.activityIndicator = nil
}
}
}
Never mind, it works. I was missing one call:
func imageNodeDidStartFetchingData(_ imageNode: ASNetworkImageNode) {
self.activityIndicator = setupActivityIndicator(bounds: imageNode.style.preferredSize)
imageNode.view.addSubview(self.activityIndicator!)
self.activityIndicator!.startAnimating()
}
I have tried a number of times and the best i get is there would be an animation which never cancels or stop regardless of the command i use.
After following #Mattias example, i updated my code and looks something like this:
// DESIGN ANIMATION... TKTRANSITIONSUBMITBUTTON
#IBOutlet weak var btnFromNib: TKTransitionSubmitButton!
#IBAction func onTapButton(sender: AnyObject) {
btnFromNib.startLoadingAnimation()
if let email = self.emailField.text where email != "", let password = self.passwordField.text where password != "" {
DataService.ds.REF_BASE.authUser(email, password: password, withCompletionBlock: { error, authData in
if error != nil {
self.btnFromNib.returnToOriginalState()
if error.code == STATUS_ACCOUNT_NONEXIST {
self.showErrorAlert("This User does not exist", msg: "Please Sign Up")
} else {
}
} else {
self.btnFromNib.startFinishAnimation(1, completion: {
let myTabbarController = self.storyboard?.instantiateViewControllerWithIdentifier("myTabbarController") as! UITabBarController
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
appDelegate.window?.rootViewController = myTabbarController
myTabbarController.transitioningDelegate = self
})
}
})
}
}
The button yet keeps spinning / animating without stopping. After checking the custom animation class the function inherits from :
public func startLoadingAnimation() {
self.cachedTitle = titleForState(.Normal)
self.setTitle("", forState: .Normal)
self.shrink()
NSTimer.schedule(delay: shrinkDuration - 0.25) { timer in
self.spiner.animation()
}
}
public func startFinishAnimation(delay: NSTimeInterval, completion:(()->())?) {
NSTimer.schedule(delay: delay) { timer in
self.didEndFinishAnimation = completion
self.expand()
self.spiner.stopAnimation()
}
}
public func animate(duration: NSTimeInterval, completion:(()->())?) {
startLoadingAnimation()
startFinishAnimation(duration, completion: completion)
}
public override func animationDidStop(anim: CAAnimation, finished flag: Bool) {
let a = anim as! CABasicAnimation
if a.keyPath == "transform.scale" {
didEndFinishAnimation?()
NSTimer.schedule(delay: 1) { timer in
self.returnToOriginalState()
}
}
}
func returnToOriginalState() {
self.layer.removeAllAnimations()
self.setTitle(self.cachedTitle, forState: .Normal)
}
I noticed it had a public overide func animationDidStop(anim: CAAnimation, finished: Bool) to be the function to stop the animation. But when i use it, i get this error!
How do i rightfully get this to work? ...
Thanks in Advance
** UPDATED QUESTION **
I checked the code of TKTransitionSubmitButton and there are public methods called startLoadingAnimation(), returnToOriginalState() and startFinishAnimation().
I suggest:
Button tapped
startLoadingAnimation()
Check credentials
If wrong/error: returnToOriginalState()
If correct: startFinishAnimation()
Transition, from TKTransitionSubmitButton documentation:
btn.startFinishAnimation {
//Your Transition
let secondVC = SecondViewController()
secondVC.transitioningDelegate = self
self.presentViewController(secondVC, animated: true, completion: nil)
}
Edit: As far as I can see .animate() of the class calls both the start and finish animation, and that's why you can't cancel it.
EDIT2 This one works as intended for me (however I'm not sure about the static cornerRadius)
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var submitButton: TKTransitionSubmitButton!
override func viewDidLoad() {
super.viewDidLoad()
submitButton.layer.cornerRadius = 15
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func buttonPressed(sender: AnyObject) {
submitButton.startLoadingAnimation()
delay(2, closure: {
self.checkCredentials()
})
}
func checkCredentials()
{
//FAKING WRONG CREDENTIALS
let userAndPasswordCorrect = false
if !userAndPasswordCorrect
{
submitButton.returnToOriginalState()
//Alert or whatever
}
}
//Faking network delay
func delay(delay:Double, closure:()->()) {
dispatch_after(
dispatch_time(
DISPATCH_TIME_NOW,
Int64(delay * Double(NSEC_PER_SEC))
),
dispatch_get_main_queue(), closure)
}
}
I checked the code of TKTransitionSubmitButton.
I resolve this issue. please try to stop animation under DispatchQueue.main.async and working well.
'func apicallforLogin() {
let urlString = "http://"
guard let requestUrl = URL(string:urlString) else { return }
let request = URLRequest(url:requestUrl)
let task = URLSession.shared.dataTask(with: request) {
(data, response, error) in
if error == nil,let usableData = data {
DispatchQueue.main.async {
self.btnLogin.startFinishAnimation(0.1, completion: {
let HomeVC = self.storyboard?.instantiateViewController(withIdentifier: "HomeViewController") as! HomeViewController
HomeVC.transitioningDelegate = self
self.navigationController?.pushViewController(HomeVC, animated: false)
})
}
print(usableData)
}else{
DispatchQueue.main.async {
self.btnLogin.returnToOriginalState()
}
}
}
task.resume()'
Well you might have got the answer by now but just I would like to give my answer. You can just copy below code and everything will work fine. I have made the change and had created the pull request for this issue.
import Foundation
import UIKit
#IBDesignable
open class TKTransitionSubmitButton : UIButton, UIViewControllerTransitioningDelegate, CAAnimationDelegate {
lazy var spiner: SpinerLayer! = {
let s = SpinerLayer(frame: self.frame)
return s
}()
#IBInspectable open var spinnerColor: UIColor = UIColor.white {
didSet {
spiner.spinnerColor = spinnerColor
}
}
open var didEndFinishAnimation : (()->())? = nil
let springGoEase = CAMediaTimingFunction(controlPoints: 0.45, -0.36, 0.44, 0.92)
let shrinkCurve = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)
let expandCurve = CAMediaTimingFunction(controlPoints: 0.95, 0.02, 1, 0.05)
let shrinkDuration: CFTimeInterval = 0.1
#IBInspectable open var normalCornerRadius:CGFloat = 0.0 {
didSet {
self.layer.cornerRadius = normalCornerRadius
}
}
var cachedTitle: String?
var isAnimating = false
public override init(frame: CGRect) {
super.init(frame: frame)
self.setup()
}
public required init!(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
self.setup()
}
func setup() {
self.clipsToBounds = true
spiner.spinnerColor = spinnerColor
}
open func startLoadingAnimation() {
self.isAnimating = true
self.cachedTitle = title(for: UIControlState())
self.setTitle("", for: UIControlState())
self.layer.addSublayer(spiner)
// Animate
self.cornerRadius()
self.shrink()
_ = Timer.schedule(delay: self.shrinkDuration - 0.25) { timer in
self.spiner.animation()
}
}
open func startFinishAnimation(_ delay: TimeInterval,_ animation: CAMediaTimingFunction, completion:(()->())?) {
self.isAnimating = true
_ = Timer.schedule(delay: delay) { timer in
self.didEndFinishAnimation = completion
self.expand(animation)
self.spiner.stopAnimation()
}
}
open func animate(_ duration: TimeInterval,_ animation: CAMediaTimingFunction, completion:(()->())?) {
startLoadingAnimation()
startFinishAnimation(duration, animation, completion: completion)
}
open func setOriginalState() {
self.returnToOriginalState()
self.spiner.stopAnimation()
}
public func animationDidStop(_ anim: CAAnimation, finished flag: Bool) {
let a = anim as! CABasicAnimation
if a.keyPath == "transform.scale" {
didEndFinishAnimation?()
_ = Timer.schedule(delay: 1) { timer in
self.returnToOriginalState()
}
}
}
open func returnToOriginalState() {
self.spiner.removeFromSuperlayer()
self.layer.removeAllAnimations()
self.setTitle(self.cachedTitle, for: UIControlState())
self.spiner.stopAnimation()
self.isAnimating = false
}
func cornerRadius() {
let cornerRadiusAnim = CABasicAnimation(keyPath: "cornerRadius")
// cornerRadiusAnim.fromValue = frame.width
cornerRadiusAnim.toValue = frame.height/2
cornerRadiusAnim.duration = shrinkDuration
cornerRadiusAnim.timingFunction = shrinkCurve
cornerRadiusAnim.fillMode = kCAFillModeForwards
cornerRadiusAnim.isRemovedOnCompletion = false
layer.add(cornerRadiusAnim, forKey: cornerRadiusAnim.keyPath)
}
func shrink() {
let shrinkAnim = CABasicAnimation(keyPath: "bounds.size.width")
shrinkAnim.beginTime = CACurrentMediaTime() + 0.1
shrinkAnim.fromValue = frame.width
shrinkAnim.toValue = frame.height
shrinkAnim.duration = shrinkDuration
shrinkAnim.timingFunction = shrinkCurve
shrinkAnim.fillMode = kCAFillModeForwards
shrinkAnim.isRemovedOnCompletion = false
layer.add(shrinkAnim, forKey: shrinkAnim.keyPath)
}
func expand(_ animation: CAMediaTimingFunction) {
let expandAnim = CABasicAnimation(keyPath: "transform.scale")
expandAnim.fromValue = 1.0
expandAnim.toValue = 26.0
expandAnim.timingFunction = animation
expandAnim.duration = 0.3
expandAnim.delegate = self
expandAnim.fillMode = kCAFillModeForwards
expandAnim.isRemovedOnCompletion = false
layer.add(expandAnim, forKey: expandAnim.keyPath)
}
}