I have the UIBarButtonItem (configurated in interface builder). If a user click this button, the "heavy process" will be started and for better user experience I want to change this button with (UIActivityIndicatorView). I do it in the following way:
self.indicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite];
indicator.hidesWhenStopped = YES;
[self.heavyBarButton initWithCustomView:self.indicator];
[self.indicator startAnimating];
[NSThread detachNewThreadSelector:#selector(animateHeavyProcess) toTarget:self withObject:nil];
animateHeavyProcess:
[self heavyProcess];
[self.indicator stopAnimating];
UIBarButtonItem *originalButton = [[UIBarButtonItem alloc] initWithImage:[UIImage imageNamed:#"maximize.png"] style:UIBarButtonItemStylePlain target:self action:#selector(startProcessClick:)];
self.heavyBarButton = originalButton;
[originalButton release];
What happens: after a user clicks the BarButton animation will be started and after processing button disappears. However, I want that the original button will be shown again.
what if you use Grand Central Dispatch for the heavy process? I would think it's more convenient. But remember that you can not use any of the UI in that block. Here is an example: iphone ios running in separate thread
You should not be doing UI updates in a secondary thread; calls to UIKit should be on the main thread.
You can split out the UI updating part of your code:
- (void)restoreBarButtonItem
{
[self.indicator stopAnimating];
UIBarButtonItem *originalButton = [[UIBarButtonItem alloc]
initWithImage:[UIImage imageNamed:#"maximize.png"]
style:UIBarButtonItemStylePlain
target:self
action:#selector(startProcessClick:)];
self.heavyBarButton = originalButton;
[originalButton release];
}
And then in your heavy process (running on the secondary thread) just call this new UI update method on the main thread:
- (void)animateHeavyProcess
{
[self heavyProcess];
[self performSelectorOnMainThread:#selector(restoreBarButtonItem:)
withObject:nil
waitUntilDone:NO];
}
You should use UIToolbar's setItems:animated: method to replace buttons.
Related
I have a buttono in the navigation item which action is BUTTON_ACTION. By pressing it, MBProgressHUD is activate and the action work. but the "dimBackground" that make "hidden" the scrren, doe not work on the navigationbar, and the button can be pressed again during the MBProgressHUD.
The code is:
HUD = [[MBProgressHUD alloc] initWithView:self.view];
[self.view addSubview:HUD];
// Regiser for HUD callbacks so we can remove it from the window at the right time
HUD.delegate = self;
HUD.labelText=#"Buscando Bares...";
HUD.dimBackground = YES;
// Show the HUD while the provided method executes in a new thread
[HUD showWhileExecuting:#selector(BUTTON_ACTION) onTarget:self withObject:nil animated:YES];
I tryed to use:
HUD = [[MBProgressHUD alloc] initWithView:self.navigationController.view];
[self.navigationController.view addSubview:HUD];
Any idea about that? thanks in advance.
To make the MBProgressHUD being displayed above all UI controls including the UINavigationBar you must do this:
HUD = [[MBProgressHUD alloc] initWithWindow:self.view.window];
[self.view.window addSubview:HUD];
#ararog is right, but it's also simple to just do
_progressHUD = [MBProgressHUD showHUDAddedTo:self.view.window animated:YES];
Hi Can anyone help me out with this, ive been looking at alot of the previous questions on this subject and they seem a little complicated. I want to setup a picker that animates in on the touch of a text field which has an IBOutlet called locationField.
I dont want to add the picker or toolbar and buttons in my story boards I would rather do it programatically.
So far I have:
- (void)viewDidLoad
{
//populate picker arrays
locationPickerArray = [[NSArray alloc] initWithObjects:#"1",#"2",#"3",#"4",#"5", nil];
actionSheet = [[UIActionSheet alloc] initWithTitle:#"" delegate:self cancelButtonTitle:nil destructiveButtonTitle:nil otherButtonTitles:nil];
[actionSheet showInView:self.view];
UIToolbar *pickerToolbar = [[UIToolbar alloc] initWithFrame:CGRectMake(0, 0,480,32)];
[pickerToolbar sizeToFit];
pickerToolbar.barStyle = UIBarStyleBlackTranslucent;
NSMutableArray *barItems = [[NSMutableArray alloc] init];
UIBarButtonItem *cancelBtn = [[UIBarButtonItem alloc] initWithTitle:#"Cancel" style:UIBarButtonSystemItemCancel target:self action:#selector(cancel_clicked:)];
[barItems addObject:cancelBtn];
UIBarButtonItem *flexSpace = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:nil];
[barItems addObject:flexSpace];
UIBarButtonItem *doneBtn = [[UIBarButtonItem alloc] initWithTitle:#"Done" style:UIBarButtonItemStyleDone target:self action:#selector(done_clicked:)];
[barItems addObject:doneBtn];
[pickerToolbar setItems:barItems animated:YES];
[actionSheet addSubview:pickerToolbar];
UIPickerView *picker = [[UIPickerView alloc] init];
picker.frame = CGRectMake(0, 44, 320, 216);
picker.delegate = self;
picker.dataSource = self;
picker.showsSelectionIndicator = YES;
[actionSheet addSubview:picker];
}
-(void)done_clicked:(id)sender
{
[actionSheet dismissWithClickedButtonIndex:0 animated:YES];
}
-(void)cancel_clicked:(id)sender
{
[actionSheet dismissWithClickedButtonIndex:0 animated:YES];
}
I have a few questions about this, it crashes on:
* Terminating app due to uncaught exception
'NSInvalidArgumentException', reason: '-[DetailsScreenViewController
numberOfComponentsInPickerView:]: unrecognized selector sent to
instance 0xb505be0'
What does this mean?
My final question is how can I populate this picker and assign it to popup when the iboutlet I created earlier is clicked (location field).
Thank you
The reason for your crash is that you've assigned an instance of DetailsScreenViewController to be the dataSource and delegate of a UIPickerView without actually implementing the methods for these protocols. The assignment takes place here:
picker.delegate = self;
picker.dataSource = self;
The specific crash is telling you that you haven't implemented numberOfComponentsInPickerView:, part of the UIPickerViewDataSource protocol. You should have gotten a compiler warning about this actually. Learning to use those warnings can be really helpful! Anyways, to fix it, provide implementations for all of the required methods on both protocols. The details of the implementations will depend on the needs for your app. If you still need help with details let me know.
Regarding your second question, I guess putting a UIPickerView inside a UIActionSheet is a thing now. I've never had a need to do it before, but here is a SO question with a lot of sample code for some background, not sure if you've read that one yet. But I think you'd just want to wait for the time you actually want to show the picker to call
[actionSheet showInView:self.view];
I think you can do this in a UITextFieldDelegate method, there are probably other ways. I'm going to try to do some experiments with that later and update this post.
EDIT:
If you assign your view controller as the delegate of whatever text field you're interested in, then you can implement this UITextFieldDelegate method:
- (BOOL)textFieldShouldBeginEditing:(UITextField *)textField
{
[actionSheet showInView:self.view];
return NO;
}
END EDIT
Hope this helps, and let me know if you have any questions.
I'm using the following code to push a view controller when user click on a UIButton
- (IBAction)showListPicker:(id)sender {
if([audioPlayer isPlaying])
{
[audioPlayer stop];
}
ListPicker *lp = [[ListPicker alloc] initWithStyle:UITableViewStyleGrouped];
[[self navigationController] pushViewController:lp animated:YES];
[lp release];
}
In the ViewDidLoad of ListPicker I use the following code to add right navigational control button
-(void)viewDidLoad{
[[self navigationController] setNavigationBarHidden:NO];
[[[self navigationController] navigationBar] setTintColor:[UIColor brownColor]];
[[self view] setBackgroundColor:[UIColor colorWithPatternImage:[UIImage imageNamed:#"background.png"]]];
UIBarButtonItem *button = [[UIBarButtonItem alloc]
initWithBarButtonSystemItem:UIBarButtonSystemItemAdd
target:self
action:#selector(doSomething:)];
[[self navigationItem] setRightBarButtonItem:button];
[button release];
}
When the view is loaded, I can see the right button briefly but then it disappear right away. What am I doing wrong?
Thank you.
My guess is that your ListPicker's XIB contains another navigation bar, obstructing the actual navigation controller's bar. This is why you see it for an instant and then it "disappears". If it's not something in the XIB, check the code all the way from instantiating that new view controller until the viewDidAppear of ListPicker.
I already read this question QLPreviewController remove or add UIBarButtonItems but it's not what I'm looking for. I would like to keep the "Print" button in the navigation Bar but also add a new "Delete Document" button in the navigation bar.
I tried this:
QLPreviewController *previewer = [[[QLPreviewController alloc] init] autorelease];
[previewer setDataSource:self];
UIBarButtonItem *saveButton = [[UIBarButtonItem alloc]initWithTitle:#"Salva Documento" style:UIBarButtonItemStyleBordered target:self action:#selector(saveFileToDocuments)];
NSArray *buttons = [NSArray arrayWithObjects:[[previewer navigationItem]rightBarButtonItem],saveButton, nil];
[[previewer navigationItem]setRightBarButtonItems:buttons];
But it didn't work.
Because you said "4.x will be fine", there's your problem.
The documentation for UINavigationItem [setRightBarButtonItems: animated:] (documentatin linked there for you) say that this function only works on iOS 5.0 and newer.
It will not work on iOS 4.0.
Also, you should add an animated: parameter to that setRightBarButtonItems: call.
UIBarButtonItem *rbb;
-(void)addRightButton{
if (!rbb) {
UIButton *orderButton = [UIButton buttonWithType:UIButtonTypeCustom];
orderButton.frame = CGRectZero;
rbb = [[UIBarButtonItem alloc] initWithCustomView:orderButton];
}
self.navigationItem.rightBarButtonItem = rbb;
}
- (void)viewDidLoad{
[super viewDidLoad];
[self performSelector:#selector(addRightButton) withObject:nil afterDelay:0.2];
}
-(void)viewDidAppear:(BOOL)animated{
[super viewDidAppear:animated];
[self addRightButton];
}
in my app, I have a table in one of the viewControllers. When one of the rows are tapped, it takes the user to a different view/viewController and it works. Within this new viewController, data is being parsed from a php script in the background.
This takes about 7-10 seconds and in this time I want the user to IMMEDIATELY see a spinner that says "Loading..". I have implemented this myself but the spinner does not start to load until 4-5 seconds in. During this time, the screen is completely frozen and I cannot tap anything or go back until the spinner/data is displayed.
I have tried to put the following code within the method that does the actually fetching of the data, within both (not at the same time) the viewDidAppear and ViewDidLoad methods as well but the same thing happens.
If anyone knows how to fix this, it would be much appreciated.
Thank you
[web loadRequest: [NSURLRequest requestWithURL: [NSURL URLWithString:#"http://xxx.xx.xx.xxx/stuff/xxx.php"]]];
[web addSubview:spinner];
timer = [NSTimer scheduledTimerWithTimeInterval:5.0 target:self selector: #selector(tick) userInfo:nil repeats:YES];
load_message = [[UIAlertView alloc] initWithTitle:#"Loading..." message:nil delegate:self cancelButtonTitle:nil otherButtonTitles:nil];
spinner= [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
spinner.center = CGPointMake(135.0, 60.0);
[load_message addSubview:spinner];
-(void) tick
{
if(!web.loading)
{
[spinner stopAnimating];
[load_message dismissWithClickedButtonIndex:0 animated:TRUE];
}
else {
[load_message show];
[spinner startAnimating];
}
}
You could start your spinner then start your long process after a delay:
- (void)someMethod
{
[spinner startAnimating];
[self performSelector#selector(doLongProcess:) withObject:someObject afterDelay:0.0];
}
- (void)doLongProcess:(id)someObject
{
//Some really long process
}
Your spinner was most likely blocked because a long process may be occurring on the same thread.
You have to do one of the following (assumes you put your web request in a method called 'startWebRequest'):
Start the UIActivityIndicatorView on the the main UI thread (if not already running from the main thread):
[self performSelectorOnMainThread:#selector(startWebRequest) withObject:nil waitUntilDone:NO];
Start the web request on a background thread:
[self performSelectorInBackground:#selector(startWebRequest) withObject:nil];
Pause like a 1/10th of second before starting the web request like with:
[NSTimer scheduledTimerWithTimeInterval:0.1f target:self selector:#selector(startWebRequest) userInfo:nil repeats:NO];