addCoordinatedAnimations animation block not running - ios

In tvOS on Xcode 7.3.1, one of the places I use UIFocusAnimationCoordinator's addCoordinatedAnimations function is running the completion before the animation:
if (coordinator != nil) {
var tempDidAnimate: Bool = false // breakpoint 1
coordinator!.addCoordinatedAnimations({
self.myFunctionThatDoesntGetCalled() // breakpoint 2
tempDidAnimate = true
}, completion: {
() in
if tempDidAnimate == false {
print("whaaaat?!??") // breakpoint 3
self.myFunctionThatDoesntGetCalled()
}
})
}
Order of breakpoints being hit is 1, 3. Never 2.
This hacky use of if tempDidAnimate == false does solve the problem, but I don't get why the problem is happening.
Any ideas what could be wrong?
One idea: I'm already inside an addCoordinatedAnimations block in the stack... I don't think so, but the stack is complicated... can't see any way to check that via code.

tl;dr: YES I was already inside an animation block #$&^##$!
Ok, turned out that in my "cleverly" refactored code, I was forgetting that I'm calling an animation block to reset any currently active autolayout animations (by requesting a new animation with duration 0). Then in that completion, my code above is being called... Since I'm officially already inside an animation block, the OS refuses to execute the main animation block above, and skips right to the completion. Don't know if this OS behavior is documented anywhere.

Related

Understanding UIView.animate and how completion closures work

Is there is a way to interrupt animations in a way that doesn't "cancel" (rewind) them, but rather "fast-forwards" them forcing their completion closures to run earlier than originally planned?
Background:
In IOS, one can "animate a view with duration" and include a completion closure as well... using UIView's static method animate() like this:
class func animate(withDuration: TimeInterval, animations: () -> Void, completion: ((Bool) -> Void)? = nil)
A real-life example might look like EXHIBIT-A here:
// assume we have a UILabel named 'bigLabel'
func animationWeNeedToDo() {
UIView.animate(withDuration: 1, animations: {
self.bigLabel.alpha = 0
}, completion: {
if $0 {
UIView.animate(withDuration: 1, animations: {
self.bigLabel.center.x -= 20
}, completion: {
if $0 {
self.updateMainDisplay()
}
}) }
})
}
So we have a UILabel, bigLabel, that we are first animating to "fade," then we are chaining to yet another animation inside the completion of the first, then yet again in the completion of the second, we run the all-important function, updateMainDisplay().
But this simple example could be much more complex involving many more views. It could be imperative that updateMainDisplay() executes. ;)
The updateMainDisplay() function is important because it "resets" all the views, returning the app to a neutral state similar to when the app is originally started... sort of "re-calibrates" everything.
Anyhoo, the trouble is, if the user does something like push the home button early enough or segue to a new activity (modally, like settings... and then come back) while the animation is taking place, it never completes... and so updateMainDisplay() does not get executed! ...and things get complicated and nasty.
So, how to handle this problem?
Seems like something needs to be done in "onPause()" (I know this isn't Android)... like making sure that the animation is cancelled AND that updateMainDisplay() is executed.
But in order to do that you would have to check for all kinds of boolean states in the "onPause()" method. I would much prefer if there were a way to guarantee that the animation will complete.
So, once again, I'm thinking it would be pretty awesome if there were a way to not cancel the animations, but to "force immediate completion" of all animations.
This is pseudo-code... but is there a way to do something like this:
var myAnimation = (animation) { // EXHIBIT-A from above }
myAnimation.execute()
// if needed:
myAnimation.forceCompletionNow()
Does anyone know if that's possible?
Thanks.
The problem with your code is that you are checking the first argument of the completion closure. That indicates whether the animation finishes or not. And you only run updateMainDisplay() if that is true.
So in fact, the completion handler will be called even if the animation is not finished. It is you that told it to do nothing if the animation does not finish.
To fix this, just remove the if $0 statement.
Now Xcode will show a warning because you did not use the first argument of the closure. To silence this warning, just put _ in at the start of the closure:
{ _ in
// some code
}
Another thing that you can try is CABasicAnimation which does not actually change the view's properties. It animates the CALayers. If you update the view again in some way, the view will have gone back to its original state before the animation. You seem to want to reset everything after the animation finishes so this might be suitable for you.

DispatchQueue.main.asyncAfter method freeze UI in a particular call

I'm calling the DispatchQueue.main.asyncAfter method after I modify a property of an object (I'm firing it with KVO). I'm only showing a confirm view for a second and then run a block which do another things on the UI (pops a viewcontroller). The problem is that when i call this from a UICollectionViewCell works fine, but when i call this method from another particular UICollectionViewCell in my app, it freezes up. The method is implemented in a extension of UIView.
public func showConfirmViewWith(title: String!, frame: CGRect? = screenBounds, afterAction: (() -> Void)?) {
let confirm = ConfirmationView(frame: frame!, title: title)
self.addSubview(confirm)
confirm.checkBox?.setCheckState(.checked, animated: true)
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(1) ) {
if afterAction != nil {
afterAction!()
}
confirm.removeFromSuperview()
}
}
When it freezes the code inside the Dispatch never gets called, I have a breakpoint inside, but never gets to that point.
UDPATE
Here's the stack trace when i pause the execution:
First time I paused the execution
I solved this tricky bug. First thing, there wasn't any problem with the DispatchQueue.main.asyncAfter call. The app was going into a loop and wasn't any signs that was lock. I ran the profiler and saw that the layout of some constraints was being called over and over. I have a tableView inside the collectionViewCell, and an animation when finish the call to asyncAfter. The problem was that the overriden method layoutSubviews has a tableView.reloadData() inside, so everytime i was trying to exit after the async call with an animation, the layoutsubviews was being called and the reloadData() triggered over and over again. I'm not sure why that reloadData was there, but seems to be working fine now.

UIView ignoring setHidden when in animation block

I have this code on an UIView subclass:
override var hidden: Bool {
willSet {
DDLogDebug("Will set hidden=\(String(newValue)) on \(item?.name))")
}
didSet {
DDLogDebug("Did set hidden=\(String(hidden)) on \(item?.name))")
}
}
For some reason, I set false but it remains true as seen in these logs:
> Will set hidden=false on Optional("74D8E4CE-5E14-4914-8483-E9F66D2A79B7"))
> Did set hidden=true on Optional("74D8E4CE-5E14-4914-8483-E9F66D2A79B7"))
The only peculiarity of this issue is that it only happens when running inside a UIView.animateWithDuration(...) block. If I remove the animation the property is set correctly.
Any thoughts on what may be going on? This is driving me crazy, heh
Edit:
Little bit more info, this UIView I want to hide is an arrangedSubview of a UIStackView. It works correctly for the first few tries but suddenly stops working without any noticeable pattern.
This is a bug in UIStackView.
Here is another question describing the exact same issue I'm having. This is an open radar for this specific issue.
The solution I found was to avoid setting hidden to the same value twice.
if (subview.hidden) {
subview.hidden = false
}
try this one, it works for me
[UIView performWithoutAnimation:^{
arrangedSubview.hidden = isHidden;
}];

chaining UITableview Updates on main thread

So what i am attempting to do is conceptually very simple however I have not been able to find a solution for it:
I am trying to remove cells from a tableView animated with the:
self.coolTableView?.deleteRowsAtIndexPaths
function, to do this I change the dataSet and perform this action, right after it is done i would like to change the data set again and use:
self.coolTableView?.insertRowsAtIndexPaths
to reflect and animate the second change to the dataset.
The Problem I run into is that if I use:
dispatch_async(dispatch_get_main_queue()) { () -> Void in
//Update tableview
}
they seem to lock each other out, the used memory just keeps skyrocketing and it hangs. I am assuming they are interfering with each other. now my next thought was to change the code to sync so:
dispatch_sync(dispatch_get_main_queue()) { () -> Void in
//Update tableview
}
however the first update hangs and and ceases operation. With the research I have done it sounds like I am putting the execution of the block in the main queue behind my current running application and vuwala, that is why it hangs.
what is the correct way to block execution until I can complete an animation on the main thread so i do not blow up my data source before animations can take place?
The animations in iOS take a block that they can execute when the animation terminates. Try putting operations on coolTableView into a closure (remember about unowned self to not create memory leaks) and pass it as completion closure to your animation.
example:
let someAnimations: () -> Void = {
//some animations
}
let manipulateTableView: (Bool) -> Void = {
isAnimationFinished in
// perform some data manipulation
}
UIView.animateWithDuration(0.5, animations: someAnimations, completion: manipulateTableView)

Run animation until a button is pressed

I am trying to make an app in which animation should run until a button is pressed. I tried to use infinite loop but with that loop my app is not running at all. It is consuming all the memory. I also tried to call the same function again on completion but that process is also consuming 100% of CPU.
Can you please guide me what should i do here?
Scenario is like in the background of the view, animation should run continuously until a button is pressed. Another view will open when the button will be pressed and again another animation will run continuously until some other button is pressed.
Following is my code:
func unlimitedLoop()
{
rotating = true
self.ProcurementSupport.transform = CGAffineTransformMakeTranslation(0, 100)
self.ProcurementSupport.hidden = false
UIView.animateWithDuration(3, delay: 2, usingSpringWithDamping: 2, initialSpringVelocity: 2, options: nil, animations: {
// self.EquipmentSupport.hidden = true
var x:NSTimeInterval = 2
springWithDelay(2, x, {
self.ProcurementSupport.transform = CGAffineTransformMakeTranslation(0,0)
self.EquipmentSupport.transform = CGAffineTransformMakeTranslation(1000,0)
x=x+2
})
springWithDelay(2, x, {
self.EquipmentSupport.transform = CGAffineTransformMakeTranslation(0,100)
x=x+2
})
springWithDelay(2, x, {
self.ProcurementSupport.transform = CGAffineTransformMakeTranslation(1000,0)
self.EquipmentSupport.transform = CGAffineTransformMakeTranslation(0,0)
x=x+2
})
springWithDelay(2, x, {
self.ProcurementSupport.transform = CGAffineTransformMakeTranslation(0, 100)
x=x+2
})
}, completion: {finished in self.unlimitedLoopAgain()})
}
When doing custom animations by creating an infinite loop you need to run this function on a separate thread. Beside the loop itself it best to stop the thread for a small amount of time every frame, for instance call sleep for 1.0/60.0 to get about 60FPS. Another problem that comes up in this approach is that when a new thread changes a parameter on the view system it will most likely call back on the main thread for the view to be refreshed "set needs display". In no case should you create such a function and in it call a built in animation as you did, you need to do the animation yourself.
A better approach is usually using some kind of timer or a display link in your case. This item will trigger every time the display should redraw so it is perfect for your animation. These calls are most usually already on the main thread so you do not need anything else. Again calling a built in animation should be avoided then.
Yet another way would be to use kind of a recursion. Every time you update the animation you call the same update method after some delay time (1.0/60.0) for instance...
Anyway your infinite loop just keeps executing and takes all the processor power but no other method is executed because of it so you can see no result at all. You need a way to let the system perform other methods in between the frames (the update calls) so either stop executing for some period of time after every update or put it on a separate thread. Both of these ways will let the main thread to do other stuff as well.

Resources