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."
Related
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
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.
I have some code here for an iOS app I am developing, and for some reason what seems like a quick and simple task takes my iPhone 4S a full second or more to do, every time.
The context is this... I have a 2 button ActionSheet popup, and if the user taps either one of the buttons, the app seems to stall for about a second. Here's the code:
-(void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex
{
if (buttonIndex == 0)
{
UIAlertView* newTimerAlertView = [[[UIAlertView alloc] initWithTitle:#"Create New Timer"
message:#"Enter a name for your new indicator"
delegate:self
cancelButtonTitle:#"Cancel"
otherButtonTitles:#"Create", nil] autorelease];
newTimerAlertView.tag = kNewTimer;
newTimerAlertView.alertViewStyle = UIAlertViewStylePlainTextInput;
[newTimerAlertView show];
}
else if (buttonIndex == 1)
{
NSLog(#"ActionSheet button 2 tapped");
UIAlertView* newTallyAlertView = [[[UIAlertView alloc] initWithTitle:#"Create New Tally"
message:#"Enter a name for your new indicator"
delegate:self
cancelButtonTitle:#"Cancel"
otherButtonTitles:#"Create", nil] autorelease];
newTallyAlertView.tag = kNewTally;
newTallyAlertView.alertViewStyle = UIAlertViewStylePlainTextInput;
[newTallyAlertView show];
NSLog(#"end");
}
}
Focusing on the second button for simplicity (although the first button behaves the same way), the log looks like this:
2012-01-25 20:35:46.330 ...[177:707] ActionSheet button 2 tapped
2012-01-25 20:35:47.194 ...[177:707] end
2012-01-25 20:35:56.154 ...[177:707] ActionSheet button 2 tapped
2012-01-25 20:35:56.180 ...[177:707] end
Notice that the first time I try it, over a second passes before the code snippet finishes executing, but the second time (and all subsequent times) the code only takes 30 or so milliseconds.
Is there something wrong with the code? or do I need to simply make up for the delay with a progress view?
Thanks!
EDIT: This only occurs on devices when running the app from Xcode... maybe it's got to do with the debugger?
I now that if the alert is not on the main thread they may be delayed
Xcode doesn't slow down alert views in any situation I have encountered.
Your code looks pretty plain vanilla, so I suspect there's something going on outside the snippet you posted.
I see that you reference timers... is is possible this is a manifestation of something else, like a timer callback, going wild on the main thread? Try using the Time Profiler or the System Trace instruments to figure out what's blocking (something certainly is).
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.
[EDIT] Hmm. Perhaps this question should be titled "what is the default user-input dialog view called in CocoaTouch?" I realize that I can create an entire view that is exactly what I want, and wrap it in a view controller and presentModalView -- but I was sort of hoping that there was a standard, normal user-input "dialog" view that came-with Cocoa-touch. "Enter your name", "enter text to search", etc., are VERY common things!
Anyway... here's the question as I originally asked it:
This code:
UIAlertView* find = [[UIAlertView alloc] init];
[find setDelegate:self];
[find setTitle:#"Find"];
[find addButtonWithTitle:#"Cancel"];
[find addButtonWithTitle:#"Find & Bring"];
[find addButtonWithTitle:#"Find & Go"];
[find addButtonWithTitle:#"Go To Next"];
[find addSubview:_findText];
CGRect frm = find.frame;
int height = frm.size.height + _findText.frame.size.height + 100; // note how even 100 has no effect.
[find setFrame:CGRectMake(frm.origin.x, frm.origin.y, frm.size.width, height)];
[find setNeedsLayout];
[find show];
[find release];
Produces this Alert view:
Find Alert http://www.publicplayground.com/IMGs/Misc/FindAlert.png
(I started with the code from this question by emi1Faber, and it works as advertised; however, as I state in my comment, the cancel button overlays the text field.)
How do I reshuffle everything to make the text field fit properly? [findAlert setNeedsLayout] doesn't seem to do anything, even after I [findAlert setFrame:tallerFrame]. Hints?
Thanks!
The simplest (and most proper way) to move the text view down is to add a message
[find setMessage:#"\n"];
Also, the reason your frame isn't taking effect is that -show sets the frame and creates the view hierarchy before starting the animation. You should also make the text view the first responder so the keyboard pops up.
Full example:
// Create Alert
UIAlertView* av = [UIAlertView new];
av.title = #"Find";
// Add Buttons
[av addButtonWithTitle:#"Cancel"];
[av addButtonWithTitle:#"Find & Bring"];
[av addButtonWithTitle:#"Find & Go"];
[av addButtonWithTitle:#"Go to Next"];
// Make Space for Text View
av.message = #"\n";
// Have Alert View create its view heirarchy, set its frame and begin bounce animation
[av show];
// Adjust the frame
CGRect frame = av.frame;
frame.origin.y -= 100.0f;
av.frame = frame;
// Add Text Field
UITextField* text = [[UITextField alloc] initWithFrame:CGRectMake(20.0, 45.0, 245.0, 25.0)];
text.borderStyle = UITextBorderStyleRoundedRect;
[av addSubview:text];
[text becomeFirstResponder];
Note: You can also modify the subviews of UIAlertView, but since Apple has already changed the UIAlertView layout once you should check their class descriptions and frames against known values before setting new ones. You can even get something like this:
(source: booleanmagic.com)
Even if you can get this working it's not going to be very iPhone-y. The UIAlertView really is not designed for user input like this. If you look in all the Apple apps you'll see that they use a new view that displayed using the presentModalViewController: method of UIViewController.
Edit: This advice is no longer as true as it was when I wrote it. Apple have increasingly used alert views as text entry boxes and iOS5 even includes native support without having to mess around with views (check out the alertViewStyle property).
I think maybe if you need to have four buttons then using a custom UIViewController is probably still the right way to go. But if you just want to enter a password with OK/Cancel buttons then it's fine.
Zoul proposed the best method, to capture user input just do:
a) Add the UITextFieldDelegate protocol to your class.
b) Do something like
UIAlertView *insertScore = [UIAlertView new];
[insertScore setDelegate:self];
[insertScore setTitle:#"New Title!"];
[insertScore addButtonWithTitle:#"Cancel"];
[insertScore addButtonWithTitle:#"Ok"];
insertScore.message = #"\n";
[insertScore addTextFieldWithValue:#"Input" label:#"player"];
[[insertScore textField] setDelegate:self];
[insertScore show];
[insertScore release];
c) The crucial part was to set the delegate of the textField to self, then to access data you can simply:
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
NSLog(#"%#",[[alertView textField] text]);
}
Hope this helps someone, since I had to think a bit to get it right.
Most probably You would want to look into the addTextFieldWithValue method of the UIAlertView? Add the following code somewhere at the top of Your class:
#interface UIAlertView ()
- (void) addTextFieldWithValue: (NSString*) val label: (NSString*) label;
- (UITextField*) textField;
#end
It’s not official, but IMHO it’s not getting You rejected from the App store and it’s much better solution than hacking the textfield into the dialog Yourself.
Explains how to set the number of columns, have not tested it.
http://iloveco.de/uikit-alert-types/
However there is a private method,
setNumberOfRows:(int)n that will allow
you to set a maximum number of rows to
display the buttons in. To use this
method we need to add our own
additions to the UIAlertView class. We
do this by adding an #interface for
UIAlertView in our .m file.
// extend the UIAlertView class to remove the warning caused
// by calling setNumberOfRows.
#interface UIAlertView (extended)
- (void) setNumberOfRows:(int)num;
#end
This will allow us to call the method without the compiler throwing us a warning.
[myAlert setNumberOfRows:2];
Try putting in some (\n)s after the title in the UIAlertView initialization. That will push down the buttons. And I agree with Stephen here. There are chances that Apple might reject an app if it uses controls in a way they shouldn't be. (there's some clause in the Human Interface Guidelines about that!)
This simpler method works for me:
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"UIAlertView"
message:#"<Alert message>" delegate:self cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[alert addTextFieldWithValue:#"" label:#"Text Field"];
Hope that helps. Oh if you needed multiple button rows then it's:
[alert setNumberOfRows:3];
Cheers
https://github.com/TomSwift/TSAlertView
This library actually creates the control from scratch rather than attempting to hack UIAlertView, which is generally a Bad Plan (TM)