2

I'm developing on iOS and I'm building my views programmatically. I noticed that when I try to access variables that have to be changed in my view from the view controller they are null. I'll post both the view and its view controller:

RootViewController.h

 #import <UIKit/UIKit.h>

@class RootView;

@interface RootViewController : UIViewController {
    RootView *rootView;
}

@property (nonatomic,retain) RootView *rootView;


@end

RootViewController.m

    #import "RootViewController.h"
#import "RootView.h"

@implementation RootViewController

@synthesize rootView;

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    if (self) {
        // Custom initialization
    }
    return self;
}



- (void)dealloc
{
    [rootView release];
    [super dealloc];
}

- (void)didReceiveMemoryWarning
{
    // Releases the view if it doesn't have a superview.
    [super didReceiveMemoryWarning];

    // Release any cached data, images, etc that aren't in use.
}

#pragma mark - View lifecycle

- (void)loadView{
    RootView *rootV = [[RootView alloc] initWithFrame:CGRectMake(10, 10, 100, 50)];
    rootV.rootViewController = self;
    self.view = rootV;
    [rootV release];
}

- (void)viewDidLoad{
    NSLog(@"TEXT: %@",self.rootView.label.text);
    self.rootView.label.text=@"HELLO!";
}

- (void)viewDidUnload
{
    [super viewDidUnload];
    // Release any retained subviews of the main view.
    // e.g. self.myOutlet = nil;
    [self setRootView:nil];
}

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
    // Return YES for supported orientations
    return (interfaceOrientation == UIInterfaceOrientationPortrait);
}

@end

RootView.h

   #import <UIKit/UIKit.h>

@class RootViewController;

@interface RootView : UIView {
    RootViewController *rootViewController;
    UILabel *label;
}

@property (nonatomic,assign) RootViewController *rootViewController;
@property (nonatomic,retain) UILabel *label;


@end

RootView.m

   #import "RootView.h"
#import "RootViewController.h"

@implementation RootView
@synthesize rootViewController;
@synthesize label;

- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        //Create the label
        UILabel *testLabel = [[UILabel alloc] initWithFrame:CGRectMake(100, 100,100, 50)];
        //Set the font bold 
        testLabel.font = [UIFont boldSystemFontOfSize:20.0];
        //Set the backgroundcolor of the label to transparent
        testLabel.backgroundColor = [UIColor clearColor];
        //Set the text alignment of the text label to left
        testLabel.textAlignment = UITextAlignmentLeft;
        //Set the text color of the text label to black
        testLabel.textColor = [UIColor blackColor];
        testLabel.text = @"01:30";

        self.label = testLabel;

        [self addSubview:label];
        [testLabel release];
    }
    return self;
}


- (void)dealloc
{
    [label release];
    rootViewController = nil;
    [super dealloc];
}

@end

I changed the code but it seems not working.....

Ok solved I forgot this line "self.rootView = rootV;"

3 Answers 3

2

Your view doesn't find out what its controller is until after its -initRootView method returns, but you're trying to use the controller from within that method.

That said, it would be much better if you followed the usual Cocoa Touch pattern for a view controller creating its view. View controllers are supposed to create their views lazily, which is to say that they defer view creation and initialization until the -loadView method is called. You can override -loadView to create your view, and also override -viewDidLoad to do any setup work that needs to be done after the view is created.

Also, it's generally not advisable for a view to know about its controller. The controller should tell the view what to do, not the other way around. If you need the view to send some information to the controller, you usually provide the controller to the view as the view's delegate. But if you just need the view controller to be able to find some subview, like your label, it's probably a good idea to either provide some accessors in the container view for that (so that the view controller can just say something like self.view.label.text = @"some text";. Another options is to set the subview's tag property to some unique value and have the controller use that to find the subview.

Sign up to request clarification or add additional context in comments.

7 Comments

Sorry I didn't get it. What I'm trying to do if i've understand well, is to have outlets in my view controller according the MVC and from the view controller i want to change label's attributes is it ok? I tried to add a button and when I press the button i try to change the text of the label but it doesn't work. Seems the reference to the label is null..any suggestion?!
I hope my edit helps clarify things, but you'll also want to read about Understanding the View Management Cycle in the docs.
So according to the usual cocoa touch pattern should i build the view in the loadview method? If so, is it wrong to have the view in a separate file as I did and instantiate the view inside the loadview method? Is it different from what i did or not?! Thanks
So in case i have the viewcontroller, the view and in the view i have a button that call a method in the view controller is it better to have the viewcontroller as delegate of the view or a reference of the viewcontroller in the view? Thanks in advance
Yes, no, and yes, respectively. Yes, create your view in -loadView. No, it's fine to keep your RootView class separate from your RootViewController class -- that's standard practice. Yes, somewhat different from what you have currently in that view creation and any view-dependant setup in the controller should be deferred until the view is actually needed, at which point -loadView will be called. There's plenty of sample code on this, e.g. TheElements.
|
1

The problem is easy to spot, but requires some work to fix.

Looking at your code, something that I immediately want to suggest is to put all your RootView initialization code the loadView method of your RootViewController. That's where it should be (see here why).

Also, if you absolutely need your RootView to have a reference back at RootViewController, you should probably do that in viewDidLoad. But I wouldn't recommend doing that.

When using the MVC pattern, it is the controller's responsibility to initialize and update views. The line self.rootViewController.rootViewLabel = testLabel; should be removed from RootView's implementation. It's not clear what your intention is there, but if you want the rootViewLabel updated, you should let the controller do that.

To sum it all up:

// RootViewController.m

- (id)initRootViewController{

    self = [super init];

    if(self){
        // other init code here
    }

    return self;
}

- (void)loadView {
    RootView *rootV = [[RootView alloc] initWithFrame:CGRectMake(0, 0, 100, 50)];        
    self.view = rootV;
    [rootV release];     
}

- (void)viewDidLoad {
    [super viewDidLoad];        
    // etc...
}

// etc.

Now, as for RootView, here is what it would look like:

RootView.h

#import <UIKit/UIKit.h>

@interface RootView : UIView {    
    UILabel *rootViewLabel;
}

// moved from RootViewController
@property (nonatomic, retain) UILabel *rootViewLabel;

@end

RootView.m

#import "RootView.h"

@implementation RootView

@synthesize rootViewLabel;

- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        // Whatever initialization code you might have
        //Create the label
        UILabel *testLabel = [[UILabel alloc] initWithFrame:CGRectMake(100, 100,100, 50)];
        //Set the font bold 
        testLabel.font = [UIFont boldSystemFontOfSize:20.0];
        //Set the backgroundcolor of the label to transparent
        testLabel.backgroundColor = [UIColor clearColor];
        //Set the text alignment of the text label to left
        testLabel.textAlignment = UITextAlignmentLeft;
        //Set the text color of the text label to black
        testLabel.textColor = [UIColor blackColor];
        testLabel.text = @"01:30";

        self.rootViewLabel = testLabel;      
        [testLabel release];

        // add rootViewLabel as a subview of your this view
        [self addSubView:rootViewLabel];
    }
    return self;
}

- (void)dealloc
{
    [rootViewLabel release];
    [super dealloc];
}

@end

I hope this gives you an idea on how to structure your view initialization code...

(Disclaimer, I can't test this code now, please point out any errors! Thanks)

4 Comments

Thanks for your post, The idea was to have a separate file for the view, since i have to write lots of line i would prefer to have separate files, i think it is not a problem to have the lines you wrote in viewDidLoad in the initwithframe method that you call in the load view isn't it?! If i separate the view and the viewcontroller how can the controller know about its outlet. I mean if i have to remove this line "self.rootViewController.rootViewLabel = testLabel" how can i change my outlets in case i have 2 different files one for the view and one for its viewcontroller?
Yes, in that case you would implement that UILabel as a property of your RootView. I'll update the code in my answer to reflect that. Then, the controller would access it via the property dot notation: self.rootView.rootViewLabel.
@octy I changed the code in my original question but it seems not working...if i understood well i try to change the label in the viewdidload and it should be correct....but when i try to print the value i got a (null)
Is it working finally? BTW, I noticed in your code that the NSLog() comes after you set the value on the label (in the viewDidLoad method). You might see a (null) printed in the logs for that reason...
0

Not sure why you're doing it like that, but you could probably make it work if you pass the controller into the view's init:

RootView *rootV = [[RootView alloc] initRootView:self];

view's init:

- (id)initRootView:(UIViewController*)controller
{
     self.rootViewController = controller;
     self.rootViewController.rootViewLabel = testLabel;

2 Comments

What is the difference of doing that rather than i did?! Is it not the same?
in your initRootView, you have self.rootViewController.rootViewLabel = testLabel; but self.rootViewController is not yet pointing to your controller.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.