3

In first place I would like to say that I'm only making this question, because I would like to understand what is happening.

I opened an old Xcode project (a very simple one) in a fresh installation on Xcode5. When I realize that it doesn't work on iOS 7. Why? Don't know..

I saw some other questions, didn't get any useful answer, so I made a simple test. In the UITableViewController all works fine except on didSelectRowAtIndexPath

Check it out

RootViewController.h:

@interface RootViewController : UITableViewController

@property (strong, nonatomic) NSMutableArray *items;
@end

RootViewController.m In viewDidLoad I init the array (with some strings).

Implement the dataSource and delegate methods (and yes I set the delegate and dataSource to the tableView)

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
    return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    return [_items count];
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *CellIdentifier = @"Cell";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil) {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
    }

    // Configure the cell...
    cell.textLabel.text = [_items objectAtIndex:indexPath.row];

    return cell;
}

The problem is here:

    - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    // Navigation logic may go here, for example:
    // Create the next view controller.
    DetailViewController *detailViewController = [[DetailViewController alloc] initWithNibName:@"DetailViewController" bundle:nil];

    // Push the view controller.
    [self.navigationController pushViewController:detailViewController animated:NO];

    detailViewController.detailLabel.text = [_items objectAtIndex:indexPath.row];
}

DetailViewController is a simple UIViewController:

(yes, I set the IBOutlet on nib file)

@interface DetailViewController : UIViewController
@property (weak, nonatomic) IBOutlet UILabel *detailLabel;

@end

The problem is that this doesn't work on iOS7, the label in DetailViewController doesn't get updated. I try to set the label text before and after of pushViewController.

This works on all previous versions of iOS.

Why is not working on iOS 7 ??

The only way I get this working is, like I saw on this other question:

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    // Navigation logic may go here, for example:
    // Create the next view controller.
    DetailViewController *detailViewController = [[DetailViewController alloc] initWithNibName:@"DetailViewController" bundle:nil];

    // Push the view controller.
    [self.navigationController pushViewController:detailViewController animated:YES];

    dispatch_async(dispatch_get_main_queue(), ^{
        detailViewController.detailLabel.text = [_items objectAtIndex:indexPath.row];
    });
}

Can somebody help me out to understand what is going on here??

Thanks!

_

EDIT

it also works like this:

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    // Navigation logic may go here, for example:
    // Create the next view controller.
    DetailViewController *detailViewController = [[DetailViewController alloc] initWithNibName:@"DetailViewController" bundle:nil];

    // Push the view controller.
    [self.navigationController pushViewController:detailViewController animated:YES];

    if (detailViewController.view) {
        // do notihing
    }
    detailViewController.detailLabel.text = [_items objectAtIndex:indexPath.row];
}
Community
  • 1
  • 1
Frade
  • 2,938
  • 4
  • 28
  • 39

2 Answers2

8

At the time when you set the value of the UILabel, the view has not yet been loaded, so if you check in debug, you'll probably find detailLabel is nil.

Why not pass the value in a custom init method? e.g.

- (id)initWithDetails:(NSString *)detailsText;

Then in viewDidLoad, assign the value?

According to your edit, in answer to your question as to why it is working:

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    TestViewController *test = [[testViewController alloc] init];

    NSLog(@"Before: %@", test.label);

    if(test.view){}

    NSLog(@"After: %@", test.label);

    [[test label] setText:@"My Test"];

    [self.navigationController pushViewController:test animated:YES];
}

I have mocked up your scenario, if you access 'view' on a ViewController, it will call viewDidLoad if the view does not exist. So your UILabel now becomes set. Here is the output from the console.

2013-10-11 16:21:04.555 test[87625:11303] Before: (null)
2013-10-11 16:21:04.559 test[87625:11303] After: <UILabel: 0x75736b0; frame = (148 157; 42 21); text = 'Label'; clipsToBounds = YES; opaque = NO; autoresize = RM+BM; userInteractionEnabled = NO; layer = <CALayer: 0x7573740>>

I recommend using a custom init method, rather than this approach - this above example is a hack. Use something like this E.g.

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    testViewController *test = [[testViewController alloc] initWithText:@"My Test"];

    [self.navigationController pushViewController:test animated:YES];
}

If you do not wish to use a custom initialiser, I suggest a public property on your DetailViewController of type NSString. Assign that using your approach after init is called. Then within DetailViewController, in viewDidLoad, or appear, set the IBOutlet to the value of the property containing the NSString.

Tim
  • 8,932
  • 4
  • 43
  • 64
  • The OP is using `initWithNibName:bundle:` to create the DetailViewController. Thus, when it's (primary) view will be accessed, the view hierarchy will be loaded and configured according the nib file. This should also invoke `viewDidLoad` for the DetailViewController. It would be worth a check if `viewDidLoad` will be called. And for testing, a statement like `UIView* view= detailViewController.view;` may be inserted to confirm the above assertion. Also, check with `isViewLoaded`. – CouchDeveloper Oct 11 '13 at 15:15
  • @CouchDeveloper Jeff is correct. In the original code, the label property is being accessed long before the view controller's view is loaded. – rmaddy Oct 11 '13 at 15:19
  • @maddy OK, would be `detailViewController.view;` a proper workaround in order to force the view to load? Appears hackish. Is there a better idea? – CouchDeveloper Oct 11 '13 at 15:21
  • Pass the value as a parameter at initialisation, or use a call back to the parent controller for the value. Or a shared datasource that can be updated, so the new view controller can access it. Or set an instance variable on the detailViewController, then in viewDidLoad, update the IBOutlet – Tim Oct 11 '13 at 15:24
  • Mine, is the approach used in apple template for UITableViewController. It should work. It works for every earlier iOS. Isn't stupid have to use a workaround!? – Frade Oct 11 '13 at 15:29
  • It isn't a workaround. I have just mocked up your scenario, in iOS 6. It is not working, because if the view is not loaded, any IBOutlets WILL be nil. It is good design not to assume that the view is loaded – Tim Oct 11 '13 at 15:30
  • I used this technique for years. But was curious, if there is another approach. Deferring the setup of other outlets via async dispatch to main, may not work always (consider that the view loading system may itself defer selectors). – CouchDeveloper Oct 11 '13 at 15:32
  • FYI using dispatch_async, is a workaround in this scenario really :) You are relying on the UI thread to finish making its updates, by pushing the view controller, so you are back of the queue, by the time it is called, the view has been loaded – Tim Oct 11 '13 at 15:33
  • wich technique, CouchDeveloper?? – Frade Oct 11 '13 at 15:34
  • I used `viewController.view;` or something that indirectly accessed the view. – CouchDeveloper Oct 11 '13 at 15:35
  • Jeff, yes it works on iOS6.. Just set the label text after pushViewController – Frade Oct 11 '13 at 15:37
  • My example above is using iOS6, and as you can see, before I access the view, the IBOutlet is (null) – Tim Oct 11 '13 at 15:40
  • The question is now, which one of those two "workarounds" should be preferred? I for one, would use `viewController.view;` even though it looks a bit weird. But it guarantees that after the method returns, all views are setup as expected. – CouchDeveloper Oct 11 '13 at 15:45
  • It isn't a question of workarounds. It is a question of what is good design. If the View Controllers were designed correctly, OP would not have this issue. I personally would either pass the text in the initialiser, or I would set a NSString property on the detailViewController and handle all view setup where Apple recommends, in the UIViewController viewDidLoad method. (or appear) – Tim Oct 11 '13 at 15:50
  • @Jeff I agree that a proper design is important. However, giving concrete ideas about a proper design in this case is far beyond the current issue. We could just require that all setup for a view controller is done in *its* `viewDidLoad`. There are a couple of opportunities how to accomplish this, including proper design principles. Again, I agree with your suggestions and voted for your answer. And even I myself used this technique occasionally, the last time it was probably three years ago ;) – CouchDeveloper Oct 11 '13 at 15:57
  • Yes, Jeff, You are right. It was thinking in a proper design that asked the question. And for understanding the issue. As I said before, my example works on iOS6, but only if pushViewController is animated (animated:YES).. I have never realized this issue before. Thanks – Frade Oct 11 '13 at 16:05
  • @Frade A proper design would employ the MVC paradigm more strictly. For example, replace Jeff's suggestion to "define a NSString property" with "define a model property". Then, your parent controller assigns the detail controller its _corresponding_ model. The model contains data, which will be rendered by the detail controller as it desires. – CouchDeveloper Oct 11 '13 at 16:14
  • yes, for sure! I get it. Instead of setting label text, I should set a property. I usually do that, maybe this time I didn't and found a problem that I didn't expected. thanks – Frade Oct 11 '13 at 16:24
0

Its not getting updated because you are making writing @property (weak, nonatomic) IBOutlet UILabel *detailLabel; You are making the UILabel as weak property. You must make it strong property. @property (strong, nonatomic) IBOutlet UILabel *detailLabel; As you are making your property weak so its getting destroyed.

To know more about weak and strong propertied you can refer Differences between strong and weak in Objective-C

Community
  • 1
  • 1
tania_S
  • 1,350
  • 14
  • 23