fade between two UIButton images

2020-02-09 04:19发布

问题:

i want to fade between two UIButton images for the purpose of setting favorites in a UITableView.

Currently the transition is done without effect - it just changes the images directly on click/touch:

trans_img = [UIImage imageNamed:@"fav_on.png"];

NSArray *subviews = [owningCell subviews];
UIButton *favbutton = [subviews objectAtIndex:2];

favbutton.imageView.animationImages =
[NSArray arrayWithObjects:trans_img,
 nil];

[favbutton.imageView startAnimating];

Everything I found was a transition between UIViews :(

It would be nice if the image fav_off gets smoothly changed into fav_on and the other way round like a fadein/fadeout.

回答1:

You could try to transition the alpha values like this to get the effect that you want:

trans_img = [UIImage imageNamed:@"fav_on.png"];

NSArray *subviews = [owningCell subviews];
UIButton *favbutton = [subviews objectAtIndex:2];
[UIView animateWithDuration:0.5 animations:^{
    favbutton.alpha = 0.0f;
} completion:^(BOOL finished) {
    favbutton.imageView.animationImages = [NSArray arrayWithObjects:trans_img,nil];
    [favbutton.imageView startAnimating];
    [UIView animateWithDuration:0.5 animations:^{
        favbutton.alpha = 1.0f;
    }];
}];


回答2:

It seems like what you're looking for is this. It animates the images on a UIButton without adding new images, creating an array or changing alpha values!

CABasicAnimation *crossFade = [CABasicAnimation animationWithKeyPath:@"contents"];
crossFade.duration = 0.7;
crossFade.fromValue = (id)oldImage.CGImage;
crossFade.toValue = (id)newImage.CGImage;
crossFade.removedOnCompletion = NO;
crossFade.fillMode = kCAFillModeForwards;
[button.imageView.layer addAnimation:crossFade forKey:@"animateContents"];

//Make sure to add Image normally after so when the animation
//is done it is set to the new Image
[button setImage:newImage forState:UIControlStateNormal];


回答3:

Here in swift:

  import UIKit

   extension UIButton {

       func changeImageAnimated(image: UIImage?) {
           guard let imageView = self.imageView, currentImage = imageView.image, newImage = image else {
               return
           }
           let crossFade: CABasicAnimation = CABasicAnimation(keyPath: "contents")
           crossFade.duration = 0.3
           crossFade.fromValue = currentImage.CGImage
           crossFade.toValue = newImage.CGImage
           crossFade.removedOnCompletion = false
           crossFade.fillMode = kCAFillModeForwards
           imageView.layer.addAnimation(crossFade, forKey: "animateContents")
       }
   }


   self.playAndPauseVideo.changeImageAnimated(UIImage(named: "pauseVideo"))


回答4:

Thank you to @jkanter for the great answer. I made mine into a Swift 3.0 extension that I thought might also be useful for anyone who stumbles upon this post.

extension UIButton {

    func setImage(_ image: UIImage?, for state: UIControlState, animated: Bool) {
        guard animated, let oldImage = imageView?.image, let newImage = image else {
            // Revert to default functionality
            setImage(image, for: state)
            return
        }

        let crossFade = CABasicAnimation(keyPath:"contents")
        crossFade.duration = 0.35
        crossFade.fromValue = oldImage.cgImage
        crossFade.toValue = newImage.cgImage
        crossFade.isRemovedOnCompletion = false
        imageView?.layer.add(crossFade, forKey: "animateContents")

        setImage(image, for: state)
    }
}


回答5:

This is a slight improvement to @Ponja's answer that generally works well. Unfortunately, the newImage doesn't "stick", so if you wanted to toggle between two images, the first fade would work smoothly, but going back to the original image would "snap" back with no fade. Fixed it by using a CATransaction:

import UIKit

extension UIButton {
    func changeImageAnimated(image: UIImage?) {
        guard let imageView = self.imageView, currentImage = imageView.image, newImage = image else {
            return
        }
        CATransaction.begin()
        CATransaction.setCompletionBlock {
            self.setImage(newImage, forState: UIControlState.Normal)
        }
        let crossFade: CABasicAnimation = CABasicAnimation(keyPath: "contents")
        crossFade.duration = 0.3
        crossFade.fromValue = currentImage.CGImage
        crossFade.toValue = newImage.CGImage
        crossFade.removedOnCompletion = false
        crossFade.fillMode = kCAFillModeForwards
        imageView.layer.addAnimation(crossFade, forKey: "animateContents")
        CATransaction.commit()
    }
}


回答6:

@SuperDuperTango's answer with tintColor added:

extension UIButton {
    func changeImageAnimated(image: UIImage?) {
        guard let imageView = self.imageView, currentImage = imageView.image, newImage = image else {
            return
        }
        CATransaction.begin()
        CATransaction.setCompletionBlock {
            self.setImage(newImage, forState: UIControlState.Normal)
        }
        let crossFade: CABasicAnimation = CABasicAnimation(keyPath: "contents")
        crossFade.duration = 0.3
        crossFade.fromValue = currentImage.CGImage
        crossFade.toValue = newImage.CGImage
        crossFade.removedOnCompletion = false
        crossFade.fillMode = kCAFillModeForwards
        imageView.layer.addAnimation(crossFade, forKey: "animateContents")
        CATransaction.commit()

        let crossFadeColor: CABasicAnimation = CABasicAnimation(keyPath: "contents")
        crossFadeColor.duration = 0.3
        crossFadeColor.fromValue = UIColor.blackColor()
        crossFadeColor.toValue = UIColor(red: 232.0/255.0, green: 85.0/255.0, blue: 71.0/255.0, alpha: 1.0)
        crossFadeColor.removedOnCompletion = false
        crossFadeColor.fillMode = kCAFillModeForwards
        imageView.layer.addAnimation(crossFadeColor, forKey: "animateContents")
        CATransaction.commit()
    }
}


回答7:

@SuperDuperTango's answer in Swift 4.2:

extension UIButton {
    func changeImageAnimated(image: UIImage?) {
        guard let imageView = self.imageView, let currentImage = imageView.image, let newImage = image else { return }

        CATransaction.begin()
        CATransaction.setCompletionBlock {
            self.setImage(newImage, for: .normal)
        }
        let crossFade: CABasicAnimation = CABasicAnimation(keyPath: "contents")
        crossFade.duration = 0.3
        crossFade.fromValue = currentImage.cgImage
        crossFade.toValue = newImage.cgImage
        crossFade.isRemovedOnCompletion = false
        crossFade.fillMode = CAMediaTimingFillMode.forwards
        imageView.layer.add(crossFade, forKey: "animateContents")
        CATransaction.commit()
    }
}


回答8:

jkanter answer in Swift:

let crossFade = CABasicAnimation.init(keyPath: "contents")
crossFade.duration = 0.7
crossFade.fromValue = oldImage.cgImage
crossFade.toValue = newImage.cgImage
crossFade.isRemovedOnCompletion = false
crossFade.fillMode = kCAFillModeForwards
button.imageView?.layer.add(crossFade, forKey: "animateContents")

//Make sure to add Image normally after so when the animation
//is done it is set to the new Image
button.setImage(newImage, for: .normal)