Alternative to runUntilDate for waiting until animation is complete - ios

We have an application that has some code similar to the following:
[UIView animationWithDuration: 0.7f animations {
// Some animations
}];
[NSRunLoop currentRunLoop] runUntilDate: [NSDate dateWithTimeIntervalSinceNow: 0.7f]];
To wait for an animation block to complete and continue executing code as if the animation block were essentially synchronous. This has worked in all past versions of iOS, but does not seem to be working properly on iPhone 12 devices running iOS 15.
Is there an alternative to this approach to waiting for an animation to complete using semaphores, dispatch barriers, or something similar? We tried moving the code that should run after the animation into the animation completion block, but it is difficult to do so because the logic is complex and there are several levels of nested conditionals. Ideally, we would like to find something that works in a similar manner to runUntilDate by yielding the main thread for a specific period of time without blocking it and then continuing execution once the asynchronous animation is completed.

Related

Why is that constantly animating UIActivityIndicator won't block main thread?

We all show activity indicator while some lengthy operation is happening in background. Though the activity indicator shows a constantly rotating wheel it won't burden the main thread, because other UIComponents in the same screen still react to the touches.
What I think I know:
I know all touch events are handled by main thread, and main Queue is being used to queue the events. Considering main queue is Serialized queue and only one task at a time can run at any given point in time, alley touch events should get queued up in main queue, while my main thread is busy in refreshing the screen/calling drawrect of UIActivityIndicator.
Study:
I have looked into the code of third party activity indicators. Most of them use CABasicAnimation and call repeat always on animation. While few work directly use NSTimer to repeatedly call drawrect with a small delay. Their code works because there is a small delay in calling drawrect and the method drawrect in itself is light weight.
None of it won't take the loads off the main thread but rather they carefully place load on main thread enough to keep their animation going yet keeping main thread free to handle touch events
What I want to know:
1 - Is this strategy to implement activity indicator is correct? or statement like this
self.timer =[NSTimer timerWithTimeInterval:0.1 target:self
selector:#selector(setNeedsDisplay) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
in one of the third party activity indicator that I saw has any special effect?
2 - If I run CABasicAnimation/transaction and repeat the animation forever will it have any special effects on the load of main thread compared to repeatedly calling setNeedsDispaly/drawrect manually?
I'm not sure whether it will help to implement your own activity indicator, but the system one UIActivitiyIndicatorView is just a UIImageView with an array of 12 images that replace each other over time.
Apple made a pretty neat trick by making their spinner discrete. It allowed them to have a simple implementation that doesn't create any computational load on CPU.
UPD
Returning to the things you want to know:
1 - It's not, because implementing manual frame drawing in drawRect is fully done by CPU. And 2 - I can't say for sure, but if one believes what Apple says in documentation and videos about Core Animation it is heavily optimised and runs on Metal or at least OpenGL underneath, so leverages power of GPU.

How to build UIActivityIndicatorView

When using a UIActivityIndicatorView, it is possible to add this view to your view hierarchy and start animating it.
After that you are completely save to block the main thread (not that this is a good approach), there is basically nothing that I know of, that could stop the activity indicator from spinning.
My question is:
How is this done? It seems like UIActivityIndicatorView uses its own thread for rendering.
Is this something that can be achieved with my own views?
I have a CoreAnimation animation that I want to keep playing while the main thread might be blocking for a couple of milliseconds.
How to do that? Thanks for any help or ideas!
//EDIT: To clarify my question: I want to know what Apple does to get UIActivityIndicatorView animating even when you block the main thread. When I trigger my own CoreAnimation and I block the main thread, the animation itself stops. Furthermore to the question what Apple does to achieve that under the hood, I want to know, if I can achieve this myself, with public API.
You best solution is to use a background thread for long processing while keeping the main thread only for display purpose. That way, you will be able to display your view without blocking and run your long precessing at the same time.
You can use Grand Central Dispatch (GCD) dispatch_async to run your long processing code on another thread and then call dispatch_async on the main thread to refresh your UI.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),
^{
// Your long processing code
dispatch_async(dispatch_get_main_queue(),
^{
// Refresh UI here
});
});

How force the UI message loop to flush in iOS before continuing

I have an application that experiences a brief delay when switching views, on the order of 500-1500ms. The change in views is subtle, so I need to provide feedback to the user that something DID just happen.
I would like to use a "Loading" overlay. Unfortunately, the work that is occupying the CPU is related to building the UI, and therefore cannot be moved to a background thread.
Since the work is occupying the main thread, if I add a loading overlay before the other operation starts, it never gets shown because the thread is working on the next workload and won't update the UI until it gets around to it.
Some operating systems have a DoEvents or FlushMessagePump method that can be used in the rare circumstances like these. Is there such a thing in iOS? SetNeedsDisplay() is not what I want, as it will only queue the update in the message pump.
Alternative suggestions are welcome too.
Instead of delaying your loading, you can force the run loop to run:
[[NSRunLoop mainRunLoop] runUntilDate:[NSDate date]];
Show your loading overlay, and then use - (void)performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay with a delay of 0 to do your actual loading. This will delay your loading until the screen has a chance to refresh, because it will queue the selector on the main thread's run loop.

UIViewController animations stop working

My app runs fine in iOS6, but in an unspecified upcoming version of iOS that I cannot name for NDA reasons, all UIViewController transition animations stop working. New views just pop into place instantly. I am not sure if this unspecified future version of iOS is the cause, as I've seen this happen occasionally in iOS6.
Sometimes animations start working for a while and then stop shortly after, making me think it's some sort of memory warning issue, but my app is using a fairly reasonable ~125MB of RAM at most times. Can anyone offer any advice or things to investigate?
The described behavior has always existed: if you do work on background threads and then call and UIKit methods then more often than not the update will be delayed in a weird way.
Because of this you should always dispatch_async onto the main queue to update the UI.
Those bugs are very hard to catch since they do not always occur predictably.
To catch them I built a method that swizzles some UIKit methods to check if they are called on the main thread. This allows you to stop on a symbolic breakpoint, whenever you have forgotten to dispatch back onto main queue.
https://github.com/Cocoanetics/DTFoundation/blob/develop/Core/Source/iOS/Debug/UIView%2BDTDebug.m
A good workaround from the Apple dev forums on this issue:
Do this:
[UIView setAnimationsEnabled:YES]
And animations start working again. I suspect that this is either a straight up iOS7 bug, or somewhere in my code an animation or UIViewController launch is happening on a background thread, causing animations to stop. Probably unrelated to the unspecified future version of iOS.
This issue appears to be caused by doing UIKit stuff in background threads. I have a pre-render cache full of NSOperations that renders complex UIViews to UIImages to cache the output. This seemed to work fine in iOS6, but probably does cross the line somewhat. I'll need to replace this functionality with something that renders images and text to a graphics buffer rather than using UIViews and UILabels at all.
All you have to do is catch hold of main queue while updating UI on receiving response from an API.Ios uses main queue by default for updating UI but it is not 100 percent efficient.Hence you have to make sure that the UI gets updated on main thread only and the way to do that is as below:
DispatchQueue.main.async{
//UI related code eg:
self.label.text = "abc"
self.button.setTitle("xyz",.normal)
self.tableView.reloadData()
}
If you are not catching hold of main thread animations may or may not work.
But if you are using main thread animations will definetely work.
Correct Code while updating UI on api response:
Alamofire.getApiCall(paramaters: parameters, completion:{
response in
// UI related code.
DispatchQueue.main.async{
self.label.text = "abc"
self.button.setTitle("xyz",.normal)
self.tableView.reloadData()
}
})
Incorrect Code which may cause animations to stop and lead to weird crashes:
Alamofire.getApiCall(paramaters: parameters, completion:{
response in
// UI related code.
self.label.text = "abc"
self.button.setTitle("xyz",.normal)
self.tableView.reloadData()
})

DrawRect not called due to active main thread

I have an architecture that takes input from the mic and then performs some calculations and then should render to screen.
The issue is that calling setNeedsDisplay never triggers a call to drawRect because the main thread is running the calculations.
What would be the best way to thread this?
Create a single serial queue and dispatch the work to this background queue using GCD and dispatch the final setNeedsDisplay back to the main queue, or is there a more efficient way of doing this?
keep everything long running off the main thread. that is best in my opinion. Long running calculations are also something
try to never block the UIThread
...work to this background queue using GCD and dispatch the final
setNeedsDisplay back to the main queue, or is there a more efficient
way of doing this?
Based on your pretty vague description, I'd say what you suggested is the best approach.
And if you do this with GCD, your code will be easy to read since the blocks of work you perform are inline in your code making it easy to understand what is going on.
Create a single serial queue and dispatch the work to this background queue using GCD and dispatch the final setNeedsDisplay back to the main queue, or is there a more efficient way of doing this?
You should use the highest level API available to you. In this case, it's NSOperation or one of its subclasses. Probably, NSBlockOperation is the one you want. You should
create a block operation with the block you want executing
Set its completion handler to invoke setNeedsDisplay
Stick it on an NSOperationQueue.
The completion handler needs to invoke setNeedsDisplay on the main thread. You can do this by sending performSelectorOnMainThread:withObject:waitUntilDone: to the view e.g.
[myViewThatNeedsUpdating performSelectorOnMainThread: #selector(setNeedsDisplay)
withObject: nil
waitUntilDone: NO];
I think this is a better approach than using GCD directly because it is more in the Objective-C idiom, it separates the work you are doing from the notification at the end and gives you many more options for how things get done than raw GCD. For instance, if you want several of these things to happen sequentially, you can make some operations dependencies of others without having to write lots of code.

Resources