5

I am hopelessly stuck and I would appreciate a pointer.

We are trying to build a view controller including multiple views that are a subclasses of UIView. Everything is working "well" except for the fact that we need to either manually initialize the view or call awakeFromNib manually again which is not allowed.

Here is the problem...

Subclassed UIView header file:

#import <UIKit/UIKit.h>
@interface introView : UIView
@property (strong, nonatomic) UIView *view;
@property (strong, nonatomic) NSString *title;
@property (strong, nonatomic) IBOutlet UILabel *titleLabel;
@end

Subclassed UiView main file:

#import "introView.h"

@interface introView ()
@end

@implementation introView

-(id)init
{
    NSArray *subviewArray = [[NSBundle mainBundle] loadNibNamed:@"introView" owner:self options:nil];
    id mainView = [subviewArray objectAtIndex:0];
    return mainView;
}
- (void) awakeFromNib
{
    [super awakeFromNib];
    [self initialize];
    [self addSubview:self.view];
}

-(void)initialize
{
    NSLog(@"title: %@", self.title);
    self.titleLabel.text = self.title;
}

We then initialize the view from a view controller doing this:

introView *view = [[introView alloc]init];
view.title = @"Test";
[self addSubview:view];

Here is the problem - by just calling this view, the view shows up correctly but title is NULL;

[43605:1957090] title: (null)

If however we call awakeFromNib again then the view initializes correctly

In the view controller:

introView *view = [[introView alloc]init];
view.title = @"Test;
[self addSubview:view];
[view awakeFromNib];

Then it works:

2014-11-21 09:33:03.500 Test[43706:1972060] title: (null)
2014-11-21 09:33:03.500 Test[43706:1972060] title: Test

Alternatively if I make the initialize method public and call that after the initialization it works as well. But that defeats the purpose in my eyes...

In the view controller:

introView *view = [[introView alloc]init];
view.title = @"Test;
[self addSubview:view];
[view initialize]; //calling the method directly...

It seems me that we are running somehow into a condition where the view is not ready yet but then it works at the second attempt (even though the awakeFromNib call is illegal)

Again the outlets are setup correctly, the file owner is set...it just takes two awakeFromNib calls.

Any help is appreciated.

----------------UPDATE------------------

Thanks guys, I appreciate the pointer. I implemented the initializer as outlined but I have the same problem. Furthermore I used the GitHub example as a clean example and tried to assign a variable to this view and even that shows the same behavior. Hence I am starting to think that I am doing something wrong somewhere else. See here:

//
//  SubClassView.h
//  CustomView
//
//  Created by Paul Solt on 4/28/14.
//  Copyright (c) 2014 Paul Solt. All rights reserved.
//

#import <UIKit/UIKit.h>
#import "PSCustomViewFromXib.h"

@interface SubClassView : PSCustomViewFromXib
@property (strong, nonatomic) NSString *title;
@end

SubclassView.m

//
//  SubClassView.m
//  CustomView
//
//  Created by Paul Solt on 4/28/14.
//  Copyright (c) 2014 Paul Solt. All rights reserved.
//

#import "SubClassView.h"

@interface SubClassView()
@property (weak, nonatomic) IBOutlet UILabel *label;
@property (weak, nonatomic) IBOutlet UISwitch *onSwitch;


@end

@implementation SubClassView

// Note: You can customize the behavior after calling the super method

// Called when loading programatically
- (id)initWithFrame:(CGRect)frame {
    self = [super initWithFrame:frame];
    if(self) {
        // Call a common method to setup gesture and state of UIView
        [self setup];
    }
    return self;
}

// Called when loading from embedded .xib UIView
- (id)initWithCoder:(NSCoder *)aDecoder {
    self = [super initWithCoder:aDecoder];
    if(self) {
        // Call a common method to setup gesture and state of UIView
        [self setup];
    }
    return self;
}

- (void)setup {
    // Add a gesture to show that touch input works on full bounds of UIView
    NSLog(@"%@", self.title);
    UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePanGesture:)];
    [self addGestureRecognizer:panGesture];
}

- (void)handlePanGesture:(UIPanGestureRecognizer *)gesture {
    NSLog(@"Pan: %@", NSStringFromCGPoint([gesture locationInView:gesture.view]));
}
- (IBAction)switchChanged:(UISwitch *)sender {
    NSLog(@"Switch: %d", sender.on);
}

@end

View Controller class

//
//  ViewController.m
//  CustomView
//
//  Created by Paul Solt on 4/28/14.
//  Copyright (c) 2014 Paul Solt. All rights reserved.
//

#import "ViewController.h"
#import "SubClassView.h"
#import "LabelMadness.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];


    // Add a custom view's programmatically (position using 1/2 width and height)

    SubClassView *oneView = [[SubClassView alloc] init];
    oneView.center = CGPointMake(80 + 80, 282 + 40);
    oneView.backgroundColor = [UIColor blueColor];
    oneView.title = @"Test2";
    [self.view addSubview:oneView];

}

@end

-------OUTPUT-----

2014-11-21 11:49:38.893 CustomView[45653:2123668] LabelMadness.initWithFrame:
2014-11-21 11:49:38.893 CustomView[45653:2123668] (null)

What am I doing wrong here?

user3757285
  • 117
  • 1
  • 1
  • 8
  • You code is somewhat confusing. In the introView class, what is self.view (in [self addSubview:self.view])? What does mainView look like (what subviews does it have)? – rdelmar Nov 21 '14 at 18:38

2 Answers2

2

You don't need to use awakeFromNib at all. If you want introView to load its own nib, you can do that like so (change the class of the view to introView in the xib file),

-(instancetype)init{
    if (self = [super init]) {
        NSArray *subviewArray = [[NSBundle mainBundle] loadNibNamed:@"introView" owner:self options:nil];
        self = [subviewArray objectAtIndex:0];
    }
    return self;
}

To set the title, you can override the setter of your title property,

-(void)setTitle:(NSString *)title {
    _title = title;
    self.titleLabel.text = title;
}

The code in your controller would be the same as what you tried initially.

rdelmar
  • 103,982
  • 12
  • 207
  • 218
  • I tried that but then I get an exception... *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[UIView setTitle:]: unrecognized selector sent to instance 0x7fbc4d8c4040' – user3757285 Nov 21 '14 at 20:11
  • @user3757285, It sounds like you didn't change the class of the view in your xib to your custom class (that's why the error says UIView not introView). – rdelmar Nov 21 '14 at 20:47
  • You are correct and it was incorrectly however it doesn't change anything. I get an exception. However I am starting to think that my whole approach is somewhat flawed. Is it not possible to assign a global variable as part of the init function? Do I have to call [view initialize] in order for the variable to be picked up? It doesnt make sense to me but that way it works... – user3757285 Nov 21 '14 at 21:05
  • @user3757285, I'm not sure why it doesn't work for you. I tested this code, and it works for me. If you're still getting that same error message, then, for some reason, you're not creating an introView, you're just creating a UIView. – rdelmar Nov 21 '14 at 21:23
0

So, do you have the view in a .xib or in storyboard?

It looks like is from a .xib so you should look at this answer

You shouldn't override the init method, and you shouldn't alloc init it in code. If you do so, the views you made in the interface builder won't be loaded.

So instead of alloc init, follow the answer I pointed out and do something like it.

Anyway, I can't see where are you initializing your

@property (strong, nonatomic) UIView *view;

Is not an IBOutlet and you never init it anywhere, so apparently that will be always nil

Community
  • 1
  • 1