Exit from void method if action done - ios

In my program I used a void method and I want to stop this when the button action is done.
in viewDidLoad
[self runningaction];
and out side of the viewDidLoad
-(void)runningaction
{
some code
}
another method for UIButton action
-(void)clicktosee
{
//here i need to write code for stop the -(void)runningaction
}
Thanks in advance.

Put check that method is implemented then just return that's what i tried
-(void)clicktosee
{
if( [self respondsToSelector:#selector(runningaction)] ) {
return;
}
}

Ok, if I understood your question (and comment) correctly, you want to start some animations on button images, and then stop them on user action.
This can be achieved like this :
-(void)runningaction {
myButton.imageView.animationImages =
[NSArray arrayWithObjects:[UIImage imageNamed:#"image1.png"],
[UIImage imageNamed:#"image2.png"],
nil];
myButton.imageView.animationDuration = 0.5; //whatever you want (in seconds)
myButton.imageView.animationRepeatCount = 0; //0 means repeat forever here
[myButton.imageView startAnimating];
}
Then in -(void)clicktosee you simple write this line : [myButton.imageView stopAnimating].
Hope I understood you correctly.
NOTE : The animation code is taken from this answer by #ender.

Related

UIImageView setImage does not change how image looks

I am writing a tone generator UI and I need to change UIImageView image every time generator starts generating and finishes generating.
To do so I have
#property (weak, nonatomic) IBOutlet UIImageView *headphonesImage;
in my UIViewController. And I add default image to it like so
- (void) viewDidLoad
{
...
headphonesImagesArray = [NSArray arrayWithObjects:
[UIImage imageNamed: #"Both Headphones"],
[UIImage imageNamed: #"Both Headphones Playing"],
nil];
[self.headphonesImage setAlpha: 0.5];
[self.headphonesImage setImage: headphonesImagesArray[0]];
...
}
My tone generator sends messages into this method
- (void) toneGeneratorControllerStateHasChangedWithNotification: (NSNotification *) notification
{
if([notification.name isEqualToString: ToneGenerationHasFinished])
[self.headphonesImage setImage: headphonesImagesArray[0]];
else
[self.headphonesImage setImage: headphonesImagesArray[1]];
}
The problem is that though headphonesImage image changes to [1] and then nothing happens. I can see in variables inspector that headphonesImage image is changed every time the method is invoked, but these changes are not present on screen of neither simulator nor iPhone. I can't even hide this damn UIImageView. setHidden: true does nothing.
Please help!
"My tone generator sends messages into this method"
If you are writing the tone generator, it sounds like it is running on a background thread. You should send notifications on the main thread, especially if they trigger UI changes (such as setting an image), e.g.:
dispatch_async(dispatch_get_main_queue(),^{
[[NSNotificationCenter defaultCenter] postNotificationName:#"toneGeneratorControllerStateHasChanged"
object:nil
userInfo:imageDict];
});
Alternatively, you can send the image change to the main thread as Igor suggests. The disadvantage there is having to do any time you use such notifications, as opposed to doing it in one place.
Do this:-
- (void) toneGeneratorControllerStateHasChangedWithNotification: (NSNotification *) notification
{
if([notification.name isEqualToString: ToneGenerationHasFinished]) {
self.headphonesImage.image = [UIImage imageNamed:#"Both Headphones"];
}
else {
self.headphonesImage.image = [UIImage imageNamed:#"Both Headphones Playing"];
}
}
instead of setImage.
in VDL
headphonesImagesArray = [NSArray arrayWithObjects:
#"Both Headphones",
#"Both Headphones Playing",
nil];
And do this :
- (void) toneGeneratorControllerStateHasChangedWithNotification: (NSNotification *) notification
{
if([notification.name isEqualToString: ToneGenerationHasFinished]) {
self.headphonesImage.image = [UIImage imageNamed: headphonesImagesArray[0]];
}
else {
self.headphonesImage.image = [UIImage imageNamed:headphonesImagesArray[1]];
}
}
It looks very shady, but it seems that I've found the solution:
- (void) toneGeneratorControllerStateHasChangedWithNotification: (NSNotification *) notification
{
BOOL finished = FALSE;
if([notification.name isEqualToString: ToneGenerationHasFinished])
finished = TRUE;
dispatch_async(dispatch_get_main_queue(),
^{
if(finished)
[self.headphonesImage setImage: headphonesImagesArray[0]];
else
[self.headphonesImage setImage: headphonesImagesArray[1]];
});
}
As far as I know all the UI drawing happens in the main thread. Looks like notification method does not work in the main thread unless you explicitely tell it to do so...

Hidden UIButton showing up, when reloading UITableView

I'm expecting to get a little help with a UIButtons staying .hidden. I'm new to this site so please give me a min to best describe this problem I face.
Below is a picture of 2 UIButtons, in the middle of these UIButtons there is another one called OnRoute. Once the Acknowledged button is pressed it is hidden to which sends a status and reveals the OnRoute UIButton. Now the Acknowledged button is hidden you will only see on screen under the Runsheet button the OnRoute button to which you also press that sends a status and then hides it self.
Once these buttons are pressed you are sent to a UITableView and at this point all is well, but when you go back to the menu screen the buttons are reappear as if the buttons have not been pressed. And you can repeat over and over sending status.
The idea of this is to send a job status once the buttons are pressed which in turn shows on software on a server. Once these have been sent and the UIButtons hide for that job number, I would like to keep them hides until job has gone from hand set.
This is complex problem but if anyone has any ideas of this, I would really be thankful.
//This is in ViewDidLoad
self.onroute.hidden = YES;
NSNumber *num = [NSNumber numberWithInt:10.00];
self.acknow.hidden = YES;
if((self.consignment.cur_status_no < num) || [self.consignment.newjob isEqual:#(YES)]){
self.acknow.hidden = NO;
//This is in IBAction
- (IBAction)acknowledgebtn:(id)sender {
if (self.onroute.hidden == YES){
self.acknow.hidden = NO;
self.onroute.hidden = NO;
self.acknow.hidden = YES;
//and this is for the other IBAction
if (self.acknow.hidden == YES){
self.onroute.hidden = YES;
As I'm new to the site it will not let me post picture of UIButton sorry for this.
My suggestion would be to use some booleans instead of relying on the buttons hidden property. Then save the booleans when transferring to a new view. Then when you return to the main menu check the booleans and see what should be hidden and what should not be.
Also when I name variables I like to pretend that someone else will be looking at my code. So instead of just onroute as the button name, I would make it onrouteBut. This makes it a lot easier when I go back through my code as well so I know exactly what each variable is just by looking at the name.
As for the code I don't know how you are presenting views, so I can't really give a full answer. But I think this will help.
in your .h
#property (nonatomic) BOOL onrouteBool;
#property (nonatomic) BOOL acknowBool;
//whatever other bools you need instead of using button.hidden == YES/NO
in your .m
#synthesize onrouteBool, acknowBool;
-(void)viewDidLoad {
onrouteBut.hidden = YES;
onrouteBool = YES;
NSNumber *num = [NSNumber numberWithInt:10.00];
acknowBut.hidden = YES;
acknowBool = YES;
if((self.consignment.cur_status_no < num) || [self.consignment.newjob isEqual:#(YES)]) {
acknowBut.hidden = NO;
acknowBool = NO;
}
}
-(IBAction)acknowledgeBtn:(id)sender {
if (onrouteBool == YES) {
acknowBut.hidden = NO;
onrouteBut.hidden = NO;
acknowBool = NO;
onrouteBool = NO;
//this part doesn't make sense you set the button to visible and then hidden right after
acknowBut.hidden = YES;
acknowBool = YES;
}
}
-(IBAction)onrouteBtn:(id)sender {
if (acknowBool == YES) {
onrouteBut.hidden = YES;
onrouteBool = YES;
}
}
So now before you transition to your next view call this method to save the bools
-(void)saveTheBools {
//save the bools however you want before you transition the view
//one way is nsuserdefaults
[[NSUserDefaults standardUserDefaults]setBool:onrouteBool forKey:#"onrouteBool"];
[[NSUserDefaults standardUserDefaults]setBool:acknowBool forKey:#"acknowBool"];
[[NSUserDefaults standardUserDefaults]synchronize];
//how you save them
}
then when you transition back to the main menu check the bools to see if the buttons should be hidden
-(void)checkTheBools {
onrouteBool = [[NSUserDefaults standardUserDefaults] boolForKey:#"onrouteBool"];
acknowBool = [[NSUserDefaults standardUserDefaults] boolForKey:#"acknowBool"];
if (onrouteBool == YES) {
onrouteBut.hidden = YES;
}
else {
onrouteBut.hidden = NO;
}
if (acknowBool == YES) {
acknowBut.hidden = YES;
}
else {
acknowBut.hidden = NO;
}
//whatever else you need to hidden or make visible
}
This is all just to give you some ideas of what to do. Use what you need to make it work. This is how I would do it, I don't know if this is best way to do it but it's a starting point. I can't really give a specific answer without seeing all of your code, since I don't know how you're transitioning views, what you are initializing, retaining, etc.
Hope this helps you out, if not my bad. Just keep working at it and you'll find something that works for you eventually.
edit:
As for the status problem you are having I can't really help because I don't have the code to look at. I think it probably has to do with saving your variables so you can access them across classes. So like I showed you how to save the booleans and use them you probably will have to do something similar to check if the status has sent or not.
I suggested using nsuserdefaults because that is the easiest thing to do, however it is not the best to rely on that for saving all of your variables. You can also look into singletons, core data, or anything that will allow you to save the variables that you need across classes. You just have to find the way that works best for what you are trying to do.
The only way you are going to learn is to struggle at times, do some research, and try different things until you find a solution. Also take advantage of the resources apple provides you with as a developer. I think you will be able to figure this one out. Good luck
Just wanted to update anyone having this problem, I managed to fix this using doubleValue.
onroute.hidden = YES;
onrouteBool = YES;
NSNumber *num1 = [NSNumber numberWithDouble:10.00];
if(([self.consignment.cur_status_no doubleValue] < [num1 doubleValue] ) ) {
if([self.consignment.newjob isEqual:#(NO)]) {
onroute.hidden = NO;
onrouteBool = NO;
}
}
acknow.hidden = YES;
acknowBool = YES;
if([self.consignment.newjob isEqual:#(YES)]) {
acknow.hidden = NO;
acknowBool = NO;
}
Thanks again for all your help.

iOS GUI refresh

I am using setNeedsDisplay on my GUI, but there update is sometimes not done. I am using UIPageControllView, each page has UIScrollView with UIView inside.
I have the following pipeline:
1) application comes from background - called applicationWillEnterForeground
2) start data download from server
2.1) after data download is finished, trigger selector
3) use dispatch_async with dispatch_get_main_queue() to fill labels, images etc. with new data
3.1) call setNeedsDisplay on view (also tried on scroll view and page controller)
Problem is, that step 3.1 is called, but changes apper only from time to time. If I swap pages, the refresh is done and I can see new data (so download works correctly). But without manual page turn, there is no update.
Any help ?
Edit: code from step 3 and 3.1 (removed _needRefresh variables pointed in comments)
-(void)FillData {
dispatch_async(dispatch_get_main_queue(), ^{
NSString *stateID = [DataManager ConvertStateToStringFromID:_activeCity.actual_weather.state];
if ([_activeCity.actual_weather.is_night boolValue] == YES)
{
self.contentBgImage.image = [UIImage imageNamed:[NSString stringWithFormat:#"bg_%#_noc", [_bgs objectForKey:stateID]]];
if (_isNight == NO)
{
_bgTransparencyInited = NO;
}
_isNight = YES;
}
else
{
self.contentBgImage.image = [UIImage imageNamed:[NSString stringWithFormat:#"bg_%#", [_bgs objectForKey:stateID]]];
if (_isNight == YES)
{
_bgTransparencyInited = NO;
}
_isNight = NO;
}
[self.contentBgImage setNeedsDisplay]; //refresh background image
[self CreateBackgroundTransparency]; //create transparent background if colors changed - only from time to time
self.contentView.parentController = self;
[self.contentView FillData]; //Fill UIView with data - set labels texts to new ones
//_needRefresh is set to YES after application comes from background
[self.contentView setNeedsDisplay]; //This do nothing ?
[_grad display]; //refresh gradient
});
}
And here is selector called after data download (in MainViewController)
-(void)FinishDownload:(NSNotification *)notification
{
dispatch_async(dispatch_get_main_queue(), ^{
[_activeViewController FillData]; //call method shown before
//try call some more refresh - also useless
[self.pageControl setNeedsDisplay];
//[self reloadInputViews];
[self.view setNeedsDisplay];
});
}
In AppDelegate I have this for application comes from background:
-(void)applicationWillEnterForeground:(UIApplication *)application
{
MainViewController *main = (MainViewController *)[(SWRevealViewController *)self.window.rootViewController frontViewController];
[main UpdateData];
}
In MainViewController
-(void)UpdateData
{
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(FinishForecastDownload:) name:#"FinishDownload" object:nil]; //create selector
[[DataManager SharedManager] DownloadForecastDataWithAfterSelector:#"FinishDownload"]; //trigger download
}
try this:
[self.view performSelectorOnMainThread:#selector(setNeedsLayout) withObject:nil waitUntilDone:NO];
or check this link:
http://blackpixel.com/blog/2013/11/performselectoronmainthread-vs-dispatch-async.html
setNeedsDisplay triggers drawRect: and is used to "redraw the pixels" of the view , not to configure the view or its subviews.
You could override drawRect: and modify your labels, etc. there but that's not what it is made for and neither setNeedsLayout/layoutSubviews is.
You should create your own updateUI method where you use your fresh data to update the UI and not rely on specialized system calls meant for redrawing pixels (setNeedsDisplay) or adjusting subviews' frames (drawRect:).
You should set all your label.text's, imageView.image's, etc in the updateUI method. Also it is a good idea to try to only set those values through this method and not directly from any method.
None of proposed solutions worked. So at the end, I have simply remove currently showed screen from UIPageControllView and add this screen again. Something like changing the page there and back again programatically.
Its a bit slower, but works fine.

iPhone NSTimer running single method

I'm new to programming for the iPhone and i'm having a problem with this function
-(IBAction)changeX {
[self timere:field1];
[self timere:field2];
}
this is a button to move an uitextfield object across the screen. The problem is i want to run this method on the first field , complete it then go on to the second. The timere function uses NSTimer to continously move the object until it reaches a certain point at which it terminates. I have two other functions shown below. The actual program im making is much longer but the objective is the same and that code is too long. The problem is running the first function then the second. Thank you for the help.
-(void)timere:(UITextField*)f1 {
UITextField*fielder1=f1;
NSInvocation*timerInvocation = [NSInvocation invocationWithMethodSignature:
[self methodSignatureForSelector:#selector(moveP:)]];
[timerInvocation setSelector:#selector(moveP:)];
[timerInvocation setTarget:self];
[timerInvocation setArgument:&fielder1 atIndex:2];
timer1 = [NSTimer scheduledTimerWithTimeInterval:0.03 invocation:timerInvocation repeats:YES];
}
-(void) moveP:(UITextField*)f1 {
UITextField*fielder1=f1;
fielder1.center = CGPointMake(fielder1.center.x+4, fielder1.center.y);
if (fielder1.center.x>237) {
[timer1 invalidate];
}
}
The standardized method of animating UIView objects is by using methods such as the following.
+ animateWithDuration:delay:options:animations:completion:
The syntax for these methods might be a bit daunting if you are unfamiliar with Objective-C Blocks. Here's a usage example which moves the first field, then the second field afterwards.
[UIView animateWithDuration:0.3 animations:^{
[field1 setCenter:CGPointMake(FINAL_CENTER_X1, FINAL_CENTER_Y1)];
} completion:^(BOOL finished) {
[UIView animateWithDuration:0.3 animations:^{
[field2 setCenter:CGPointMake(FINAL_CENTER_X2, FINAL_CENTER_Y2)];
}];
}];
EDIT: For a general fix on your specific problem, I would make the following modifications:
-(IBAction)changeX {
[self timere:field1];
}
-(void) moveP:(UITextField*)f1 {
UITextField*fielder1=f1;
fielder1.center = CGPointMake(fielder1.center.x+4, fielder1.center.y);
if (fielder1.center.x>237) {
[timer1 invalidate];
// I'm really not sure about the scope of field1 and field2, but
// you can figure out the encapsulation issues
if (f1 == field1) {
[self timere:field2];
}
}
}
A more generic and indeed low-level solution would be to have a state variable. Perhaps you would have some sort of NSArray *fields of UITextField * and int state = 0. Where I added the conditional in moveP above, you would state++ and call [self timere:[fields objectAtIndex:state]] if state < [fields count]. You timer invalidation code is correct, anyway.

What is the best way to play a UIImageView animation once and remove it from memory immediately after playing it?

I'm having a hard time removing my animation from memory. What is the best way to do this?
This is my animation code:
-(IBAction)animationOneStart {
NSMutableArray *images = [[NSMutableArray alloc] initWithCapacity:88];
for(int count = 1; count <= 88; count++)
{
NSString *fileName = [NSString stringWithFormat:#"testPic1_%03d.jpg", count];
UIImage *frame = [UIImage imageNamed:fileName];
[images addObject:frame];
}
loadingImageView.animationImages = images;
loadingImageView.animationDuration = 5;
loadingImageView.animationRepeatCount = 1; //Repeats indefinitely
[loadingImageView startAnimating];
[images release];
}
Thanks!
[images removeAllObjects];
[images release];
Don't need to release it anymore with ARC under Xcode 4.2! Update today :)
As far as I can tell, there is not a callback to know when the imageView's animation is finished. This is unfortunate, because it means that to remove the animation when the imageView is finished animating, you will need repeatedly check the imageView's isAnimating method until it returns false. The following is an example of what I mean:
You might be able to do something like this—in your original code, add the following line
[loadingImageView startAnimating];
[images release];
[self performSelector:#selector(checkIfAnimationIsFinished) withObject:nil afterDelay:4.5f];
Then create a method
-(void)checkIfAnimationIsFinished {
if (![loadingImageView isAnimating]) {
[loadingImageView release];
} else {
[self performSelector:#selector(checkIfAnimationIsFinished) withObject:nil afterDelay:0.5f];
}
}
This was just an example, I used the values 4.5f and 0.5f because, unfortunately, the timing is not precise, it is more of a ballpark figure. 4.9f and 0.1f might work better. Regardless, the problem is that if you set the method to fire 5.0f seconds after you start the animation, it is not 100% certain that the animation will be be finished or not. So you will need to check repeatedly if it is not finished.
Anyway, this is not the cleanest solution, but unless someone else knows how to get a callback when an imageView is finished animating, I don't know of a better solution.

Resources