I have created MyCustomView.xib/h/m: which extends UIView class. Then in my Main.storyboard, put UIView object, changed the class to MyCustomView and linked to MainController.h. So, MainController contains reference to MyCustomView instance.
For loading from xib, in MyCustomView I do the following:
- (instancetype)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self) {
if (self.subviews.count == 0) {
[self commonInit];
}
}
return self;
}
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
[self commonInit];
}
return self;
}
- (void) stretchToSuperView:(UIView*)view
{
view.translatesAutoresizingMaskIntoConstraints = NO;
NSDictionary *bindings = NSDictionaryOfVariableBindings(view);
NSString *formatTemplate = #"%#:|[view]|";
for (NSString * axis in #[#"H",#"V"]) {
NSString * format = [NSString stringWithFormat:formatTemplate,axis];
NSArray * constraints = [NSLayoutConstraint constraintsWithVisualFormat:format options:0 metrics:nil views:bindings];
[view.superview addConstraints:constraints];
}
}
- (void)commonInit
{
MyCustomView* view = nil;
NSArray *views = [[NSBundle mainBundle] loadNibNamed:#"MyCustomView" owner:self options:nil];
view = views.firstObject;
[self addSubview:view];
[self stretchToSuperView:views.firstObject];
}
This works quite well, until I want to declare delegate in MyCustomView in order to notify MainController to any change(button click, etc). So, my ManController conforms MyCustomViewDelegate and implements methods.
EDIT 1 setting delegate
//MainViewController.m file
#interface MainViewController ()
#property (strong, nonatomic) IBOutlet MyCustomView *customView;
#end
#implementation MainViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.customView.delegate = self;
}
The problem here is that delegate becomes nil and I don't understand the reason, so don't know what's the mistake.
Edit 2 I think somehow I have 2 different instances of MyCustomView.
I have added new property in MyCustomView:
#interface MyCustomView : UIView
#property (weak, nonatomic) IBOutlet UIButton *firstItem;
#property(nonatomic, weak)id <MyCustomViewDelegate> delegate;
// this is test property
#property(nonatomic, assign)int testProperty;
#end
And when I set this property in viewDidLoad and then click to first button, I see that testProperty has value 0. So, this could mean something wrong with IBOutlet MyCustomView *customView.
You are correct, that you have two view objects. The one that you added to the storyboard and the one you created via loadNibNamed.
Bottom line, loadNibNamed will create the view for you (assuming that you've specified MyCustomView as the base class for the view specified in the NIB; you can leave NIB's "File owner" blank). You can then write a convenience method in MyCustomView to instantiate the NIB-based view:
+ (instancetype)myCustomViewWithDelegate:(id<MyCustomViewDelegate>)delegate {
NSArray *array = [[NSBundle mainBundle] loadNibNamed:#"MyCustomView" owner:delegate options:nil];
MyCustomView *view = array.firstObject;
NSAssert([view isKindOfClass:[MyCustomView class]], #"Base class of NIB's top level view is not MyCustomView");
view.delegate = delegate;
return view;
}
Then, rather than specifying the view on the storyboard, you must instantiate and add it to the view hierarchy programmatically, e.g.:
- (void)viewDidLoad {
[super viewDidLoad];
MyCustomView *view = [MyCustomView myCustomViewWithDelegate:self]; // since `self.myCustomView` should be `weak`, let's hold the view in local variable
[self.view addSubview:view];
self.myCustomView = view; // then set the property after the view is safely added to view hierarchy
NSDictionary *views = #{#"myCustomView" : self.myCustomView};
self.myCustomView.translatesAutoresizingMaskIntoConstraints = false;
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[myCustomView]|" options:0 metrics:nil views:views]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[myCustomView]|" options:0 metrics:nil views:views]];
// ...
}
Obviously, when adding a view programmatically, you have to specify its frame and autoresizingMask, or use constraints like shown above.
Unfortunately, I can't make a simple comment, so I have to ask, and give advices as 'answer'.
First of all, I don't see where do you set the delegate to your MainController, but I think in the viewDidLoad() of the controller.
Second thing. It's really important how do you setup your MyCustomView.xib, because you create a brand new object MyCustomView, and its properties will be unavailable from the controller.
MyCustomView* view = nil;
NSArray *views = [[NSBundle mainBundle] loadNibNamed:#"MyCustomView" owner:self options:nil];
view = views.firstObject;
[self addSubview:view];
Without knowing the setup of the xib, and setting method of the delegate, here is my guess what could help you:
In MyCustomView.xib set the class of the rootView to UIView.
In MyCustomView.xib set the class of the File's Owner to MyCustomView
Create your subview as a UIView.
UIView* view = nil;
NSArray *views = [[NSBundle mainBundle] loadNibNamed:#"MyCustomView" owner:self options:nil];
view = views.firstObject;
[self addSubview:view];
Set the delegate of the myCustomView object in your controller.
self.myCustomView.delegate = self;
I hope it will help, if not, then please extend your question with the delegate setter code block, and your xib setup method. (class of file's owner, class of root view etc)
Related
There are two classes in my code, one is NameCell, which contains a simple UILabel with text. The second one is NameValueCell, which inherits from that class, but adds also the property UIView *valueView.
One layout constraint needs to be altered. I'm looking for a way to override:
H:|[nameView]| - nameView should occupy full width in NameCell
with
H:|[nameView][valueView(==nameView)]| - nameView to valueView width ratio should be 1:1 in NameValueCell
What is the best practice out there for overriding NSLayoutConstraint? I have to stick to inheritance in my code because my application requires many different UITableViewCell specializations.
NameCell.h:
#interface NameCell : UITableViewCell
#property (nonatomic, retain) IBOutlet UIView *nameView;
#property (nonatomic, retain) IBOutlet UILabel *nameLabel;
#end
NameValueCell.h:
#interface NameValueCell : NameCell
#property (nonatomic, retain) IBOutlet UIView *valueView;
#end
NameCell.m:
#implementation NameCell
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
UIView *nameView = [[UIView alloc] init];
self.nameView = nameView;
[self.contentView addSubview:self.nameView];
UILabel *nameLabel = [[UILabel alloc] init];
self.nameLabel = nameLabel;
[self.nameView addSubview:self.nameLabel];
NSDictionary *views = NSDictionaryOfVariableBindings(nameView, nameLabel);
NSArray *constraints;
// The constraint that should be overridden
constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"H:|[nameView]|"
options: 0
metrics:nil
views:views];
[self.contentView addConstraints:constraints];
}
return self;
}
#end
NameValueCell.m:
#implementation NameValueCell
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
NSString *reuseID = reuseIdentifier;
UIView *valueView = [[UIView alloc] init];
self.valueView = valueView;
[self.contentView addSubview:self.valueView];
NSDictionary *views = #{
#"nameView": self.nameView,
#"nameLabel": self.nameLabel,
#"valueView": self.valueView
};
NSArray *constraints;
// The overriding constraint
constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"H:|[nameView][valueView(==nameView)]|"
options: 0
metrics:nil
views:views];
[self.contentView addConstraints:constraints];
}
return self;
}
#end
The subclass wants to augment the superclass behavior, for example, by adding additional subviews; but it also wants to override the superclass when it comes to creating constraints. To do both, the best approach is to factor out the view creating and the constraint creating code, then in the subclass, control whether we're augmenting or overriding by calling super selectively.
First, factor out...
// in NameCell.m initWithStyle
// super initWithStyle..., if (self) { ...
[self addCustomSubviews];
[self addCustomConstraints];
In NameCell, these new methods should be implemented exactly as you have them inline in the question, but in the subclass: (1) don't implement init at all, allowing the superclass init to call the factored code, and (2) override the factored code as follows...
// NameValueCell.m
- (void)addCustomSubviews {
// augmenting super here, so call super...
[super addCustomSubviews];
// add the value view
UIView *valueView = [[UIView alloc] init];
// and so on
}
- (void)addCustomConstraints {
// overriding super here, so don't call super, just add constraints
NSDictionary *views = #{
// and so in
}
In a less fussy but less clear alternative, you could just leave your inits as they are, but in the subclass init, remove the constraints that were just created in the super...
// in NameValueCell.m, in the initWithStyle method, before creating constraints
[self removeConstraints:self.constraints]; // then ...
NSDictionary *views = #{ // and so on...
I wouldn't call this alternative a best (or even good) practice, but I think it should work.
First: don't add constraints; activate them. It's far simpler and less error-prone.
Okay, then. Just keep a reference to the constraints that might need replacing in an NSArray instance variable:
constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"H:|[nameView]|"
options: 0
metrics:nil
views:views];
self.removeableConstraints = constraints; // an instance property
[NSLayoutConstraint activateConstraints: constraints];
Now all the subclass has to do is deactivate self.removeableConstraints and activate its substitute constraints.
[NSLayoutConstraint deactivateConstraints: self.removeableConstraints];
constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"H:|[nameView][valueView(==nameView)]|"
options: 0
metrics:nil
views:views];
[NSLayoutConstraint activateConstraints: constraints];
That is the general pattern for swapping out constraints, and there is no reason that the class/subclass relationship here shouldn't use it.
I created a xib file.
Below is the code:
#import "LoginView.h"
#implementation LoginView
-(id)initWithFrame:(CGRect)frame{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
[self setup];
}
return self;
}
-(void) setup{
[[NSBundle mainBundle] loadNibNamed:#"LoginView" owner:self options:nil];
self.bounds = self.loginView.bounds;
[self addSubview:self.loginView];
}
Now I created a view controller and add a UIView to it. In viewWillAppear, I am trying to load the xib file into a defined UIView in my view controller.
-(void)viewWillAppear:(BOOL)animated{
self.containerView.layer.borderColor = [UIColor blackColor].CGColor;
self.containerView.layer.borderWidth = 2.0f;
LoginView *loginView = [[LoginView alloc] initWithFrame:CGRectMake(self.containerView.frame.origin.x,self.containerView.frame.origin.y, self.containerView.frame.size.width, self.containerView.frame.size.height)];
[self.containerView addSubview:loginView];
}
But, the xib file is going outside the defined UIView which I named containerView. I have linked the containerView to UIView in ViewController.
#property(nonatomic, weak) IBOutlet UIView *containerView;
Can anyone help me why I am getting this output? I need to get the custom uiview inside the containerView in ViewController. Like the textfields and buttons should be inside the black border.
As you are adding your view on container view then x and y should be 0,0
You need to replace this :
LoginView *loginView = [[LoginView alloc] initWithFrame:CGRectMake(self.containerView.frame.origin.x,self.containerView.frame.origin.y, self.containerView.frame.size.width, self.containerView.frame.size.height)];
by this:
LoginView *loginView = [[LoginView alloc] initWithFrame:CGRectMake(0,0, self.containerView.frame.size.width, self.containerView.frame.size.height)];
Hope this Helps!
I created a .xib file form my custom view.
I created .h/.m files for that view.
I ctrl dragged from button to header file to create an IBAction and set the value to touchUpInside. Here is what is happening:
http://screencast.com/t/R1WTpK7xp
WTF?
It triggers event when up is outside the button?
EDIT:
Here is the screenshot:
And what is the thing with down vote? I don't see a point in that.
View.h
#import <UIKit/UIKit.h>
#import "DrawingViewDelegate.h"
#interface DrawingBottomToolbarView : UIView
#property (weak) id <DrawingViewDelegate> delegate;
- (IBAction)lineSegmentButtonPush:(id)sender;
#end
View.m
#import "DrawingBottomToolbarView.h"
#implementation DrawingBottomToolbarView
#synthesize delegate;
- (id)initWithFrame:(CGRect)frame
{
NSLog(#"frame");
self = [super initWithFrame:frame];
if (self) {
// Initialization code
[self addSubview:[[[NSBundle mainBundle] loadNibNamed:#"DrawingBottomToolbarView" owner:self options:nil] objectAtIndex:0]];
//[[[NSBundle mainBundle] loadNibNamed:#"DrawingBottomToolbarView" owner:self options:nil] objectAtIndex:0];
//[self addSubview:self.];
}
return self;
}
//-(id)initWithCoder:(NSCoder *)aDecoder{
//
// NSLog(#"coder");
// if ((self = [super initWithCoder:aDecoder])){
// [self addSubview:[[[NSBundle mainBundle] loadNibNamed:#"DrawingBottomToolbarView" owner:self options:nil] objectAtIndex:0]];
// }
// return self;
//}
- (IBAction)lineSegmentButtonPush:(id)sender
{
NSLog(#"line push");
}
#end
I don't get it where is the problem.
EDIT 2:
I tried setting buttons as outlets and add target/action in code and same thing happens:
.h
#property (weak, nonatomic) IBOutlet UIButton *lineSegmentButton;
.m
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
[self addSubview:[[[NSBundle mainBundle] loadNibNamed:#"DrawingBottomToolbarView" owner:self options:nil] objectAtIndex:0]];
self.currentSelectedPathSegment = NoneSegment;
[self.lineSegmentButton addTarget:self action:#selector(lineSegmentButtonPush:) forControlEvents:UIControlEventTouchUpInside];
}
return self;
}
EDIT 3: Here is where I add two views. drawing view is created in code, bottomToolbar is created from .xib file.
kBottomToolbarHeight is constant with same value as height defined in .xib file.
- (void)viewWillAppear:(BOOL)animated
{
[self.view addSubview:self.drawingView];
[self.view addSubview:self.bottomToolbar];
CGRect selfRect = self.view.bounds;
CGRect drawingViewRect = selfRect;
CGRect bottomToobarRect = selfRect;
drawingViewRect.size.height = selfRect.size.height - kBottomToolbarHeight;
bottomToobarRect.size.height = kBottomToolbarHeight;
bottomToobarRect.origin.y = drawingViewRect.size.height;
self.drawingView.frame = drawingViewRect;
self.bottomToolbar.frame = bottomToobarRect;
}
The behavior you mention and show in the screencast is exactly the same as I experienced when I somewhere in the view hierarchy had a parent view with a UITapGestureRecognizer.
I'm unsure whether or not to flag your question as a possible duplicate of this one which helped me solve my problem.
For reference this is not a problem in iOS 6.0, only earlier versions.
I have a problem with a custom uiview (MyCustomView) loading its view from a NIB (MyCustomView.xib).
The problem is the MyCustomView is reused in other XIBs so awakeFromNib is called.
The CustomView.xib also contains an instance of MyCustomView so loadNibNamed in MyCustomView.awakeFromNib goes into infinite loop
im getting infinite loop as:
SomeVC.xib
+ calls MyCustomView awakeFromNib
++ which calls loadNibNamed: MyCustomView.nib
+++++ but top level View is also instance of MyCustomView
....so awakeFromNib: goes into infinite loop
Im just wondering if when a custom view loads its self from a nib then top level UIView shouldnt be instance of MyCustomView?
If so how do I wire up outlet/actions (do I make MyCustomView the files owner instead?)
anyway cheers
LONG EXPLANATION:
I have a row of buttons which I want to reuse on a number of screens.
So I created a custom view
#interface ScreenMenuButtonsView : UIView
#property (retain, nonatomic) IBOutlet UIButton *buttonHome;
#property (retain, nonatomic) IBOutlet UIButton *buttonMyMusic;
#property (retain, nonatomic) IBOutlet UIButton *buttonStore;
#property (retain, nonatomic) IBOutlet UIButton *buttonSocial;
#property (retain, nonatomic) IBOutlet UIButton *buttonSettings;
#end
I wanted to layout the view in IB so I created a NIB
ScreenMenuButtonsView_iPad.xib
Files Owner: no set NSObject
VIEW: ScreenMenuButtonsView
+ UIButton
+ UIButton
+ UIButton
I want the custom view to load its own NIB internally and I used the example from
http://nathanhjones.com/2011/02/20/creating-reusable-uiviews-with-a-drop-shadow-tutorial/
http://nathanhjones.com/wp-content/uploads/2011/02/ReusableTableHeaders.zip
- (id)initWithFrame:(CGRect)frame {
//self = [super initWithFrame:frame]; // not needed - thanks ddickison
if (self) {
NSArray *nib = [[NSBundle mainBundle]
loadNibNamed:#"HeaderView"
owner:self
options:nil];
[self release]; // release object before reassignment to avoid leak - thanks ddickison
self = [nib objectAtIndex:0];
self.layer.shadowColor = [[UIColor blackColor] CGColor];
self.layer.shadowOffset = CGSizeMake(1.0, 1.0);
self.layer.shadowOpacity = 0.40;
}
return self;
}
Which is ok except this presumes you call initWithFrame somewhere to instantiate the CustomView.
The HeaderView example calls it like this:
// load the header - this could be done in viewWillAppear as well
HeaderView *header = [[HeaderView alloc]
initWithFrame:CGRectMake(0, 0, 320, 60)];
header.lblTitle.text = #"That. Just. Happened.";
tblData.tableHeaderView = header;
[header release];
I wanted to be able to use my custom view class in any other XIB
HomeScreenViewController.xib
Files Owner: HomeScreenViewController
View:
+ ....other controls
+ ScreenMenuButtonsView
The problem is when HomeScreenViewController is loaded it calls awakeFromNib on my customView:
so I moved the nib loading out initWithFrame and into a method called setupView and called it from initWithFrame AND awakeFromNib
- (id) initWithFrame:(CGRect)frame
{
if ( ( self = [super initWithFrame: frame] ) )
{
[self setUpView];
}
return self;
}
- (void)awakeFromNib
{
[self setUpView];
}
- (void) setUpView{
if (self) {
NSArray* nibViewsArray = nil;
if([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad){
//IPAD
nibViewsArray = [[NSBundle mainBundle] loadNibNamed:#"ScreenMenuButtonsView_iPad"
owner:self
options:nil];
...
}
My problem is that when HomeScreenViewController.xib is loaded:
HomeScreenViewController.xib
Files Owner: HomeScreenViewController
View:
+ ....other controls
+ ScreenMenuButtonsView
It tries to create ScreenMenuButtonsView and because its in a NIB
it calls
ScreenMenuButtonsView awakeFromNib
BUT in here I call loadNibNamed:
- (void) setUpView{
_viewSetupAlready = TRUE;
//http://nathanhjones.com/2011/02/20/creating-reusable-uiviews-with-a-drop-shadow-tutorial/
//self = [super initWithFrame:frame]; // not needed - thanks ddickison
if (self) {
NSArray* nibViewsArray = nil;
if([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad){
//IPAD
nibViewsArray = [[NSBundle mainBundle] loadNibNamed:#"ScreenMenuButtonsView_iPad"
owner:self
options:nil];
}else{
//IPHONE
nibViewsArray = [[NSBundle mainBundle] loadNibNamed:#"ScreenMenuButtonsView_iPhone"
owner:self
options:nil];
}
if(nibViewsArray){
//[self release]; // release object before reassignment to avoid leak - thanks ddickison
UIView * myView = [nibViewsArray objectAtIndex: 0];
//Check the top level object in NIB is same type as this custom class
//if wrong you probably forgot to use correct NIB name in loadNibNamed:
if([myView isMemberOfClass:[self class]]){
self = (ScreenMenuButtonsView *)myView;
}else{
NSLog(#"ERROR:top level view is not same type as custom class - are you loading the correct nib filename in loadNibNamed:\n%#", myView);
}
}else{
NSLog(#"ERROR:nibViewsArray is nil");
}
}
}
loadNibName: load
ScreenMenuButtonsView_iPad.xib
Files Owner: no set NSObject
VIEW: ScreenMenuButtonsView
+ UIButton
+ UIButton
+ UIButton
But top view is ScreenMenuButtonsView
so creates a new instance and calls awakeFromNib
and I get infinite loop.
I know why the loops occurring and instance on ScreenMenuButtonsView is loading another instance from the xib.
My question is in
ScreenMenuButtonsView_iPad.xib
Files Owner: no set NSObject
VIEW: ScreenMenuButtonsView
+ UIButton
+ UIButton
+ UIButton
should I change
VIEW: UIView
but what about outlets and actions for the button:
should I change
Files Owner: no set NSObject
to be the view class
Files Owner: ScreenMenuButtonsView
Let's say I've created a subclass of UIView and I'm loading it with a nib file.
I do this:
MySubView.m
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"MySubView" owner:self options:nil];
[self release];
self = [[nib objectAtIndex:0] retain];
self.tag = 1;
[self fire];
}
return self;
}
- (void)fire {
NSLog(#"Fired MySubView");
}
Now I want to create some variations, but I don't want to copy the nib file, so I try to subclass MySubView like this, changing the background color:
RedMySubView.m
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
self.backgroundColor = [UIColor redColor];
[self fire];
}
return self;
}
- (void)fire {
NSLog(#"Fired RedMySubView");
}
The view is created, the background color is changed, but the fire action is not overridden by the subclass. If I call the fire method, the result is Fired MySubView in the console.
How can I resolve this?
I want to keep the nib layout, but give it a new class.
I would say that with [self release] in the MySubview initializer initWithFrame you are throwing away the class you want to create with the initializer. The class is loaded by the loadNibName method and therefore has the same class as defined in the nib.
So it is useless to call the initializer in the subclass.
Try to implement your own nib constructor in MySubview (e.g. initWithNibFile):
- (id) initWithNibFile:(NSString *) nibName withFrame:(CGRect) frame
etc. and call this constructor in RedMySubview
- (id) initWithNibFile:(NSString *) nibName withFrame:(CGRect) frame {
self = [super initWithNibFile:mynib withFrame:MyCGRect];
if (self)
....
If you now look up that your nib file really has RedMySubview as class, fire should be
overridable. If you use both MySubview and RedMySubview, you must duplicate the xib.
Or you create an abstract class (a stub) which implements only the initWithNibFile initializer and the UIViews you want to create are subclasses of it:
MyAbstractNibUIView initWithNibFile:withFrame:
MyRedSubview : MyAbstractNibUIView red.xib
MyGreenSubview :MyAbstractNibUIView green.xib
MyBlueSubview : MyAbstractNibUIView blue.xib
When you call self = [[nib objectAtIndex:0] retain] you basically override your "self" object to become a MySubView, since a MySubView is the base object in the nib file. This is undesired because if the calling class is a RedMySubView, then it will be overridden into a MySubView.
Instead you want to change your - (id)initWithFrame:(CGRect)frame in MySubView into this:
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"MySubview" owner:self options:nil];
// The base of the nib file. Don't set self to this, instead copy all of its
// subviews, and "self" will always be the class you intend it to be.
UIView *baseView = [nib objectAtIndex:0];
// Add all the subviews of the base file to your "MySubview". This
// will make it so that any subclass of MySubview will keep its properties.
for (UIView *v in [baseView subviews])
[self addSubview:v];
self.tag = 1;
[self fire];
}
return self;
}
Now, everything should work in the initializer of "MyRedSubView", except that fire will fire twice, since you call it both in MySubView and RedMySubView.