Objective-C: Accessing integer value of a ViewController in View? - ios

I'm trying to start a NSTimer in my UIView class called "ClockView" with a method as selector that manipulates an initial float which was declared in the ViewController "ClockViewController".
My ClockViewController declares int timerIntWhite as an integer (for example 500). My ClockView needs this Value for the - (void)start method which runs a method called - (void)updateWhiteClock every second:
- (void)start {
timerIntWhite = PLEASE HELP ME AT THIS POINT!;
randomTimerWhite = [NSTimer scheduledTimerWithTimeInterval:(1.0/1.0)target:self selector:#selector(updateWhiteClock) userInfo:nil repeats:YES];
}
Is it possible to access the integer of ClockViewController in ClockView?

Try the following:
In your ClockView also declare the variable (property) int timerIntWhite and set this variable from your View Controller after the View gets created, for example, in viewDidLoad.
-(void)viewDidLoad {
[super viewDidLoad];
self.view.timerIntWhite = self.timerIntWhite;
}
After doing this, ClockView can access it's own timerIntWhite variable:
- (void)start {
timerIntWhite = self.timerIntWhite;
randomTimerWhite = [NSTimer scheduledTimerWithTimeInterval:(1.0/1.0)target:self selector:#selector(updateWhiteClock) userInfo:nil repeats:YES];
}
I'm assuming that your ClockViewController class knows that its view IS-A ClockView. This is very important! Otherwise you'll get a warning.
I also want to mention that according to the MVC rules it's a better idea if your ClockViewController class takes care of the NSTimer. Views should be used to display information to the user only.
Hope this helps!

Related

pass info from Model to viewController without NSNotification

In my viewController, I have a button that calls a method to start a timer in a Timer class. Just like this
main view controller
[self.timer startTimer];
In the timer class, startTimer calls a countdownTime method, both of which you can see below. Very simple. At the end of the countdownTime, method, the time is put in the label in the view like this as I iterate through a loop of the clocks.
Timer *timerblah = self.gameClocks[i];
[self.gameClocks[i] setText: [NSString stringWithFormat:#"%#", self.time]];
In other words, the Timer class has an array property that holds all the clocks and that is connected to the view, hence the ability to set the text.
The problem with this code is that the model (i.e. Timer class) is setting the text in the view. I want the view Controller to get the time from the model and have the text set in the view controller i.e. have the viewController and only the viewController communicate with the view. It is no problem for me to get the array of clocks in the viewController, however, I'm sure how to pass the time back from the countdownTime method to the viewController. It would seem like overkill to set up an NSNotification class to send the time back every second, wouldn't it?
-(void)startTimer{
self.NStimer = [NSTimer scheduledTimerWithTimeInterval:1.0
target:self
selector:#selector(countdownTime:)
userInfo:nil
repeats:YES];
}
-(void)countdownTime:(NSTimer *)timer
{
#logic to countdown time
self.minutes = self.secondsRemaining / 60;
self.stringMinutes = [NSString stringWithFormat:#"%i", self.minutes];
self.seconds = self.secondsRemaining - (self.minutes * 60);
self.stringSeconds = [NSString stringWithFormat:#"%i", self.seconds];
if (self.seconds < 10) self.stringSeconds = [NSString stringWithFormat:#"0%i", self.seconds];
self.time = [NSString stringWithFormat:#"%#:%#", self.stringMinutes, self.stringSeconds];
self.secondsRemaining -= 1;
if (self.secondsRemaining == 0){
[self stopTimer];
}
#adding to the view (from the model i.e. in Timer.m class)
for(int i = 0; i < [self.gameClocks count]; i++){
Timer *timerblah = self.gameClocks[i];
if (timerblah.systemClock == YES){
[self.gameClocks[i] setText: [NSString stringWithFormat:#"%#", self.time]];
}
}
}
Update
There's a comment suggesting that I use a block to do this. In the timer class, I added a method like this to return a string with the time (the time is set to self.time above)
-(NSString *)passTheTime:(NSString (^)(void))returnBlock
{
return self.time;
}
I then, in view controller, call the method passTheTime on the timer class. I created a property in the view controller that will store the time, so I pass that as a parameter to the block,
[self.timer passTheFoo:^NSString * (self.timeToSet){
}];
a) I'm unsure of what to do in the block here.
b) I'm unsure of whether it was necessary to pass self.timeToSet into the block. How do I connect self.time from the Timer class to self.timeToSet in the view controller
c) there's an error incompatible block pointer type sending NSString *((^)(void)) to parameter of type NSString(^)(void)
d) alternatively, can I pass a block to startTimer, and have that block passed in the selector countdownTime and return the self.time once the calculations in countdownTime are finished?
You can use block to update the label in the view controller class as below
In Timer Class, create a block
TimerClass.h
#import <Foundation/Foundation.h>
typedef void(^TimerCallback)(NSString *time);
#interface TimerClass : NSObject{
TimerCallback timerCallback;
NSTimer *timer;
}
- (void)startTimerWithCallBack:(TimerCallback)callback;
#end
In TimerClass.m , update the code as below
#import "TimerClass.h"
#implementation TimerClass
- (void)startTimerWithCallBack:(TimerCallback)callback{
timerCallback = callback;
timer = [NSTimer scheduledTimerWithTimeInterval:1.0
target:self
selector:#selector(countdownTime:)
userInfo:nil
repeats:YES];
}
-(void)countdownTime:(NSTimer *)timer
{
// countdown logic here
// call the callback method with time as a parameter to the viewcontroller class
// here for demo purpose I am passing random number as a value in callback, you need to pass the time that you want to display here
NSString *str = [NSString stringWithFormat:#"%d",1+arc4random()%10];
timerCallback(str);
}
#end
In ViewController class, call the timer start method as follows
TimerClass *timer = [[TimerClass alloc] init];
[timer startTimerWithCallBack:^(NSString *time) {
lbl.text = time; // display time in label
}];

NSTimer stops after incrementing once

I have an NSTimer which I want to update a label every second. My code is:
- (IBAction)OnClickEmergencyButton:(id)sender
{
emergencyAlertTimer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(emergencyTimer) userInfo:nil repeats:YES];
[emergencyAlertTimer fire];
}
- (void)emergencyTimer
{
int i = 0;
_emergencyAlertTriggerTimerLabel.text = [NSString stringWithFormat:#"%d", ++i];
}
When I ran it, the label displayed "1" initially and then stopped.
I want the label to continuously count up every second, like "1", "2", "3", ...
There is no issue with your timer. The issue is with the variable declaration inside the emergencyTimer, you declared it as a local variable. So each time when the timer fires the variable will be initialized to 0 again. So declare the variable as static, so that it can preserve the value.
Change the method like:
-(void)emergencyTimer
{
static int timeValue = 0;
_emergencyAlertTriggerTimerLabel.text = [NSString stringWithFormat:#"%d",++timeValue];
}
Why static variable and Why not instance variable ?
I didn't used instance variable, for keeping the variable "Scope" safe. If I put it as an instance variable, it can be accessed by other methods of the same class, if there is no need of that functionality, I think using a static variable will be better.
Issue is with this code
int i=0;
Every time when timer method gets called, the integer i gets initialized and label will be displayed as "1" always.
Make this variable global or static to fix your issue.
Remove int i=0; from your timer action method because it will always have a value of zero. You should be using an instance variable (#property) to store the timerCounter and increment that and use it to populate the label.
At some point in time you need to invalidate the timer. This is particularly important to do before you create a new timer and replace the reference to the old timer. Currently, if you press the button twice, you will then have 2 timers running and your label will increment twice a second...
#property (nonatomic, assign) NSInteger timerCounter;
- (IBAction)OnClickEmergencyButton:(id)sender {
[emergencyAlertTimer invalidate];
emergencyAlertTimer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(emergencyTimer) userInfo:nil repeats:YES];
[emergencyAlertTimer fire];
}
- (void)emergencyTimer {
_emergencyAlertTriggerTimerLabel.text = [NSString stringWithFormat:#"%d", self.timerCounter];
self.timerCounter++;
}
You should also invalidate the timer when the view is removed from display / deallocated.
Always timer get called emergencyTimer but your value of i won't changed because it's an local variable, scope of i will remain at end of function call. Try this with static variable which remain globally...
-(void)emergencyTimer{
static int i=0; // initialize at first time only..
_emergencyAlertTriggerTimerLabel.text = [NSString stringWithFormat:#"%d",++i];
if ( i == 100)
[ emergencyAlertTimer invalidate] // stop at certain condition
}
Firstly everyone is right you will only display a 0 no matter what you do currently so use an instance variable.
With regards to the only firing once, instead of [NSTimer fire] try this:
emergencyAlertTimer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(emergencyTimer) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:emergencyAlertTimer forMode:NSRunLoopCommonModes];

Call custom Class' obj of type NStimer into viewController

In the sample tutorial I found, I noticed a timer made using NStimer class: it was implemented directly into viewController.m
I tried to make it as "separate" obj class in it's own timer.m and relative header.
This is what i got
#import "Timer.h"
#implementation Timer
-(void) startTimer{
seconds = 31;
myTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(subtractTime:) userInfo:nil repeats:YES];
NSLog(#"%i",seconds);
}
-(void)subtractTime: (Timer*)myTimer{
seconds--;
NSLog(#"seconds %i",seconds);
if (seconds == 0) {
[myTimer invalidate];
}
}
#end
According to the output in NSLog, the countdown itself works perfectly; my issues start when i try to display it into a label using the form label.text = into the viewController.m
(here's just the method implementation part)
(Xcode gives me no error bout #implementation part both in Timer.h and viewController.h, also project build runs ok but the countdown into the label is locked to 0)
-(void)setupGame{
count = 0;
scoreLabel.text = [NSString stringWithFormat:#"Score:\n%i",count];//these strings are to make a score label increasing of 1 every time a button's pressed
Timer *newTimer = [[Timer alloc]init]; //i create a new obj of my Timer class
[newTimer startTimer];
[newTimer subtractTime:(Timer*) myTimer]; //i set the methods i created
timerLabel.text = [NSString stringWithFormat:#"%i",seconds]; //??? i don't know which var put here, actually, i cannot get here the refreshing seconds to make timerLabel changing
NSLog(#"newTimer %#",myTimer);//just a try to see this output, it's not what i need of course
}
What am i doing wrong?
There are a couple of issues here. Firstly make Timer an instance variable of the view controller as when you do this:
-(void)setupGame{
// blah
Timer *newTimer = [[Timer alloc]init];
// blah
}
The Timer object will be destroyed when this method returns, as the object goes out of scope.
The second problem is that you need to update the UI control when the time changes, so perhaps pass a reference to the UI control (UILabel or whatever it is), and store this as an instance variable of the Timer class, so the timer object can update it itself.
In this particular case I don't see the value of a separate Timer object as it would be easier to implement what you want just within the view controller class.
As Trojanfoe says, you are over-complicating this. Make your view controller create the timer and be done with it.
If you are bound and determined to create a custom class to manage a timer then you have a bunch of work to do.
I would NOT pass the label to the Timer object, as that breaks the encapsulation of the view controller. You should always treat a view controller's views as private to that view controller.
Instead, here's what I would do:
You need to add a Timer instance variable to your view controller, as Trojanfoe says.
You need to define a TimerProtocol. In that protocol, define a "timerFired" method, that would include the remaining time value as a parameter. You probably also want a "timerFinished" method
When you create a Timer object, set yourself up as the delegate of that timer.
Rewrite your Timer object's subtractTime method to send a message to the delegate with the remaining time value. Then in the view controller's timerFired method, update the label.
In your view controller's timerFinished method, nil out the instance var to the Timer object so it will be deallocated, since you then done with it.
Again, this is an over-complicated solution that does not add any value. The only reason to do it this way is as a learning exercise, but it is not a good design decision.

new to ios - unrecognized selector sent to instance message

here is my code:
-(void)flash_random_winning_number:(NSInteger)param_which_magic_number
{
NSInteger dd = 9;
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:.5
target:self
selector:#selector(ShowLabel22:dd:)
userInfo:Nil
repeats: YES
];
}//flash_random_winning_number
the problem is with selector:#selector(ShowLabel22:dd:) because I am sending a parameter to this method called ShowLabel22:
-(void)ShowLabel:(NSInteger)param_which_magic_number
{
random_magic_number1.hidden = NO;
}
however if I were to remove the parameters from all this code, then there is no error. Therefore it seems as if I have mistake in the way I am using parameters.
The selector you pass to scheduledTimerWithTimeInterval:target:selector:userInfo:repeats: is akin to the name of a function. What you’re trying to do is to pass in the name of a function and the value of its parameters, but that won’t work. The method you name using #selector(...) will be passed exactly one argument: the NSTimer object calling the method.
If you have data you need to make available to ShowLabel it needs to be attached to the timer object or to self or something like that. To attach your number to the timer object you could do this:
- (void) flash_random_winning_number:(NSInteger) param_which_magic_number
{
NSInteger dd = 9;
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:.5
target:self
selector:#selector(ShowLabel:)
userInfo:#(dd)
repeats:YES];
}
- (void) ShowLabel:(NSTimer *)timer
{
NSInteger dd = [[timer userInfo] integerValue];
/* do stuff with dd */
}
(Since you’re new to Objective-C I want to mention the conventions that have arisen about method naming. The usual style, which you should always use, is for methods to be named with camelCasedNamesLikeThis. In particular, flash_random_winning_number and ShowLabel are both likely to confuse other Objective-C developers because they don’t “look like” method names. ShowLabel22 is also weird because of the numbers, but since you included that in one place in your question but not another I guess that’s just a typo.)
Look at this SO answer. When you have a selector with arguments you have to use this method:
[self performSelector:#selector(myTest:) withObject:myString];

How do I invalidate timer when I don't create NSTimer object

I don't want to create NSTimer object. How do I invalidate timer? I want to invalidate timer in viewWillDisappear.
-(void) viewDidLoad
{
[super viewDidLoad];
[NSTimer scheduledTimerWithTimeInterval:10 target:self selector:#selector(onTimer:) userInfo:nil repeats:YES];
}
A
you have to hold on to the timer you create:
#interface MONObject ()
#property (nonatomic, retain) NSTimer * timerIvar;
#end
#implementation MONObject
...
- (void)viewDidLoad
{
[super viewDidLoad];
self.timerIvar = [NSTimer scheduledTimerWithTimeInterval:10 target:self selector:#selector(onTimer:) userInfo:nil repeats:YES];
}
- (void)invalidateTimer
{
[self.timerIvar invalidate];
self.timerIvar = nil;
}
- (void)viewWillDisappear:(BOOL)animated
{
...
[self invalidateTimer];
}
B
another option would be to invalidate the timer that is passed in the callback, but that won't occur within viewDidUnload:. therefore, it doesn't quite apply in this scenario:
- (void)onTimer:(NSTimer *)pTimer
{
[pTimer invalidate];
}
If you want to be able to cancel the timer, you have to refer to the timer you’re cancelling, and that means you have to keep the pointer to the timer around, see justin’s answer.
Keeping a reference to the timer is the right way to do it, but for the sake of completeness you may also use the -performSelector:withObject:afterDelay: method as a poor man’s timer. That call may be invalidated using +cancelPreviousPerformRequestsWithTarget:. Sample code:
- (void) viewDidLoad
{
[super viewDidLoad];
[self performSelector:#selector(timerTick) withObject:nil afterDelay:10];
}
And then:
- (void) viewWillDisappear
{
[NSObject cancelPreviousPerformRequestsWithTarget:self];
[super viewWillDisappear];
}
But this is not the right way to do it, because there might be other perform-selector requests pending on your object that you would cancel. It’s best to keep your timer around, that way you know exactly what you’re cancelling.
By the way, it’s also probably a bad idea to run a timer in -viewDidLoad. View loading may happen anytime, without any relation to view being displayed.
Maybe this method can help you:
[self performSelector:#selector(onTimer:) withObject:nil afterDelay:10];
If you don't want to hold on to your timer, the NSTimer object will be passed to the timer method (in your case onTimer:), so in that method you could check whether the timer is still needed and invalidate it. However, you will run into trouble if the view comes back before you invalidated the timer, and you create a new one.
By far the best way is to store the timer into an instance variable. It works, no clever tricks, and you'll know six months later what you did. I'd probably write a
#property (readwrite, nonatomic) BOOL hasTimer;
getter returns YES iff the timer is not nil, setter invalidates the timer or creates a new one.

Resources