Xcode load slow things after the View is visible - ios

I have a very annoying problem. I have a ViewController with an UIImageView in it. The UIImageView should display a slide show. The images for the are coming from NSURL, so it takes a bit of time.
- (void)viewDidLoad {
[super viewDidLoad];
[self loadImages];
}
This is how I get the Images from the NSURL. The problem I have is, that while the Images are loading I only see a black screen.
At the beginning and at the end of the -(void)loadImages method I implemented a UIActivityIndicator to display the loading time.
I already tried -(void)viewWillAppear and -(void)viewDidLayoutSubviews but nothing worked.
Thanks for help
Jannes

What you need to do is load your images asynchronously and populate them as they come in (with delegate or block callbacks, depending on the API you use). You should absolutely never run networking code, or any other potentially long-running operation, synchronously on the main thread. The reason for this is that the UI runs on the main thread, so if you block it with your own operations, the UI cannot update and your app will become unresponsive.
Whether you're using Apple's networking frameworks or something like AFNetworking (highly recommended), there are many ways to do networking asynchronously with minimal work.

You're probably downloading your images synchronously. Try wrapping it in a dispatch_async block to run it on a different thread.
Example:
-(void)loadImages {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void) {
// download the images here
dispatch_async(dispatch_get_main_queue(), ^(void) {
// update the UI on the main thread
});
});
}
Check out the SDWebImage library as well as most of this heavy lifting is done for you.

Related

Multiple "Main Queue" Tasks For User Perspective

This is perhaps more existential than a concrete question, but I'm struggling with a bit of a user experience issue in my app.
In my app, I have a task that converts a UIView to an UIImage, does some off-screen processing, and updates the UI for the user. All of this happens on the Main Queue, as is required for such UIKit tasks.
I want to have an activity indicator in my app (I'm using a custom designed one, but a regular UIActivityIndicator demonstrates the same issue), which I also have running on the Main Queue, prior to the aforementioned task.
My issue is that once the UIView processing kicks in, my activity indicator freezes until the task completes. This is obviously due to the main queue handling another, more intensive task, hereby hanging the spinner. My curiosity is; how can I ensure the spinner continues, regardless of the UI work happening on the main queue?
Thanks!
I'm afraid this is impossible unless you do the heavyweight operation on the background thread.
You can try to give the main thread a little bit air to breathe by chunking the operation to smaller parts, if that can be done. That would at least allow some updates of the spinner.
While I think that you should keep taking an image and updating the UI on the main thread, considering putting processing the image at the background thread, if that is possible.
I agree with Milan. I'd suggest a slightly different flow:
Start your activity indicator spinning.
Grab your view and convert it to an image.
Pass the image processing to a GCD background queue for processing, and pass in a completion handler.
When the background processing is complete, invoke the completion handler on the main thread. In the body of the completion handler, stop/hide the activity indicator.
I would suggest something like this.
-(void)render {
startSpinner();
backgroundRenderQueue = dispatch_queue_create("backgroundQueue",DISPATCH_QUEUE_CONCURRENT);
dispatch_async(backgroundQueue, ^{
//Render image here
UIImage *image = [UIImage imageWithCGImage:CGBitmapContextCreateImage(context)];
dispatch_async(dispatch_get_main_queue(), ^{
self.imageview.image = image;
stopeSpinner()
});
});
}

Screen freeze while loading UIWebview

I am Loading HTML String in UIWebView,text content get loaded in a sec but there is lot of images in HTML String, cause of this image my UIWebView get freeze, after loaded all images my screen get free to use.
Any Suggestion on this?
Load webview in dispatch thread, try this:
dispatch_async(dispatch_get_main_queue(), ^{
[self.webView loadHTMLString:#"htmlstring" baseURL:[NSURL fileURLWithPath:[[NSBundle mainBundle] bundlePath]]];
});
I think it's freezing because you're loading a images from web in your main thread, Which is also responsible for redrawing your UI. By loading a large images inside your thread, your UI freezes as well.
I would recommend moving the code that does images loading into a separate thread.
(you can use #Andey's answer)
Like #Andey said, you should dispatch off the main queue whenever the thing you're doing will take some time and you don't want your view to freeze.
Try lecture 9:
https://itunes.apple.com/en/course/developing-ios-8-apps-swift/id961180099
UPDATE (Swift ver.):
Although I do not know how to write this in Obj-C, it will look something like this in Swift, which you can later translate it back to Obj-c.
dispatch_async(notTheMainQueue) {
// Some time consuming stuff you're doing (downloading data, calculating..)
...
dispatch_async(dispatch_get_main_queue) {
// Set your view here, which dispatches *back* to the main queue and will not block your UI
...
}
}

Long-running task in application:didFinishLaunchesWithOptions:

I have some long-running startup tasks (like loading Parse objects from local data storage) in my app. Those tasks should be finished before the interface starts to appear. The app was originally created using storyboards, so the interface starts to appear automatically after application:didFinishLaunchesWithOptions: method finishes. I can't block main thread because Parse SDK fires all it's callbacks on main thread (so blocking results in deadlock). I also need to delay return from application:didFinishLaunchesWithOptions: to finish setup. So what I did is:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Dispatch long-running tasks
dispatch_group_t startup_group = dispatch_group_create();
dispatch_group_async(startup_group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// Perform some long-running setup here
});
// Run main run loop until startup tasks finished (is it OK to do so?)
while (dispatch_group_wait(startup_group, DISPATCH_TIME_NOW))
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
return YES;
}
Is it the proper usage of NSRunLoop? Are there any potential caveats? Can you propose more elegant (preferably GCD) solution?
UPDATE:
Loading Parse object from local data storage (i.e. loading tiny file from SSD) is not so long operation as loading something from the web. The delay is barely noticeable, but long enough to trigger warnBlockingOperationOnMainThread, so UX is not an issue.
The real issue is (infrequent) crashes caused by spinning another main runloop above regular main runloop, which sometimes leads to reentering UIApplication delegate methods, which apparently is not thread-safe.
Of cause introduction of splash screen is an obvious solution, but I was looking for a something more simple and elegant. I have a feeling it exist.
While this technically works depending on what your setup is doing (for example web service calls) the application may never launch or launch with unexpected results.
The more user friendly method would be to add a "loading" view controller to your storyboard which would be your landing view. Perform your long running setup here while providing the user with information/status/whatever is appropriate, then push your original view controller.
Imho I think it's better to do your stuff in your main View Controller and show something to user like a custom spinner: in this way you can download your data and the user know what happens.
It's never a good idea take so long time to launch an app with a blank screen or just a launch screen with no info for the user.
This is creative but a really bad idea. You should make your UI load in a failsafe state - in other words, assume the network is not available/slow, and show a view that won't explode if it doesn't get data before being rendered. Then you can safely load data on a background queue and either update the view or transition to the data-centric view once it is done loading.
dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^{
//Load your data here
//Dispatch back to the main queue once your data is loaded.
dispatch_async(dispatch_get_main_queue(), ^{
//Update your UI here, or transition to the data centric view controller. The data loaded above is available at the time this block runs
});
});
Lagging the return of didFinishLaunchingWithOptions: is really a bad idea.This will give the bad impression of your app and its a bad design.
Instead you could land in the view controller and show a overlay view that shows the progress or activity you are doing when completed you could dismiss it.This is the right approach.
Here is a link to Third party library that eases showing/hiding the overlay view and setting some text on the view.
You can find so many libraries like this just google iOS HUD.HUD means Heads Up Display or you can design the custom thing yourself using UIAnimation easily.
Generally speaking,
I have to appreciate that code you have written in the sense you have stopped the current flow or run loop using While and NSRunloop.
May be you can alter your code little bit and use in some other place of your program to stop the current flow or runloop and wait for something.
NSDate *runloopUntill = [NSDate dateWithTimeIntervalSinceNow:0.1];
while (YourBoolFlag && [[NSRunLoop currentRunLoop] runMode: NSDefaultRunLoopMode runloopUntill])
The following apple documentation will give you deep insights in NSRunloop 1 2

UIViewController didn't present immediately

I have
[self presentViewController:viewController animated:YES completion:^{}];
And view controller presents after 5-6 seconds after i click the button first time only. After first time presenting latter works ok.
What seems to be the problem?
Thanks
I guess there is some url download process has been started in ViewController viewDidLoad method, which is eventually blocking till the download has been completed. Though it works second time fine, because of cache, and it downloads faster than first time.
I suggest you to use dispatch block like below
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
//Download Code
[self download];
});
or, alternatively you can create a function say
-(void)download{
}
and call it using
[self performSelector:#selector(download) withObject:nil afterDelay:.1];
all in viewDidLoad.
All will work without any problem.
And too you can choose to add UIActivityIndicator to show user an activity for download.
Hope it helps.
Cheers.
Not only the awakeFromNib issue said by #rckoenes, but also check for any heavy load processes in viewDidLoad of presentingViewController as those processes will run on main thread. The heavy load processes may be
1. Downloading an image and fixing it to image,
2. Drawing any high clarity image,
3. Downloading the data from internet (Ex: [NSData dataWithContentsOfURL:url])etc.
This can be avoided by using threads.
If this is not the case, please let me know what is the process you wanted to run in viewDidLoad of presentingViewController.
Are you sure this is happening on the main thread?
You can confirm as follows:
if(! [NSThread isMainThread] )
{
dispatch_async( dispatch_get_main_queue(),
^{
// Continue here
} );
}

View does not appear because of iOS 7

I tried my app on my iPhone with iOS 7 and everything works perfectly except one thing. On iOS 6 versions, when I executed the following code, the loading view (with the activityindicator) appeared and disappeared at the end of the loading. On iOS 7, the view does not appear at all during the loading.
self.loadingView.alpha = 1.0;
[self performSelector:#selector(accessServices) withObject:nil afterDelay:0.0f];
AccessServices method :
- (void)accessServices {
// Getting JSON stuff
// The code is OK, I just don't copy/paste it here
self.loadingView.alpha = 0.0;
}
What happens ? Is it an iOS 7 bug ?
I wouldn't have expected the behavior of the above code to change, though I'm not surprised that it might not work the way you expect. If you do the JSON stuff on the main queue, your code will be dependent on idiosyncrasies of when the UI update takes place, rather than making it explicit.
You probably want to explicitly dispatch the JSON code to a background queue, and then dispatch the final UI update back to the main queue. The standard pattern for something like this is:
self.loadingView.alpha = 1.0;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// Getting JSON stuff
// The code is OK, I just don't copy/paste it here
dispatch_async(dispatch_get_main_queue(), ^{
self.loadingView.alpha = 0.0;
});
});
You can use GCD (like above) or operation queues, or whatever. But the idea is the same, that UI updates should happen on the main queue but that anything remotely computationally expensive or slow else should happen on a background queue.
This pattern should work regardless of iOS version.

Resources