Replace UIBarButtonItem with UIActivityIndicatorView not working - objective-c

I want to replace refresh button with activity indicator when the user press the refresh button and after the tableview is refreshed, i want to change it back to refresh button. But when I press the refresh button, it didn't change to activity indicator but the refresh button is highlighted until the data reloading is finished.
the code is as below. Did I miss something?
-(void) reloadNewsStarted{
UIActivityIndicatorView *activityIndicatorRightBarItem = [[UIActivityIndicatorView alloc] initWithFrame:CGRectMake(0, 0, 20, 20)];
[activityIndicatorRightBarItem startAnimating];
UIBarButtonItem *activityItem = [[UIBarButtonItem alloc] initWithCustomView:activityIndicatorRightBarItem];
[activityIndicatorRightBarItem release];
self.navigationItem.rightBarButtonItem = activityItem;
[activityItem release];
[self reloadNewsEnded];
}
-(void) reloadNewsEnded {
//reload data process
UIBarButtonItem *reloadNewsBtn = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemRefresh target:self action:#selector(reloadNewsStarted)];
reloadNewsBtn.style = UIBarButtonItemStyleBordered;
self.navigationItem.rightBarButtonItem = reloadNewsBtn;
[reloadNewsBtn release]; }

Instead of writing
[self reloadNewsEnded];
Please write
[NSThread detachNewThreadSelector:#selector(reloadNewsEnded) toTarget:self withObject:nil];
this line start new thread in background so the activity indicator & the data reload will be done simultaneously in two thread.

why is this line at the end of the reloadNewStarted method
[self reloadNewsEnded];
it appears to just undo the changes to the button? You should be waiting until the action is complete before calling it

Changing the button isn't going to take effect until the run loop regains control from your code. It sounds like your reload operation is synchronous and blocks the event queue, which won't allow that to happen. If your processing takes a noticeable amount of time, you need to use either a background thread or asynchronous handling on the main run loop (for example, by using NSURLConnection to manage a network task and callbacks) so you don't block.

Related

UITab freeze in iOS

I am developing an iOS 5.1 application on Xcode 4.2.
I have a uitablcontroller with different tabs. My problem is when a tab is clicked , the application 'freezes' for few seconds and does all the codes it's meant to do, but it does not load the UIAlertView first as it should be.
I have the UIAlertView declared in the viewDidLoad.
Here is a code snippet:
- (void)viewDidLoad
{
NSLog(#"##### VIEW DID LOAD 1 #####");
// Display Alert: Loading
alertView = [[UIAlertView alloc] initWithTitle:#"Loading"
message:#"\n"
delegate:self
cancelButtonTitle:nil
otherButtonTitles:nil];
UIActivityIndicatorView *spinner = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
spinner.center = CGPointMake(139.5, 75.5); // .5 so it doesn't blur
[alertView addSubview:spinner];
[spinner startAnimating];
[alertView show];
[super viewDidLoad];
NSLog(#"##### VIEW DID LOAD 2 #####");
self.tableView.dataSource = self;
self.tableView.delegate = self;
[self callMainMethod];
}
When the tab is clicked, I can see that the first NSLog's are displayed in the Log, and then the main method is called, but the UIAlertview is not displayed.
When viewDidLoad is running, it may be that the frame of the associated UIView has zero size. I don't know, but the UIAlertView may be trying to present itself in this zero-size frame. Does it make sense if you present the UIAlertView in viewDidAppear?
If [self callMainMethod] is taking lots of compute power, then the display might not be updated until it finishes. You could try moving it to viewDidAppear. You could also try delaying it, so that the main run loop for the UI thread, the thread that the display is updated on and the thread that executesviewDidLoad and all the other view... methods, has time to complete everything and become idle before you start the heavy processing. It's only when the run loop has done all the processing it can that it starts actually to update the display. Like this:
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 200000000), dispatch_get_main_queue(), ^{
[self callMainMethod];
}
The documentation for that is at http://developer.apple.com/library/ios/#documentation/Performance/Reference/GCD_libdispatch_Ref/Reference/reference.html
If that suspicion is correct, you should then get the UIAlertView popping straight up, only to have everything go dead for a while, while callMainMethod executes.
I wish I knew how to write, "dispatch this block only after you've managed to finish updating the display with everything up to here", but I don't know how to do that. So the dispatch call above should delay the call to callMainMethod by 200ms, which is usually plenty.
If that works, you should probably start another question, something like "How can I stop the display freezing while I execute this method."

ABPersonViewController in Modal

I would like to display ABPersonViewController as a modal instead of pushing it on the navigation stack. I've got this working but to keep a done button present I've had to use an NSTimer to add the button every 0.25 seconds because the done button may be removed when the view appears and is always removed when the app enters the forground. This is a pretty lame hack so I'm wondering if anyone has a better idea :)
I made a subclass of ABPersonViewController that adds the done button and starts the timer on view did load and invalidates it when the view is deallocated.
Here is what my code looks like to show the modal:
- (IBAction)showContactModal:(id)sender{
CNABPersonViewController *personViewController = [[CNABPersonViewController alloc] init];
personViewController.displayedPerson = self.contact.record;
personViewController.addressBook = [[CNAddressBookManager sharedManager] addressBook];
personViewController.viewDelegate = self;
personViewController.shouldShowLinkedPeople = YES;
UINavigationController *navigationController =
[[UINavigationController alloc] initWithRootViewController:personViewController];
navigationController.modalPresentationStyle = UIModalPresentationFormSheet;
[self presentViewController:navigationController animated:YES completion:nil];
}
I had success in doing it like this. Insert this line to add a button to the navigation bar:
personViewController.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc]initWithTitle:#"Test" style:UIBarButtonItemStylePlain target:self action:#selector(_yourAddressBookAction)];
If this does not solve your problem, please show us the code that you had the issue with.

Show UIActivityIndicator during segue navigation, after UIBarButtonItem press

I have a UIBarButtonItem in the navigation bar which switches to another screen, using a segue. This other screen takes some time to initialize, and I wanted to put a UIActivityIndicator on the UIBarButtonItem to show the tap has been registered, and the iPad is busy executing the action.
My approach was to add a UIActivityIndicator to the UIBarButtonItem after it was pressed, then call performSegueWithIdentifier:, and in the viewDidLoad method of the second view, put the initialization into a dispatch_sync() call. You can guess it does not work... why?
The IBAction on the first screen:
- (void)tappedEdit: (UIBarButtonItem *)editButton {
// put activity indicator somewhere
UIActivityIndicatorView *indicator;
indicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
indicator.frame = CGRectMake (200, 5, 50, 50);
[self addSubview: indicator];
[indicator startAnimating];
// follow segue
[self performSegueWithIdentifier: SegueShowDesignMode sender: editButton];
}
The initialization on the second screen:
- (void)viewDidLoad {
[super viewDidLoad];
// put costly operations into another queue to free main queue for activity indicator
dispatch_sync (dispatch_get_global_queue (0, 0),
^{ // do initialization here
});
}
The effect of this is that the UIBarButtonItem stays tapped while the initialization is performed, then the UIActivityIndicator is visible for a quick moment, and lastly the segue animation is shown, displaying the second screen.
Any idea? Thanks a lot!
EDIT: Probably an addition to the problem is that the initialization does some UIKit stuff: When I tried to use a semaphore, the initialization dumps with a BAD ACCESS in some UITextView method. I guess this is because it runs on some 'get_global_queue' and not on the 'get_main_queue'.
EDIT AGAIN: Well, no. Using 'get_main_queue' results in a dead-lock, as announced in the Apple docs. So the question boils down to
"How can I do background UIView creation (lots of!) while still having a spinner running?"
I think one problem is that you are using dispatch_sync in your viewDidLoad method in your second screen. dispatch_sync will block your thread until the block with // do initialization here has completed. Try changing it to dispatch_async instead.
The other thing I'd consider doing if I were you is getting the UIActivityIndicator to appear as part of the second screen, not the first. That way, the second screen can also dismiss it after your view has finished the costly operations. Of course, this assumes that the view can actually display before those operations have completed.
if you are using a storyboard
ButtonBarItem.h
#import "CheckWifi.h"
interface LoadingViewController : UIViewController
-(IBAction)BarItemPressed:(id)sender;
ButtonBarItem.m:
-(IBAction)BarItemPressed:(id)sender {LoadingViewController *DIS = [self.storyboard instantiateViewControllerWithIdentifier:#"CheckWifi"];
DIS.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
[self presentModalViewController:DIS animated:YES];}
LoadingViewController.h:
#interface LoadingViewController : UIViewController {
IBOutlet UIActivityIndicatorView *spinner
}
-(IBAction)StopAnimating;
#end
LoadingViewController.m:
-(void)ViewDidLoad {
spinner.hidden = NO;
}
-(IBAction)StopAnimating {
spinner.hidden = YES;
}
If you're using an XIB File, then I cannot help you really, Sorry.

performSelectorInBackground causes random crash when view is dismissing

I'm having some random crashes at this part of my code:
-(void) goBack {
[self performSelectorInBackground:#selector(addActivityIndicator) withObject:nil];
[self.navigationController popViewControllerAnimated:YES];
}
- (void)addActivityIndicator {
#autoreleasepool {
UIActivityIndicatorView *activityView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite];
UIBarButtonItem * barButton = [[UIBarButtonItem alloc] initWithCustomView:activityView];
[activityView startAnimating];
self.navigationItem.leftBarButtonItem = barButton;
}
}
When I want to exit the screen where these method exists, the other ViewController have to process some data. To inform the user that processing is occurring I add an activity indicator to the left button in my navigation bar.
The problem is that sometimes I get an exc_bad_access in addActivityIndicator method. The frequency is very random, sometimes the XCode shows the error at the end of #autoreleasepool, sometimes at the line self.navigationItem.leftBarButtonItem = barButton;
I imagine that sometimes my viewController is destroyed but the thread is still running and try to access the navigationItem of a object that don't exists anymore. But I'm not sure if that is the problem and I don't know how to fix it.
I'm using ARC in my project and this problem occurs in all iOS versions that I tested.
Please, anyone can explain me what is happening and how can I fix this?
Thanks.
You should never do UIKit stuff in the background.
By calling [self performSelectorInBackground:#selector(addActivityIndicator) withObject:nil]; you are updating the UI on a background thread. You should only ever update the UI on the main thread.
Edit
Based on your comment you are trying to have the UI update before the view pops. The way to do that would be:
[self addActivityIndicator]
[navigationController performSelector:#selector(popViewControllerAnimated:) withObject:[NSNumber numberWithBool:YES] afterDelay:0];
You could also look into dispatch_after

Adding an activity indicator in another method to avoid hanging

I have implemented Reachability.h from Apple into my demo app. The problem is that I noticed that my app stalls while checking connection.
So, I added an Activity Indicator (From MBProgressHUD) . But the indicator does not animate. It stalls with the application too.
So, I thought of putting the activity indicator inside another thread different than the main thread but still it is not animating.
Note: I'm not very experienced
UPDATE: Also, I have tried the native Activity Indicator with no luck.
- (void)anotherThread
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
HUD = [[MBProgressHUD alloc] initWithView:self.navigationController.view];
[self.navigationController.view addSubview:HUD];
// Set determinate mode
HUD.mode = MBProgressHUDModeDeterminate;
HUD.delegate = self;
HUD.labelText = #"loading";
// myProgressTask uses the HUD instance to update progress
[HUD showWhileExecuting:#selector(crazyCounter) onTarget:self withObject:nil animated:YES];
[pool release];
}
UI code should be kept in main thread. So instead of putting the activity indicator into another thread, you may want to use GCD (grand central dispatch) to throw your checking connection code in another thread. When it finishes, you can then remove or hide your activity indicator.
PS. I'm not quite sure what MBProgressHUD does, but you do want to make sure you have something like [activityIndicator startAnimating]. At least for normal activity indicators, you need to manually turn it on.