I am making an app where there is a test. I made a mistake of having different classes for each like 10 questions, to group them. Now I have a working stopwatch in the first class, but obviously it stops when i go to the next class (10 questions), how can I access the NSTimer in the other class from the new class and CONTINUE the timer. Here is my timer code:
- (void)viewDidLoad {
gameTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(gameTimerVoid) userInfo:nil repeats:YES];
[super viewDidLoad];
}
-(void)gameTimerVoid {
int now;
now = [gameTime.text intValue];
int after;
after = now + 1;
gameTime.text = [NSString stringWithFormat:#"%i", after];
}
In your case the timer seems the sharable assets across most of your files So you would consider having it in the separate class and could be accessed by any files (class) from there,then for doing such you need to use the singleton class design pattern,
Relocate the timer from your question group class to whatever class should logically manage the timer. It may seem like too much work to refactor your code now, but the pain will only get worse if you let this sort of problem fester.
Related
I am using Objective-C, Xcode 4.5.1 and working on an app for the iPhone.
I have a method A in which I want to call another method B to do a series of calculations every x seconds. In method A I start playing an audio file. Method B will monitor the audio every x seconds for the duration of the audio file.
I have found NSTimer as a potential solution, but am having a hard time getting it to work/understanding it.
I simply want to call Method B every x seconds and run its calculations, but NSTimer requires me to provide several things of which I'm not sure what I'm supposed to tell it.
[NSTimer scheduledTimerWithTimeInterval:(NSTimeInterval)
target:(id) select:(SEL) userInfo:(id) repeats:(BOOL)];
It is my understanding that at NSTimeInterval I provide the interval at which I want NSTimer to operate. But, how do I tell it to run Method B?
I have looked at example code, and am currently under the impression that I provide the method at the 'select:'. But, what do I write at the 'target:'? Why would I need a target? I tried entering 'self', but Xcode tells me:
Use of undeclared identifier 'self'
[NSTimer scheduledTimerWithTimeInterval:0.1 target:self
select:#selector(targetMethod:myVolumeMonitor()) userInfo:nil repeats:YES];
So, I figure 'self' is supposed to be a pointer to an object, but where do I want to point to?
Below is a simplification of my code:
MethodA()
{
//Start playing an audio file.
//NSTimer calling Method B, as long the audio file is playing, every x seconds.
}
MethodB()
{
//Do calculations.
}
I would be grateful if somebody could provide me with some answers/point me in the right direction! (:
Target is the recipient of the message named in select.
In Objective-C functions are not called. There are rather messages sent to objects. The Object internally refers to its symbol table and determines which of its methods is being called. That is a selector. Your selector is #selector(MethodB).
(BTW: you should start method names with lower case. "methodB" would be more appropriate here.)
This leads to the question: how to determine the object to which the message is sent? That is the target. In your case, it is simply self.
BTW: In this case the selector is expected to return void and accept an id, which is the id of the NSTimer object itself. That will come handy if you want the timer to stop firing based on some conditions according to your program logic.
Most important: Your selector is then methodB: rather than methodB.
- (void) methodA
{
//Start playing an audio file.
//NSTimer calling Method B, as long the audio file is playing, every 5 seconds.
[NSTimer scheduledTimerWithTimeInterval:5.0f
target:self selector:#selector(methodB:) userInfo:nil repeats:YES];
}
- (void) methodB:(NSTimer *)timer
{
//Do calculations.
}
try this
NSTimer *aTimer = [NSTimer timerWithTimeInterval:(x) target:self selector:#selector(timerFired:) userInfo:nil repeats:YES];
NSRunLoop *runner = [NSRunLoop currentRunLoop];
[runner addTimer:aTimer forMode: NSDefaultRunLoopMode];
[popUpImageView release];
- (void)timerFired:(NSTimer*)theTimer
{
if(condition)
{
[theTimer isValid]; //recall the NSTimer
//implement your methods
}
else
{
[theTimer invalidate]; //stop the NSTimer
}
}
If you look at your code and compared to the one below
[NSTimer scheduledTimerWithTimeInterval:0.1 target:self
select:#selector(targetMethod:myVolumeMonitor()) userInfo:nil repeats:YES];
self means that you are invoking a method in same instance of your class, in your example the method is myVolumeMonitor
[NSTimer scheduledTimerWithTimeInterval:0.1 target:self
selector:#selector(MethodB) userInfo:nil repeats:YES];
and you are good to go though
method be should look like this
- (void)MethodB:(NSTimer*)timer {
// do something
}
Well you are trying to call an normal C method, NSTimer can't to that.
The target is the an instance of the class on which to call the selector, this selector adn not select. The selector here is a SEL type which you can create with the #selector(METHOD_NAME) function.
For example this will call the handleTimer : ever 0.1 second: (For this example the AppDelegate is used):
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
//SNIP, some code to setup the windos.
[NSTimer scheduledTimerWithTimeInterval:0.1f target:self selector:#selector(handleTimer:) userInfo:nil repeats:YES];
return YES;
}
- (void) handleTimer:(NSTimer *)timer {
// Hanlde the timed event.
}
Im making a class that creates a timer that then calls a void using #selector or (SEL). The app crashes when [mytimer timer…]; is called because "unknown selector called". The error is that it isnt finding the void I'm giving to it but i know that the void works fine. My question is how to I write the myselector part correctly?
+ (void) timer:(NSTimer*)timer interval:(int)interval selector:(SEL)myselector
{
timer = [NSTimer scheduledTimerWithTimeInterval:interval
target:self
selector:myselector
userInfo:nil
repeats:YES];
}
This is the method being implemented:
[mytimer timer:NameOfTimerTheWorks interval:0.1 selector:#selector(myVoid)];
...
- (void)myVoid
{
Do stuff
}
Also I understand that this class looks completely useless but there is more to it that I didnt include. My problem revolves around the selector.
There's nothing wrong with your use of #selector(), your issue is with Objective C classes, and what self means in different scopes.
The problem is that you're creating your timer in a class + method and self at that point is not the instance you want, it's actually the Class itself. I wouldn't be doing it in a class method, but if you must, just pass in the reference to the target and it should send th message your'e expecting.
Change your implementation to the following so you pass in the target, and return the timer
+ (NSTimer *)timerWithTarget:(id)target
interval:(int)interval
selector:(SEL)myselector
{
return [NSTimer scheduledTimerWithTimeInterval:interval
target:target
selector:myselector
userInfo:nil
repeats:YES];
}
NameOfTimerTheWorks = [mytimer timetWithTarget:self interval:0.1 selector:#selector(myVoid)];
- (void)myVoid
{
//Do stuff
}
Additionally, in Objective C the standard way to name classes is with a capital letter, and camel case where appropriate. Your class should be MyTimer and not mytimer and iVars are lowercase eg nameOfTimerTheWorks.
I believe the problem is that you're trying to access an instance level method using a class level method.
I changed + to - so that it was an instance level method.
If you wanted your method to be class level it would require that you make the method you want to call also class level.
- /* <-- + */(void) timer:(NSTimer*)timer interval:(int)interval selector:(SEL)selector
{
timer = [NSTimer scheduledTimerWithTimeInterval:interval
target:self
selector:selector
userInfo:nil
repeats:YES];
}
I've got a GameScreen.m file like (this is a simplified piece of the code):
- (IBAction) onCellClick:(id) sender
{
points +=1;
self.myScore.text = [[NSNumber numberWithInt: points] stringValue];
//myScore is a label in GameScreenViewController xib
}
That is, upon clicking a cell in the view, it will increase a text label by 1. So far so good.
then, in the same code, I've got a timer:
- (void) startTimer
{
[NSTimer scheduledTimerWithTimeInterval:1.0f
target:self
selector:#selector(updateCounter:)
userInfo:nil
repeats:YES];
}
its updateCounter method is:
- (void) updateCounter:(NSTimer *)theTimer
{
int seconds;
static int count = 0;
count +=1;
timeElapsed = [[NSString alloc] initWithFormat:#"%d", seconds + count];
self.time.text = timeElapsed;
//time is a label in GameScreenViewController xib
}
the thing is that "time" label is not updated (1 sec each time) in this case. I've inserted an AlertView to check if the startTimer method is valid and correctly called, and it actually is (it shows an annoying alertview each second with the timeElapsed value). However, I can' get the time label value to be changed.
Why is my score label updated upon action, while time label isn't updated every second? Is there any way I can update it without including my code in the ViewController?
//note: my coding splits into three files: the appDelegate flips screens and sends values among them; my viewControllers just the windows and, finally, my GameScreen class manages all the processes. From the xib, File's Owner is connected to the ViewController, and the view is connected to GameScreen class.
Thanks a lot for any feedback, please feel free to ask for any piece of additional code needed.
You have to do that (UI related operations) in main thread.
Instead of the line,
self.time.text = timeElapsed;
do as follows:
[self.time performSelectorOnMainThread:#selector(setText:) withObject:timeElapsed waitUntilDone:NO];
Edit:
- (void) updateCounter:(NSTimer *)theTimer
{
//int seconds;
static int count = 0;
count +=1;
NSString *timeElapsed1 = [[NSString alloc] initWithFormat:#"%d", count];
[self.time performSelectorOnMainThread:#selector(setText:) withObject:timeElapsed1 waitUntilDone:NO];
[timeElapsed1 release];
//time is a label in GameScreenViewController xib
}
I have gone through an UGLY walkaround. It works to some extent, but I went through such a crappy fix that I'm too embarassed to share...
Basically, I moved my timer directly to ViewController since I want it to be fired upon view load and can't get it to work with a call from ViewController's -(void)viewDidLoad to GameScreen's -(void) startTimer. All other stuff involving both methods is, pretty much, duplicated (ok, not duplicated, let's say 'polymorphed' since I handle some variables to fire them).
It seems my GameScreen.m IBActions can only fire other methods within my GameScreen.m, not on GameScreenViewController.m. Thus, I'm handling my buttons' behavior on GameScreen.m and, on GameScreenViewController.m, I just handle 'automatic' stuff; that is, anything not depending on user interaction. It made me have some IBOutlets duplicated depending on input/output needed so I guess that, since it's now working, you can't tell the difference if you don't go under the hood...
Thanks everyone for their feedback though.
I have an NSTimer that has an NSNumber in its userInfo
fireTimer = [NSTimer scheduledTimerWithTimeInterval:6.0
target:self
selector:#selector(fireTileAddingToColumn:)
userInfo:myNumber
repeats:YES];
After the NSTimer is created and has run a couple of times, I would like to be able to change the value of myNumber and have it reflect in fireTileAddingToColumn: I have not had any luck getting this to work. Does anyone have any suggestions?
You can always pass an object holding the userInfo to the timer:
#interface Holder
#property id data;
-(id) initWithData: (id) data;
#end
// implement it
Holder *holder = [[Holder alloc] initWithData:myNumber];
fireTimer = [NSTimer scheduledTimerWithTimeInterval:6.0 target:self
selector:#selector(fireTileAddingToColumn:) holder
repeats:YES];
[Holder setData: myNumber2];
and the data change will be reflected in the selector
You're best off creating a new timer. If the class doesn't provide an interface for changing that attribute, then you should consider it private and read-only.
It's not even possible in this case to do the usual end run around that, using KVC:
[timer setValue:newNumber forKey:#"userInfo"];
since NSTimer is not KVC-compliant for that key.
The userInfo property of an NSTimer isn't intended to be a general-purpose data storage facility. It's there so that you can have some context go along with your timer, so that the target of the timer can distinguish one timer from another if multiple timers invoke the same action.
Lazy people's mutable userInfo:
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:6.0
target:self
selector:#selector(fireTileAddingToColumn:)
userInfo:#{#"mutable": [#{#"key": <value>} mutableCopy]
repeats:YES];
And to access it:
timer.userInfo[#"mutable"][#"key"] = <newValue>;
I am looking for an Objective-C function to call a function to update the UI at a specified interval (between 2 and 5 seconds). Something that can be used (roughly) like this pseudocode:
array[String]; // already populated
function1(){
if (array.hasMoreElements()){
call function2() in x seconds;
}
}
void function2(){
update gui with next string in the array;
function1();
}
I simply can't use sleep() for x seconds, because the GUI will become unresponsive; and I can't create a new thread to update the GUI because the iOS UI elements are not thread safe.
I have researched ualarm(), but it is old and very crude, and someone told me that there is a similar utility in the iOS library; however I have not been able to find it.
You're talking about Obj-C but writing something like C pseudocode. If you're really writing normal Obj-C, the -performSelector:withObject:afterDelay is the general family you want that drops in here. (You can use "blocks" on iOS 4.x and above). For example in your use case:
- (void)displayNextString
{
if ([strings count] > 0) {
[self updateUIwithString:[strings objectAtIndex:0]];
[strings removeObjectAtIndex:0];
[self performSelector:#selector(displayNextString) withObject:nil afterDelay:2.0];
}
}
You may use NSTimer. This API fits perfectly what you want to do.
You can simply define a method like
-(void)updateUI {
//update interface
}
then do something like
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:interval target:self selector:#selector(updateUI) userInfo:nil repeats:YES];
This way the timer will continue call the method you set. Check the reference to see how to stop or invalidate the timer.