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".
Related
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.
Im trying to make a small game. And there is some problem with the animation. Im new to Swift. So lets take a look. I create a UIImageView picture and want to do animation of this picture appear in a different places on a screen. I believe that the algorithm will look like this:
Infinite loop{
1-GetRandomPlace
2-change opacity from 0 to 1 and back(with smooth transition)
}
Looks simple, but I can't understand how to do it correctly in Xcode.
Here is my test code but it looks useless
Thank you for help and sorry if there was already this question, I can't find it.
class ViewController: UIViewController {
#IBOutlet weak var BackgroundMainMenu:UIImageView!
#IBOutlet weak var AnimationinMenu: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
// MoveBackgroundObject(AnimationinMenu)
// AnimationBackgroundDots(AnimationinMenu, delay: 0.0)
// self.AnimationinMenu.alpha = 0
//
// UIImageView.animateWithDuration(3.0,
// delay: 0.0,
// options: UIViewAnimationOptions([.Repeat, .CurveEaseInOut]),
// animations: {
// self.MoveBackgroundObject(self.AnimationinMenu)
// self.AnimationinMenu.alpha = 1
// self.AnimationinMenu.alpha = 0
// },
// completion: nil)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func viewWillAppear(animated: Bool) {
AnimationBackgroundDots(AnimationinMenu, delay: 0.0)
}
func ChangeOpacityto1(element: UIImageView){
element.alpha = 0
UIView.animateWithDuration(3.0) {
element.alpha = 1
}
}
func ChangeOpacityto0(element: UIImageView){
element.alpha = 1
UIView.animateWithDuration(3.0){
element.alpha = 0
}
}
func AnimationBackgroundDots(element: UIImageView, delay: Double){
element.alpha = 0
var z = 0
while (z<4){
MoveBackgroundObject(AnimationinMenu)
UIImageView.animateWithDuration(3.0,
animations: {
element.alpha = 0
element.alpha = 1
element.alpha = 0
},
completion: nil)
z++
}
}
func MoveBackgroundObject(element: UIImageView) {
// Find the button's width and height
let elementWidth = element.frame.width
let elementHeight = element.frame.height
// Find the width and height of the enclosing view
let viewWidth = BackgroundMainMenu.superview!.bounds.width
let viewHeight = BackgroundMainMenu.superview!.bounds.height
// Compute width and height of the area to contain the button's center
let xwidth = viewWidth - elementWidth
let yheight = viewHeight - elementHeight
// Generate a random x and y offset
let xoffset = CGFloat(arc4random_uniform(UInt32(xwidth)))
let yoffset = CGFloat(arc4random_uniform(UInt32(yheight)))
// Offset the button's center by the random offsets.
element.center.x = xoffset + elementWidth / 2
element.center.y = yoffset + elementHeight / 2
}
}
The problem is in AnimationBackgroundDots.
You are immediately creating 4 animations on the same view but only one can run at a time. What you need to do is wait until one animation is finished (fade in or fade out) before starting a new one.
Also, the animations closure is for setting the state you want your view to animate to. It looks at how your view is at the start, runs animations, then looks at the view again and figures out how to animate between the two. In your case, the alpha of the UIImageView starts at 0, then when animations runs, the alpha ends up being 0 so nothing would animate. You can't create all the steps an animation should take that way.
Want you need to do it move your view and start the fade in animation. The completion closure of fading in should start the fade out animation. The completion closure of the fading out should then start the process all over again. It could look something like this.
func AnimationBackgroundDots(element: UIImageView, times: Int) {
guard times > 0 else {
return
}
MoveBackgroundObject(element)
element.alpha = 1
// Fade in
UIView.animateWithDuration(3.0, animations: {
element.alpha = 1
}, completion: { finished in
// Fade Out
UIView.animateWithDuration(3.0, animations: {
element.alpha = 0
}, completion: { finished in
// Start over again
self.AnimationBackgroundDots(element, times: times-1)
})
})
}
You called also look at using keyframe animations but this case is simple enough that theres no benefit using them.
Also as a side note. The function naming convention in swift is to start with a lowercase letter, so AnimationBackgroundDots should be animationBackgroundDots.
I am working on a simple (ok, it should be simple) calculator that estimates risk based on a few inputs (toggle on/off)
Everything is working properly, except that the UIButtons need to be tapped 2 times to toggle initially. After that, the UIButtons work as expected.
Here is the code (contained within viewDidLoad())
#IBOutlet weak var confusionButton: UIButton!
var counter = 0
var confusionToggle = 0
and here is the action
#IBAction func pressConfusion(sender: UIButton) {
if confusionToggle == 0 {
confusionButton.backgroundColor = UIColor.clearColor()
confusionButton.alpha = 1;
confusionToggle++;
counter++;
} else {
confusionButton.backgroundColor = UIColor.grayColor()
confusionButton.alpha = 0.5;
confusionToggle = 0
counter--;
}
}
Any thoughts on this one? My Swift skills are very limited (my first attempt with Swift)
Think your just getting yourself confused. Use print statements to first understand what is happening, then do all your fancy button UI changes.
#IBAction func tapButton(sender: UIButton) {
// user println to help debug
println(confusionToggle)
if confusionToggle == 0 {
sender.backgroundColor = UIColor.grayColor()
sender.alpha = 0.5;
confusionToggle++;
counter++;
}
else {
// RESET
sender.backgroundColor = UIColor.clearColor()
sender.alpha = 1;
confusionToggle = 0
counter--;
}
}
I don't know what you trying to do.
Just let you know that confusionToggle will be 0 at first time. So, when you click on the button your first condition will be called, It will clear the background color and increase the value of confusionToggle to 1 then after it's because of 1 it will not go to the first condition. So, it goes to else part and give the background color to the gray. confusionToggle again goes to 0.
And it will iterate every time like 0 - 1 - 0 - 1 ...
If you want to do gray color to the button when you tap then you have to change your logic.
if confusionToggle == 0 {
confusionButton.backgroundColor = UIColor.grayColor()
confusionButton.alpha = 0.5;
confusionToggle = 1;
counter++;
} else {
confusionButton.backgroundColor = UIColor.clearColor()
confusionButton.alpha = 1;
confusionToggle = 0
counter--;
}
Hope it helps to you.
I am making a simple game in SpriteKit, and I have a scrolling background. What simply happens is that a few background images are placed adjacent to each other when the game scene is loaded, and then the image is moved horizontally when it scrolls out of the screen. Here is the code for that, from my game scene's didMoveToView method.
// self.gameSpeed is 1.0 and gradually increases during the game
let backgroundTexture = SKTexture(imageNamed: "Background")
var moveBackground = SKAction.moveByX(-self.frame.size.width, y: 0, duration: (20 / self.gameSpeed))
var replaceBackground = SKAction.moveByX(self.frame.size.width, y: 0, duration: 0)
var moveBackgroundForever = SKAction.repeatActionForever(SKAction.sequence([moveBackground, replaceBackground]))
for var i:CGFloat = 0; i < 2; i++ {
var background = SKSpriteNode(texture: backgroundTexture)
background.position = CGPoint(x: self.frame.size.width / 2 + self.frame.size.width * i, y: CGRectGetMidY(self.frame))
background.size = self.frame.size
background.zPosition = -100
background.runAction(moveBackgroundForever)
self.addChild(background)
}
Now I want to increase the speed of the scrolling background at certain points of the game. You can see that the duration of the background's horizontal scroll is set to (20 / self.gameSpeed). Obviously this does not work, because this code is only run once, and therefore the movement speed is never updated to account for a new value of the self.gameSpeed variable.
So, my question is simply: how do I increase the speed (reduce the duration) of my background images' movements according to the self.gameSpeed variable?
Thanks!
You could use the gameSpeed variable to set the velocity of the background. For this to work, firstly, you need to have a reference to your two background pieces (or more if you so wanted):
class GameScene: SKScene {
lazy var backgroundPieces: [SKSpriteNode] = [SKSpriteNode(imageNamed: "Background"),
SKSpriteNode(imageNamed: "Background")]
// ...
}
Now you need your gameSpeed variable:
var gameSpeed: CGFloat = 0.0 {
// Using a property observer means you can easily update the speed of the
// background just by setting gameSpeed.
didSet {
for background in backgroundPieces {
// Minus, because the background is moving from left to right.
background.physicsBody!.velocity.dx = -gameSpeed
}
}
}
Then position each piece correctly in didMoveToView. Also, for this method to work each background piece needs a physics body so you can easily change its velocity.
override func didMoveToView(view: SKView) {
for (index, background) in enumerate(backgroundPieces) {
// Setup the position, zPosition, size, etc...
background.physicsBody = SKPhysicsBody(rectangleOfSize: background.size)
background.physicsBody!.affectedByGravity = false
background.physicsBody!.linearDamping = 0
background.physicsBody!.friction = 0
self.addChild(background)
}
// If you wanted to give the background and initial speed,
// here's the place to do it.
gameSpeed = 1.0
}
You could update gameSpeed in update for example with gameSpeed += 0.5.
Finally, in update you need to check if a background piece has gone offscreen (to the left). If it has it needs to be moved to the end of the chain of background pieces:
override func update(currentTime: CFTimeInterval) {
for background in backgroundPieces {
if background.frame.maxX <= 0 {
let maxX = maxElement(backgroundPieces.map { $0.frame.maxX })
// I'm assuming the anchor of the background is (0.5, 0.5)
background.position.x = maxX + background.size.width / 2
}
}
}
You could make use of something like this
SKAction.waitforDuration(a certain amount of period to check for the updated values)
SKAction.repeatActionForever(the action above)
runAction(your action)
{ // this is the completion block, do whatever you want here, check the values and adjust them accordly
}
So i got a set of rings one inside the other. I want to be able to zoom then in and, when I zoom close enough the zooming should animate the snap in and then I should keep zooming again.
I've done the zooming part pretty easily, using CGAffineTransform and UIPinchGesture recognizer, here is how the scale method looks like:
func scale(gesture: UIPinchGestureRecognizer) {
if !animationInProgress {
for var i = 0; i < scrollViews.count; i++ {
var view = scrollViews[i]
var j = 0
for ; j < scrollViews.count; j++ {
if view == scrollViews[j] {
break
}
}
println("Animation in progress: \(animationInProgress)")
if view.frame.origin.x < -80 || view.frame.origin.y < -140 {
if !view.hidden {
currentViewIndex = j + 1
view.hidden = true
view.visible = false
println("new view got out of the screen")
snapIn = true
animateSnap(snapIn)
}
} else {
if view.hidden == true {
currentViewIndex = j
view.hidden = false
view.visible = true
println("new view got into the screen")
snapIn = false
animateSnap(snapIn)
}
}
view.transform = CGAffineTransformScale(view.transform, gesture.scale, gesture.scale)
println("transformed")
}
gesture.scale = 1
}
}
so we iterate through our views, the frame.origin x and y checks, whether the views is out of the screen and I set some flags, which turn of some calculations.
The idea is the following, when one circle gets out of the screen, the rest animate zoom in and zoom out, when the new view gets on the screen.
Here is a animateSnap function:
private func animateSnap(snapIn: Bool) {
let factor: CGFloat = snapIn ? 1.5 : 0.5
for var a = currentViewIndex; a < scrollViews.count; a++ {
let next = scrollViews[a]
var transformAnimation = CABasicAnimation(keyPath: "transform")
transformAnimation.duration = 1
transformAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)
transformAnimation.removedOnCompletion = false
transformAnimation.fillMode = kCAFillModeForwards
transformAnimation.delegate = self
let transform = CATransform3DScale(next.layer.transform, factor, factor, 1)
transformAnimation.toValue = NSValue(CATransform3D:transform)
next.layer.addAnimation(transformAnimation, forKey: "transform")
}
}
The question is: does somebody know an elegant solution to this or see any flaw in my approach ? The problem happens when the animation ends the views kinda go back to where they were at the start. I don't know why because remove on completion is set to false. In addition, can the fact that I use CGAffineTransform for zooming and CATransform3D somehow affect the result ?
Thank you very much !
For everybody who might encounter the same problem, when you work with 2D use CGAffineTransform instead of CATransform3D. That solved the problem, in addition I suspect that they also change different things(for example, CATransform3D changes layer) that's why when animation finished the result did not look as expected.