I have a custom class called CNJobMapView which is a subclass of UIView. This custom class loads a view from a nib file and adds it as a subview. I do this so that I can add a UIView object to a view in a storyboard, give it the CNJobMapView custom class, and it will appear in that view when I run the app.
I load the nib in CNJobMapView's awakeFromNib method, like so:
-(void)awakeFromNib {
[[NSBundle mainBundle] loadNibNamed:#"CNJobMapView" owner:self options:nil];
[self addSubview: self.contentView];
[self internalSetup];
}
in this case, self.contentView is the main view inside the nib named "CNJobMapView". It is linked from IB.
In iOS 7 and 7.1, this all works correctly. It appears like so:
In iOS 8, this does not work correctly. The contentView appears in completely the wrong position. Like so:
I have no idea why it's different in iOS 8. I would love some help figuring out this issue!
I have found the answer!
Apparently in iOS 7, it assumes the correct constraints for placing the contentView inside the CNJobMapView. In iOS 8, this is no longer the case. I have modified the awakeFromNib function as follows:
-(void)awakeFromNib {
[[NSBundle mainBundle] loadNibNamed:#"CNJobMapView" owner:self options:nil];
[self addSubview: self.contentView];
[self internalSetup];
NSDictionary *views = NSDictionaryOfVariableBindings(contentView);
NSArray *horizContentConstraints = [NSLayoutConstraint constraintsWithVisualFormat:#"H:|[contentView]|" options:0 metrics:nil views:views];
[self addConstraints:horizContentConstraints];
NSArray *vertContentConstraints = [NSLayoutConstraint constraintsWithVisualFormat:#"V:|[contentView]|" options:0 metrics:nil views:views];
[self addConstraints:vertContentConstraints];
}
And the problem is now fixed.
Related
Background:
Just started with learning and using auto layout. So I might be going wrong with constraints.
Supporting both iOS7 and iOS8, so haven't ventured into size classes.
Scenario:
I have a superview created programmatically which is present in all the screens of the app. Now I tried loading an XIB and assigned it as a subview to this superview. It looks fine in the iPhone (as the XIB was designed with iPhone dimensions). But in an iPad, using the same XIB, the subview is keeping the iPhone dimensions.
Is there any way this setup will work on an iPad, having the XIB resize to fill the screen without setting up a constraint between the subview and the superview?
I will post the existing constraints if you think it will help.
You can achieve it by either setting the frame of subview while adding it or adding constraints to subview programmatically.
Enjoy.. :)
you should add constraints for your view from xib after you create it programmatically. Think about it as a usual subview of your main view. so
Create your view from xib
UIView *someView = [[[NSBundle mainBundle] loadNibNamed:NSStringFromClass([YOUR_VIEW_CLASS_NAME class]) owner:self options:nil] lastObject];
Add it as subview
[yourSuperview addSubview: someView];
Add constraints for top, bottom, left, right
NSDictionary *views = #{#"someView":self.background};
[yourSuperview addConstraints:[NSLayoutConstraint
constraintsWithVisualFormat:#"H:|[someView]|"
options:0
metrics:nil
views:views]];
[yourSuperview addConstraints:[NSLayoutConstraint
constraintsWithVisualFormat:#"V:|[someView]|"
options:0
metrics:nil
views:views]];
Call update via [someView setNeedsLayout];
I am trying to use auto layout with size classes. I have a basic page with UIImageView as background and UIButton (with image as background). There are total of 6 constraints: 0 space for top/right/bottom/left for UIImageView and X center align and 230 for top space on UIButton. Here is what the interface builder looks like:
Her is what the preview looks like:
However, this is what I get when I run application on simulator (or device):
I cannot figure out why the constraints are not being executed?
I should mention that this behavior is experienced when I try to set up auto layout in the .xib file. If I try to do same thing in the project that has storyboard, everything seems to be in order for simulator and real device.
How are you loading the view from xib?
e.g. Assuming your view is in a file called View.xib, I can recreate your problem with
- (void)viewDidLoad {
[super viewDidLoad];
UIView *v = [[NSBundle mainBundle] loadNibNamed:#"View" owner:self options:nil][0];
[self.view addSubview:v];
}
The reason this doesn't work, is because there is nothing telling auto layout to position and size the view to fill the screen, so the view just fits to its contents - which will be the size of the background image (because you have the constraints pinning the image to its parent view).
To fix it, first ensure the view is using auto layout by setting translatesAutoresizingMaskIntoConstraints = NO (it defaults to YES otherwise, when loading from XIB) and then pin the view to its parent:
- (void)viewDidLoad {
[super viewDidLoad];
UIView *v = [[NSBundle mainBundle] loadNibNamed:#"View"
owner:self
options:nil][0];
[self.view addSubview:v];
v.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addConstraints:
[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[view]|"
options:0
metrics:nil
views:#{#"view": v}]];
[self.view addConstraints:
[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[view]|"
options:0
metrics:nil
views:#{#"view": v}]];
}
I'm trying to create a card view that flips based on this tutorial. It creates a UIView and adds the card view.
For this I created a custom UIView (with Xib) and so far it works fine. I've added the correct constraints in my Storyboard for the view on which addSubview is called. This is working so far, but when I add the custom UIView it refers to its size in the xib and not the size of the superview.
How can I add the necessary Autolayout constraints to make the subview fit into its superview?
Thanks!
P.S. I've written it in Objective-C but it doesn't matter to me, if the answer is in swift or Objective-C.
Not sure, what wrong is there. I did the same thing in following way:
1) ProductItemView, a sub class of UIView and created ProductItemView.xib which is having a UIView object.
2) In .xib set File's owner class to ProductItemView so that UIView object of .xib and other subviews can be loaded and linked with IBOutlet.(see image)
3) In init method (initWithFrame: in my case) method put below code
NSArray *nibViewArray = [[NSBundle mainBundle] loadNibNamed:#"ProductItemView" owner:self options:nil];
if (nibViewArray.count) {
UIView *nibView = [nibViewArray objectAtIndex:0];
[self addSubview:nibView];
nibView.translatesAutoresizingMaskIntoConstraints = NO;
NSDictionary *viewDict = NSDictionaryOfVariableBindings(nibView);
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-0-[nibView]-0-|" options:0 metrics:nil views:viewDict]];
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-0-[nibView]-0-|" options:0 metrics:nil views:viewDict]];
}
and finally create object of ProdutItemView and with frame or set Constraints and add it to super view. If you are setting constraint then do not forget to set translatesAutoresizingMaskIntoConstraints property to NO.
Hope it will be helpful for you.
if you want to do it in code. seems to work out better for me and makes more sense.
// make sure myCustomView is added to the superview
// and ignore the layout guides if the superview is not your view controller
[myCustomView setTranslatesAutoresizingMaskIntoConstraints: NO];
id topGuide = self.topLayoutGuide;
id bottomGuide = self.bottomLayoutGuide;
NSDictionary * viewsDictionary = NSDictionaryOfVariableBindings(myCustomView, topGuide, bottomGuide);
[mySuperiew addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[myCustomView]|" options:0 metrics: 0 views:viewsDictionary]];
[mySuperiew addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[topGuide][myCustomView][bottomGuide]|" options:0 metrics: 0 views:viewsDictionary]];
I've defined a custom view in a NIB file and would like to instantiate a copy in a StoryBoard but I'm having issues with autolayout.
In a simple example, the custom view has single label with a fixed size and centered both vertically and horizontally, all using autolayout.
The file owner is set to my class, it has an outlet to the top view. In the custom view implementation I do:
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if(self)
{
[[NSBundle mainBundle] loadNibNamed:#"FMCompassView" owner:self options:nil];
[self addSubview:self.topView];
}
return self;
}
Now, in my storyboard, I add a UIView, set it's class to my custom class and layout it out sized and centered on my page, again using autolayout.
And my widget is positioned and size properly but it's content is not resized as illustrated below:
I tried adding more constraints after loading from the NIB, something along the lines of:
UIView* subV = self.topView;
NSDictionary *viewsDictionary = NSDictionaryOfVariableBindings(subV);
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"|[subV]|" options:NSLayoutFormatAlignAllBaseline metrics:nil views:viewsDictionary]];
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[subV]|" options:NSLayoutFormatAlignAllBaseline metrics:nil views:viewsDictionary]];
But this causes invalid layout constraints.
Any ideas on how to get this to work?
Cheers!
The view created from loading the NIB needs to be told not to convert auto resizing mask to constraints, EVEN if the content of the NIB is actually created with autolayout enabled.
So after loading the NIB and before adding it's top view to the custom view, I need to call:
[self.topView setTranslatesAutoresizingMaskIntoConstraints:NO];
In addition to what mkrus suggests above, this is what my initWithCoder: looks like for the custom class nib:
- (instancetype)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self) {
NSString *className = NSStringFromClass([self class]);
self.view = [[[NSBundle mainBundle] loadNibNamed:className owner:self options:nil] firstObject];
[self.view setTranslatesAutoresizingMaskIntoConstraints:NO];
[self addSubview:self.view];
[self.view mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(self);
}];
}
return self;
}
The reason for this initWithCoder: approach is explained here.
I've added the Masonry auto layout constraints so that it works with the constraints defined in interface builder.
Disabling "Use Size Classes" worked for me. I didn't have to do what mkrus has mentioned.
My UIViewController creates its view by overwriting the loadView method:
- (void)loadView {
UIView *view = [[UIView alloc] init];
view.autoresizingMask = UIViewAutoresizingFlexibleHeight|UIViewAutoresizingFlexibleWidth;
self.view = view;
}
Now I'd like to switch to AutoLayout and therefore add an
view.translatesAutoresizingMaskIntoConstraints = NO;
to the loadView method. Now I have to specify the same constraints which were autogenerated before. My approach was to overwrite updateViewConstraints with
- (void)updateViewConstraints {
if (0 == [[self.view constraints] count]) {
NSDictionary* views = #{#"view" : self.view};
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[view]|" options:0 metrics:0 views:views]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[view]|" options:0 metrics:0 views:views]];
}
[super updateViewConstraints];
}
But I get an exception because I think this kind of constraints should go with the super view:
*** Terminating app due to uncaught exception 'NSGenericException', reason: 'Unable to install constraint on view. Does the constraint reference something from outside the subtree of the view? That's illegal.
So, how do the correct Contraints have to look like?
You need to set the constraints on the superview. The exception is caused by referencing the superview by passing "|" in the visual format. If you update your code like the following it will work:
- (void)updateViewConstraints {
if (self.view.superview != nil && [[self.view.superview constraints] count] == 0) {
NSDictionary* views = #{#"view" : self.view};
[self.view.superview addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[view]|" options:0 metrics:0 views:views]];
[self.view.superview addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[view]|" options:0 metrics:0 views:views]];
}
[super updateViewConstraints];
}
In practice you'll probably want to check for something other than 0 constraints on the superview but this should help.
You don't have to set the constraints on your root view as Matt Neuburg explains the Chapter 19 of his Programming iOS 6 book, in section Manual Layout:
We have not bothered to give our view (self.view) a reasonable frame. This is because we are relying on someone else to frame the view appropriately. In this case, the “someone else” is the window, which responds to having its rootViewController property set to a view controller by framing the view controller’s view appropriately as the root view before putting it into the window as a subview.
The problem with CEarwood's approach is that this is a ViewController, and its view is not the subview of any other view, so calling self.view.subview just results in nil. Remember that the Apple documentation and guidelines strongly suggest that a UIViewController occupies more or less the whole screen (besides the navigation bar or tab bar etc.).
Palimondo's answer is basically the right one: your UIViewController needs to init its view in loadView, but it doesn't need to specify its frame or constraints because those are automatically set to the window's frame and constraints. This is exactly what is done by default if you don't implement loadView yourself.
I'm not sure you need to set the constraints for the root view of the window.
That said, your constraints look correct, I think the exception you get is because this:
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[view]|" options:0 metrics:0 views:views]];
uses the | notation to represent the view's superview. As the root level view, it has no superview. Something like this may work better:
- (void)loadView {
UIView *customView = [[UIView alloc] init];
[self.view addSubview:customView];
NSDictionary* views = #{#"customView" : customView};
[customView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[customView]|" options:0 metrics:0 views:views]];
[customView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[customView]|" options:0 metrics:0 views:views]];
}