views:

285

answers:

4

Hello fellows. I'm just trying to get my head around the Objective-C event model on iPhone, and by the looks of things I have fundamentally misunderstood something here.

For the purpose of experimentation, in a view controller's -viewDidLoad method, I am setting a UILabel's text, then sleeping for two seconds, and then changing the label's text again.

My expectations are as follows: the label will first read, "First Text", then two seconds later it will be updated to read, "Second Text." Of course, this isn't quite how it happens. Instead, the view isn't visible at all for two seconds, and finally when it becomes visible its label reads, "Second Text."

Could somebody please explain to me what is going on? I'm interested to find out how you guys would achieve what I'm going for here.

Cheers.

UPDATE 1: Here's the viewDidLoad method:

- (void)viewDidLoad {
    [super viewDidLoad];
    label.text = @"First Label";
    sleep(2);
    label.text = @"Second Label";
}

UPDATE 2: I made a silly mistake here, so please ignore this update.

UPDATE 3: I have now added the following to my viewDidAppear method:

- (void)viewDidAppear: (BOOL)animated {
    [super viewDidAppear: animated];
    label.text = @"First Label";
    sleep(2);
    label.text = @"Second Label";
}

Unfortunately I'm having exactly the same problem.

UPDATE 4: Following gerry3 and Felix's suggestions, I have now implemented a performSelector, and boom! Works a treat!! I'm going to have to give it to gerry3 though as he certainly put the most amount of effort into helping me out. Thanks for all your contributions!

+1  A: 

Put your code that modifies the view in viewDidAppear:.

The view is not yet visible when viewDidLoad and viewWillAppear: are called.

UPDATE
Just to be clear, I agree with the others that the correct way to do this is with a delayed method call or timer.

UPDATE 2
Here is the code that I am suggesting:

- (void)viewDidLoad {
    [super viewDidLoad];
}

- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];

    NSLog(@"View will appear!");
}

- (void)viewDidAppear:(BOOL)animated {
    [super viewDidAppear:animated];

    NSLog(@"View did appear!");

    label.text = @"First Label";
    sleep(2);
    label.text = @"Second Label";
}

And the "correct" way:

- (void)viewDidLoad {
    [super viewDidLoad];

    label.text = @"First Label";
}

- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];

    NSLog(@"View will appear!");
}

- (void)viewDidAppear:(BOOL)animated {
    [super viewDidAppear:animated];

    NSLog(@"View did appear!");

    [self performSelector:@selector(changeLabelText) withObject:nil afterDelay:2.0f];
}

- (void)changeLabelText {
    label.text = @"Second Label";
}
gerry3
Cheers Gerry. I have updated my original question with viewDidAppear and viewWillAppear methods.
David Foster
You must use the correct method signatures: both the view*Appear: calls have a boolean argument (typically "animated"). This is why I ended their names with a colon. Also, you should call super for both and pass the argument.
gerry3
Perfect. That last chunk of code is precisely what I'm looking for. Thanks a bunch Gerry.
David Foster
+2  A: 

I guess the reason is that viewDidLoad runs on the mainThread as do all UIKit interactions. The UI can only be updated on the mainThread hence if you block the viewDidLoad with sleep(2) you are putting the mainThread to sleep and halt all userinterface updates on that thread.

Use NSTimer if you want to update the UI after a certain amount of time, rather than using sleep(2). Or use performSelector:withDelay: on self to perform a method later without blocking the mainThread.

Same holds true for viewDidAppear and viewWillAppear. Both run on the mainThread.

Felix
Hmmm... This makes a lot of sense, and explains why it's doing what it's doing. My next question of course is how do I set this up so that doesn't do it! I'd vote you up if I had enough reputation!
David Foster
This answer is not correct. As my answer explains, the view is not yet visible when `viewDidLoad` and `viewWillAppear:` are called, so the behavior that David is seeing is expected. If the code is moved to `viewDidAppear:` however, it will work as desired.
gerry3
the `viewDidLoad` method is called just before `viewDidAppear`, so where you call `performSelector:withDelay:` should make little difference. Is the elaboration on the mainThread wrong? Else putting sleep(2) into the viewDidLoad postpones the viewWillAppear accordingly..
Felix
Actually, I'd say this one is right. :-) True, the view is not on screen yet when this method is called. But if not for the sleep, the view would have gone on screen shortly. The sleep paused everything. And if this code was just moved to viewDidAppear, it would not work either, because no drawing or user interaction would occur during the sleep.
Ken
Yep, Ken's correct here. I know from experience!
David Foster
A: 

I do not think you really want to be calling sleep in that method. This is something you should try and use a Timer for to avoid blocking the UI and the whole application. A Timer will let you call some code either just one or repeatedly. See the guide for more info

willcodejavaforfood
A: 

The signature of the viewDidAppear and viewWillAppear are wrong in your sample..they should be

- (void)viewWillAppear: (BOOL)animated {
    NSLog(@"View will appear!");
}

-(void)viewDidAppear : (BOOL)animated {
    NSLog(@"View did appear!");
}

The frameworks will not call implementations with the wrong signature as the message dispatch does not find them.

Kevin
Correct. I mentioned this in a comment to my answer. Also, I think you should call super.
gerry3
Thanks for pointing this out Kevin. Oops!
David Foster