performSelector:withObject:afterDelay: not working on modal view controller - ipad

I have a modal view controller that appears, checks a service on the Internet and then dismisses itself when done. The nib contains an activity indicator and a label to inform the user what is going on.
When the update is complete, the label changes to "Update Complete" and then dismisses the view controller. However, I want it to delay the dismiss for a couple of seconds to give the user a chance to see the text before it disappears. So I've done this:
#pragma mark - AssetLoaderServiceDelegate
- (void)assetLoaderServiceDidFinishLoading:(AssetLoaderService *)service
{
[self.spinner stopAnimating];
self.infoLabel.text = #"Update complete";
[self performSelector:#selector(dismissUpdater) withObject:nil afterDelay:2.0];
}
- (void)dismissUpdater
{
[self dismissModalViewControllerAnimated:YES];
}
But for some reason, the selector is never being called. I've tried running it in mode NSRunLoopCommonModes too, but that doesn't work either.
I must be doing something wrong, but I can't see what...
EDIT: The delegate callback is actually happening within an NSOperationQueue, which might mean it's not on the same thread when it sends the message back to the view controller? So I tried
[self performSelector:#selector(downloadQueueComplete) withObject:nil afterDelay:0.0 inModes:[NSArray arrayWithObject:NSRunLoopCommonModes]];
followed by
- (void)downloadQueueComplete
{
[delegate assetLoaderServiceDidFinishLoading:self];
}
But the performSelector doesn't seem to be working here either.

Following up your suggestion about the thread issue, would you try with:
[self performSelectorOnMainThread:#selector(downloadQueueComplete) withObject:nil waitUntilDone:YES]];

Sorted! In the AssetLoaderService, I had to perform selector on main thread:
[self performSelectorOnMainThread:#selector(downloadQueueComplete) withObject:nil waitUntilDone:YES];
After that, all the following calls were on the correct thread. :)

Related

using didDismissCompletionHandler in MZFormSheetController

I'm using MZFormSheetController to present modals in my app. There is a situation where I want to present a second sheet controller right after I dismiss the first one. In order to do that, there is a completion block, but I can't figure out how to actually use it.
The code looks like this:
[self mz_presentFormSheetController:formSheet
animated:YES
completionHandler:^(MZFormSheetController *formSheetController) {
formSheetController.didDismissCompletionHandler;
}];
in that completion handler, what am I supposed to do to get notified of the sheet dismissal so I can then call the second sheet?
This is actually pretty simple, but not totally intuitive if you haven't spent some time in this type of environment.
[self mz_presentFormSheetController:formSheet
animated:YES
completionHandler:^(MZFormSheetController *formSheetController) {
formSheetController.didDismissCompletionHandler = ^(UIViewController *presentedViewController){
[self presentOtherController];
};
}];

Is it safe to dismiss a viewController without closing the document Object

My application is a document based application I present the saved content using an UIViewController subclass EditViewsController with the help of my customized UIDocument object.
EditViewsController will look like below
Tapping the close Button on the left top corner will fire the below method
-(IBAction)closeForm:(id)sender
{
// _formDocument is my UIdocument subclass Object
[_formDocument closeWithCompletionHandler:^(BOOL completion){
[self dismissViewControllerAnimated:YES completion:nil];
}];
}
My problem is that while calling the method closeWithCompletionHandler my application freezes for a while before closing.
My question is that is it right to dismiss the viewcontroller without closing the document (simply call dismissViewControllerAnimated: inside the firing method) or I have to run that method in background thread to get rid of
freezing?
-(IBAction)closeForm:(id)sender
{
// _formDocument is my UIdocument subclass Object
[_formDocument closeWithCompletionHandler:^(BOOL completion){
[NSOperationQueue mainQueue] addOperationBlock:^{
// Dismissing it on main thread
[self dismissViewControllerAnimated:YES completion:nil];
}];
}];
}

Lag between viewWillAppear and viewDidAppear

I have a strange issue using presentViewController as part of a library.
The code that is using the library calls this method. It takes up to 12 seconds from calling presentViewController to running the completion block. But not all the time normally it's almost instantaneous.
However if I touch any where on the screen while it is "lagging" it will fire instantly.
-(void) advancedMenuWithPresentingViewController
:(UIViewController *)presentingViewController
animated:(BOOL)animated
onClose:(void (^)(void))onClose
onPrint:(void (^)(NSString *, NSString *))onPrint{
AdvancedMenuViewController *amc = [[AdvancedMenuViewController alloc] init];
AdvancedMenuViewController * __weak weakAmc = amc;
[amc setOnClose:^(void)
{
[weakAmc dismissViewControllerAnimated:animated completion:^{
onClose();
}];
}];
[amc setOnPrint:onPrint];
//Time from here
[presentingViewController presentViewController:amc
animated:animated
completion:^{
//To here
}];
}
viewDidLoad and viewWillAppear are called without any lag and then there is a long delay (unless you touch the screen) until viewDidAppear is called.
There is nothing inside any of these methods that would slow it down. As it normally works fine.
If any one could shed any light on this issue I would be most grateful, the most confusing part is that if you interact with the screen after firing advancedMenuWithPresentingViewController it will occur instantly.
Replacing
[presentingViewController presentViewController:amc
animated:animated
completion:^{
//To here
}];
with
dispatch_async(dispatch_get_main_queue(), ^{
[presentingViewController presentViewController:amc
animated:animated
completion:^{
//To here
}];
});
Resolved the issue.
Thanks to rmaddys suggestion.
Run the app in Instruments with the Time Profiler.
That should at least tell you if you have some slow drawing code or if something in UIKit is slowing it down.

UICollectionView rendering and activityindicator

I loaded a UIView from a UIViewController. This UIView contains a (big) UICollectionView.
The transition from the first UIView to the second UIView is very slow: It seems that when the rendering of all collection's cells is done the second view can show up.
In the second UIView, I tried.
- (void)viewDidAppear:(BOOL)animated
{
[activityView stopAnimating];
NSLog(#"did appear %#",[NSDate date]);
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[activityView startAnimating];
NSLog(#"will appear %#",[NSDate date]);
}
In the NSLog, there is no time difference between the two events, and in fact the second UIView shows up in about 1 second after the event viewDidAppear.
At this point, I would start a UIActivityIndicator, as in the code. But the indicator is never shown.
Any hint?
Your problem here is that you're probably blocking the main thread by maybe doing some disk IO or network activity or heavy computations, and that is why you're experiencing this delay.
I'd recommend that you do all this on a secondary thread while showing a UIActivityIndicator. On the completion you can then hide the activity indicator and show the collection view.
EDIT:
N.B. There is probably a better way to go, but i'm not very familiar with collection views.
A really easy fix would be to keep a BOOL ivar in the view controller where you load the collection view. Call it shouldLoadData and set it to NO in your viewDidLoad method. Then all you need to do is to return 0 to your UICollectionViewDelegate methods numberOfSectionsInCollectionView: and collectionView:numberOfItemsInSection:.
Finally in your viewDidAppear method, you set shouldLoadData to YES and call reloadData on your collectionView. The tricky part at this point is to figure out a way to tell when the collection view finished reloading its data so that you can stop the activity indicator.
I found out that it is not even that tricky, reloadData just queues up on the main thread, so you can just queue another task on the main thread after you make the call to reloadData. Just do:
[self.collectionView reloadData];
dispatch_async(dispatch_get_main_queue(), ^{
[self.activity stopAnimating];
});
And you'll get the desired behaviour. You should be aware, however, that this would still block the main thread.
E.g. if you have a back button, it could not be pressed until the data is fully loaded (it could actually be pressed, but it would not have any visible effect until then).

Waiting For Method Or Running In The Background

I have a piece of code that firstly calls a method that does quite a bit of thumbnail generation so it slows the device down for about a second. I was hoping to run a method that generates a "loading message" before the first method is called and then remove it when the first method is finished.
[picker dismissViewControllerAnimated:YES completion:^{
NSLog(#"Loading");
[self generatingThumbnailMessageShow];
[self loadAllEffects];
}];
The problem seems to be that although the "generatingThumbnailMessageShow" method is before the "loadAllEffects" method it still seems to get called after the "loadAllEffects" message is finished. What is the best method to call the "loadAllEffects" method only when the first method is finished?
The problem is that [self loadAllEffects] runs on the main thread and blocks the UI. Changes to the UI become only visible after program control has returns to the main runloop.
You have to move the execution of [self generatingThumbnailMessageShow]; to a background thread, something like
[picker dismissViewControllerAnimated:YES completion:^{
NSLog(#"Loading");
// Show "loading" message (must be done on main thread)
[self generatingThumbnailMessageShow];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// perform long running task on background thread
[self loadAllEffects];
dispatch_sync(dispatch_get_main_queue(), ^{
// Hide "loading" message (must be done on main thread again).
[self generatingThumbnailMessageHide];
})
})
}];

Resources