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]];
Related
I am adding a UIView to a UITableViewCell using the code below
[cell.contentView addSubview:self.mTravelSearchView];
The travel search view appears in the cell. In the interface builder I have added correct constraints to self.mTravelSearchView so everything within this view renders correctly.
However I believe I need to programmatically add constraints so that the mTravelSearchView occupies the entire cell as at present it doesn't.
I have tried the below code, but the width of the view is wrong still and only uses a portion of the cell. What am I doing wrong?
[cell.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[travelSearch]|" options:0 metrics:nil views:#{#"travelSearch" : self.mTravelSearchView}]];
[cell.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[travelSearch]|" options:0 metrics:nil views:#{#"travelSearch" : self.mTravelSearchView}]];
I'm not really familiar with programmatically adding the constraints.
Nevermind, found that I needed this line
self.mTravelSearchView.translatesAutoresizingMaskIntoConstraints = NO;
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.
I'm trying to do something very simple:
Creating a view from scratch, adding it to the controller's view, and have it stretch edge to edge to its superview, but I get console warning once the app runs, saying the constraints cannot be simultaneously added to the view.
backView = [UIView new];
backView.backgroundColor = [UIColor redColor];
[[self view] insertSubview:backView aboveSubview:tableview];
[[self view] addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"|-0-[back]-0-|"
options:0
metrics:nil
views:#{#"back": backView}]];
[[self view] addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-0-[back]-0-|"
options:0
metrics:nil
views:#{#"back": backView}]];
When you create a UIView in code it translates auto-resizing mask to constraints.
i.e. it takes the frame you gave it when you created it and the auto-resizing mask and converts them into NSLayoutConstraints.
These auto-generated constraints are then conflicting with the constraints that you are adding manually.
If I'm right then you will be able to fix it by adding the line...
backView.translatesAutoresizingMaskIntoConstraints = NO;
right after the first line and it should fix the problem.
I made a few UIScrollView's in different views, they all worked without Autolayout.
I turned Autolayout on, because it was better for my app.
But since then, there's a big problem with my UIScrollView's:
No one is scrolling, they don't work.
Here's my code for a UIScrollView:
.m:
-(viewDidLoad) {
scrollerHome.contentSize = CGSizeMake(320, 1000);
scrollerHome.scrollEnabled = YES;
[self.view addSubview:scrollerHome];
scrollerHome.showsHorizontalScrollIndicator = false;
scrollerHome.showsVerticalScrollIndicator = false;
[super viewDidLoad];
}
.h:
#interface ViewController : UIViewController{
IBOutlet UIScrollView *scrollerHome;
}
Do I have to add some code because I turned on Autolayout?
You should call [super viewDidLoad] before doing anything !
In autolayout, you do not set the contentSize manually. Autolayout works slightly differently with scrollviews, whereby the contentSize of the scroll view is dictated by the constraints of the scrollview's subviews.
If you're trying to force the contentSize to some large size (for example, you're implementing some infinite scroller), you can just add a subview of the appropriate size, e.g.:
UIView *containerView = [[UIView alloc] init];
containerView.translatesAutoresizingMaskIntoConstraints = NO;
[self.scrollView addSubview:containerView];
NSDictionary *views = NSDictionaryOfVariableBindings(containerView);
[self.scrollView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[containerView]|" options:0 metrics:nil views:views]];
[self.scrollView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[containerView(1000)]|" options:0 metrics:nil views:views]];
But if you were trying to set the contentSize in anticipation of adding subviews, you generally don't have to do anything, such as the above snippet. Just add your subviews, provide their constraints, and autolayout will adjust the scroll view's contentSize automatically.
As mentioned above, with autolayout, you can just add the subviews to your scrollview (with their constraints), and the contentSize will be calculated automatically for you.
There is a trick here, though. You sometimes you want to size a subview based upon the dimensions of the screen. But the usual technique of using the | symbols won't work. For example, for an imageview1 inside a scrollview, the usual #"H:|[imageview1]|" won't set the imageview1 to be the width of the screen, but rather it will define the scroll view's contentSize to match the width of imageview1, but it says nothing about what the width of that image view should be!
So, it's useful to capture a reference to the scroll view's superview. That way, you can use something like #"H:|[imageview1(==superview)]|", which not only says "make the scroll view's contentSize equal to the width of imageview1", but also "define the width of imageview1 to be equal to the width of the scroll view's superview."
Thus, for example, to add three images in a paging scroll view, you might do something like:
UIImageView *imageview1 = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"_DSC0004.jpg"]];
imageview1.contentMode = UIViewContentModeScaleAspectFit;
imageview1.translatesAutoresizingMaskIntoConstraints = NO;
[self.scrollView addSubview:imageview1];
UIImageView *imageview2 = ... // configured similar to imageview1
UIImageView *imageview3 = ... // configured similar to imageview1
UIView *superview = self.scrollView.superview;
NSDictionary *views = NSDictionaryOfVariableBindings(imageview1, imageview2, imageview3, superview);
// not only define the image view's relation with their immediate scroll view,
// but also explicitly set the size in relation to the superview, too!
[superview addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[imageview1(==superview)][imageview2(==superview)][imageview3(==superview)]|" options:0 metrics:nil views:views]];
[superview addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[imageview1(==superview)]|" options:0 metrics:nil views:views]];
[superview addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[imageview2(==superview)]|" options:0 metrics:nil views:views]];
[superview addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[imageview3(==superview)]|" options:0 metrics:nil views:views]];
self.scrollView.pagingEnabled = YES;
From the Apple iOS 6.0 release notes:
"In general, Auto Layout considers the top, left, bottom, and right edges of a view to be the visible edges. That is, if you pin a view to the left edge of its superview, you’re really pinning it to the minimum x-value of the superview’s bounds. Changing the bounds origin of the superview does not change the position of the view.
The UIScrollView class scrolls its content by changing the origin of its bounds. To make this work with Auto Layout, the top, left, bottom, and right edges within a scroll view now mean the edges of its content view."
You can find the full notes here and find the answer to your question in the section that I quoted from. They give code examples on how to use UIScrollView in a mixed Auto Layout environment.
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]];
}