button.enable not refreshing IOS - 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.

Related

When I first load a Detail view from Master View it takes twice as long as each time after that

I have a UITableViewController with 60 cells. The detail view for the cells have 5 imageViews and 5 labels. When I press on a cell for the first time, it takes 2-3 seconds to load the detail view. When I go back to the MasterView and press on a cell, the same one or a different one, it is instantaneous. What could cause this massive lag and how could I fix it?
#implementation DetailViewController{
NSArray *nameArray;
NSArray *flag;
NSArray *pop;
NSArray *yearOfUnion;
NSArray *area;
NSArray *city;
NSArray *abbreviations;
NSArray *resNamea;
NSArray *mainViewa;
NSArray *plateViewa;
}
#synthesize passDataTest;
#synthesize scrollView;
#synthesize stateint;
#synthesize cgvalue;
- (void)viewDidLoad
{
[super viewDidLoad];
NSLog(#"%f", cgvalue);
[scrollView setScrollEnabled:YES];
if ([[UIScreen mainScreen] bounds].size.height == 480) {
[scrollView setContentSize:CGSizeMake(320, 920)];
} else {
[scrollView setContentSize:CGSizeMake(320, 834)];
}
[scrollView addSubview:_contentView];
[_testLabelTaco setText:passDataTest];
NSString *path = [[NSBundle mainBundle] pathForResource:#"statesdata" ofType:#"plist"];
NSDictionary *dict = [[NSDictionary alloc] initWithContentsOfFile:path];
flag = [dict objectForKey:#"StateFlag"];
pop = [dict objectForKey:#"Population"];
yearOfUnion = [dict objectForKey:#"Year"];
area = [dict objectForKey:#"Area"];
city = [dict objectForKey:#"LargeCity"];
abbreviations = [dict objectForKey:#"Abbreviations"];
resNamea = [dict objectForKey:#"ResNames"];
mainViewa = [dict objectForKey:#"MainViewPictures"];
plateViewa = [dict objectForKey:#"LicensePlates"];
_flagView.image = [UIImage imageNamed:[flag objectAtIndex:stateint]];
_populationLabel.text = [pop objectAtIndex:stateint];
_unionYearLabel.text = [yearOfUnion objectAtIndex:stateint];
_areaLabel.text = [area objectAtIndex:stateint];
_cityLabel.text = [city objectAtIndex:stateint];
_abbrLabel.text = [abbreviations objectAtIndex:stateint];
_resNameLabel.text = [resNamea objectAtIndex:stateint];
_mainView.image = [UIImage imageNamed:[mainViewa objectAtIndex:stateint]];
_plateView.image = [UIImage imageNamed:[plateViewa objectAtIndex:stateint]];
// Do any additional setup after loading the view.
}
I am putting in a lot of assumption because what is relevant to your issue here is how the masterView Controller calls the detail view controller and that is not in the code you displayed.
Based on what you showed I am assuming that you are creating a new instance of your detailViewController every time you change the selection - if you are - this is what is causing your overhead.
What you need to do to make your app responsive is to separate the individual setup requirement from - (void) viewDidLoad into another method - And in your MasterViewController call that method and bypass reinitialising the DetailVC instance again. Note this will involve meticulous cleanup and setup but your code avoid a lot of overhead that you would otherwise incur if you keep on recreating [alloc] init] the detail view controller instance all the time - (note - this will also keep the memory requirement down)
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(#"%f", cgvalue);
[scrollView setScrollEnabled:YES];
if ([[UIScreen mainScreen] bounds].size.height == 480) {
[scrollView setContentSize:CGSizeMake(320, 920)];
} else {
[scrollView setContentSize:CGSizeMake(320, 834)];
}
[scrollView addSubview:_contentView];
[self setupDetailVC:self];
}
-(void) setupDetailVC:(ID) sender {
[_testLabelTaco setText:passDataTest];
// Do any additional cleanup and setup after loading the view. }
In your masterViewController: Do this
#synthesize *detailVC; - should have been created in appDelegate code;
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
[self configureDetailItemForRow:indexPath.row];
}
- (void)configureDetailItemForRow:(NSUInteger)row {
detailVC.property1 = Currentproperty;
detailVC.Utility_sw = NO;
// detailVC.managedObjectContext = managedObjectContext;
[detailVC setupDetailVC:self];
}
One thing you could try is to set the images on a separate thread, using Grand Central Dispatch. This would take the workload off the main thread and your UI shouldn't slow down. You could do something like this:
__weak typeof(self)weakSelf = self; // to avoid a retain cycle
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void){
//Background Thread
__strong typeof(weakSelf)strongSelf = weakSelf;
UIImage * flagImage = [UIImage imageNamed:[strongSelf->flag objectAtIndex:stateint]];
UIImage * mainImage = [UIImage imageNamed:[strongSelf->mainViewa objectAtIndex:stateint]];
UIImage * plateImage = [UIImage imageNamed:[strongSelf->plateViewa objectAtIndex:stateint]];
dispatch_async(dispatch_get_main_queue(), ^(void){
//Run UI Updates on main thread
strongSelf->_flagView.image = flagImage;
strongSelf->_mainView.image = mainImage;
strongSelf->_plateView.image = plateImage;
});
});

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];

dispatch_queue not executing

i was undergoing stanford cs 193 p assignment5.The problem is that when i segue a data to destination view controller and use dispatch queue to fetch the data in viewDidLoad method.The dispatch_async does'nt execute in the destination view controller .Here is my code for View controller A and destination view controller.
view controller A code
-(void)viewDidLoad
{
[super viewDidLoad];
if(!self.places){
self.spinner.hidesWhenStopped = YES;
self.spinner.center = self.tableView.center;//
[self.view addSubview:self.spinner];
[self.spinner startAnimating];
dispatch_queue_t dispatchQueue = dispatch_queue_create("queue_top_places", NULL);
dispatch_async(dispatchQueue, ^{
self.places = [self getRecentPlacesFromFlicker];
// main queue to load table view data
dispatch_async(dispatch_get_main_queue(), ^{
// load table data
if(self.tableView.window){
[self.tableView reloadData];
[self.spinner stopAnimating];
}
});
});
dispatch_release(dispatchQueue);
}
// Uncomment the following line to preserve selection between presentations.
self.clearsSelectionOnViewWillAppear = YES;
}
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
if([segue.identifier isEqualToString:#"Show Recent Photo List"]){
int currentRow = self.tableView.indexPathForSelectedRow.row;
// set up photo list controller model
[segue.destinationViewController setPhotosList:[self.places objectAtIndex:currentRow]];
}
}
Here is my code for destination view controller
- (void)viewDidLoad
{
[super viewDidLoad];
// get the current top place name and fetch photos at that place from flicker
if ([self.photosList isKindOfClass:[NSDictionary class]])
{
UIActivityIndicatorView *spinner = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
spinner.hidesWhenStopped = YES;
spinner.center = self.tableView.center;//
[self.view addSubview:spinner];
[spinner startAnimating];
dispatch_queue_t dispatchQueue1 = dispatch_queue_create("queue_top_50_photos", NULL);
dispatch_async(dispatchQueue1, ^{
self.photosList = [FlickrFetcher photosInPlace:self.photosList maxResults:50];
dispatch_async(dispatch_get_main_queue(), ^{
if (self.tableView.window ){
[self.tableView reloadData];
[spinner stopAnimating];
}
});
});
dispatch_release(dispatchQueue1);
}
// Uncomment the following line to preserve selection between presentations.
self.clearsSelectionOnViewWillAppear = YES;
self.title = #"50PhotoList";
}
The condition ([self.photosList isKindOfClass:[NSDictionary class]]) is satisfied checked with a debugger
I was just thinking about this, have you tried running this code in viewDidAppear instead of viewDidLoad? I'd give that a try.
Any reason why you create a new queue for each operation? What if you remove the dispatch_release instruction at the end? Maybe your async operation takes longer than the time it gets for your queue to get released.
Try to use a global queue instead of one that you create yourself, one you don't have to release when you're done.
Replace
dispatch_queue_t dispatchQueue1 = dispatch_queue_create("queue_top_50_photos", NULL);
with
dispatch_queue_t dispatchQueue1 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
and remove the dispatch_release(dispatchQueue1);
dispatch_async returns immediately and performs the task in background and executes rest of the code in the viewDidLoad or other viewDidAppear methods.
One solution would be to use dispatch_sync which waits for the task to execute and to nest it in dispatch_async like this
dispatch_queue_t dispatchQueue1 = dispatch_queue_create("queue_top_50_photos", NULL);
dispatch_async(dispatchQueue1, ^{
dispatch_queue_t dispatchSyncQueue = dispatch_queue_create("queue_top_50_photos", NULL);
dispatch_sync( dispatchSyncQueue,^){
self.photosList = [FlickrFetcher photosInPlace:self.photosList maxResults:50];
});
dispatch_sync(dispatch_get_main_queue(), ^{
if (self.tableView.window ){
[self.tableView reloadData];
[spinner stopAnimating];
}
});
dispatch_release(dispatchSyncQueue);
});
dispatch_release(dispatchQueue1);

Fastest way to dismiss a UIPopoverController?

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];
});
});
}

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

Resources