First we generate a random number (for example from say 0 to 10):
If randomNumber = 0
Animate imageSet0 [here we create an ImageArray and an animate function – please see code below)
Else if randomNumber = 1
Animate imageSet1
Else if randomNumber = 2
Animate imageSet2
And so on…
Then we place a DispatchQueue timer that waits for the above animation to complete (time delay is equal to animationDuration), then we repeat the first step above and generate another random number and play another animation set:
DispatchQueue.main.asyncAfter(deadline: .now() + imageView.animationDuration) {
[Insert code that repeats the first step above and generates another random number to play another animation set]
}
In theory this random animation could play indefinitely until the user moves past this scene.
Here is my code thus far:
func createImageArray(total: Int, imagePrefix: String) -> [UIImage]{
var imageArray: [UIImage] = []
for imageCount in 0..<total {
let imageName = "\(imagePrefix)-\(imageCount).png"
let image = UIImage(named: imageName)!
imageArray.append(image)
}
return imageArray
}
func animate(imageView: UIImageView, images: [UIImage]){
imageView.animationImages = images
imageView.animationDuration = 1.0
imageView.animationRepeatCount = 1
imageView.startAnimating()
DispatchQueue.main.asyncAfter(deadline: .now() + imageView.animationDuration) {
[Create code that repeats the first step above and generates another random number to play another animation set]
}
}
You can use UIView.animate(withDuration: delay: options: animations:) api for that. Note delay parameter will let you start animation after certain delay.
Related
I am trying to create an animation that scrolls through a series of images. The following code does not have any animation and only the last image of the series appears on the screen. How do I fix this animation?
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(true)
imageSelection(score: 50)
}
func imageSelection(score: Int) {
var myScore = score
if score < 10 {
myScore = 1
}
else if score < 20 && score > 10 {
myScore = 2
}
else if score < 30 && score > 20 {
myScore = 3
}
else if score < 40 && score > 30 {
myScore = 4
}
else if score < 50 && score > 40 {
myScore = 5
}
else if score == 50 {
myScore = 6
}
for i in 1...myScore{
UIView.animate(withDuration: 0.5) {
self.cookieImage.image = UIImage(named: "rewardsCookie\(i)")
self.view.layoutIfNeeded()
}
}
}
As Matt said, you are not animating any animatable properties. You can achieve what you want to by using layer animations. Replace your for loop with the following code.
var images = [CGImage]()
for i in 1...myScore{
images.append(UIImage(named: "rewardsCookie\(i)")!.cgImage!)
}
let animation = CAKeyframeAnimation(keyPath: "contents")
animation.values = images
let delay = 3.0 // in case you need to delay your animation
animation.duration = 5.0 // change to the total duration (ex: 0.5 * myScore)
animation.beginTime = CACurrentMediaTime() + delay
self.cookieImage.layer.add(animation, forKey: nil)
Note that for layer animations, you are not actually seeing the UIImageView, but a cached version of it. This is removed from the screen once the animation completes and the original layer shows itself again. So you will see the image that was visible on the cookieImageView before the animation began. In order to persist the last image, add the following line after the code above
self.cookieImage.image = UIImage(named: "rewardsCookie\(myScore)")
For more information go through Apple's Documentation.
Are you trying do a "flip book" animation where you flip between a sequence of images? The easy way to do that is with UIImageView animation. See the UIImageView class reference in Xcode for more information (see the section titled "Animating a Sequence of Images".
I have animateButtons() function of which I need to set completion handler only when the animation has finished. The problem is that in animateButtons() I can only set the completion handler just after imageView.startAnimating() so the whole timing gets compromised, as it's completion is used to launch other animations. I read in a another post with same exact issue, that I should set an NSTimer to set the completion handler , as I actually thought of doing, but I don't really know how to . I have set NSTimer to call setCompletion() function, but how would I set it to call animateButtons() completion handler ?
Can you point me in the right direction?
This is the function and the selector function:
static func animateButtons(completed: #escaping(Bool) ->(), imageView: UIImageView, images: [UIImage]) {
imageView.animationImages = images
imageView.animationDuration = 1.0 // check duration
imageView.animationRepeatCount = 1
imageView.startAnimating()
_ = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(setCompletion), userInfo: nil, repeats: false)
// completed(true)
}
#objc func setCompletion() {
}
You can try to use inline callback
static func animateButtons(completed: #escaping(Bool) ->(), imageView: UIImageView, images: [UIImage]) {
imageView.animationImages = images
imageView.animationDuration = 1.0 // check duration
imageView.animationRepeatCount = 1
imageView.startAnimating()
Timer.scheduledTimer(withTimeInterval: 1.0, repeats: false, block: { (t) in
t.invalidate()
completed(true)
})
}
BTW a dispatch after can do the job also
static func animateButtons(completed: #escaping(Bool) ->(), imageView: UIImageView, images: [UIImage]) {
imageView.animationImages = images
imageView.animationDuration = 1.0 // check duration
imageView.animationRepeatCount = 1
imageView.startAnimating()
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
completed(true)
}
}
Approach 1 (I prefer this one) (If you are animating Gif images)
Use a third-party library like Gifu https://github.com/kaishin/Gifu to calculate the runtime of your gif. It will work even if you change the gif image in the future.
static func animateButtons(completed: #escaping(Bool) ->(), imageView: UIImageView, images: [UIImage]) {
imageView.prepareForAnimation(withGIFNamed: "welcome", loopCount: 1) {
self.imageView.startAnimatingGIF()
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2 + self.imageView.gifLoopDuration, execute: {
// Do your Task after animation completes
)
}
}
Approach 2 (Calculate the run Time of the animation and set a timer)
I have the case, where the connect button is pressed, and continuously animated until the VPN has connected. Then I would like to stop the animation and load up a new set of images and animate that only once.
#IBOutlet weak var vpnButton: UIImageView!
var premiumImages: [UIImage] = []
var loadingImages: [UIImage] = []
#objc private func vpnStatusDidChange(_ notification: Notification) {
let nevpnconn = notification.object as! NEVPNConnection
let status = nevpnconn.status
switch status {
case NEVPNStatus.connecting:
self.animateImages(imageView: self.vpnButton, images: loadingImages, duration: 1.0, isInfinite: true)
self.vpnButton.image = UIImage(named: "Red-1")
}
The method that takes care of animation:
func animateImages(imageView: UIImageView, images: [UIImage], duration: Double, isInfinite: Bool=false) {
imageView.stopAnimating()
imageView.animationImages = images
imageView.animationDuration = duration
if !isInfinite {
imageView.animationRepeatCount = 1
} else {
imageView.animationRepeatCount = 0
}
imageView.startAnimating()
}
So whenever I call this, it would stop any existing animation in place and animate it with new images.
And once the VPN has connected:
case NEVPNStatus.connected:
self.vpnButton.highlightedImage = UIImage(named: "OrangePressed")
self.animateImages(imageView: self.vpnButton, images: premiumImages, duration: 0.25)
self.vpnButton.image = UIImage(named: "OrangePremium-3")
Only the loadingImages get properly animated. The second set premiumImages doesn't animate and only shows the last frame.
I suspect that stopAnimating() takes some time to accomplish, and ideally, I needed a completion handler here to know when to kick off the imageView.startAnimating().
So here is where I am at now. I still haven't been able to get it to work and I've tried a lot of variations. Here is my original code for a single animation.
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var imageView: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
var images = [UIImage]()
var images2 = [UIImage]()
images += [#imageLiteral(resourceName: "circle1"), #imageLiteral(resourceName: "circle2"), #imageLiteral(resourceName: "circle3"), #imageLiteral(resourceName: "circle4")]
images2 += [#imageLiteral(resourceName: "circle4"), #imageLiteral(resourceName: "circle3"), #imageLiteral(resourceName: "circle2"), #imageLiteral(resourceName: "circle1")]
imageView.animationImages = images
imageView.animationDuration = 4.0
imageView.animationRepeatCount = 2
imageView.startAnimating()
}
}
I am able to animate the first images array with the properties I've set for duration and count. However, I just do not understand how I would write this to add a second set of images to animate immediately after. I know I need to use this somehow:
UIView
.animate(withDuration: 4.0,
animations: {
//animation 1
},
completion: { _ in
UIView.animate(withDuration: 6.0,
animations: {
//animation 2
})
})
Can someone show me how I would write this given my current images arrays using the same imageView object to display both animations? I want my first animation (images) to have a duration of 4 seconds and my second animation (images2) to have a duration of 6 second immediately following the first animation. I can't figure out what I need inside the animations parameter.
i think the error is that you mix here 2 things:
UIView.animate -> animate controls and properties
UIImageView.startAnimating -> start a loop of images in an UIImageView
but they don't do the same they are very independent. but UIView animation is normally for an other use case. only one thing is that they maybe have the same duration like your UIImageView animation, but you don't set the duration of your UIImageView. maybe when you set the animation duration of your image view to the duration of UIView animation then it is done on the same time range.
myImageView.animationDuration = 2;
and for the second loop
myImageView.animationDuration = 4;
Other Solutions
the thing is you need to know when an image loop ist completed. but there is no event for this (i did not found any)
there are some solutions on StackOverflow for this:
1 performSelector afterDelay
Set a timer to fire after the duration is done. for this you need also to add an animation duration to your image view:
myImageView.animationDuration = 0.7;
solution from here: how to do imageView.startAnimating() with completion in swift?:
When the button is pressed you can call a function with a delay
self.performSelector("afterAnimation", withObject: nil, afterDelay: imageView1.animationDuration)
Then stop the animation and add the last image of imageArray to the imageView in the afterAnimation function
func afterAnimation() {
imageView1.stopAnimating()
imageView1.image = imageArray.last
}
2 Timer
this is similar to performSelector afterDelay but with NSTimer.
you find a description here UIImageView startAnimating: How do you get it to stop on the last image in the series? :
1) Set the animation images property and start the animation (as in
your code in the question)
2) Schedule an NSTimer to go off in 'animationDuration' seconds time
3) When the timer goes off, [...]
add to point 3: then start the next animation
3 CATransaction
solution from here: Cocoa animationImages finish detection (you need to convert it to swift 3 syntax but it can be a starting point
Very old question, but I needed a fix now, this works for me:
[CATransaction begin];
[CATransaction setCompletionBlock:^{
DLog(#"Animation did finish.");
}];
UIImageView *imageView = [[UIImageView alloc] initWithFrame:self.window.bounds];
imageView.animationDuration = 0.3 * 4.0;
imageView.animationImages = #[[UIImage AHZImageNamed:#"Default2"],
[UIImage AHZImageNamed:#"Default3"],
[UIImage AHZImageNamed:#"Default4"],
[UIImage AHZImageNamed:#"Default5"]];
imageView.animationRepeatCount = 1;
[self.window addSubview:imageView];
[imageView startAnimating];
[CATransaction commit];
4 Offtopic: Manual do the Image Animation
thats a little offtopic, because here they do the image animation manually. for your use case you just change the logic which image from index is visible. count forward until last image, then backwards until first image. and then stop the loop. not nice solution but in this solution is added a image transition animation:
Solution from here: Adding Image Transition Animation in Swift
class ViewController: UIViewController {
#IBOutlet weak var imageView: UIImageView!
let images = [
UIImage(named: "brooklyn-bridge.jpg")!,
UIImage(named: "grand-central-terminal.jpg")!,
UIImage(named: "new-york-city.jpg"),
UIImage(named: "one-world-trade-center.jpg")!,
UIImage(named: "rain.jpg")!,
UIImage(named: "wall-street.jpg")!]
var index = 0
let animationDuration: NSTimeInterval = 0.25
let switchingInterval: NSTimeInterval = 3
override func viewDidLoad() {
super.viewDidLoad()
imageView.image = images[index++]
animateImageView()
}
func animateImageView() {
CATransaction.begin()
CATransaction.setAnimationDuration(animationDuration)
CATransaction.setCompletionBlock {
let delay = dispatch_time(DISPATCH_TIME_NOW, Int64(self.switchingInterval * NSTimeInterval(NSEC_PER_SEC)))
dispatch_after(delay, dispatch_get_main_queue()) {
self.animateImageView()
}
}
let transition = CATransition()
transition.type = kCATransitionFade
/*
transition.type = kCATransitionPush
transition.subtype = kCATransitionFromRight
*/
imageView.layer.addAnimation(transition, forKey: kCATransition)
imageView.image = images[index]
CATransaction.commit()
index = index < images.count - 1 ? index + 1 : 0
}
}
Implement it as a custom image view would be better.
I know you said that this didn't work for you, but if you just want to execute animations right after another, I believe this really is the best option.
You should be able to do something like this:
let imageView = UIImageView()
UIView.animate(withDuration: 0.5, animations: {
//animation 1
}, completion: { (value: Bool) in
UIView.animate(withDuration: 1.0, animations: {
//animation 2
})
})
This does one animation directly after another, which a longer duration. Of course you still need to define what the animations are, but I hope this helps.
I'm not sure if this is the best way to do this, but I've found a solution to my problem. Please give feedback if there is a better way. I was able to accomplish multiple animations in a single UIImageView consecutively by using: self.perform(#selector(ViewController.afterAnimation), with: nil, afterDelay: 4.0)
This calls the afterAnimation function:
func afterAnimation() {
imageView.stopAnimating()
imageView.animationImages = images2
imageView.animationDuration = 6.0
imageView.animationRepeatCount = 1
imageView.startAnimating()
}
I needed animations to each last a specific amount of time and to be able to chain many of them together. It solves my problem.
I'm trying to animate images in particular time- duration. It is working fine in Objective C. However, it is not working for Swift, where is my mistake?
The code for Objective-C is -
-(void)viewDidLoad
{
[super viewDidLoad];
NSMutableArray *imgListArray=[NSMutableArray array];
for (int i=0; i<=11; i++)
{
NSString *strImageName=[NSString stringWithFormat:#"c%d.png", i];
NSLog(#"%#",strImageName);
UIImage *image=[UIImage imageNamed:strImageName];
[imgListArray addObject:image];
}
self.imgView.animationImages = imgListArray;
self.imgView.animationDuration =1.0f;
[self.imgView startAnimating];
// Do any additional setup after loading the view, typically from a nib
}
The Code for swift is-
override func viewDidLoad()
{
super.viewDidLoad()
var imgListArray :NSMutableArray = []
for countValue in 1...11
{
var strImageName : String = "c\(countValue).png"
var image = UIImage(named:strImageName) // suggested by Anil
imgListArray.addObject(image)
}
// Swift code HERE for Objective c
}
[UIImage imageNamed (strImageName)]
This not swift code. In swift it would be
UIImage(named:strImageName)
Modified code:
var imgListArray :NSMutableArray = []
for countValue in 1...11
{
var strImageName : String = "c\(countValue).png"
var image = UIImage(named:strImageName)
imgListArray .addObject(image)
}
self.imageView.animationImages = imgListArray;
self.imageView.animationDuration = 1.0
self.imageView.startAnimating()
for Swift 2, use [UIImage] instead.
var images: [UIImage] = []
for i in 1...2 {
images.append(UIImage(named: "c\(i)")!)
}
myImageView.animationImages = images
myImageView.animationDuration = 1.0
myImageView.startAnimating()
In swift you can go one step further and have a simple line to do this:
let loadingImages = (1...11).map { UIImage(named: "c\($0)")! }
Then you can do the rest and inject this into an imageView
self.imageView.animationImages = loadingImages
self.imageView.animationDuration = 1.0
self.imageView.startAnimating()
In swift 3 - Create Array of images and just animate that.
func animate_images()
{
let myimgArr = ["1.jpg","2.jpg","3.jpg"]
var images = [UIImage]()
for i in 0..<myimgArr.count
{
images.append(UIImage(named: myimgArr[i])!)
}
imgView_ref.animationImages = images
imgView_ref.animationDuration = 0.04
imgView_ref.animationRepeatCount = 2
imgView_ref.startAnimating()
}
And for stop animation just write
imgView_ref.stopAnimating()
Swift 3+
UIImageViews can animate images in two different ways (the other answers already cover the first way in deep):
Create an [UIImage] array with the images to animate
Set the animationImages property of the view with this array
Set the animationDuration property
Start the animation
Encapsulate the animation in an UIImage using animatedImage(with:duration:)
Set the normal image property of the view
The following code uses #imageLiterals:
let images = [img1, img2, img3] // use of #imageLiterals here
let animation = UIImage.animatedImage(with: images, duration: 1)
self.myImageView.image = animation
Pro:
If you have to change the image of an UIImageView a lot and maybe one of those images should be animated, then you don't have to start/stop the animation every time anymore neither you need to set the animatedImages property back to nil to display the image stored in the image property of the image view.
Con:
You can't start/stop the animation, if it's encapsulated in a single UIImage instead of an array.
More on #imageLiterals:
If you want to use an image literal, either type imageLiteral or just type the name of an image from your assets folder and Xcode's code completion will suggest it.
Further reading:
Another good blog post about using #imageLiterals and #colorLiterals with animations to follow.
another approach if you want to animate an array of images:
var counter = 1
var timer = NSTimer()
#IBOutlet weak var someImg: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
timer = NSTimer.scheduledTimerWithTimeInterval(0.3, target: self, selector: Selector("doSomeAnimation"), userInfo: nil, repeats: true)
}
func doSomeAnimation() {
//I have four pngs in my project, which are named frame1.png ... and so on
if counter == 4 {
counter = 1
}else {
counter++
}
someImg.image = UIImage(named: "frame\(counter).png")
}
Hope it helps!!
For anyone running multiple animations, depending on a variable, try the below.
For example, you could run an animation sending 0 or 1 (maybe based on if your app knows its daytime or nighttime):
func runAnimation (imgView: UIImageView, arrayPos: Int) {
let timingArray = [7.0,10.0] //Durations of each
let frameArray = [43,45] //Frames for each
var imgArray: Array<UIImage> = [] //Setup empty array
for i in 1 ... frameArray[arrayPos] {
//Fill empty array with images!!
let imageToAdd = UIImage(named: "animationNum_\(arrayPos)-frameNum_\(i)")
imgArray.append(imageToAdd!)
}
imgView.animationImages = imgArray
imgView.animationDuration = timingArray[arrayPos]
imgView.animationRepeatCount = 2
imgView.startAnimating()
}