oddly a uibuttons text label won't change - objective-c

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.

Related

UIImageView setImage not working in a delegate method?

I have a problem calling the setImage function in the opencv delegate method processImage.
When I call setImage in viewDidLoad, I can see the image, but when I do the same in processImage, it doesn't work.
What's the problem here?
- (void)viewDidLoad
{
[super viewDidLoad];
// This works !
[processImageView setImage:[UIImage imageNamed:#"resistor3.jpg"]];
}
- (void)processImage:(cv::Mat&)img {
// This does not work anymore !
[processImageView setImage:[UIImage imageNamed:#"resistor3.jpg"]];
}
When you modify the UI you must do it from the main thread, chances are that the delegate method, if it's being called, is called on another thread. Try this.
- (void)processImage:(cv::Mat&)img {
dispatch_async(dispatch_get_main_queue(), ^{
[processImageView setImage:[UIImage imageNamed:#"resistor3.jpg"]];
// I also think you should use the dot syntax, but that's purely a style thing
// processImageView.image = [UIImage imageNamed:#"resistor3.jpg"];
});
}
EDIT: Add recommendation about using dot syntax

MBProgress hud not showing Labeltext

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];
});
}

Custom segue animating with delay after code being run. (Objective C)

I have several dispatch_async methods linked so they authenticate a user in my system.
The next async method only happens when the previous one finishes, as they each have completion handlers.
When the last one is finished I perform a custom segue with 2 uiview animation blocks.
However, when I log when each of these actually run, there is a considerable gap between the log and the animation actually happening, eventually the views animate and the completion block is called.
I don't really know how useful it would be adding my code here, but i have tested and it must be the async methods because if I comment them out and just return YES the animations happen without delay at the same time as the log.
Does anyone know why this might happen?
EDIT *(with code)
Typical 'exist check' used for email, user, user id.
- (void)existsInSystemWithCompletionHandler:(void (^)(BOOL))block
{
self.existsInSystem = NO;
if (self.isValid) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//
// Get data
//
if (dataIsValid) {
block(YES);
} else {
block(NO);
}
});
} else {
block(self.existsInSystem);
}
}
Check user exists
[potentialUser existsInSystemWithCompletionHandler:^(BOOL success) {
if (success) {
// Perform segue
//
[self performSegueWithIdentifier:#"Logging In" sender:self];
}
}];
Segue
- (void)perform
{
NSLog(#"Perform");
LogInViewController *sourceViewController = (LogInViewController *)self.sourceViewController;
LoggingInViewController *destinationViewController = (LoggingInViewController *)self.destinationViewController;
destinationViewController.user = sourceViewController.potentialUser;
// Animate
//
[UIView animateWithDuration:0.2f
animations:^{
NSLog(#"Animation 1");
//
// Animate blah blah blah
//
}];
[UIView animateWithDuration:0.4f
delay:0.0f
options:UIViewAnimationOptionCurveEaseIn
animations:^{
NSLog(#"Animation 2");
//
// Animate blah blah blah
//
}
completion:^(BOOL finished) {
NSLog(#"Completion");
//
// Finished
//
[sourceViewController presentViewController:destinationViewController animated:NO completion:nil];
}];
}
LoggingIn VC
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
[self.user loginWithCompletionHandler:^(BOOL success) {
if (success) {
[self performSegueWithIdentifier:#"Logged In" sender:self];
}
}];
}
Fixed! It seems to work now that in my code I have added the lines:
dispatch_async(dispatch_get_main_queue(), ^{
completionBlock(success);
});
From your description it seems likely that your segue is waiting for a process to finish.
Maybe your nested async methods that follow each other through their completion methods have produced somewhat convoluted code. This might be the reason why you could be overlooking the blocking method.
One way to tidy up your code is use a sequential queue. The blocks pushed to the queue will start only when the previous one has finished.

Grand Central Dispatch. When using GCD [spinner startAnimating] similar to [myView setNeedsDisplay]?

In Grand Central Dispatch I want to start a spinner - UIActivityIndicatorView - spinning prior to beginning long running task:
dispatch_async(cloudQueue, ^{
dispatch_async(dispatch_get_main_queue(),
^{
[self spinnerSpin:YES];
});
[self performLongRunningTask];
dispatch_async(dispatch_get_main_queue(),
^{
[self spinnerSpin:NO];
});
});
Here is the spinnerSpin method:
- (void)spinnerSpin:(BOOL)spin {
ALog(#"spinner %#", (YES == spin) ? #"spin" : #"stop");
if (spin == [self.spinner isAnimating]) return;
if (YES == spin) {
self.hidden = NO;
[self.spinner startAnimating];
} else {
[self.spinner stopAnimating];
self.hidden = YES;
}
}
One thing I have never seen discussed is the difference - if any - between [myView setNeedsDisplay] and [myActivityIndicatorView startAnimating]. Do they behave the same?
Thanks,
Doug
The [UIView setNeedsDisplay] method has nothing to do with a UIActivityIndicatorView's animation state.
setNeedsDisplay simply informs the system that this view's state has changed in a way that invalidates its currently drawn representation. In other words, it asks the system to invoke that view's drawRect method on the next drawing cycle.
You very rarely need to invoke setNeedsDisplay from outside of a view, from code that is consuming the view. This method is meant to be invoked by the view's internal logic code, whenever something changes in its internal state that requires a redraw of the view.
The [UIActivityIndicatorView startAnimating] method is specific to the UIActivityIndicatorView class and simply asks the indicator to start animating (e.g. spinning). This method is instant, without requiring you to call any other method.
On a side note, you could simplify your code by simply calling startAnimating or stopAnimating without manually showing/hiding it. The UIActivityIndicatorView class has a hidesWhenStopped boolean property that defaults to YES, which means that the spinner will show itself as soon as it starts animating, and hide itself when it stops animating.
So your spinnerSpin: method could be refactored like this (as long as you haven't set the hidesWhenStopped property to NO):
- (void)spinnerSpin:(BOOL)spin {
if (YES == spin) {
[self.spinner startAnimating];
} else {
[self.spinner stopAnimating];
}
}

UI not updating even though updates are called from main thread

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.