I wan't to show Progress indicator (MBprogress hud), here's the code i implemented.
[NSThread detachNewThreadSelector: #selector(showMe) toTarget:self withObject:NULL];
in show method , i have (tried to) displayed MBprogress Hud but it is not showing label text.
-(void)showMe
{
if(hudForBal) // hudForBal is my MBprogressHud's object
{
[hudForBal removeFromSuperview];
[hudForBal release];
hudForBal = nil;
}
hudForBal =[[MBProgressHUD alloc]init];
hudForBal.labelText =#"Please wait...";
hudForBal.delegate = Nil;
[self.view addSubview:hudForBal];
[hudForBal show:YES];
}
it is working but it is not showing label text .What am i doing wrong?
Thanks in advance!
There's no need to create a new thread to do this, in fact, making changes to the UI on any thread other than the main thread is undefined behavior. If you're already on the main thread when you would be calling this method, then all you have to do is perform the selector as you normally would, without sending it to a different thread.
However, if you're already on a background thread when you would be performing this selector, and you want to update the UI, you can use dispatch_async() as a quick and easy way to move back to the main thread.
- (void)showMe {
dispatch_async(dispatch_get_main_queue(), ^{
if(hudForBal) {
[hudForBal removeFromSuperview];
[hudForBal release];
hudForBal = nil;
}
hudForBal =[[MBProgressHUD alloc]init];
hudForBal.labelText =#"Please wait...";
hudForBal.delegate = Nil;
[self.view addSubview:hudForBal];
[hudForBal show:YES];
});
}
Related
I have an iPad app that makes appointments. If the user happens to overlap an existing appointment, I need to let them know via a UIAlertView. The problem is of course, that the UIAlertView doesn't get displayed until the 'Save' method completes it's processing.
I was thinking of using a separate thread (call it 'B') for displaying the alert, and passing the button that was tapped back to the main thread (call it 'A'). My way of doing this was to have the main thread ('A') call another method, which would create the thread ('B'), show the alert on the new thread ('B') and return to the main thread ('A') after the user has tapped a button on the alert, returning some value indicating which button was tapped.
I was hoping that since I placed the thread creation in a separate method, the calling method would wait for it to return before continuing it's processing in the main thread ('A').
Is this feasible?
UPDATE
I just tried this, and it didn't work (alert was displayed way after the processing has completed and the main thread continued processing -- not what I wanted!):
if(overlapFlag == [NSNumber numberWithInt:1]) { // there IS an overlap
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ //Here your non-main thread.
NSLog (#"Hi, I'm new thread");
UIAlertView *testView = [UIAlertView alertViewWithTitle:#"Warning!" message:#"This appointment overlaps an existing appointment. Tap Continue to save it or Cancel to create a new appointment."];
[testView addButtonWithTitle:#"Continue" handler:^{ NSLog(#"Yay!"); }];
[testView addButtonWithTitle:#"Cancel" handler:^{ [self reloadAppointmentList]; }];
[testView show];
dispatch_async(dispatch_get_main_queue(), ^{ //Here you returns to main thread.
NSLog (#"Hi, I'm main thread");
});
});
}
This is possible.
But working with UI in the background thread isn't good practice. Why don't you show UIAlertView before starting the 'Save' method, and call the 'Save' method in another thread? For example, this would be a better solution:
- (void) someSaveMethod{
[self showAlertView];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE _PRIORITY_DEFAULT, 0), ^(){
// call you save method
// after end save method call:
dispatch_async(dispatch_get_main_queue(), ^{
[self hideAllertView];
});
});
}
No need to use separate threads. Break your save process up into everything you can process before finding out about overlap and the rest of the save process.
- (void)saveBegin
{
// start save process
// ...
// now check for overlap
if(overlapFlag == #1)
{ // there IS an overlap
NSString *message = #"alert message goes here";
UIAlertView *testView = [UIAlertView alertViewWithTitle:#"Warning!"
message:message];
[testView addButtonWithTitle:#"Continue"
handler:^{ [self saveComplete]; }];
[testView addButtonWithTitle:#"Cancel"
handler:^{ [self reloadAppointmentList]; }];
[testView show];
}
else
{
[self saveComplete];
}
}
- (void)saveComplete
{
// complete save process
// ..
}
Im using the MBProgressHUD to make an overview loading screen while logging out in my ipad app. That progress takes some time because I have to encrypt some bigger files.
Because Im doing it in a background thread and the MBProgressHUD is animating on the main thread, I had to do something to know when my background thread is finished.
As a test, I did it like that:
MBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:self.view animated:YES];
hud.mode = MBProgressHUDAnimationFade;
hud.labelText = #"Do something...";
[self performSelectorInBackground:#selector(doSomethingElse) withObject:nil];
And method doSomethingElse:
-(void)doSomethingElse
{
[self encrypt];
[self performSelectorOnMainThread:#selector(doSomethingElseDone) withObject:nil waitUntilDone:YES];
}
And method doSomethingElseDone:
-(void)logoutInBackgroundDone
{
[MBProgressHUD hideHUDForView:self.view animated:YES];
}
The solution works but I think there must be a better way? How can I do that on a better way?
Any help is really appreciated.
You can directly dismiss the MBProgressHUD from doSomethingElse method using dispatch_async
-(void)doSomethingElse
{
[self encrypt];
dispatch_async(dispatch_get_main_queue(), ^{
[MBProgressHUD hideHUDForView:self.view animated:YES];
});
}
Create an atomic property you can get to
#property BOOL spinning;
and
- (void)myTask
{
while ( self.spinning )
{
usleep(1000*250); // 1/4 second
}
}
then use something in your view controller like
HUD = [[MBProgressHUD alloc] initWithView:self.view];
[self.view addSubview:HUD];
[HUD showWhileExecuting:#selector(myTask) onTarget:self withObject:nil animated:YES];
This way the HUD will remove itself when spinning becomes false. The spinner has to be atomic since it will be referenced in a background thread. Whatever you are waiting on can simply set the spinner property to false from any thread to indicate it's done.
This is under ARC.
I've read all related questions I could find but I'm still stuck, so I hope someone will spot my reasoning error.
I'm trying to periodically update some UIView. For simplicity, I've reduced the code to what's below. Summary: In viewDidLoad, I call a method on a new background thread. That method calls a method on the main thread which is supposed to update some UILabel. The code seems to work correctly: the background thread is not the main thread and the method calling the UILabel update is on the main thread. In code:
In viewDidLoad:
[self performSelectorInBackground:#selector(updateMeters) withObject:self];
This creates a new background thread. My method updateMeters (for simplicity) now looks like this:
if ([NSThread isMainThread]) { //this evaluates to FALSE, as it's supposed to
NSLog(#"Running on main, that's wrong!");
}
while (i < 10) {
[self performSelectorOnMainThread:#selector(updateUI) withObject:nil waitUntilDone:NO];
//The code below yields the same result
// dispatch_async(dispatch_get_main_queue(), ^{
// [self updateUI];
// });
[NSThread sleepForTimeInterval: 1.05];
++i;
}
Finally, updateUI does just that:
if ([NSThread isMainThread]) { //Evaluates to TRUE; it's indeed on the main thread!
NSLog(#"main thread!");
} else {
NSLog(#"not main thread!");
}
NSLog(#"%f", someTimeDependentValue); //logs the value I want to update to the screen
label.text = [NSString stringWithFormat:#"%f", someTimeDependentValue]; //does not update
For all I know, this should work. But it doesn't, unfortunately... The commented out dispatch_async() yields the same result.
Most likely you have your format statement wrong.
label.text = [NSString stringWithFormat:#"%f", someTimeDependentValue];
Make sure that someTimeDependentValue is a float. If it is an int it will likely get formatted to 0.0000.
Here's a repo showing a working version of what you describe. Whatever is wrong is not related to the threading.
To expand on my comment, here's a scenario that might be best achieved using a NSTimer:
-(void)viewDidLoad
{
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:<number of seconds per tick> target:self selector:#selector(timerTick:) userInfo:nil repeats:YES];
}
-(void)timerTick:(id)sender
{
label.text = ...;
}
There is a more elaborate approach which I use extensively in my projects. And that is the concept of an engine.
I would have an engine that runs in the background using a timer. And at key moments, it would post a notification using NSNotificationCenter on the main thread using dispatch_async/dispatch_get_main_thread() and any one of your views can then subscribe and handle that notification by updating their UI.
when i click on button which title is click here to enlarge then i want show activity indicator on the first view and remove when load this view.
but i go back then it show activity indicator which is shown in this view.
in first vie .m file i have use this code for action.
-(IBAction)btnSelected:(id)sender{
UIButton *button = (UIButton *)sender;
int whichButton = button.tag;
NSLog(#"Current TAG: %i", whichButton);
UIActivityIndicatorView *spinner = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
[spinner setCenter:CGPointMake(160,124)];
[self.view addSubview:spinner];
[spinner startAnimating];
if(whichButton==1)
{
[spinner stopAnimating];
first=[[FirstImage alloc]init];
[self.navigationController pushViewController:first animated:YES];
[spinner hidesWhenStopped ];
}}
in above code i have button action in which i call next view. Now i want show/display activity indicator when view upload. In next view i have a image view in which a image i upload i have declare an activity indicator which also not working. How do that?
Toro's suggestion offers a great explanation and solution, but I just wanted to offer up another way of achieving this, as this is how I do it.
As Toro said,
- (void) someFunction
{
[activityIndicator startAnimation];
// do computations ....
[activityIndicator stopAnimation];
}
The above code will not work because you do not give the UI time to update when you include the activityIndicator in your currently running function. So what I and many others do is break it up into a separate thread like so:
- (void) yourMainFunction {
activityIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
[NSThread detachNewThreadSelector:#selector(threadStartAnimating) toTarget:self withObject:nil];
//Your computations
[activityIndicator stopAnimating];
}
- (void) threadStartAnimating {
[activityIndicator startAnimating];
}
Good luck!
-Karoly
[self.navigationController pushViewController:first animated:YES];
Generally, when you push a view controller into navigation controller, it will invoke the -(void)viewWillAppear: and -(void)viewDidAppear: methods. You can add activity indicator view inside the viewWillAppear: and call startAnimation of indicator view. You CANNOT invoke startAnimation and stopAnimation at the same time. For example,
- (void)viewWillAppear:(BOOL)animated
{
[aIndicatorView startAnimation];
// do somethings ....
[aIndicatorView stopAnimation];
}
Because the startAnimation and stopAnimation are under the same time, then no animation will show.
But if you invoke startAnimation in -(void)viewWillAppear: and invoke stopAnimation in another message, like followings.
- (void)viewWillAppear:(BOOL)animated
{
[aIndicatorView startAnimation];
// do somethings...
}
- (void)viewDidAppear:(BOOL)animated
{
[aIndicatorView stopAnimation];
}
Because viewWillAppear: and viewDidAppear: are invoked with different event time, the activity indicator view will work well.
Or, you can do something like followings:
- (void)viewWillAppear:(BOOL)animated
{
[aIndicatorView startAnimation];
// Let run loop has chances to animations, others events in run loop queue, and ... etc.
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate date]];
// do somethings ....
[aIndicatorView stopAnimation];
}
The above example is a bad example, because it invokes two or more animations in the -runUntilDate:. But it will let the activity indicator view work.
Create a webview. Add a activity indicator to the webview. If you are loading a image via url into the webview then implement the webview delegate methods. Once the url is loaded then stopanimating the activity indicator.
Let me know which step you are not able to implement.
This is very strange. My view is receiving a notification with an index number that I use to find a UIbutton by tag and then change its text label. The only trouble is the text will not change. I've put in break points and the code is being executed.
Is there some peculiarity with variable scopes when using notifications?
- (void)receiveEvent:(NSNotification *)notification {
int pass = [[[notification userInfo] valueForKey:#"index"] intValue];
NSLog(#"button index is %i", pass);
UIButton *changebut = (UIButton *) [self.view viewWithTag:pass];
ButtonStatusModel *m= [self.buttoneventpendingarray objectAtIndex:pass];
NSLog(#"current but status = %i",m.btnstatus);
if (m.btnstatus==3 ) {
//this should change the label but it does not;
[changebut setTitle:#"playing" forState:UIControlStateNormal];
m.btnstatus=2;
NSLog(#"should set text to 'playing");
//[engine addActionToQue:buttonid actionSample:actionsample action:1];
}else if (m.btnstatus==1) {
//this should change the label but it does not;
[changebut setTitle:#"idle" forState:UIControlStateNormal];
NSLog(#"should set text to 'idle");
m.btnstatus=0;
//[engine addActionToQue:buttonid actionSample:actionsample action:0];
}
}
EDIT 1
Thanks to commenters I've determined that even though my reciveevent function in on my main view controller the function is not being executed from the main thread (dont really understand this) and so thats why the button label will not change.
I used the following code to determine if it was the main thread or not. I think now to change the button label I need to call perform selector on main thread? Is this the correct thing to do? If so I need to pass a function 2 variables, I dont see how perform selector on main thread accomodates this.
-(void) updatebutton:(int)tag changeto:(int) val
{
if ([NSThread isMainThread]) {
{
NSLog(#"is main thread");
}
} else {
NSLog(#"is not thread");
[self performSelectorOnMainThread:#selector( /*function with 2 parameters here*/) withObject:nil waitUntilDone:YES];
}
}
EDIT 3*
Using blocks instead of main selector helped me out of my problem.
I really don't understand the whole main thread though. I assumed that if code was in my view controller that any executed code would be performed on the main thread. That seems to be big fat wrong. I would appreciate a little enlightenment on the matter.
Heres what I used anyway
-(void) updatebutton:(int)tag changeto:(int) val
{
UIButton *changebut = (UIButton *) [self.view viewWithTag:tag];
if ([NSThread isMainThread]) {
{
NSLog(#"is main thread");
if (val==2) {
[changebut setTitle:#"playing" forState:UIControlStateNormal];
}
if (val==0) {
[changebut setTitle:#"idle" forState:UIControlStateNormal];
}
}
} else {
NSLog(#"is not thread");
//[self performSelectorOnMainThread:#selector(updatebutton::) withObject:nil waitUntilDone:YES];
if (val==2) {
dispatch_async(dispatch_get_main_queue(), ^{
[changebut setTitle:#"playing" forState:UIControlStateNormal];
});
}
if (val==0) {
dispatch_async(dispatch_get_main_queue(), ^{
[changebut setTitle:#"idle" forState:UIControlStateNormal];
});
}
}
}
Try putting
[changebut setTitle:#"idle" forState:UIControlStateNormal];
in the -viewDidAppear method. If it doesn't work either, make sure that all the outlets are connected in IB. Moreover, try adding a few spaces as the button title in your .xib file (if you have one).
Is -receiveEvent called on another than the main thread? UI changes must be performed on the main thread.