I'm trying to change the enabled property of an UIBarButtonItem after doing some stuff in an NSThread. After pressing the button I set the enabled to NO then perform the threaded part and at the end I try to re enable the button. Fairly basic.
Somehow this fails, however I'm able to change any other property of the the UIBarButtonItem correctly (for ex. title).
What am I doing wrong here?
#interface myViewController : UIViewController <UITableViewDataSource, UITableViewDelegate> {
IBOutlet UIBarButtonItem *myButton;
}
#property (nonatomic, retain) IBOutlet UIBarButtonItem *myButton;
- (IBAction)mysub:(id)sender;
#end
#implementation myViewController
#synthesize myButton;
- (IBAction)mysub:(id)sender {
[myButton setEnabled:NO];
[NSThread detachNewThreadSelector:#selector(mysub_threaded) toTarget:self withObject:nil];
}
- (void) mysub_threaded {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
… do threaded stuff
[myButton performSelectorInBackground: # selector(setEnabled :) withObject: [NSNumber numberWithBool:YES]];
[pool drain];
}
You want performSelectorOnMainThread instead.
[myButton performSelectorOnMainThread:#selector(setEnabled:)
withObject:[NSNumber numberWithBool:YES]
waitUntilDone:NO];
Always do anything that touches the UI on the main thread.
But sometimes passing arguments like this is funky too. I find it best to wrap up everything you need done in another method
- (void)mysub_complete {
[myButton setEnabled:YES];
}
Then call that with
[self performSelectorOnMainThread:#selector(mysub_complete)
withObject:nil
waitUntilDone:NO];
Now you can do as much other UI stuff as you want without worry about it.
Related
I have searched all over the web with no luck of making this project work with a timer, I end up with a crashed app every time when using the timer.
For testing and learning purposes I build little simple apps. It is a button that sends a rocket from the bottom of the screen and disappears off screen, along with a rocket sound effect.
I am wanting to add a Timer to the button so that when the button is held down the rocket will launch, reset, and launch over and over until I release the button. I think my only hope now is to paste code from my .h & .m files and hopefully someone can tell me what I need to do and where the proper code needs to be added for this project.
Thank you so much for the help, it is greatly appreciated.
.H FILE:
// MVViewController.h
#import <UIKit/UIKit.h>
#import <AVFoundation/AVFoundation.h>
#interface MVViewController : UIViewController
#property (strong, nonatomic) IBOutlet UIImageView *moveMe2;
//New action to repeat launch (Touch Down)
- (IBAction)rocketRepeat:(id)sender;
//New action to stop launch (Touch Up Inside)
- (IBAction)rocketStop:(id)sender;
//This is the original launch button (Touch Down)
- (IBAction)yourRocketButton:(id)sender;
#end
.M FILE
// MVViewController.m
#import "MVViewController.h"
#interface MVViewController ()
#end
#implementation MVViewController {
AVAudioPlayer *audioPlayer;
}
#synthesize moveMe2;
- (void)viewDidLoad
{
[super viewDidLoad];
}
- (void)viewDidUnload
{
[self setMoveMe2:nil];
[super viewDidUnload];
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return ((interfaceOrientation == UIInterfaceOrientationPortrait) || (interfaceOrientation == UIInterfaceOrientationPortraitUpsideDown));
}
- (IBAction)rocketRepeat:(id)sender
//Getting error "Use of undeclared identifier 'yourRocketButton'
{
[yourRocketButton addTarget:self action:#selector(rocketRepeat:) forControlEvents:UIControlEventTouchDown];
}
- (IBAction)rocketStop:(id)sender
//Getting error "Use of undeclared identifier 'yourRocketButton'
{
[yourRocketButton addTarget:self action:#selector(rocketStop:) forControlEvents:UIControlEventTouchUpInside];
}
- (IBAction)yourRocketButton:(id)sender {
moveMe2.center = CGPointMake(100.0f, 408.0f);
[UIView animateWithDuration:2.0 animations:^{moveMe2.center = CGPointMake(100, -55);}];
}
#end
########
EDIT *This is what finally worked*
// RKViewController.m
#import "RKViewController.h"
#interface RKViewController ()
#end
#implementation RKViewController
#synthesize RocketMove;
#synthesize Launch;
- (void)viewDidLoad
{
[super viewDidLoad];
}
- (void)viewDidUnload
{
[self setRocketMove:nil];
[self setLaunch:nil];
[super viewDidUnload];
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return ((interfaceOrientation == UIInterfaceOrientationPortrait) || (interfaceOrientation == UIInterfaceOrientationPortraitUpsideDown));
}
- (IBAction)rocketRepeat:(id)sender {
[self performSelector:#selector(rocketRepeat:) withObject:self afterDelay:1.0];
RocketMove.center = CGPointMake(100.0f, 408.0f);
[UIView animateWithDuration:1.0 animations:^{RocketMove.center = CGPointMake(100, -55);}];
}
- (IBAction)rocketStop:(id)sender {
[NSObject cancelPreviousPerformRequestsWithTarget:self];
}
#end
// RKViewController.h
#import <UIKit/UIKit.h>
#interface RKViewController : UIViewController
#property (strong, nonatomic) IBOutlet UIImageView *RocketMove;
#property (strong, nonatomic) IBOutlet UIButton *Launch;
- (IBAction)rocketRepeat:(id)sender;
- (IBAction)rocketStop:(id)sender;
#end
You have to use UIControlEvent for this purpose.
1. You need two separate IBActions for each purpose, say one for holding down the button, one after the button is released.
2. For holding down the button you need to use UIControlEventTouchDown. So have a rocketRepeat action where you keep calling the rocket action using the NSTimer with regular intervals and use:
[yourRocketButton addTarget:self action:#selector(rocketRepeat:) forControlEvents:UIControlEventTouchDown];
3. Then use another action with UIControlEventTouchUpInside where you will invalidate the NSTimer so the rocket stops. Call that action rocketStop or something and use:
[yourRocketButton addTarget:self action:#selector(rocketStop:) forControlEvents:UIControlEventTouchUpInside];
---EDIT---
Action 1 :
- (IBAction)rocketRepeat:(id)sender
{
//code for starting rocker, timer action
}
Action 2:
- (IBAction)rocketStop:(id)sender
{
//Code for stopping rocket
}
yourButton is not an action, its a UIButton. I hope you have created a button in the IB, drag and dropped the button. And in viewDidLoad you write these 2 lines of code :
Instead of yourButton you write the name of the button you have dragged dropped from the IB. I hope you know how to add a button from the interface builder and connect it.
- (void)viewDidLoad
{
[yourRocketButton addTarget:self action:#selector(rocketRepeat:) forControlEvents:UIControlEventTouchDown]; //For touch down button action
[yourRocketButton addTarget:self action:#selector(rocketStop:) forControlEvents:UIControlEventTouchUpInside]; //When button is let go.
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
If you want that the rockets are autolaunched once you press the button, you should add the following code to your rocketLaunch: method. If you want them to start appearing from the beginning, call this from your viewDidLoad method.
- (void)launchRocketsTimer
{
[self.timer invalidate]; //you have to create a NSTimer property to your view controller
self.timer = [NSTimer scheduledTimerWithTimeInterval:2.5 target:self selector:#selector(scheduledRocketLaunch:) userInfo:nil repeats:YES];
}
- (void)scheduledRocketLaunch:(NSTimer *)t
{
moveme2.center = _bottom_point_; //set it where you would like it to start
[UIView animateWithDuration:2.0 animations:^{moveMe2.center = CGPointMake(100, -55);}];
}
Remember to release your timer in dealloc.
Oh, and one more thing:
You have a memory leak on your rocketsound: method when you allocate your AVAudioPlayer. You may replace the code with this one:
- (IBAction)rocketsound:(id)sender
{
NSURL *url = [NSURL fileURLWithPath: [NSString stringWithFormat:#"%#/rocketlaunch.mp3", [[NSBundle mainBundle] resourcePath]]];
NSError *error;
if (self.audioPlayer == nil)
{
self.audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:&error] autorelease]; //Note the use of setter property and the autorelease. (you could use your own code because the if will prevent the leak in this case).
}
audioPlayer.numberOfLoops = 0;
if (audioPlayer == nil)
NSLog(#"%#", [error description]);
else
[audioPlayer play];
}
Recently I'm working on app. project for iPhone(iOS).
I wonder why there isn't performSelectorOnMainThread: in NSObject Protocol.
I need to call to delegate's methods on main thread 'cause they have to handle UI components.
Here's sample I wrote:
#protocol MyOperationDelegate;
#interface MyOperation : NSOperation {
id <MyOperationDelegate> delegate;
}
#property (nonatomic, assign) id <MyOperationDelegate> delegate;
#end
#protocol MyOperationDelegate <NSObject>
#optional
- (void) didFinishHandleWithResult:(NSDictionary *)result;
#end
#implementation MyOperation
#synthesize delegate;
- (void) main {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSDictionary *aDict = [[MySingleton sharedObject] fetchSomethingMeaningful];
//do something and call delegate
if ([delegate respondsToSelector:#selector(didFinishHandleWithResult:)]) {
[delegate performSelector:#selector(didFinishHandleWithResult:) withObject:
}
[pool release];
}
#end
#interface MyViewCon : UIViewController <MyOperationDelegate> {
NSOperationQueue *queue;
}
#end
#implementation MyViewCon
- (void) viewDidLoad {
MyOperation *op = [[MyOperation alloc] init];
op.delegate = self;
[queue addOperation:op];
[op release];
}
- (void) reloadUserInterface:(NSDictionary *)dict {
// Do reload data on User Interfaces.
}
- (void) didFinishHandleWithResult:(NSDictionary *)myDict {
// Couldn't execute method that's handling UI, maybe could but very slow...
// So it must run on main thread.
[self performSelectorOnMainThread:#selector(reloadUserInterface:) withObject:myDict];
}
#end
Can I run didFinishHandleWithResult: delegate method on main thread in MyOperation class?
Since here I am implementing UI handling method every time I call MyOperation instance.
Any suggestion will be helpful for me.
Let's try:
dispatch_async(dispatch_get_main_queue(), ^{
//your main thread code here
});
NSObject does have a method performSelectorOnMainThread:withObject:waitUntilDone. You can use that.
Yes you can make use of the method like this
[delegate performSelectorOnMainThread:#selector(reloadUserInterface:) withObject:myDict waitUntilDone:YES];
I need to view a subview with an activity indicator.
This is my code but the subview doesn't appear:
#interface ProgressViewController : UIViewController {
IBOutlet UIActivityIndicatorView *myActivityIndicator;
}
#property (nonatomic, retain) IBOutlet UIActivityIndicatorView *myActivityIndicator;
#end
#implementation ProgressViewController
#synthesize myActivityIndicator;
- (void)viewDidLoad {
[myActivityIndicator startAnimating];
[super viewDidLoad];
}
- (void)viewWillDisappear:(BOOL)animated {
[myActivityIndicator stopAnimating];
}
#end
#import "ProgressViewController.h"
#interface MyViewController : UIViewController {
ProgressViewController *progressViewController;
}
#property (nonatomic, retain) ProgressViewController *progressViewController;
#end
#implementation MyViewController
#synthesize progressViewController
- (void)viewDidLoad
{
progressViewController = [[ProgressViewController alloc] initWithNibName:#"ProgressViewController" bundle:nil];
[self.view addSubview:progressViewController.view];
sleep(4);
[progressViewController.view removeFromSuperview];
[super viewDidLoad];
}
#end
There could be several causes, and it's still a bit unclear from the code you sent, which one it is.
First, you shouldn't use sleep(4) in your code - it messes up the application engine iOS runs to support user input, screen refresh, etc.
Your code could easily be changed to:
[self performSelector:#selector(removeMyProgressView:) withObject:progressViewController.view afterDelay:4.0];
and have removeFromSuperview in your removeMyProgressView: function.
Also, this line of code is buggy:
progressViewController = [[ProgressViewController alloc] initWithNibName:#"ProgressViewController" bundle:nil];
It should be
self.progressViewController = [[ProgressViewController alloc] initWithNibName:#"ProgressViewController" bundle:nil];
Otherwise you don't call the setter function (#sythesized property), and the object isn't retained. It could be that it is released, and therefore you don't see it.
If this none of this is right, we'll keep pounding at it :)
Good luck!
Oded.
Everything in your -viewDidLoad method happens in one runloop. This means that you add and remove the activity indicator without giving the system a chance to actually draw it. The 4 seconds of sleep don't help. Those just make the runloop take longer to finish.
call [super viewDidLoad] before anything in - (void)viewDidLoad methods
So, I am following this Xcode tutorial book: Iphone and Ipad Game Development for Dummies. Its a good book, but there are some flaws here and there. Most of the time I can figure a way around these flaws, but now I stumbled upon something I cant fix. Even their website or contacts dont know the answer.
Im trying to make a game with cars and stuff. But the problem lies with the main menu. I already made buttons with custom button mode. And used IbActions to make them work(actually, "New Game" is the only button that works now cause it has its function already scripted). But now I have to make IBOutlets to give them an Animation. The book told me to use the QuartzCore.framework. Now the problem is that when I try to build it I get this error: No Declaration of property 'newGameButton' found in the Interface.
This is my script.
The header file:
#import <UIKit/UIKit.h>
#import <QuartzCore/QuartzCore.h>
#interface MainMenuViewController : UIViewController {
//This has changed
#property(nonatomic,retain) IBOutlet UIButton* newGameButton;
#property(nonatomic,retain) IBOutlet UIButton* statsButton;
#property(nonatomic,retain) IBOutlet UIButton* settingsButton;
CAKeyframeAnimation* popAnimation;
}
-(IBAction) newGame:(id)sender;
-(IBAction) showStats:(id)sender;
-(IBAction) showSettings:(id)sender;
#end
And the .m file (its called implantation right?):
#import "MainMenuViewController.h"
#import "TrafficAppDelegate.h"
#import "TrafficViewController.h"
#implementation MainMenuViewController
-(IBAction) newGame:(id)sender{
TrafficViewController* traffic = [[TrafficViewController alloc] initWithNibName:#"TrafficViewController" bundle:nil];
[self.navigationController pushViewController:traffic animated:NO];
}
-(IBAction) showStats:(id)sender{
}
-(IBAction) showSettings:(id)sender{
}
#synthesize newGameButton, statsButton, settingsButton;
-(void)viewDidLoad{
[super viewDidLoad];
popAnimation = [CAKeyframeAnimation animationWithKeyPath:#"transform.scale"];
popAnimation.keyTimes = [NSArray arrayWithObjects:[NSNumber numberWithFloat:0.0], [NSNumber numberWithFloat:0.7], [NSNumber numberWithFloat:1.0], nil];
popAnimation.values = [NSArray arrayWithObjects: [NSNumber numberWithFloat:0.01], [NSNumber numberWithFloat:1.1], [NSNumber numberWithFloat:1.0], nil];
[popAnimation retain];
}
-(void)popView:(UIView*)view{
[view setHidden:NO];
[[view layer] addAnimation:popAnimation forKey:#"transform.scale"];
}
-(void)viewWillAppear:(BOOL)animated{
[popAnimation setDuration:0.3];
[newGameButton setHidden:YES];
[statsButton setHidden:YES];
[settingsButton setHidden:YES];
[self performSelector:#selector(popView:) withObject:newGameButton afterDelay:0.25];
[self performSelector:#selector(popView:) withObject:statsButton afterDelay:0.3];
[self performSelector:#selector(popView:) withObject:settingsButton afterDelay:0.35];
}
#end
Now I first tought that they werent connected to the buttons in the User Interface builder. But that didnt work either. I tried to remake the whole script. I tried to reload the script in Xcode. I even tried to make my own version. Im dont have any options left. Does anyone know what is wrong with my script?
set the property of the objects in header file as
#property(nonatomic, retain) IBOutlet UIButton* newGameButton;
.
.
.
and in .m file synthesis them using
#synthesis newGameButton;
do this for all the objects that you have declared
and then connect them in the IB
Are you loading the proper UIViewController?? i mean, if you are working with the interface builder check out that in your main window xib, the viewController object you are using corresponds with your MainMenuViewController class
You have said you were seeing:
"No Declaration of property 'newGameButton' found in the Interface"
Any property that is declared in either the .h (header) or .m (implementation) must be synthesised:
#property (nonatomic,retain) IBOutlet UIButton* newGameButton;
Like this for your properties in the .m (implementation)
#synthesize newGameButton;
Is that possible to make a UIToolbarButton pass an object to its target by using some exoteric method (as it seems not to be possible using regular button use)?
I mean something like
UIBarButtonItem *Button = [[UIBarButtonItem alloc] initWithImage:buttonImage
style:UIBarButtonItemStylePlain target:self action:#selector(doSomething:) **withObject:usingThis**];
I know I can trigger a method that will launch the full method with the object, but for the sake of elegance I was trying to minimize the code... I suspect it is not possible, but as you guys out there are insanely good you may come with an transcendental answer... who knows...
You would have to extend the UIBarButtonItem class.
Here is an example creating RCBarButtonItem class. I've used initWithTitle for ease, I'm sure you could change it...
The UIBarButtonItem Subclass
#import <UIKit/UIKit.h>
#interface RCBarButtonItem : UIBarButtonItem {
id anObject;
}
#property (nonatomic, retain) id anObject;
- (id)initWithTitle:(NSString *)title style:(UIBarButtonItemStyle)style target:(id)target action:(SEL)action withObject:(id)obj;
#end
#implementation RCBarButtonItem
#synthesize anObject;
-(void)dealloc {
[anObject release];
[super dealloc];
}
- (id)initWithTitle:(NSString *)title style:(UIBarButtonItemStyle)style target:(id)target action:(SEL)action withObject:(id)obj {
if (self = [super initWithTitle:title style:style target:target action:action]) {
self.anObject = obj;
}
return self;
}
#end
Then this could then be implemented like so:
#import "RootViewController.h"
#import "RCBarButtonItem.h"
#implementation RootViewController
- (void)viewDidLoad {
[super viewDidLoad];
RCBarButtonItem *button = [[RCBarButtonItem alloc] initWithTitle:#"Hello"
style:UIBarButtonItemStylePlain
target:self
action:#selector(doSomething:)
withObject:#"Bye"];
self.navigationItem.rightBarButtonItem = button;
}
- (void)doSomething:(id)sender {
NSLog(#"%#", [(RCBarButtonItem *)sender anObject]);
}
What I've done in this situation is create an NSDictionary property called, say, buttonArguments:
self. buttonArguments = [[NSDictionary alloc] initWithObjectsAndKeys: usingThis, Button, ... , nil];
Then in your doSomething: method, look up the object based on the sender parameter.
I prefer using categories:
UIBarButtonItem+BarButtonItem.h
#interface UIBarButtonItem (BarButtonItem)
#property (strong, nonatomic) NSDictionary *userInfo;
#end
UIBarButtonItem+BarButtonItem.m
static void *kUserInfo = &kUserInfo;
#implementation UIBarButtonItem (BarButtonItem)
- (NSDictionary *)userInfo {
return objc_getAssociatedObject(self, kUserInfo);
}
- (void)setUserInfo:(NSDictionary *)userInfo {
objc_setAssociatedObject(self, kUserInfo, userInfo,
OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
#end