currentTimeViewController problems/questions


#1

Hi there
I’m using Xcode 4.2, arc, and storyboard mode.

I couldn’t link a storyboard view controller to my currentTimeViewController class, so I created a xib file as the book said to do, and it worked fine.

Except for the viewWillAppear method:

this is my method:

- (void)viewWillAppear:(BOOL)animated { NSLog(@"CurrentTimeViewController will appear"); [super viewWillAppear]; [self showCurrentTime:nil]; }

When I switch the view to time, the console shows the proper NSLog message, so I know it’s calling the method, but the time does not update.
My question is: what is going on when I set showCurrentTime: to nil?
showCurrentTime: is a method that is called when I press the button. What does sending nil to it do?
I can understand setting the formatter variable to nil, but apparently that isn’t happening. Also, since the variable is declared inside the method, I was unable to set it to nil outside the method. There are workarounds that I can think of to make the program behave as expected, but I just wanted to understand what’s going on here.

this is my showCurrentTime method:

[code]- (IBAction)showCurrentTime:(id)sender
{
NSDate *now = [NSDate date];

// Static here means "only once." The *variable* formatter
// is created when the program is first loaded into memory.
// The first time this method runs, formatter will
// be nil and the if-block will execute, creating
// an NSDateFormatter object that formatter will point to.
// Subsequent entry into this method will reuse the same
// NSDateFormatter object.

static NSDateFormatter *formatter = nil;

if (!formatter) {
    formatter = [[NSDateFormatter alloc] init];
    [formatter setTimeStyle:NSDateFormatterShortStyle];
    [timeLabel setText:[formatter stringFromDate:now]];
}

}
[/code]

Thanks so much for any insight.


#2

ok. here’s what I figured:

the viewWillAppear: method below

- (void)viewWillAppear:(BOOL)animated { NSLog(@"CurrentTimeViewController will appear"); [super viewWillAppear]; [self showCurrentTime:nil];

is calling the showCurrentTime: method, which sets the time the first time, but does nothing after that, cause in that method, formatter is set, and everything happens inside an if (!formatter) block. It makes the button irrelevant, and the time is displayed as soon as I switch to the time view.

So if I want the time to update every time I switch to that view, without having to press the button a second time (but having to press it the first time), I have to do the following changes:

  • Move the formatter variable declaration to the CurrentTimeViewController header file (without ‘static’), so that formatter can be used by the viewWillAppear: method.
  • Change the viewWillAppear: method as follows:

- (void)viewWillAppear:(BOOL)animated { NSLog(@"CurrentTimeViewController will appear"); [super viewWillAppear]; if (formatter) { formatter = nil; [self showCurrentTime:nil]; } }

I set formatter to nil and run the showCurrentTime: method only if there is already a formatter, which means the button was pressed once.

I welcome any thoughts, views, and whatnots :slight_smile:


#3

Hi,

When you call [self showCurrentTime:nil]; you are executing the showCurrentTime method and passing a nil argument.

The signature of showCurrentTime is - (IBAction)showCurrentTime:(id)sender

So,
IBAction is defined as void and is used as a means to get InterfaceBuilder to recognise the method. So this method doesn’t return anything.
sender can be any object (id) and when hooked up using InterfaceBuilder it is set (behind the scenes) to the UI control that invoked the method.
If you hooked up a UIButton in InterfaceBuilder to invoke the showCurrentTime method then sender would be that UIButton. If you hooked it up to a UISwitch then sender would be that UISwitch.

So in this case we are triggering showCurrentTime outside of InterfaceBuilder and therefore there is no UIButton or UISwitch that is relevant and so we just send nil.

The static keyword can be a bit confusing.
If you declare an ordinary int within a method then it only lives for the duration of that method. If you declare it as static then it is initialised once and keeps it’s state for the duration of the whole program.

(From Wikipedia)

void func() {
static int x = 0; // x is initialized only once across three calls of func()
NSLog(@"%d", x); // outputs the value of x
x = x + 1;
}

int main(int argc, char * const argv[]) {
func(); // prints 0
func(); // prints 1
func(); // prints 2
return 0;
}

If you took out the static keyword you would get 0,0,0

So to finally get to the point of your question :unamused:

static NSDateFormatter *formatter = nil;

if (!formatter) {
    formatter = [[NSDateFormatter alloc] init];
    [formatter setTimeStyle:NSDateFormatterShortStyle];
    [timeLabel setText:[formatter stringFromDate:now]];
}

The first time this runs - formatter is nil and so the if(!formatter) is true and therefore formatter will be created and the timeLabel text will be set.

Second time around and thereafter - formatter is not nil and so none of this code will run.

So what you should do is just move the setText outside of the if statement.

static NSDateFormatter *formatter = nil;

if (!formatter) {
    formatter = [[NSDateFormatter alloc] init];
    [formatter setTimeStyle:NSDateFormatterShortStyle];
}

[timeLabel setText:[formatter stringFromDate:now]];

Then, first time through a formatter will be created and setText timeLabel text will be set.
Second time through - formatter is already created so only the setText will run and the time will be updated.

HTH
Gareth


#4

Hi,

Just noticed the bit about wanting to press the button the first time but not thereafter.

How about:

static NSDateFormatter *formatter = nil;

if (!formatter) {
    formatter = [[NSDateFormatter alloc] init];
    [formatter setTimeStyle:NSDateFormatterLongStyle];
    [timeLabel setText:@""];        
}
else
    [timeLabel setText:[formatter stringFromDate:now]];

Gareth


#5

Gareth. Thanks so much. Your explanation really took me baby step by baby step and I got everything.

I had totally forgotten about IBAction being set as void. It was something I read in the book, but then I never retained it or understood it in a practical manner until now.

Also, your solution is elegant and works perfectly. That way we’re not setting formatter to nil and then back again every time.
wow

thanks!!! :smiley: