Swift - Force Synchronous Execution of Work and Subsequent UI Rendering

Wait until swift for loop with asynchronous network requests finishes executing

You can use dispatch groups to fire an asynchronous callback when all your requests finish.

Here's an example using dispatch groups to execute a callback asynchronously when multiple networking requests have all finished.

override func viewDidLoad() {
super.viewDidLoad()

let myGroup = DispatchGroup()

for i in 0 ..< 5 {
myGroup.enter()

Alamofire.request("https://httpbin.org/get", parameters: ["foo": "bar"]).responseJSON { response in
print("Finished request \(i)")
myGroup.leave()
}
}

myGroup.notify(queue: .main) {
print("Finished all requests.")
}
}

Output

Finished request 1
Finished request 0
Finished request 2
Finished request 3
Finished request 4
Finished all requests.

What is the most robust way to force a UIView to redraw?

The guaranteed, rock solid way to force a UIView to re-render is [myView setNeedsDisplay]. If you're having trouble with that, you're likely running into one of these issues:

  • You're calling it before you actually have the data, or your -drawRect: is over-caching something.

  • You're expecting the view to draw at the moment you call this method. There is intentionally no way to demand "draw right now this very second" using the Cocoa drawing system. That would disrupt the entire view compositing system, trash performance and likely create all kinds of artifacting. There are only ways to say "this needs to be drawn in the next draw cycle."

If what you need is "some logic, draw, some more logic," then you need to put the "some more logic" in a separate method and invoke it using -performSelector:withObject:afterDelay: with a delay of 0. That will put "some more logic" after the next draw cycle. See this question for an example of that kind of code, and a case where it might be needed (though it's usually best to look for other solutions if possible since it complicates the code).

If you don't think things are getting drawn, put a breakpoint in -drawRect: and see when you're getting called. If you're calling -setNeedsDisplay, but -drawRect: isn't getting called in the next event loop, then dig into your view hierarchy and make sure you're not trying to outsmart is somewhere. Over-cleverness is the #1 cause of bad drawing in my experience. When you think you know best how to trick the system into doing what you want, you usually get it doing exactly what you don't want.

How to make one code run only after the other is done in Swift

Your should DispatchGroup. By using DispatchGroup one function/line of code will wait until the other function completes execution.

For example

let myGroup = DispatchGroup()
myGroup.enter()
let info = getInfoFromTheWeb()

When you get info from simple call

myGroup.leave()

When your call leave() function following code will be execute

myGroup.notify(queue: DispatchQueue.main) {

saveToDB(info: info)
/// Update UI elements
}

Is there a way to make drawRect work right NOW?

Updates to the user interface happen at the end of the current pass through the run loop. These updates are performed on the main thread, so anything that runs for a long time in the main thread (lengthy calculations, etc.) will prevent the interface updates from being started. Additionally, anything that runs for a while on the main thread will also cause your touch handling to be unresponsive.

This means that there is no way to "force" a UI refresh to occur from some other point in a process running on the main thread. The previous statement is not entirely correct, as Tom's answer shows. You can allow the run loop to come to completion in the middle of operations performed on the main thread. However, this still may reduce the responsiveness of your application.

In general, it is recommended that you move anything that takes a while to perform to a background thread so that the user interface can remain responsive. However, any updates you wish to perform to the UI need to be done back on the main thread.

Perhaps the easiest way to do this under Snow Leopard and iOS 4.0+ is to use blocks, like in the following rudimentary sample:

dispatch_queue_t main_queue = dispatch_get_main_queue();
dispatch_async(queue, ^{
// Do some work
dispatch_async(main_queue, ^{
// Update the UI
});
});

The Do some work part of the above could be a lengthy calculation, or an operation that loops over multiple values. In this example, the UI is only updated at the end of the operation, but if you wanted continuous progress tracking in your UI, you could place the dispatch to the main queue where ever you needed a UI update to be performed.

For older OS versions, you can break off a background thread manually or through an NSOperation. For manual background threading, you can use

[NSThread detachNewThreadSelector:@selector(doWork) toTarget:self withObject:nil];

or

[self performSelectorInBackground:@selector(doWork) withObject:nil];

and then to update the UI you can use

[self performSelectorOnMainThread:@selector(updateProgress) withObject:nil waitUntilDone:NO];

Note that I've found the NO argument in the previous method to be needed to get constant UI updates while dealing with a continuous progress bar.

This sample application I created for my class illustrates how to use both NSOperations and queues for performing background work and then updating the UI when done. Also, my Molecules application uses background threads for processing new structures, with a status bar that is updated as this progresses. You can download the source code to see how I achieved this.



Related Topics



Leave a reply



Submit