Swift: Gui is not updated / much too late - ios

I am currently programming a Quiz-App for iphone using swift and xcode. Since a week I a stuck with a problem, i wasn't able so solve up to today.
Little Background Information:
All Questions are stored online on a server and are downloaded just in time. The Questions always contain images, so it takes some time to load them. This is why I am pre-loading the next question in background while the user is still thinking about the current question. Therefore I use the class QuestionLoader which extends Thread.
The Problem Situation:
When the user answers a question and presses the continue-button, the function goToNextQuestion() is triggered which shows the next question to the user. Either the pre-loading of the next question is finished and it can be shown instantly or the next question is still loading and we have to wait.
The Problem:
Now if we have to wait, I want to show a Loading-Label and -Indicator. Both are in the gui (placed with interface-builder) and hidden at the time. I do it like this:
func goToNextQuestion()
{
//check if next question is already ready
if (nextQuestionLoader.isFinished==false)
{
//not ready, so wait and show waiting indication
labelLoading.isHidden = false
loadingIndicator.startAnimating()
while(nextQuestionLoader.isFinished == false) {}
//question i now ready, hide waiting indication
labelLoading.isHidden = true
loadingIndicator.stopAnimating()
}
//so next question is ready and all good
}
What I want to happen is that when the user pressed continue and the next question is not ready yet, that the loadingIndicator and the labelLoading are shown until the question is ready.
Now what happens is that continue is pressed the there is nothing shown and the gui is blocked util the question is ready, then it shows the next question and the same thing happens with the following question.
Screenshots of the area of my gui that is concerned for better visualitzation
(Sorry for the different number of points gained in the screenshots, does not have to do with the problem, simply 2 different screenshots)
So the gui update does not happen before the question is loaded completely and does not happen immediately as it should!
Additional Information
I tried the showing of the loading indicators at other points of the code and they show correctly, so there is no problem with this.
When i put print-command before and after the while-loop and follow the output in the terminal, I see that the print-commands are correctly executed before and after the while-loop. But the gui-update does only happen afterwards
The function goToNextQuestion is completely executed in the main-thread!
So how can i achieve that the loading-indication is shown directly and after it is shown, the while-loop waits for the finish of the other thread?
I am really desperate because i have no idea at all how to solve this.
I would really really appreciate some help from you!
Thank you very much in advance for your time!!

There are several problems with your approach.
First, you should not use NSThread/Thread. The system class URLSession handles async downloading for you. (And creating threads is expensive and error-prone.) Use URLSession instead. You should be able to find lots of examples online, including here.
Second, you can't block the main thread like you're doing with your
while(nextQuestionLoader.isFinished == false) {}
Code. Since your download is running on a background thread it will keep running, but by blocking the main thread everything will freeze until the download completes. Your UI changes don't get a chance to be drawn on the screen until the download is complete.
What you should do is use URLSession using either a delegate method or a completion closure. It supports both approaches.
I have a project called Async_demo on Github that includes working code that shows how to use URLSession. It's a simplified example, but it should get you started.

Related

Where to put code that gets checked frequently?

I have some code that needs to get called frequently, such as check what day it is, if it's the next day then move the day strings in the tableView.
Now I thought that the viewDidLoad would get called all the time and so it would be 'fine' to put it in there. However, I've left the simulator overnight, and I've pressed the home button and clicked again, changed VCs etc. and viewDidLoad hasn't been hit.
What are my options for doing sporadic checks such as, is it a new day? As x happened etc.
In this specific case, you can subscribe to NSCalendarDayChangedNotification to be notified when the date changes and respond accordingly in your view controller. In general, didBecomeActive or viewDidAppear would likely work.
What are my options for doing sporadic checks such as, is it a new day
It depends what the meaning of "is" is! In particular, "is" when? You say "sporadic", but that's just fluff. When do you need to know this? To what stimulus do you want to respond? When the user opens your app? Then put it in applicationDidBecomeActive. Every day at noon? Then run an NSTimer. Really, the problem here is that you don't seem to know, yourself, just when you need to perform these checks.
Whilst in your app, its quite easy to continually check for something. You simply create a background thread. However, what you describe is a thread that persists from outside the app's lifecycle.
Have a read on this documentation provided by Apple itself. You have to have good excuse to put a background thread. The scope of such thread is limited to only certain scenarios such as downloading background stuff, playing sounds etc.
For your scenario, I'd look at applicationDidBecomeActive(_:) found in your Application Delegate. There you can mimic such continual check. Beware however, don't put heavy word load on start up or your app might be killed automatically if it fails to become active in reasonable amount of time.

grand central dispatch, operation queues, async, view will and did disappear

Hopefully these questions will seem helpful to people out there. I've been learning objective c, mostly from this book, which I found to be amazing and helpful even for a noob. My questions all have to deal with this:
What happens to the queue when the user changes the view? I can't seem to find a good explanation anywhere.
From my understanding, using the NSOperation and its queue, you can always cancel it using the "cancel"...but what if you don't want it to cancel? What if, say a user selects multiple images to upload to the server, and you create a queue with the order, and the user switches to a new view controller? This might be time consuming, especially on a slow mobile network. I remember reading somewhere that iOS gives around 20 seconds extra time for a method to finish its work, but I think that's only when the app enters to the background.
For the GCD, there is no cancel method...so what happens in the background if you use async? I guess if you don't have a response to the queue, I mean you don't update the UI in any way, shouldn't the queue finish since it's sent to another thread?
I'm sure there is much more that I don't understand as far as threading goes, but I hope I made my question clear. And please please don't tell me to use the AFNetworking stuff...I tried using all those keychain wrappers out there and it all failed. Thanks to the book, the straight Apple code from the book did everything easily. I would rather learn the basics first before using the easier way out.
I would really appreciate if someone took the time to talk about this. Thanks in advance!
Your concern about only having a set amount of time to finish tasks only applies to when you switch away from your app to another app. And in that case you can use the beginBackgroundTaskWithExpirationHandler method so your app can request time to finish those tasks if your app happens to go into background. See the Executing a Finite-Length Task in the Background section of the App States and Multitasking section of the iOS App Programming Guide for more information.
But if you're still within your app (whether you transitioned to another view controller or not), anything you've added to your operation queue will continue to run until you cancel those operations (or the app is suspended or terminated). Likewise, anything you've added to a GCD queues will continue to run until the app is suspended or terminated.
In both of these scenarios, the above beginBackgroundTaskWithExpirationHandler will give you a few minutes to finish your queued tasks/operations after your app goes into the background.

How to make UIAlertView pause thread and wait user response, iOS dev

Please read through all my question and see if it is duplicate with this question
Sometimes I believe iOS is the best OS and xCode is the most elegant IDE too, but sometimes I think they're both the stupidest thing in the world and maybe the whole universe!
What I need SEEMS easy, code logic like this
//code BEFORE asking user's decision
//with a lot of variables, this is important
//call UIAlertView to ask user Yes/No question, now the thread should be paused and waiting for user's tap on button
//code AFTER get user tap a button, selected YES/NO
//MANY variables before asking should be continuely used here
answerIsYES = //response from UIAlertView
if (answerIsYES)
{
//code response to YES
}else{
//code response to NO
}
If you noticed "MANY variables" before and after UIAlertView show, you will not suggest me to use UIAlertViewDelegate. If that, I have to make all the variables at class-level and what if I have to ask user's decision in FOR loop? That cannot jump to SelectedButtonAtIndex method and come back! Am I right? I want to make my code integrated but the UIAlertView and its delegate seems keeping split them.
Put it simple, what I want is just a MessageBox from .NET, it can wait for user's click. So is there any code that can ahieve this on iOS? Is there a code in iOS can just simple "pause" a thread and then triggerd by some event and "resume" it??
I have searched a lot and cannot find a satisfied answer, the answers involved "NSCondition", "NSThread", "loop", but none really works well. But I did found one in the link at first, respond by #Joseph Gagliardo, I don't know if that's the best solution.
Any suggestion will be greatly appreciated.
UIAlertView, like most other aspects of UIKit, is event driven. You don't want the main thread blocked while an alert is being displayed. Many times other things need to keep going while the alert is in view. But there is a good solution to your issue. Blocks.
UIKit classes that have been added more recently are starting to make use of blocks. This makes writing event driven code easier. It eliminates the need for separate delegate methods just to handle the completion of some event.
Do a Google search on "UIAlertView blocks". You will find several 3rd party versions of UIAlertView that make use of blocks. The use of this type of alert view will allow you to keep all of your code in the one method. These solutions don't pause the main thread or anything. It just makes it much easier for the completion block to access all of the variables you have setup just prior to displaying the alert view.

Adding pin on mapView is not working on appstore version

I have submitted my app and been accepted couple of weeks ago. However, I have an issue that one of my critical functionality which is adding pin is not working but it works in building version. Adding pin requires URL connection. Could you please look at the answer that I get from apple developer and guide me please?
It shouldn't be causing your problem (at least not directly), but in general
having networking code like this live inside your UI code is very problematic
for a number of reasons:
-On general architecture grounds, it's a large violation of the MVC (model
view controller) architecture. The pin annotation is obviously heavily tied
to your UI (the view), while networking is clearly part of your backend (the
model).
-Because iOS REQUIRES the main thread to remain responsive, attaching code
that does IO or computation to the main thread is inherently risky. It can be
done and work well, but doing it incorrectly will inevitably lead to crashes.
NSURLConnection works in background and interactions with UI should go on main thread.
So, if you add your pin at the connectionDidFinish method for example, you should do like this:
[mapView performSelectorOnMainThread:#selector(addAnnotation:) withObject:yourAnnotation waitUntilDone:[[NSThread currentThread] isMainThread]];
Hope that helps you. I wrote from windows but the line should work.
I'm not exactly sure about what you are trying to achieve. Are App folks complaining because you are fetching URL directly in the Main thread ? Could you just send a code snippet showing how you fetch urls ?
One option would be to drop the pin (not tied to URL connection at all) and once the pin drops, load your data for the callout or annotation (which is what I'm assuming you are doing). You can always put a loading indicator or progressHUD up to tell the user they need to wait for something. It isn't exactly ideal, but in instances where you need to wait for data, what are you going to do?
Apple really doesn't care about blocking the UI so long as the user is aware you are blocking it. Like a progress bar or progress HUD. The user can see the app is still working and doing something. Not showing anything and having the UI just freeze while it waits for the main thread/main loop to return isn't going to work. It is just bad design and Apple will likely ding you for it.

waiting on a task in iOS

I have what sounded like a really simple issue at first, and I think still is, but I'm missing the answer.
When my app logs in, I start a background task to download some data from the server. If that data isn't downloaded by the time the user taps one of two buttons, I want to put up a spinner view and wait until that task is finished. Once finished, remove the spinner view and then continue to push the view controller.
What I seem to be missing is how to do this and wait so that the main thread isn't blocked.
Once the data is downloaded, I can set a global flag, or send out a notification, but the view controller has to have a way to wait on that condition to either be set, or to know that there was a download error.
Any thoughts?
Stack overflow is not meant to be used in the way you're using it, so you're going to get downvoted.
But, what you are looking for is dispatch_async. You use it to create queues.
If you need more information, this page has a good explanation, as well as this page.

Resources