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.
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;
}];
}];
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];
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"))
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)
}
}
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()
}
}
@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()
}
}
@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()
}
}
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)
来源:https://stackoverflow.com/questions/16801948/fade-between-two-uibutton-images