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
Related
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."
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.
I am trying to show an MFMessageComposeViewController with the following code:
controller = [[MFMessageComposeViewController alloc] init];
if([MFMessageComposeViewController canSendText])
{
controller.body = [NSString stringWithFormat:#"%#%#%#", itemString, amountString, callTimeString];
controller.recipients = [NSArray arrayWithObject:#"12345678"];
controller.messageComposeDelegate = self;
[self presentModalViewController:controller animated:YES];
}
The problem is when I press the button to bring up the MFMessageComposeViewController, the alloc takes a few seconds. I have tried moving the alloc line to my viewDidLoad method, however this just moves the problem and I end up waiting a few seconds for the view to load.
Is there any method to speed up the alloc and showing of my MFMessageComposeViewController or use a delegate method or something?
Thanks.
Well, what you are doing seems about right. You are not doing anything wrong. Check this - Can't set recipients of MFMessageComposeViewController? & the link attached in the answer
I had a similar problem, check out my answer here: I need a callback when MFMessegeComposeViewController finally loads
For me it wasn't the alloc taking too long, it was the presentModalViewController of the MFMessageComposeViewController, which blocks on Apple's code on the UI thread, so no way to background it except for throwing up your own progress view.
I dont want to add a sub view, but instead change the "self.view" to another view eg (A warning view) then after the user suppresses the warning I would like to switch back. When ever i try to switch back to the original view i just get a blank screen for reasons i cant understand.
Here is what i currently have in one of my UITableViewControllers
//Show warning view controller
self.warningViewControler = [[[WarningViewController alloc] init] autorelease];
self.view = self.warningViewController.view;
//Then later
self.view = self.tableView; //<< Dosnt work
If you want to change your view, and if the original view is defined/linked into XCode, you must retain it before changing self.view to another view. If not, the original view is released and using it back can cause bad things to happen.
Warning :
self.warningViewControler = [[[WarningViewController alloc] init] autorelease];
self.view = self.warningViewController.view
is a bad bad call. Because you autorelease the controller but you use its view. So you get a view retained with a released controller after some time. Retain the controller and release it yourself when its view is not needed anymore.
Here's the better way to do what I think you're trying to do:
WarningViewController *warningViewController = [[WarningViewController alloc] initWithNibName:#"theRightNiborNil" bundle:nil];
[self presentModalViewController:warningViewController animated:YES];
// or if you don't need to support iOS4 any more:
[self presentViewController:warningViewController animated:YES completion:nil]
// and if you aren't using ARC yet, then [warningViewController release];
Then in your WarningViewController you want some action that calls:
[self dismissModalViewControllerAnimated:YES];
// or again if this is iOS5..
[self dismissModalViewControllerAnimated:YES completion:nil];
Hope that helps.
I have the following code which works from my 'scorer' class apart from in the following scenario. I push another viewcontroller editscore, in that view do something, return to scorer pop the editscore and then in some circumstances i will end up pushing my legorderviewcontroller as shown below (it calls the same code). Only this time the new navigation bar is written over the top of the 'scorer' navigation bar and legorderviewcontroller doesn't appear. Does anyone know why this happens in this scenario?
legOrderViewController *controller = [[legOrderViewController alloc] initWithStyle:UITableViewStyleGrouped];
controller.leg = self.leg;
controller.delegate = self;
controller.match =self.match;
controller.set = self.set;
controller.managedObjectContext = self.managedObjectContext;
[self.navigationController pushViewController:controller animated:NO];
controller.playerChangeArray = playerOrder;
[controller release];
This might happen if you call pushViewController too fast after performing popviewcontroller WITH an animation. I ran into that issue once, disabling the animation solved it.