Swift: removeFromSubview happens before animation - ios

I have a little setup where I have an MPMoviePlayerController setup as a video background. I'm trying to get it to have a smooth transition when it updates/changes. Here's my current code. BTW, newBackground.view.alpha is initially 0.
let oldBackground = currentBackground
self.view.insertSubview(newBackground.view, aboveSubview: oldBackground.view)
UIView.animateWithDuration(1.5 as NSTimeInterval, animations: {
newBackground.view.alpha = 1
}, completion: {
finished in
oldBackground.view.removeFromSuperview()
println("The subview should be removed now")
})
When this executes, the oldBackground.view is immediately removed before the newBackground starts to fade in. The println, however, happens after the animation is completed. I don't understand why the removeFromSuperview happens immediately, but the println happens when I expect it to. If I remove oldBackground.view.removeFromSuperview(), the animation fades in and looks fine (but the view obviously hasn't been removed, it's just sitting behind newBackground).
EDIT: Bizarrely enough, it seems to work as expected in the simulator. Running on my iPhone 6 Plus gives me the issue every time. I've uninstalled and re-run it from Xcode and the problem persists.
If anyone has any advice for me, I would be very happy. Thank you.

I guess when the newBackground.view.alpha = 1 is executed during animation it brings the background to visibility and overlays the oldBackground.

When you put the new subview above the old subview, old subview goes to the back. This happens before the animation code starts. Make sure your new subview's alpha is 0 before inserting it. And if you want a fade out animation you should set your oldBackground alpha to 0.
Like this :
let oldBackground = currentBackground
newBackground.view.alpha = 0
self.view.insertSubview(newBackground.view, aboveSubview: oldBackground.view)
UIView.animateWithDuration(1.5 as NSTimeInterval, animations: {
newBackground.view.alpha = 1
oldBackground.view.alpha = 0
}, completion: {
finished in
oldBackground.view.removeFromSuperview()
println("The subview should be removed now")
})

Related

Native layout system is animating

I recently faced an issue where all layouts animated. I can't find out what causes this. Any idea about how can I detect the issue source would be welcome.
Controller hierarchy:
UITabBarController
UINavigationControler
UIViewController
UIPageViewController
UICollectionView
GoogleMap
Visible animated stuff: (without being inside animation block)
layout:
labels and views moving across the cell to the destination position (self-sizing cell)
horizontal collection view items comes from corner to their position
layer:
corner radius from 0 to height/2 animated.
view:
isHidden not works sometimes. It is inside UIStackView and when is hide it, it's just push it out from the stack view (visible because of the bug, strange but it's true)
setting title on buttons animated (setTitle:forState: method. And not the flashing animation, some kind of morphing animation)
Where are the layout and styling codes?
first after viewDidLoad and inside datasource didSet observer
What about threads?
I double checked all UI works dispatched in Main queue and Main Thread Checker is on.
- Show me The code !!!
Unfortunately this is an epic production project and changes that caused this issue is UNKNOWN. I wasn't able to reproduce the issue in some demo app to post it. Sorry
I found the issue after about 3 days of investigating and the answer in just a couple of minutes:
In some point of the code there was a animation block:
UIView.animate(withDuration: 1, animations: {
self.view.layoutIfNeeded()
})
I don't know why but this caused all layout animated for the rest of the controller's life (even after animation is done)
So for get around this I tried this:
UIView.animate(withDuration: 1, animations: {
DispatchQueue.main.async {
self.view.layoutIfNeeded()
}
})
Problem solved BUT!!! new issue appeared:
The original animation is not working at all!
So for the final workaround I changed the code a bit:
DispatchQueue.main.async {
UIView.animate(withDuration: 1, animations: {
self.view.layoutIfNeeded()
})
}
And fortunately it works! I knew:
all UI works should be done on main thread
and also animation blocks doesn't capture self, and perform their job on current thread. but strangely done.

UIview animation misbehaves when image of an unrelated button is changed

On my viewcontroller, I have a button to enable/disable audio using the following action:
func audioControl() {
playMusic = playMusic ? false : true // toggle
if (playMusic) {
startMusic()
audioBtn.setImage(UIImage(named: "musicOn"),for: .normal)
}
else {
stopMusic()
audioBtn.setImage(UIImage(named: "musicOff"),for: .normal)
}
}
The button toggles its image, as expected.
There is also an unrelated UIImageView object on the same VC which is being translated as follows:
UIView.animate(withDuration: 10.0, delay: 0.0, options: [ .curveLinear, .repeat ], animations: {
self.movingImg.center = CGPoint(x: screenSize.minX, y: screenSize.maxY)
})
This simple animation works fine by itself. However, as soon as the button is tapped, the movingImg object goes outside the boundary of screen and it is no longer visible.
I am not sure why a change of image on a button causes another object animation to misbehave. Any thoughts? I am using Swift 3.
Thank you.
After reading through discussions in a number of SO threads, I found that with the image change (along with any other layout changes), the viewcontroller's viewDidLayoutSubviews() gets called. As such, the followings are two things I did to resolve this issue:
Within the audioControl() method, I removed the animation of movingImg (i.e. movingImg.layer.removeAllAnimations()) before updating the button image.
I added viewDidLayoutSubviews() to my viewcontroller and started the animation of movingImg there.
Hopefully, this approach helps others too.

Not all content is animated inside a stack view when hiding it

I'm currently working on a iOS (swift 3) app. I have a simple vertical stack view containing 2 horizontal stack views. In some cases I want to hide the bottom one. I do so by using the following code
UIView.animate(withDuration: 3) {
self.bottomStackView.isHidden = true;
};
The animation shown below doesn't really do what I would expect:
While the border of the buttons is animated properly when hiding, the text inside each button doesn't seem to be affected until the very end. Any idea as to how I could fix this?
I kept doing some research on the subject, and it seems like most articles were suggesting that using stacks to perform animation would work fine. However I have also found that animations would only work with animatable properties, isHidden not being one of them.
In the end after some trial and errors I have found that isHidden can be animated with stack views, but you can expect children to misbehave. So far the only workaround I have found is like so:
let duration = 0.5;
let delay = 0;
UIView.animate(withDuration: duration, delay: delay, animations: {
self.bottomStack.isHidden = self.hideBottomStack;
})
UIView.animate(withDuration: duration/2, delay: delay, animations: {
self.bottomStack.alpha = 0;
})
You'll note here that I basically "turn" the alpha property down to 0 in half the time I take to hide the stack. This has the effect to hide the text before it overlaps with the upper stack. Also note that I could also have decided to do something like this:
UIView.animate(withDuration: duration, delay: delay, animations: {
self.bottomStack.alpha = 0;
}, completion: { (_) in
self.bottomStack.isHidden = true;
})
This would also hide the bottom stack, but you lose the hiding motion in favor of a fading motion and hide the stack once the fading is done.
I'm not sure about this, I think stackviews can cause weird behaviour sometimes. Have you tried adding "self.view.layoutIfNeeded()" inside the UIView.animate block? Like this:
UIView.animate(withDuration: 3) {
self.bottomStackView.isHidden = true
self.view.layoutIfNeeded()
}
I think it should also work if you put the "self.bottomStackView.isHidden = true" above the UIView.animate, not sure though, not an expert at it.
Also, I don't think you need to use ";" after your line of code in swift :)

Adding and removing animation on a UILabel

I am creating a game for english typing. The words fall from the top to the edge of keyboard. For animating the fall of a label I have used:
UIView.animateWithDuration(25, delay: 0, options: UIViewAnimationOptions.CurveLinear, animations: {
label.frame = CGRectMake(self.view.frame.width - (label.frame.origin.x+label.frame.width), self.view.frame.height-self.keyboardYPosition!, label.frame.width, width)
}){(success) in
label.removeFromSuperview()
for label in self.currentLabelsArray {
label.layer.removeAllAnimations()
label.removeFromSuperview()
}
self.gameOver()
}
Everything goes fine. The problem I am facing is : I am calling this animation block on creation of every single label. Actually this animation block is in a function called createLabels(). And the createLabel function is called every 5 seconds using NSTimer. Once the game is over I show a view above with restart button. Now here comes the problem:
Before the game ends we might have 3-4 labels already created that are pushed into the animation block. But the very first label might end the game. I get a gameover view above it with a restart button. Once I tap restart, my game again ends because the labels created earlier are still calling the completion block of UIAnimation. I cannot restart the game unless all my labels have completed the animation.
Is there any way to remove animation once the game if completed so that the already created labels no more come into the completion block?
I have used the following code to remove the label from view and remove its animation:
for label in self.currentLabelsArray {
label.layer.removeAllAnimations()
label.removeFromSuperview()
}
A quick guess is that, perhaps you missed to invalidate the timer object.
for label in self.currentLabelsArray {
label.layer.removeAllAnimations()
label.removeFromSuperview()
}
myTimer.invalidate();
So the timer will not call createLabels after the 5 sec. Once the game restarts schedule the time again.
Hope that helps!
EDIT:
Another pointer could be to clear the labels array.
self.currentLabelsArray.removeAllObjects();
and call self.gameOver() iff, there is no request of game over in queue.
EDIT:
UIView.animateWithDuration(25, delay: 0, options: UIViewAnimationOptions.CurveLinear, animations: {
label.frame = CGRectMake(self.view.frame.width - (label.frame.origin.x+label.frame.width), self.view.frame.height-self.keyboardYPosition!, label.frame.width, width)
}){(success) in
//label.removeFromSuperview()
for label in self.currentLabelsArray {
label.layer.removeAllAnimations()
label.removeFromSuperview()
}
if(self.currentLabelsArray.count > 0) {
self.currentLabelsArray.removeAllObjects()
self.gameOver()
}
}

How to read current displayed frame of animation?

There is a image and a button on Watchkit interface.
Image animation starts and i press the button, animation pause.
Now i need to read which frame is currently displayed.
Is it possible?
As Aleksander said it is not possible to get the right frame name from your animation. However you could divide your animation in parts and call the next part when the previous part is done animating. You could try my framework I just released to ease your work with animations and also have a completion handling.
TimelineKit - Framework
func startAnimationWithRange(range: Range)
{
Timeline.with(identifier: "MyAnimation", delay: 0.0, duration: 2.0, execution: {
// put your animation code in here
// assumed animation time == duration (2.0 seconds)
}, completion: {
// check if your button was clicked and return or call the next animation part
self.startAnimationWithRange(range: nextAnimationRange)
}).start
}
Check the TimelineKit.swift source file for documentation. Feedback is also welcome. :)

Resources