Fastest way to dismiss a UIPopoverController? - ios

I am posting a notification from the UIPopoverController back to my main view, which then immediately calls dismissPopoverAnimated, and then goes about doing some fairly heavy work loading web views. All of this code works; the problem is that on some older ipads, the popover is not actually dismissed until after the cpu intensive work is completed (verified in debugger). This causes the popover to appear to hang for second after being dismissed. How can I ensure the popover is dismissed immediately instead of doing the intensive code first?
The method which responds to the notification is as follows:
- (void)changeDefaultView:(NSNotification *)note
{
[self closePopover];
int i;
for(i = 0; i < [arrWebViewControllers count]; i++)
{
WebViewController *wvc = [arrWebViewControllers objectAtIndex:i];
[[wvc webview] stopLoading];
[[wvc webview] removeFromSuperview];
[[wvc imageview] removeFromSuperview];
wvc = nil;
}
[arrWebViewControllers removeAllObjects];
[arrLinks removeAllObjects];
[arrImageViews removeAllObjects];
[self loadCategory:[note object]];
[self addWidgetsToView];
}
and closePopover is:
- (void)closePopover
{
if(popover != nil)
{
[popover dismissPopoverAnimated:YES];
popover = nil;
}
}

The animation of the disappearing popover happens on the main run loop; if you're doing heavy CPU work on the main thread, then the loop won't get a chance to run. So what you need to do is to perform your work on a background thread.
The currently-preferred way to do this is using Grand Central Dispatch, like so: (I'm assuming that loadCategory: is the CPU-intensive operation)
- (void)changeDefaultView:(NSNotification *)note
{
[self closePopover];
int i;
for(i = 0; i < [arrWebViewControllers count]; i++)
{
WebViewController *wvc = [arrWebViewControllers objectAtIndex:i];
[[wvc webview] stopLoading];
[[wvc webview] removeFromSuperview];
[[wvc imageview] removeFromSuperview];
wvc = nil;
}
[arrWebViewControllers removeAllObjects];
[arrLinks removeAllObjects];
[arrImageViews removeAllObjects];
// perform the expensive operation on a background thread
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[self loadCategory:[note object]];
// now get back onto the main thread to perform our UI update
dispatch_async(dispatch_get_main_queue(), ^{
[self addWidgetsToView];
});
});
}

Related

Display multiple views serially using GCD

I have 4 tabs and all have UIWebView. I want that the first tab should load and displayed immediately without waiting for others to load. For which I did this in UITabBarController class:
for(UIViewController * viewController in self.viewControllers){
if(![[NSUserDefaults standardUserDefaults]boolForKey:#"isSessionExpired"])
{
if((int)[self.viewControllers indexOfObject:viewController] != 4)
{
viewController.tabBarItem.tag = (int)[[self viewControllers] indexOfObject:viewController];
[viewController view];
}
}
}
But the main thread waits for all tabs to load.
I tried this with GCD using dispatch_async
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ // 1
dispatch_async(dispatch_get_main_queue(), ^{
UIViewController *firstContrl = [self.viewControllers objectAtIndex:0];
firstContrl.tabBarItem.tag = (int)[[self viewControllers] indexOfObject:firstContrl];
[firstContrl view];
dispatch_async(dispatch_get_main_queue(), ^{ // 2
UIViewController *secContrl = [self.viewControllers objectAtIndex:1];
secContrl.tabBarItem.tag = (int)[[self viewControllers] indexOfObject:secContrl];
[secContrl view];
dispatch_async(dispatch_get_main_queue(), ^{ // 3
UIViewController *thirdContrl = [self.viewControllers objectAtIndex:2];
thirdContrl.tabBarItem.tag = (int)[[self viewControllers] indexOfObject:thirdContrl];
[thirdContrl view];
dispatch_async(dispatch_get_main_queue(), ^{ // 4
UIViewController *fourthContrl = [self.viewControllers objectAtIndex:3];
fourthContrl.tabBarItem.tag = (int)[[self viewControllers] indexOfObject:fourthContrl];
[fourthContrl view];
});
});
});
});
});
But this doesn't work either. It takes the same time to display the first tab.
How can this be fixed?
Ehm.. GCD and UIWebView's -loadRequest: doesn't work as you think. All the code above is pretty equal to:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
dispatch_async(dispatch_get_main_queue(), ^{
UIViewController *firstContrl = [self.viewControllers objectAtIndex:0];
firstContrl.tabBarItem.tag = (int)[[self viewControllers] indexOfObject:firstContrl];
[firstContrl view];
UIViewController *secContrl = [self.viewControllers objectAtIndex:1];
secContrl.tabBarItem.tag = (int)[[self viewControllers] indexOfObject:secContrl];
[secContrl view];
UIViewController *thirdContrl = [self.viewControllers objectAtIndex:2];
thirdContrl.tabBarItem.tag = (int)[[self viewControllers] indexOfObject:thirdContrl];
[thirdContrl view];
UIViewController *fourthContrl = [self.viewControllers objectAtIndex:3];
fourthContrl.tabBarItem.tag = (int)[[self viewControllers] indexOfObject:fourthContrl];
[fourthContrl view];
});
});
To solve your problem you need to implement queue depends on - (void)webViewDidFinishLoad:(UIWebView *)webView. The idea is:
First view controller's webView starts loading request
asynchronously;
When request is loaded (or failed), webView will notify your
delegate;
Notify all other view controllers with webView, to let them start
loading their requests;
If I were you, I would implement it in next way:
// FirstViewController.h
extern NSString * const FirstViewControllerDidLoadWebView;
// FirstViewController.m
NSString * const FirstViewControllerDidLoadWebView=#"FirstViewControllerDidLoadWebView";
- (void)webViewDidFinishLoad:(UIWebView *)webView
{
// notify other controllers, to let them load their web views:
[[NSNotificationCenter defaultCenter] postNotificationName:FirstViewControllerDidLoadWebView
object:nil];
}
- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error
{
// loading failed. Try to load it few more times but anyway notify other controllers after:
[[NSNotificationCenter defaultCenter] postNotificationName:FirstViewControllerDidLoadWebView
object:nil];
}
After that, 'subscribe' all other(only tabs) view controllers for FirstViewControllerDidLoadWebView notification and load their WebView after it arrives.
For more details check NSNotificationCenter and UIWebViewDelegate

button.enable not refreshing IOS

I have a UIViewController loading data from web, the process takes about 15 secs, So I put my long running process on 2nd thread, when the 2nd thread is completed, i will set the button enable or disable. but the button is not refreshing when the process is done.
#import "ViewController.h"
#import "FunctionNSObject.h"
#interface ViewController ()
#end
#implementation ViewController
NSMutableArray *schoolsAvaliable;
NSDictionary *dict;
NSString *schoolNameCh, *schoolNameEn;
int schoolYear, schoolID;
- (void)viewDidLoad
{
[super viewDidLoad];
}
-(void)viewWillAppear:(BOOL)animated
{
//initial set the button disable
self.button.enabled = NO;
//2nd thread
dispatch_queue_t downloadQueue = dispatch_queue_create("loadSchool", NULL);
dispatch_async(downloadQueue, ^{
//get avalible school info from JSON
schoolsAvaliable = [FunctionNSObject loadDataFromWeb:#"http://some web service"];
//get school year
schoolYear = [FunctionNSObject getSchoolYear];
if (schoolsAvaliable.count != 0)
{
//select the first row from array
dict = schoolsAvaliable[0];
//get the value from dictionary of that row
schoolID = (int)[[dict objectForKey:#"SchoolId"] integerValue];
schoolNameCh = [dict objectForKey:#"SchoolName"];
schoolNameEn = [dict objectForKey:#"SchoolNameEn"];
self.button.enabled = YES;
[self.button setNeedsDisplay];
}
else
{
self.button.enabled = NO;
[self.button setNeedsDisplay];
}
//2nd thread end then
dispatch_async(dispatch_get_main_queue(), ^{
//[self.pickerSchool reloadAllComponents];
self.labelSchool.text = [NSString stringWithFormat:#"%d - %d",schoolYear,schoolYear+1];
NSLog(#"%d",self.button.enabled);
});
});
}
#end
You call related to UI methods on non-main thread. Usually it results to unpredictable behaviour.
Try to call methods related to UI on main thread like this:
dispatch_async(dispatch_get_main_queue(), ^{
self.button.enabled = YES;
});
As #David noticed you needn't call [set.button setNeedsDisplay] because call of setEnabled: method results to call of this method.

Hiding UIView after Network Notification

I'm using the following code to detect when a user has connectivity to the internet. If there's no connectivity, add a subview to show that. If the user regains connectivity, remove the view.
Adding the view works just fine, however removing it isn't working. Anybody know why? (Yes, I have double checked that removeNetworkIndicator is getting called)
- (void)testInternetConnection
{
__weak typeof(self) weakSelf = self;
internetReachable = [Reachability reachabilityWithHostname:#"google.com"];
// Internet is reachable
internetReachable.reachableBlock = ^(Reachability*reach)
{
// Update the UI on the main thread
dispatch_async(dispatch_get_main_queue(), ^{
[weakSelf removeNetworkIndicator];
});
};
// Internet is not reachable
internetReachable.unreachableBlock = ^(Reachability*reach)
{
// Update the UI on the main thread
dispatch_async(dispatch_get_main_queue(), ^{
[weakSelf addNetworkIndicator];
});
};
[internetReachable startNotifier];
}
- (void)addNetworkIndicator {
NetworkIndicatorViewController *networkIndicatorView = [[NetworkIndicatorViewController alloc] initWithNibName:#"NetworkIndicatorViewController" bundle:nil]; //creat an instance of your custom view
networkIndicatorView.activityIndicator.hidden = YES;
networkIndicatorView.view.tag = 400;
[self.view addSubview:networkIndicatorView.view];
}
- (void)removeNetworkIndicator {
UIView *networkIndicator = (UIView *)[self.view viewWithTag:400];
NSLog(#"networkindicator: %#",networkIndicator);
networkIndicator.hidden = YES;
[networkIndicator removeFromSuperview];
}
Sidenote: the NSLog networkindicator is logging (null) but I don't understand why...
It's not immediately clear to me why your current code isn't working, but in the meantime, you can do this:
- (void)removeNetworkIndicator {
for(UIView *subview in self.view.subviews) {
if ([subview isKindOfClass:[NetworkIndicatorViewController class]]) {
[subview removeFromSuperview];
}
}
}
Also... make sure you're using a UIView subclass and not a UIViewController subclass.
create a property for the view in your view controller:
#property (nonatomic, strong) NetworkIndicatorViewController *networkIndicatorView
and then simply use this to show:
[self.view addSubview:self.networkIndicatorView.view];
and this to hide:
[self.networkIndicatorView.view removeFromSuperview];

Cant see the UIActivityIndicatorView during Call to Server for data

I'm writing an app that is calling a server to get data to show to the user as the app starts, the call to the server is a sync call, i want to show the user a UIActivityIndicatorView, but i cant see it Although i'm activating the UIActivityIndicatorView in a new Thread, hear is the code:
- (void)viewDidLoad {
[super viewDidLoad];
spinner = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
spinner.frame = CGRectMake(50, 50, 50, 50);
[self.tableView addSubview:spinner];
[self.tableView bringSubviewToFront:spinner];
singeltoneData *sing = [singeltoneData sharedInstance];
firstTimeSearch = YES;
firstTimeSearchClick = YES;
NSNumber *num = [[NSNumber alloc]initWithInt:-1];
[NSThread detachNewThreadSelector:#selector(spin:) toTarget:self withObject:nil];
[self getData:num];
filterCalls = [[sing.globalCallsDitionary objectForKey:#"Calls"]mutableCopy];
allCalls = [[sing.globalCallsDitionary objectForKey:#"Calls"]mutableCopy];
callsDetails = [[sing.globalCallsDitionary objectForKey:#"CallDetails"]mutableCopy];
filteredCallsDetails = [[sing.globalCallsDitionary objectForKey:#"CallDetails"]mutableCopy];
#if defined(__IPHONE_5_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_5_0
if ([self.navigationController.navigationBar respondsToSelector:#selector( setBackgroundImage:forBarMetrics:)]){
[self.navigationController.navigationBar setBackgroundImage:[UIImage imageNamed:#"top.png"] forBarMetrics:UIBarMetricsDefault];
}
#endif
[self buildBar];
woman = [UIImage imageNamed:#"woman.png"];
}
next my spin function looks like this
/************************************************************/
/* Spinner */
/************************************************************/
- (void) spin:(id)data{
[spinner startAnimating];
}
I'm also calling it from refresh data call to the server:
- (void)activateActions:(id)sender {
[NSThread detachNewThreadSelector:#selector(spin:) toTarget:self withObject:nil];
singeltoneData *sing = [[singeltoneData sharedInstance]autorelease];
[allCalls removeAllObjects];
[callsDetails removeAllObjects];
[filterCalls removeAllObjects];
[filteredCallsDetails removeAllObjects];
NSNumber *num = [[NSNumber alloc]initWithInt:-1];
[self getData:num];
filterCalls = [[sing.globalCallsDitionary objectForKey:#"Calls"]mutableCopy];
filteredCallsDetails = [[sing.globalCallsDitionary objectForKey:#"CallDetails"]mutableCopy];
allCalls = [[sing.globalCallsDitionary objectForKey:#"Calls"]mutableCopy];
callsDetails = [[sing.globalCallsDitionary objectForKey:#"CallDetails"]mutableCopy];
[self.tableView reloadData];
}
still i dont see the spinner
any help?
[NSThread detachNewThreadSelector:#selector(spin:) toTarget:self withObject:nil];
- (void) spin:(id)data{
[spinner startAnimating];
}
Based on above you are animating the spinner on a different thread..but all UI update should be done on the main thread..so that might be the reason you don't see the activity animating

dismissModalViewControllerAnimated function not dismissing the view

I am trying to use dismissModalViewController:Animated: to dismiss my view, but it is not dismissing it, no matter what I try. You can see my attempts to release the view in the hideSplash method at the bottom. Please, if anyone can help it would be greatly appreciated. My code posted below:
#import "SplashViewController.h"
#implementation SplashViewController
- (void) didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
}
- (void) viewDidUnload {
}
- (void) dealloc {
[super dealloc];
}
-(void) showSplash {
modalViewController = [[UIViewController alloc] init];
modalViewController.view = modelView;
[self presentModalViewController:modalViewController animated:NO];
[activityIndicator startAnimating ];
//[self bigcalculation];
//[self performSelector:#selector(hideSplash) withObject:nil afterDelay:2.0];
}
- (void) viewDidAppear:(BOOL)animated {
NSLog(#"View Did Appear");
[self bigcalculation];
}
- (void) bigcalculation {
NSLog(#"Big Calc Start");
for (int i = 0; i <= 648230; i++) {
for (int j = 0; j <= 1200; j++) {
}
}
NSLog(#"Big Calc End");
[self performSelector:#selector(hideSplash) withObject:nil];
}
- (void) hideSplash {
NSLog(#"Hide");
//[self dismissModalViewControllerAnimated:NO];
//[[self parentViewController] dismissModalViewControllerAnimated:YES];
[[self modalViewController] dismissModalViewControllerAnimated:YES];
NSLog(#"End Hide");
}
#end
The modal view controller is not responsible for dismissal. That burden is placed on the view controller that called the modalViewController.
Try replacing:
[[self modalviewController] dismissModalViewControllerAnimated:YES];
with
[self dismissModalViewControllerAnimated:YES];
Try to use this:
[self.parentViewController dismissModalViewControllerAnimated:NO];
I found the solution in case anyone else has this issue the line
[self performSelector:#selector(hideSplash) withObject:nil];
Should be
[self performSelector:#selector(hideSplash) withObject:nil afterDelay:0.0];

Resources