chaining UITableview Updates on main thread - ios

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)

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.

Swift: Update UI - Entire function on main thread or just the UI update?

I've read that the UI should always be updated on the main thread. However, I'm a little confused when it comes to the preferred method to implement these updates.
I have various functions that perform some conditional checks then the result is used to determine how to update the UI. My question is should the entire function run on the main thread? Should just the UI update? Can / should I run the conditional checks on another thread? Does it depend on what the function does or how fast you want it done?
Example a function that changes the image inside an ImageView without threading:
#IBAction func undoPressed(_ sender: Any) {
if !previousDrawings.isEmpty {
previousDrawings.remove(at: previousDrawings.count - 1)
if let lastDrawing = previousDrawings.last {
topImageView.image = lastDrawing
}
else {
// empty
topImageView.image = nil
}
}
}
Should I be setting topImageView.image on the main thread? Like this:
#IBAction func undoPressed(_ sender: Any) {
if !previousDrawings.isEmpty {
previousDrawings.remove(at: previousDrawings.count - 1)
if let lastDrawing = previousDrawings.last {
DispatchQueue.main.async {
self.topImageView.image = lastDrawing
}
}
else {
DispatchQueue.main.async {
self.topImageView.image = nil
}
}
}
}
Should I be using a background thread for the conditional checks? Like this:
#IBAction func undoPressed(_ sender: Any) {
DispatchQueue.global(qos: .utility).async {
if !previousDrawings.isEmpty {
previousDrawings.remove(at: previousDrawings.count - 1)
if let lastDrawing = previousDrawings.last {
DispatchQueue.main.async {
self.topImageView.image = lastDrawing
}
}
else {
DispatchQueue.main.async {
self.topImageView.image = nil
}
}
}
}
}
If someone could explain what method is preferred and why that would be really helpful.
Back up. Except in special circumstances, all your code is run on the main thread. UIAction methods, for example, are ALWAYS executed on the main thread, as are all the methods defined by UIViewController and it's various subclasses. In fact, you can safely say that UIKit methods are performed on the main thread. Again, your methods will only be called on a background thread in very special circumstances, which are well documented.
You can use GCD to run blocks of code on background threads. In that case, the code is being run on a background thread because you explicitly asked for that to happen.
Some system functions (like URLSession) call their delegate methods/run their completion handlers on background threads by default. Those are well documented. For third party libraries like AlamoFire or FireBase, you'll have to read the documentation, but any code that's called on a background thread should be very well documented because you have to take special precautions for code that runs on a background thread.
The usual reason to use a background thread is so that a long-running task can run to completion without freezing the user interface until it's done.
A common pattern for, example, is using URLSession to read some JSON data from a remote server. The completion handler is called on a background thread since it might take time to parse the data you get back. Once you are done parsing it, though, you'd wrap a call to update the UI in a GCD call to the main thread, since UI changes must be performed on the main thread.
First off, your undoPressed method will be called on the main queue.
In the first set of code, everything will be on the main queue.
In the second set of code, using DispatchQueue.main.async is pointless since the rest of the code is already on the main queue.
So really your only two sensible options are 1 and 3.
Given your code, option 1 is fine. You would only want to use option 3 if the code being run in the background took more than a trivial amount of time to execute. Since the code you have here is trivial and will take virtually no time to execute, there is no point in option 3 here.
So simply use your first set of code and you'll be fine.
Worry about moving code to the background when it need to perform a big loop or calculate a complicated algorithm or perform any sort of network access.
To make it simple, make the calculation and then everything related to that updated calculation that needs to be reflected in the UI should be done from:
DispatchQueue.main.async{ //code }
that is using main thread.

Swift: feedback when function ends

Let's say I have a ViewController A and a class B.
when I press some button inside A, it calls an IBAction that calls a function B.foo() which returns an Int
B.foo() takes 8~10 seconds to finish and while it runs I'd like to put an Loading... animation on A, and when B.foo() finishes, the animation would stop.
How can I do this? this is an pseudo-code example of what I wish:
#IBAction func buttonPressed(_ sender: UIButton){
UIView.animate(blablabla......)
DO({
self.answer = B.foo()
}, andWhenItFinishesDo: {
self.someone.layer.removeAllAnimation()
})
}
This is a very common problem. One way to solve it would be to use different queues (You can think of them as lines of work that can happen in parallel).
The the basic idea is that once your button is pressed, you show your loading indicator and "dispatch" the long work to a secondary queue, that will operate in the background and do the work. This ensures that your main queue does not block while the work happens and the user interface stays responsive.
The trick is now that you want to get notified when the long work is finished so that you can stop showing the loading indicator (and possibly do even more).
While you actually could use some kind of notification system, there are other, sometimes more appropriate ways. It would actually be even more convenient, if you could just tell the long running function to call you back specifically with code that you provide.
That would be the basic concept of a "completion handler" or "callback".
The whole thing would look something like that:
// Some made up class here
class B {
// This is only static because I do not have an instance of B around.
static func foo(completion: #escaping (Int) -> Void ) {
// The method now does all of its work on a background queue and returns immediately
DispatchQueue.global(qos: .background).async {
// In the background this may take as long as it wants
let result = longCalculation()
// VERY important. The caller of this function might have a certain
// expectation about on which queue the completion handler runs.
// Here I just use the main queue because this is relatively safe.
// You could let the caller provide a queue in the function
// parameters and use it here
DispatchQueue.main.async {
// The completion handler is a function that takes an Int.
// That is exactly what you are providing here
completion(result)
}
}
}
}
#IBAction func buttonPressed(_ sender: UIButton){
self.showLoadingIndicator()
// The foo function now takes a completion handler that gets the result in.
// You have to provide this function here and do something with the result
//
// The completion handler will only be run when the foo function calls it
// (which is after the computation as you can see in the method above.
//
// I am also telling the completion handler here that self should not be
// held on to as the view controller might already have gone away when the
// long calculation finished. The `[weak self]` thingy makes that inside
// your completion handler self is an optional and might be nil (and it
// doesn't hold a strong reference to self, but that's a whole other topic)
B.foo(completion: { [weak self] result in
// Do something with the result
// Since we are called back on the main queue we can also do UI stuff safely
self?.hideLoadingIndicator()
})
}
I hope this helps a bit.
Asynchronous programming can be quite difficult to learn but there are tons of tutorials and examples you can find on this topic.
Hey Hamish you can do this in two simple ways,
First one is using the defer statements provided for functions.
Defer statement block is executed after the functions goes out of scope.
here is a simple example to describe the same.
func print1000000() {
//start displaying the loading indicator
defer {
// hide the loading indicator and move to the next ViewController
let seVC = storyboard?.instantiateViewController(withIdentifier: "SecondVC") as! SecondVC
self.navigationController?.pushViewController(seVC, animated: true)
}
// here goes the task you want to execute such as downloading a file or the one i did here
for index in 0...1000000 {
print(index)
}
}
The above function prints numbers upto 1000000 and then pushes the control to another ViewController
=========================================================================
Second way of doing it is by using closures, as described by Thomas in his answer.

dispatch_async when initializing UIView objects

so here is the problem:
I've got a few heavy views with many subviews, which I need to load and then to display. I want to do it asynchronously so that I don't block my main thread. When I tried to do it asynchronously I encountered the following dilemma:
After all of the heavy lifting job has been done and I return to the main queue to actually display that stuff, I get problems. First of all even though everything is done it takes 30-60 seconds for all the views to become visible. Sometimes they get misplaced. What could I be doing wrong and what should I be looking for ?
private func loadScrollViews() {
let qos = Int(QOS_CLASS_USER_INTERACTIVE.value)
dispatch_async(dispatch_get_global_queue(qos, 0)) { () -> Void in
// Creating many UIViews
for var i = 0; i < 100; i++ {
let view = UIView(frame: someFrame)
self.viewCollection.append(view)
}
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.displayViews()
})
}
}
private func displayViews() {
for view in self.viewCollection {
self.contentView.addSubview(view)
}
self.activityIndicator.stopAnimating()
self.contentView.hidden = false
}
After displayViews gets executed as I said views take almost a minute to appear on the screen.
UIView manipulation should be done on main thread
from doc
Threading Considerations
Manipulations to your application’s user interface must occur on the
main thread. Thus, you should always call the methods of the UIView
class from code running in the main thread of your application. The
only time this may not be strictly necessary is when creating the view
object itself but all other manipulations should occur on the main
thread.
if you have to create many UIView object then do it like this
dispatch_async() but use main_queue for this purpose. And one more thing if you want use background thread then think about using CALayer we can do most of CALayer work on background thread

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