Setting text for IBOutlet labels in a custom UIView - ios

I have a custom UIView (MyView) object that gets loaded from a xib file. In the MyView.m I have a method that gets called in initWithCoder that does some initialization. This works for the most part, but I was trying to set the text in some IBOutlet label properties here, and it wasn't working, I realized because the properties were not initialized at this point.
I tried setting the text of the outlets in the drawRect method, and it does work. But I think I read that implementing drawRect if you don't need to can cause some performance hits. My question is, where would the best place to set text of my IBOutlet label properties? (What I'm actually doing is setting it to localized versions of what's in the xib. I know about localized xib files, but I want to set the text in code.)
Here is some code. Here is how I initialize the view in my view controller:
NSArray *nibContents = [[NSBundle mainBundle] loadNibNamed:#"MyView"
owner:self
options:nil];
_myView = [nibContents objectAtIndex:0];
Here is the initWithCoder method in MyView.m
- (id)initWithCoder:(NSCoder *)coder
{
self = [super initWithCoder:coder];
if (self) {
[self localizeLabels];
}
return self;
}
And here is the localizeLabels method:
- (void)localizeLabels
{
self.powerPlant.text = NSLocalizedString(#"POWERPLANTBREAKDOWN_HEADERCOLUMN1", nil);
self.output.text = NSLocalizedString(#"POWERPLANTBREAKDOWN_HEADERCOLUMN2", nil);
self.capacity.text = NSLocalizedString(#"POWERPLANTBREAKDOWN_HEADERCOLUMN3", nil);
}
I have checked that these methods do get called using breakpoints, and that the properties are nil in the localizeLabels method.

UPDATED:
Your MyView.m class should be like this in the start.
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
NSArray *nibContents = [[NSBundle mainBundle] loadNibNamed:#"MyView"
owner:self
options:nil];
UIView *_myView = [nibContents objectAtIndex:0];
[self addSubview: _myView];
[self localizeLabels];
}
return self;
}
- (id)initWithCoder:(NSCoder *)aDecoder {
self = [super initWithCoder:aDecoder];
if (self) {
// Initialization code
[self localizeLabels];
}
return self;
}
and in your viewcontroller. you need to initialize your MyView like this.
MyView _view = [[MyView alloc]initWithFrame:CGRectMake(0, 0, 100, 100)];
[self.view addSubview:_view];

Your IBOutlet properties are initialized if they are properly wired up to the xib in Interface Builder. The initialisation takes place as the xib is loaded into the app. These properties are only weak references to xib-instantiated objects after all.
So my best guess is that you have not wired up the IBOutlets to their respective xib objects - check in Interface Builder.

Related

Adding IBOulets to custom UIView subclass crashes 'is not key-value compliant'

I have created two different subviews EPStudentProgressOpenQuestion and EPStudentProgressMultipleChoiceQuestion. They both inherit from EPStudentProgressQuestion as they both subviews share some common information and behaviours.
Each one of the views has its own XIB file.
Inside EPStudentProgressQuestion there is the following code:
#import "EPStudentProgressQuestion.h"
#interface EPStudentProgressQuestion ()
#property (assign, nonatomic) EPStudentProgressQuestionType questiontype;
#end
#implementation EPStudentProgressQuestion
#pragma mark - UIView lifecycle
- (instancetype)initWityQuestionType:(EPStudentProgressQuestionType)questionType {
self = [super init];
if (self) {
self.questiontype = questionType;
[self setupView];
}
return self;
}
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
[self setupView];
}
return self;
}
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
self = [super initWithCoder:aDecoder];
if (self) {
[self setupView];
}
return self;
}
#pragma mark - Private methods
- (void)setupView {
NSBundle *bundle = [NSBundle bundleForClass:[self class]];
UIView *view = [[bundle loadNibNamed:[self nibNameForQuestionType] owner:[self class] options:nil] firstObject];
view.frame = self.bounds;
[self.layer setCornerRadius:2.f];
[self.layer setBorderWidth:1.f];
[self.layer setBorderColor:[UIColor colorWithWhite:232/255.f alpha:1.f].CGColor];
[self setClipsToBounds:YES];
[self setTranslatesAutoresizingMaskIntoConstraints:NO];
[self addSubview:view];
}
- (NSString*)nibNameForQuestionType {
switch (self.questiontype) {
case EPStudentProgressQuestionTypeOpen:
return #"EPStudentProgressOpenQuestion";
case EPStudentProgressQuestionTypeMultipleChoice:
return #"EPStudentProgressMultipleChoiceQuestion";
}
}
As you can see, very simple code.
As I said above, each EPStudentProgressQuestion view has its own XIB file, connecting the Files Owner through the Identity Inspector class.
This is EPStudentProgressOpenQuestion:
#import "EPStudentProgressOpenQuestion.h"
#interface EPStudentProgressOpenQuestion ()
#property (weak, nonatomic) IBOutlet UILabel *lblQuestion;
#end
#implementation EPStudentProgressOpenQuestion
#end
Exactly the same for EPStudentProgressMultipleChoiceQuestion just without any IBOutlet. But as soon as I create IBOutlets to any of those views, I get the error ... IBOutlet is not key-value compliant...
Without the IBOutlets everything works fine. Each view loads correctly and it's well placed in the view I want to. But as soon as I link some IBOutlets from the XIB to the corresponding class, it crashes...
This is the crash:
*** Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<EPStudentProgressQuestion 0x1020196b0> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key lblQuestion.'
And this is how I instantiate the EPStudentProgressQuestion view:
EPStudentProgressOpenQuestion *questionView = [[EPStudentProgressOpenQuestion alloc] initWityQuestionType: EPStudentProgressQuestionTypeOpen];
[self.vQuestionsContainer addSubview:questionView];
Any idea on how to be able to link IBOutlets without having problems?
Thank you in advance!!
EDIT:
If I change the bundle and owner classes as follows:
NSBundle *bundle = [NSBundle bundleForClass:[EPStudentProgressOpenQuestion class]];
NSArray *views = [bundle loadNibNamed:[self nibNameForQuestionType] owner:[EPStudentProgressOpenQuestion class] options:nil];
UIView *view = [views firstObject];
I get the same error but instead of EPStudentProgressQuestion I get the error for EPStudentProgressOpenQuestion...
EDIT 2:
Test project link: https://mega.nz/#!oBhWkawC!RSOzrPOfq_UTVWd3jraRkneuCIyIkS61PKGeca2Bilc
The problem that's crashing you is that you are passing [self class] instead of just self as the nib owner. Change your nib loading line to this:
NSArray *views = [bundle loadNibNamed:[self nibNameForQuestionType] owner:self options:nil];
You have another problem which is that you're loading the nib twice. In initWityQuestionType:, you call [super init]. What you don't realize is -[UIView init] calls [self initWithFrame:CGRectZero]. So you end up calling into your overridden initWithFrame:, which calls setupView. Then when it returns back to initWityQuestionType:, that also calls setupView.
I recommend you get rid of your initWithFrame: override entirely.
Your crash indicates that EPStudentProgressQuestion (your superclass) is not key-value compliant. This means at the point where you are accessing the IBOutlet you have a reference to EPStudentProgressQuestion instead of either EPStudentProgressOpenQuestion or EPStudentProgressMultipleChoiceQuestion.
Just check the code where you are using the new IBOutlets and either change the type of the variable used there or add a cast to the correct class.
Your view controller may have the wrong class in your xib. Please change the UIView class name not the in the File's Owner like the pic
Also you called a [self setup] method in:
- (instancetype)initWityQuestionType:(EPStudentProgressQuestionType)questionType {
self = [super init];
if (self) {
self.questiontype = questionType;
[self setupView];
}
return self;
}
You don't need to call it again
- (instancetype)initWithFrame:(CGRect)frame {
}
and
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
}
it is creating a init loop and causing a memory leak.

use a custom uiview inside a storyboard

So, I am using storyboard and I have dragged inside my UIViewController a UIView.
Let's call it customView, and it's class is called SPView. The class is set in the inspector window. Inside the SPView.h there are a number of properties
I have tried the following :
If I drag a label inside the UIView (in the storyboard), the label is shown, but I cannot connect it to one of my outlets in the SPView.h by drag and drop.
If I create a new XIB file, with the label inside, I can do the connections as I like.
Then inside my UIViewController I have tried either of these:
self.customView =[[SPView alloc] initWithFrame:CGRectMake(60, 60, 200, 260)]
self.customView =[[SPView alloc]init];
and inside my SPView, if I use this:
- (id)initWithCoder:(NSCoder *)aDecoder {
NSLog(#"initWitchCoder called");
if ((self = [super initWithCoder:aDecoder])) {
//[self addSubview:[[[NSBundle mainBundle] loadNibNamed:#"SPView" owner:self options:nil] objectAtIndex:0]];
[self baseInit];
}
the label is not shown.
If I uncomment the comment, the initWithCoder is called for ever and the app eventually crashes.
What I want is :
to have a custom UIView inside a UIViewController, set either in the storyboard or programmatically (but it will be better if the graphic data are set in storyboard or in a separate .XIB file so as to inspect them more easily).
Can you help me on that?
you cannot drag outlets to custom class uiview class . only its allowed when using xib.
you can drop outlets of that custom view in its parent viewcontroller only.
you can set tags in subviews of your custom view. and can easily access them by using this below code .
lets say you have a subview in your custom view with tag :2
- (id)initWithCoder:(NSCoder *)aDecoder {
NSLog(#"initWitchCoder called");
if ((self = [super initWithCoder:aDecoder])) {
//[self addSubview:[[[NSBundle mainBundle] loadNibNamed:#"SPView" owner:self options:nil] objectAtIndex:0]];
UILabel *lbl = (UILabel *)[self viewWithTag:2];
[self baseInit];
}
so you had to set tags to subviews to access them
in your uiviewcontroller you had to drop outlet directly . no need for this
like this it will be initialized automatically
#property (weak, nonatomic) IBOutlet SPView *customView; //right and easy way
X self.customView =[[SPView alloc] initWithFrame:CGRectMake(60, 60, 200, 260)]
X self.customView =[[SPView alloc]init];
You can have following method defined in your UIViewController class and have an XIB created for SPView.
- (SPView*) loadCustomViewFromNib
{
SPView *spView = nil;
NSArray *array = [[NSBundle mainBundle] loadNibNamed:#"SPView"
owner:nil
options:nil];
for (id object in array)
{
if ([object isKindOfClass:[SPView class]])
{
spView = (SPView*)object;
break;
}
}
return spView;
}
And have SPView initialized as shown below
self.customView = [self loadCustomViewFromNib];

Custom UIView from nib inside another UIViewController's nib - IBOutlets are nil

I'm trying to create a custom UIView which holds references to its own IBOutlets. I then want to put this custom UIView into another nib.
I'm doing some additional logic in the custom UIView's awakeFromNib method. Unfortunately, when I try to access the IBOutlets in awakeFromNib, they are nil.
Here's the setup:
I have a UIView subclass, CustomView.
I have a custom .xib file with three subviews
In the other nib (that belongs to the view controller), I have dragged a UIView onto the view, and then changed the custom class to CustomView.
I tried setting the view in the CustomView nib in IB to a custom class CustomView and connecting the IBOutlets to the view, but they were still nil.
I tried setting file owner to CustomView and connecting the IBOutlets to file's owner, but they were still nil.
I also tried using another IBOutlet UIView *view and then adding that as a subview to self in awakeFromNib but that also didn't do anything.
Here's the code:
// CustomView.h
#interface CustomView : UIView
#property (nonatomic, retain) IBOutlet UITextField *textField;
#property (nonatomic, retain) IBOutlet UIView *subview1;
#property (nonatomic, retain) IBOutlet UIView *subview2;
// CustomView.m
#implementation CustomView
#synthesize textField, subview1, subview2;
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
[[NSBundle mainBundle] loadNibNamed:#"CustomView" owner:self options:nil];
}
return self;
}
- (id)initWithCoder:(NSCoder *)aDecoder {
self = [super initWithCoder:aDecoder];
if (self) {
}
return self;
}
- (void)awakeFromNib {
[super awakeFromNib];
[self setup];
}
- (void)setup {
// Fails because self.textField is nil
self.textField.text = #"foo";
}
I ended up using the steps in the most recent edit here and they worked beautifully.
You use a plain UIView as the top level view in the xib.
You then set file's owner to the custom subclass (CustomView).
Finally, you add a line:
[self addSubview:[[[UINib nibWithNibName:#"CustomView" bundle:nil] instantiateWithOwner:self options:nil] objectAtIndex:0]];
in the if (self != nil) block in both initWithCoder and initWithFrame.
Voila! The IBOutlets are hooked up and ready to go after the call. Really pleased with the solution, but it was very difficult to dig up.
Hope this helps anyone else.
EDIT: I updated the link to one that isn't dead. Since I never spelled out the full code, here is what it looks like after modification:
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
UIView *nib = [[[UINib nibWithNibName:#"CustomView" bundle:nil] instantiateWithOwner:self options:nil] objectAtIndex:0];
[self addSubview:nib];
}
return self;
}
- (id)initWithCoder:(NSCoder *)aDecoder {
self = [super initWithCoder:aDecoder];
if (self) {
UIView *nib = [[[UINib nibWithNibName:#"CustomView" bundle:nil] instantiateWithOwner:self options:nil] objectAtIndex:0];
[self addSubview:nib];
}
return self;
}
- (void)awakeFromNib {
[super awakeFromNib];
[self setup];
}
- (void)setup {
// Doesn't fail because life is awesome
self.textField.text = #"foo";
}
This pattern has become so common that I actually created a category on UIView called UIView+Nib, which implements the following method:
+ (UIView *)viewWithNibName:(NSString *)nibName owner:(id)owner {
return [[[UINib nibWithNibName:nibName bundle:nil]
instantiateWithOwner:owner options:nil]
objectAtIndex:0];
}
So the above code can be simplified to:
[self addSubview:[UIView viewWithNibName:#"CustomView" owner:self]];
Note also that the above code can be refactored even more, since the logic is exactly the same in initWithFrame: and initWithCoder:. Hope that helps!
As in Dr. Acula's answer, This is probably because custom view's nib is lazy loaded when loaded from another nib (Nested nib loading), so we need to instantiate it manually. In swift code will look like this :
override func awakeFromNib() {
super.awakeFromNib()
self.customview = UINib(nibName: "CustomViewNib", bundle: nil).instantiateWithOwner(self, options:nil)[0] as! UIView
self.customview?.frame = self.viewContainer.bounds
self.viewContainer.addSubview(self.customview!)
}
I am assuming the XIBs' structure is something like this
CustomView.xib
CustomView
UITextField -> linked to IBOutlet textField
other views
CustomViewController.xib
CustomView
If this is right, then your CustomView will be created but as it is not read from CustomView.xib it doesn't have any IBOutlets assigned.
However, if your CustomViewController.xib looks like following
CustomViewController.xib
CustomView
UITextField -> linked to IBOutlet textField of CustomView
then this should work. The IBOutlet of CustomView instance should be set by the CustomViewController.xib.
Better than setting any IBOutlets in the CustomViewController.xib would be to implement awakeAfterUsingCoder: in your CustomView and create a replacement object by loading your CustomView.xib in there. This way your CustomView remains truly custom and you don't have to edit other XIBs to change the structure, add/remove IBOutlets, etc.

Setting IBOutlet properties on a custom view with a nib

I have a generic custom view that has a nib file. I subclass this custom view and initialize it like this:
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self)
{
NSArray *nibContents = [[NSBundle mainBundle] loadNibNamed:#"GenericCustomView"
owner:self
options:nil];
UIView *view = [nibContents objectAtIndex:0];
[self addSubview:view];
}
return self;
}
I want to set some properties on the IBOutlets of the generic view, but if I set them in the initWithFrame method, the IBOutlets in the generic view haven't been loaded yet and are still nil. The awakeFromNib method in the custom view is never called. How can I set the properties of the generic nib files IBOutlets in the custom view class?
I'm only targeting iOS 7.0 and up, using Xcode 5.

iOS UIView subclass with NIB file does not display the UI designed in IB

I have a class that inherits from UIView, and this class has some controls that I have placed on it in IB.
Then, in the NIB file for my main view controller, I placed a view, and changed the class to my subclass, and created an outlet for the subclass. However, when I run my application, the app does not display the UI that I put on the subclass, it is just blank.
I am getting the initWithCoder and awakeFromNib messages in the subclass, here is what the subclass .m file basically looks like:
#import "AnalyticsDetailView.h"
#implementation AnalyticsDetailView
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
return self;
}
- (void)awakeFromNib
{
[super awakeFromNib];
}
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self)
{
NSArray *v = [[NSBundle mainBundle] loadNibNamed:#"AnalyticsDetailView" owner:self options:nil];
[self addSubview:[v objectAtIndex:0]];
}
return self;
}
- (void)dealloc
{
[super dealloc];
}
#end
I am not sure if the initWithFrame is correct, but since that method is not firing, I suspect that it doesn't matter at this point. If I put a breakpoint in my app after I have seen the subclass methods fire, I can look at the outlet subclass and the frame is the same as what I have created in IB.
Anyone have any suggestions (missing code, bad IB connections, etc.) on what to look for that I have missed or am doing incorrectly? Thanks.
To get your interface to appear, you'll need to explicitly instantiate a AnalyticsDetailView from your parent view controller.
So in somewhere like the viewDidLoad: or viewWillAppear: methods, you'll add a line that says:
AnalyticsDetailView * newView = [[AnalyticsDetailView alloc] initWithFrame: CGRectMake(x,y,height,width)];
[parentView addSubview: newView];
[newView release]; // subview retains for us

Resources