0

I'm trying to add a subview to a view controller programatically, but it seems like if I add the subview in the viewDidLoad of the view controller whatever I have added as subview in the view itself is not rendered in the view controller.

But if I move the [self.view addSubview:myUIView] in the init method of the view controller, everything is rendered.

Also, if I call the same methods in the viewDidAppear I get all the elements rendered, but it's right after the view controller was displayed, and I can see when the elements are rendered in the view controller.

This would be my view controller:

//interface
@class RLJSignInView;
@protocol RLJSignInViewControllerDelegate;


@interface RLJSignInViewController : UIViewController <UITextFieldDelegate>
@property (nonatomic, readwrite) RLJSignInView *signInView;
@property (nonatomic, assign) id<RLJSignInViewControllerDelegate> delegate;
@end


//implementation
#import "RLJSignInViewController.h"
#import "RLJSignInView.h"
#import "RLJSignInViewControllerDelegate.h"
#import "UIColor+RLJUIColorAdditions.h"


@implementation RLJSignInViewController
- (id)init
{
    self = [super init];

    if (self) {
        _signInView = [[RLJSignInView alloc] initWithFrame:self.view.bounds];

        [self.view addSubview:self.signInView];
    }

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

    NSLog(@"%@", self.signInView);

    [self.view setBackgroundColor:[UIColor rgbWithRed:250 green:250 blue:250]];
}
@end

And this would be my view:

//interface
@interface RLJSignInView : UIView
@property (strong, nonatomic, readwrite) UITextField *username;
@property (strong, nonatomic, readwrite) UITextField *password;
@property (strong, nonatomic, readwrite) UIButton *signIn;
@property (strong, nonatomic, readwrite) UIButton *signUp;
@end


//implementation
#import "RLJSignInView.h"
#import "UITextField+RLJUITextFieldAdditions.h"
#import "UIButton+RLJUIButtonAdditions.h"


@implementation RLJSignInView
- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];

    if (self) {

        CGRect usernameInputFrame = CGRectMake(10.0, 40.0, self.bounds.size.width - 20, 40);
        CGRect passwordInputFrame = CGRectMake(10.0, 79.0, self.bounds.size.width - 20, 40);
        CGRect signInButtonFrame = CGRectMake(10.0, 160, self.bounds.size.width - 20, 40);
        CGRect signUpButtonFrame = CGRectMake(10.0, 220, self.bounds.size.width - 20, 40);

        UIView *usernameInputLeftView = [[UIView alloc] initWithFrame:CGRectMake(0.0, 0.0, 10.0, 40)];
        UIView *passwordInputLeftView = [[UIView alloc] initWithFrame:CGRectMake(0.0, 0.0, 10.0, 40)];

        self.username = [[UITextField alloc] initWithFrame:usernameInputFrame
                                             textAlignment:NSTextAlignmentLeft
                                                 textColor:[UIColor blackColor]
                                               clearButton:UITextFieldViewModeWhileEditing
                                                  leftView:usernameInputLeftView
                                               placeholder:@"Username"
                                           backgroundColor:[UIColor whiteColor]
                                               strokeWidth:2.0
                                               strokeColor:[UIColor lightGrayColor]
                                              keyboardType:UIKeyboardTypeEmailAddress
                                         byRoundingCorners:UIRectCornerTopLeft | UIRectCornerTopRight
                                               cornerRadii:CGSizeMake(4.0, 4.0)
                                                    secure:NO];

        self.password = [[UITextField alloc] initWithFrame:passwordInputFrame
                                             textAlignment:NSTextAlignmentLeft
                                                 textColor:[UIColor blackColor]
                                               clearButton:UITextFieldViewModeWhileEditing
                                                  leftView:passwordInputLeftView
                                               placeholder:@"Password"
                                           backgroundColor:[UIColor whiteColor]
                                               strokeWidth:2.0
                                               strokeColor:[UIColor lightGrayColor]
                                              keyboardType:UIKeyboardTypeDefault
                                         byRoundingCorners:UIRectCornerBottomLeft | UIRectCornerBottomRight
                                               cornerRadii:CGSizeMake(4.0, 4.0)
                                                    secure:YES];

        self.signIn = [[UIButton alloc] initWithFrame:signInButtonFrame
                                                title:@"Sign In"
                                          colorNormal:[UIColor whiteColor]
                                     colorHighlighted:[UIColor whiteColor]
                                        colorDisabled:[UIColor whiteColor]
                                     backgroundNormal:[UIColor colorWithRed:82 / 255.0 green:156 / 255.0 blue:201 / 255.0 alpha:1.0]
                                         cornerRadius:4.0];

        self.signUp = [[UIButton alloc] initWithFrame:signUpButtonFrame
                                                title:@"Sign Up"
                                          colorNormal:[UIColor blackColor]
                                     colorHighlighted:[UIColor blackColor]
                                        colorDisabled:[UIColor blackColor]
                                     backgroundNormal:[UIColor whiteColor]
                                         cornerRadius:4.0];

        [self addSubview:self.username];
        [self addSubview:self.password];
        [self addSubview:self.signIn];
        [self addSubview:self.signUp];
    }

    return self;
}
@end

I'm not sure what I could do about it, but I know for sure that I would not like the view to be added to the subview on initialisation.

Or maybe I'm doing the rendering of the subviews of the view wrong. I would appreciate some input on this matter if anyone encountered the same thing or if noticed that I messed up something.

Roland
  • 9,321
  • 17
  • 79
  • 135

2 Answers2

2

Don't call self.view from inside your init method. This will trigger viewDidLoad being called before your init method even returns, which is incorrect behavior.

Instead, follow this pattern:

  • create your objects (properties / instance variables) in init, or by using lazy instantiation
  • add the subviews in viewDidLoad
  • set the frames in viewWillLayoutSubviews

This pattern will avoid bugs like the one you posted here, and set you up for success handling rotation and resizing events in the future.

Aaron Brager
  • 65,323
  • 19
  • 161
  • 287
  • I understand, but this only applies for my view controller, as the view does not have those methods, so I need to add subviews in the init method, or is there a better way for that as well ? I did not know about not calling `self.view` in my init methods, thanks for the information :) – Roland Nov 16 '13 at 20:29
  • You should add the view in `viewDidLoad` but provide a fixed frame using `CGRectMake()` in stead of using self.view.bounds. – Leijonien Nov 16 '13 at 20:32
  • And yes, that worked as you presumed it will. The only issue now is that the subviews in the view are not having the frames that they are supposed due to the fact that the bounds at the point of initialisation is `0`, what can I do about that ? – Roland Nov 16 '13 at 20:33
  • Ok, the reason I had `self.bounds` was because I wanted to have the same bounds as the view controller has. – Roland Nov 16 '13 at 20:34
  • How do I use autolayout, and I've read that would not be the best idea and that is pretty complicated sometimes. But I do not speak from my personal experience as I never used it – Roland Nov 16 '13 at 20:37
  • It doesn't matter what you initially set the frame to if you set it in `viewWillLayoutSubviews`. This method will always get called at the appropriate time. – Aaron Brager Nov 16 '13 at 20:37
  • @rolandjitsu If the frames of the view's subviews aren't getting set up properly, you need to set their frames in your UIView subclass's `layoutSubviews` method. – Aaron Brager Nov 16 '13 at 20:38
  • Btw, is there a better `UIView` methods that I can overwrite to add subviews ? Or is it fine if I do it in the initialisation ? – Roland Nov 16 '13 at 21:33
  • In UIView you should do this during initialization. Keep in mind that there are two separate methods (`initWithFrame:` and `initWithCoder:`) depending on if you're creating the view programmatically or in Interface Builder. – Aaron Brager Nov 18 '13 at 13:26
1

The key might be you are using self.view.bounds too early. It might work when you provide an initial size frame (like IB would do).

init is too early. The view controller is initializing, the view is not loaded yet, which means there's no view and the bounds will return CGRectZero.

viewDidLoad is also too early to rely on the view's bounds. The view is just loaded, it does not have a superview yet and might be resized later.

viewWillAppear is the first moment you can rely on the view's bounds. Since it will be resized according to the autoresizingmasks or autolayout constraints.

You can add the subview in viewDidLoad but you you should provide an initial frame (and you set the correct autoresizingmasks or constraints) it should all work fine.

Leijonien
  • 1,415
  • 14
  • 16
  • I was just writing the same answer myself, you beat me to it! Particularly under auto layout, the view controller's view will not have a frame until viewDidLayoutSubviews has been called. So, you can add it in viewDidLoad but, as Leijonien says, you need to provide an initial frame, or set the frame in `viewDidLayoutSubviews`. – jrturton Nov 16 '13 at 20:33