Swift: feedback when function ends - ios

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.

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.

How do I make a closure wait until the player pressed a button in a different view controller?

I am writing a chess GUI in Swift 3 and use nvzqz/Sage as the chess model/library. Now I face a problem with a Sage closure used for piece promotion.
Sage uses (in its game class) the execute(move: promotion:) method for promotion move execution which has a closure that returns a promotion piece kind. This allows to prompt the user for a promotion piece or perform any other operations before choosing a promotion piece kind, as follows:
try game.execute(move: move) {
...
return .queen
}
I implemented a promotion view controller ("pvc") which is called in this closure so that the player may select the new piece:
// This is in the main View Controller class
/// The piece selected in promotionViewController into which a pawn shall promote
var newPiece: Piece.Kind = ._queen // default value = Queen
try game.execute(move: move) {
boardView.isUserInteractionEnabled = false
// promotionview controller appears to select new promoted piece
let pvc = PromotionViewController(nibName: nil, bundle: nil)
pvc.delegate = self
pvc.modalPresentationStyle = .overCurrentContext
self.present(pvc, animated:true)
return newPiece
}
When the button for the new piece in the pvc is pressed, the pvc dismisses itself and the data of the selected piece (the constant selectedType) is transferred back to the main view controller via delegation:
// This is in the sending PVC class
protocol PromotionViewControllerDelegate {
func processPromotion(selectedType: Piece.Kind)
}
func buttonPressed(sender: UIButton) {
let selectedType = bla bla bla ...
delegate?.processPromotion(selectedType: selectedType)
presentingViewController!.dismiss(animated:true)
}
// This is in the receiving main View Controller class
extension GameViewController: PromotionViewControllerDelegate {
func processPromotion(selectedType: Piece.Kind) {
defer {
boardView.isUserInteractionEnabled = true
}
newPiece = selectedType
}
The problem I have is that the closure (in the game.execute method) does not wait until the player made his selection in the pvc (and immediately returns the newPiece variable which is still the default value) so that I never get another promotion piece processed other than the default value.
How do I make the closure wait until the player pressed a button in the pvc?
Of course, I tried to find a solution and read about callbacks, completion handlers or property observers. I do not know which is the best way forward, some thoughts:
Completion handler: the pvc dismisses itself upon button-press event so the completion handler is not in the receiving (main) view controller. How do I deal with this?
Property observer: I could call the try game.execute(move) method only after the promotion piece was set (with didset) but that would make the code difficult to read and not use the nice closure the game.execute method provides.
Callbacks may be related to completion handlers, but am not sure.
So your block in game.execute(move: move) will fully execute which is so designed by the Sage API. You can not pause it as easy but it is doable, still let's try to solve it the other way;
Why do you need to call the presentation of the view controller within this block? By all means try to move that away. The call try game.execute(move: move) { should only be called within processPromotion delegate method. You did not post any code but wherever this try game.execute(move: move) { code is it needs to be replaced by presenting a view controller alone.
Then on delegate you do not even need to preserve the value newPiece = selectedType but rather just call try game.execute(move: move) { return selectedType }.
So about pausing a block:
It is not possible to directly "pause" a block because it is a part of execution which means the whole operation needs to pause which in the end means you need to pause your whole thread. That means you need to move the call to a separate thread and pause that one. Still this will only work if the API supports the multithreading, if the callback is called on the same tread as its execute call... So there are many tools and ways on how to lock a thread so let me just use the most primitive one which is making the thread sleep:
var executionLocked: Bool = false
func foo() {
DispatchQueue(label: "confiramtion queue").async {
self.executionLocked = true
game.execute(move: move) {
// Assuming this is still on the "confiramtion queue" queue
DispatchQueue.main.async {
// UI code needs to be executed on main thread
let pvc = PromotionViewController(nibName: nil, bundle: nil)
pvc.delegate = self
pvc.modalPresentationStyle = .overCurrentContext
self.present(pvc, animated:true)
}
while self.executionLocked {
Thread.sleep(forTimeInterval: 1.0/5.0) // Check 5 times per second if unlocked
}
return self.newPiece // Or whatever the code is
}
}
}
Now in your delegate you need:
func processPromotion(selectedType: Piece.Kind) {
defer {
boardView.isUserInteractionEnabled = true
}
newPiece = selectedType
self.executionLocked = false
}
So what happens here is we start a new thread. Then lock the execution and start execution on game instance. In the block we now execute our code on main thread and then create an "endless" loop in which a thread sleeps a bit every time (the sleep is not really needed but it prevents the loop to take too much CPU power). Now all the stuff is happening on main thread which is that a new controller is presented and user may do stuff with it... Then once done a delegate will unlock the execution lock which will make the "endless" loop exit (on another thread) and return a value to your game instance.
I do not expect you to implement this but if you will then ensure you make all precautions to correctly release the loop if needed. Like if view controller is dismissed it should unlock it, if a delegate has a "cancel" version it should exit...

Where should I put this code that should be done in background?

I am building an app that calculates stuff.
The user will enter his/her inputs in OperationViewController and then he/she will click "Calculate". After calculating the results, a segue is performed to show the ResultsViewController.
Some of the calculations will take a long time, so I think they should be done in the background thread. And I should show a message saying that it is calculating and an activity indicator.
I also grabbed some code from somewhere that makes doing things in the background super swifty. Here is the code:
import Foundation
infix operator ~> {}
/**
Executes the lefthand closure on a background thread and,
upon completion, the righthand closure on the main thread.
Passes the background closure's output, if any, to the main closure.
*/
func ~> <R> (
backgroundClosure: () -> R,
mainClosure: (result: R) -> ())
{
dispatch_async(queue) {
let result = backgroundClosure()
dispatch_async(dispatch_get_main_queue(), {
mainClosure(result: result)
})
}
}
/** Serial dispatch queue used by the ~> operator. */
private let queue = dispatch_queue_create("serial-worker", DISPATCH_QUEUE_SERIAL)
Then, the problem arises.
In OperationViewController, there is a method called getResults:
private func getResults () -> [(name: String, from: String, result: String)]? {
// irrelevent code about getting the user's inputs from UITextFields
return operation.calculate(input) // This will take a few seconds
}
The calculate method there requires a few seconds to return.
The Calculate button, mentioned earlier has a segue that's connected to ResultsViewController. I didn't explicitly call performSegueWithIdentifier. I just control dragged the button to the results view controller.
In prepareForSegue, I call getResults:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "showOperationHelp" {
// irrelevent
} else if segue.identifier == "showResults" {
let vc = segue.destinationViewController as! ResultsViewController
vc.results = getResults()
}
}
Now I tried to put the getResults part in a background thread by doing this:
} else if segue.identifier == "showResults" {
let vc = segue.destinationViewController as! ResultsViewController;
{ self.getResults() } ~> { vc.results = $0 };
}
But that doesn't work because prepareForSegue returns before the calculation finish. This means that vc.results is nil when prepareForSegue returns. This will cause the ResultsViewController to show nothing.
Another method I have tried is to put the "do in background" thingy in getResults:
private func getResults () -> [(name: String, from: String, result: String)]? {
// irrelevent code about getting the user's inputs from UITextFields
var results: [(name: String, from: String, result: String)]?;
{ self.operation.calculate(input) } ~> { results = $0 };
return results
}
Again, getResults will just return nil.
Back then when I was writing C#, I can use the async/await keywords to achieve this.
I can just do something like:
var results = await operations.Calculate(input);
When execution reaches await, it pauses there and allows the UI thread to go on. After the async operation has finished, the execution goes back to where it had stopped and continues on.
Question: Can I do something like the above in Swift? If I can't, how can I wait until the calculations finish and show ResultsViewController?
Just in case you didn't understand me, I'll describe exactly what I want:
the user enters some inputs
the user taps the calculate button
A message shows up telling the user that it is calculating
Some time later
Calculation finishes
ResultsViewController is shown
After some trying, I can only get this:
the user enters some inputs
the user taps the calculate button
A message shows up telling the user that it is calculating
ResultsViewController is shown, with no results
Some time later
Calculation finishes but ResultsViewController don't know.
P.S. I don't know how to improve the question title...
Editing original response for clarity:
Have the the calculate button call a method called calculate() or something that shows the activity indicator, then calls the getResults method.
give getResults() a completionHandler, and on successful completion dismiss the activity indicator and perform the segue with the completed calculation.

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)

Resources