I'm trying to force myself to start using AutoLayout everywhere. I have a scenario that is extremely simple using frames but I can't make it work using Autolayout.
I have a StoryBoard I can't touch because it belongs to a library. When the ViewController related to the storyboard loads I want to programmatically add a View. So I created a UIView in the viewDidLoad, added the constraints for height and width and also two other constraints to position them. Everything worked fine BUT the width, I want to have the same width as an other view. How should I do this?
I logged the size of the "other view" in the viewDidLoad and the size is not right, I also logged the self.view.frame.size.width, and is not correct either.
I read that the correct sizes are not set until viewDidLayoutSubviews, so I tried to add my code there but I get in a loop of calls to viewDidLayoutSubviews that never ends. What am I doing wrong?
Try putting the code in viewDidAppear. Check if the subview is already a descendent of the view and if no, then add the subview with the constraints.
If you want two views to have the same width, you should do that with
constraintWithItem:attribute:relatedBy:toItem:attribute:multiplier:constant:. You can do this in viewDidLoad.
NSLayoutConstraint *widthCon = [NSLayoutConstraint constraintWithItem:view1 attribute:NSLayoutAttributeWidth relatedBy: NSLayoutRelationEqual toItem:view2 attribute:NSLayoutAttributeWidth multiplier:1 constant:0];
This assumes that the view that comes from the library storyboard was made using constraints.
You can surely use this method of UIViewController's,
- (void)updateViewConstraints
{
[super updateViewConstraints] ;
// now update your subviews contraints or log your view sizes
}
As you are using constraint based layout and wants to update the subviews constraints depending to UIViewController's constraints then you should always use the following method.
Related
I am having an UIImageView (say imageView1) and a UITextView(say textView1) which have to be displayed vertically (one [imageView1] below the other [textView1]) beginning with the same margin position as of textView1. I have to achieve this through autolayout programmatically.
I know that this can be done by setting the vertical constraints like below for both the views.
NSLayoutConstraint constraintsWithVisualFormat:#"V:|[textView1]"
But the problem I have here is I already have many text views(textView2, textView3) arranged in horizontal before and after this textView1.
I have already added many autolayout constraints to this textView1 through storyboard. Based on the different screen size and orientation the textView1 margin differs as per the constraints that are provided on the storyboard for this.
Now how can I provide the autolayout constraint programmatically in such a way that my imageView1 is to align in par vertically with the same margin as that of textView1?
p.s: imageView1 is created programmatically in code but where as all other views that I mentioned above are created through storyboard.
+ Adding images for easy understanding
In the image, imageView1 is the UI Image. I have created it in storyboard just for understanding purpose but in real it will be created programmatically and this have to be aligned to the margin of UITextView (textView1) present below it.
This is the constraint that I want to create it through programmatically(In case this is the real question here :).
This constraint is to always make sure that imageView1 and textView1 start originating from the same margin.
How to define this constraint programmatically ?
Rather than using the visual format, you can just instantiate a constraint directly, e.g.
[NSLayoutConstraint
constraintWithItem:imageView1 attribute:NSLayoutAttributeLeading
relatedBy:NSLayoutRelationEqual
toItem:textView1 attribute:NSLayoutAttributeLeading
multiplier:1.0 constant:0.0]
Use Masonry for setting constraints programmatically.It is very easy to use and reduce lots of complexities for the user.
https://github.com/SnapKit/Masonry
...you can try to build an UIView, set the constraints that it will need, and use it as a placeholder for your UIImageViews.(later you can add them inside of such a view) or, by the otherside, using an UICollectionView instead.
You can set the options argument in
constraintsWithVisualFormat:options:metrics:views: check Apple Class Reference.
Your code might be as follows
NSString* leadingConstraintsExpression = #"V:[imageView1][textView1]";
NSDictionary* viewsDictionary = NSDictionaryOfVariableBindings(imageView1,textView1);
NSArray* leadingConstraints = [NSLayoutConstraint
constraintsWithVisualFormat:leadingConstraintsExpression
options:NSLayoutFormatAlignAllLeading
metrics:nil
views:viewsDictionary];
[self.view addConstraints:leadingConstraints];
My layout constraints are fine in Interface Builder but an exception occurs at runtime thanks to some part of the framework applying fixed height and width constraints that I really don't want. Why are they there, and how to turn them off?
They're the last two constraints shown in the logged list:
2014-04-26 09:02:58.687 BBCNews[32058:60b] Unable to simultaneously satisfy constraints.
Probably at least one of the constraints in the following list is one you don't want. Try this: (1) look at each constraint and try to figure out which you don't expect; (2) find the code that added the unwanted constraint or constraints and fix it. (Note: If you're seeing NSAutoresizingMaskLayoutConstraints that you don't understand, refer to the documentation for the UIView property translatesAutoresizingMaskIntoConstraints)
(
"<NSLayoutConstraint:0xbf478a0 UIView:0xbf4a3c0.height == 0.28125*UIView:0xbf4a3c0.width>",
"<NSLayoutConstraint:0xbf47190 UIView:0xbf4a3c0.leading == BNMyNewsCell_landscape:0xbf48b10.leading>",
"<NSLayoutConstraint:0xbf47160 UIView:0xbf4a3c0.trailing == BNMyNewsCell_landscape:0xbf48b10.trailing>",
"<NSLayoutConstraint:0xbf47130 BNMyNewsCell_landscape:0xbf48b10.bottom == UIView:0xbf4a3c0.bottom>",
"<NSLayoutConstraint:0xbf47100 UIView:0xbf4a3c0.top == BNMyNewsCell_landscape:0xbf48b10.top>",
"<NSLayoutConstraint:0xd4c3c40 'UIView-Encapsulated-Layout-Width' H:[BNMyNewsCell_landscape:0xbf48b10(304)]>",
"<NSLayoutConstraint:0xd4c38a0 'UIView-Encapsulated-Layout-Height' V:[BNMyNewsCell_landscape:0xbf48b10(290)]>"
}
Will attempt to recover by breaking constraint
<NSLayoutConstraint:0xbf478a0 UIView:0xbf4a3c0.height == 0.28125*UIView:0xbf4a3c0.width>
Based on a ton of observation I believe (but cannot know for certain) that the constraints named UIView-Encapsulated-Layout-Width and UIView-Encapsulated-Layout-Height are created by UICollectionView and friends, and exist to enforce the size returned by the sizeForItemAtIndexPath delegate method. I guess it's there to ensure that the UICollectionViewCell set up by cellForItemAtIndexPath ends up the size that it was told it would be.
Which answers my initial question here. A second question is why were the constraints unsatisfiable? The cell's intrinsic height should have been the same as UIView-Encapsulated-Layout-Height. Again, I don't know for certain, but I suspect it was a rounding error (i.e. intrinsic height came to 200.1 pixels, the UIView-Encapsulated-Layout-Height maybe rounded to 200. The fix I came up with was to just lower the priority of the relevant cell constraint to allow UIView-Encapsulated-Layout-Height to have the last word.
This may not answer your question, but it could help others like me who got here from search.
I was getting a strange AutoLayout broken constraint error accompanied by a UIView-Encapsulated-Layout-Width constraint because I was adding a tableHeaderView to a table view that hadn't been sized with AutoLayout yet. So the system was trying to apply my header subviews' constraints inside a tableview with a frame of {0,0,0,0}. Since UITableView likes control over the width of its elements, its generated width constraint, UIView-Encapsulated-Layout-Width, was set to zero, causing all kinds of confusion with my header elements that were expecting 320+pt width.
The takeaway: make sure you are adding/manipulating your supplementary/header/footer views after the tableview has been sized by AutoLayout.
I was facing the same weird constraint and had no idea why, until I remembered the darned translatesAutoresizingMaskIntoConstraints property. Setting this to false solved the problem.
What happens in the background is that the auto resizing masks (the old layout engine for iOS) are converted to constraints. Very often you don't want these constraints and want your own ones. In such cases you should set this property to false and you'll be fine:
view.translatesAutoresizingMaskIntoConstraints = false
Definitely seeing this on a UITableView's tableHeaderView. I was able to get this to work with a custom header view by explicitly setting the width equal to that of the tableView after setting the tableHeaderView, THEN resetting it after a layout pass has completed.
Example code for iOS 9, which assumes you have a UITableView passed into your method as tableView and an item to configure it as item:
//Create the header view
self.contentDetailHeaderView = MyCustomHeaderView()
//Turn on autolayout
self.contentDetailHeaderView.translatesAutoresizingMaskIntoConstraints = false
//Add the header to the table view
tableView.tableHeaderView = self.contentDetailHeaderView
//Pin the width
let widthConstraint = NSLayoutConstraint(item: self.contentDetailHeaderView,
attribute: .Width,
relatedBy: .Equal,
toItem: tableView,
attribute: .Width,
multiplier: 1,
constant: 0)
tableView.addConstraint(widthConstraint)
//Do whatever configuration you need to - this is just a convenience method I wrote on my header view.
self.contentDetailHeaderView.setupForItem(item)
//Lay out the configured view
self.contentDetailHeaderView.layoutIfNeeded()
//Reset the table header view, because ¯\_(ツ)_/¯
tableView.tableHeaderView = self.contentDetailHeaderView
Couple of notes, mostly for when I look this up again because I have the memory of a goldfish:
You do not have to call this from viewDidLayoutSubviews - I was able to use this technique as long as the tableView has the appropriate width during setup.
You do need to make sure your header view is set up to automatically resize itself. I did this by creating a .xib and then making sure all items were pinned so that as the view changed width, the height would then update.
If you're trying to do this for viewForHeaderInSection, you're probably better off grabbing something offscreen which you can lay out a la this technique. I haven't had much luck with the self-sizing bits.
We've started seeing tons of layout conflicts in iOS 11 that include references to these constraints and they are in fact added via the translatesAutoresizingMaskIntoConstraints flag. It seems that in iOS 11 there's a lot more AutoLayout magic happening when a view is added to the hierarchy rather than just when the view is laid out (as it seemed to work in previous iOS versions).
This is the case that we were running into:
Create a view whose internal layout helps define the views size (e.g., the view has internal constraints that includes explicit padding, etc.)
*** Add this view to the hierarchy.
Set the translatesAutoresizingMaskIntoConstraints to false some time later, before the layout pass.
The second step (***) will result in a conflict because the system will add zero size constraints to the view at the time the view is added to the hierarchy. We were setting translatesAutoresizingMaskIntoConstraints later as a result of using the PureLayout framework which automatically sets this flag correctly when you constrain the view... That said, in iOS 11 you need to remember to turn off translatesAutoresizingMaskIntoConstraints at construction time, before the view is added to the hierarchy.
I suspect Apple thought that defaulting this flag to YES would be way more helpful than it is painful. Unfortunately, this has not been the case.
After banging my head for a while i found this link. In my case it was happening on the UITableViewHeaderFooterView when i was using insertRows or deleteRows from my UIVieController. 'estimatedSectionHeaderHeight' and 'estimatedRowHeight' where set, my constraints redone 3 times ... The error shown was:
"<NSLayoutConstraint:0x280648140 H:|-(8)-[UIImageView:0x106e94860] (active, names: '|':DAT_Air_Vinyl.ExportsAlbumTableViewHeader:0x106e8fb50 )>",
"<NSLayoutConstraint:0x280648230 H:[UIImageView:0x106e94860]-(8)-[DAT_Air_Vinyl.MainLabel:0x106ea5750'The Coral - The Invisible...'] (active)>",
"<NSLayoutConstraint:0x280648410 H:[UIButton:0x106ea5a40]-(8)-| (active, names: '|':DAT_Air_Vinyl.ExportsAlbumTableViewHeader:0x106e8fb50 )>",
"<NSLayoutConstraint:0x280648460 H:[DAT_Air_Vinyl.MainLabel:0x106ea5750'The Coral - The Invisible...']-(8)-[UIButton:0x106ea5a40] (active)>",
"<NSLayoutConstraint:0x2806493b0 'UIView-Encapsulated-Layout-Width' DAT_Air_Vinyl.ExportsAlbumTableViewHeader:0x106e8fb50.width == 0 (active)>"
As stated on the link:
"When you do insertRows or deleteRows with certain animation types, UIKit will animate the row height from 0 to full height or back. At the 0-end of that animation, the layout equations are impossible to solve if entire vertical axis is set to priority=1000. But lower just one constraint to 999 – say that bottom space to superview margin – and all is fine; the content will just drop-down, outside the cell’s bounds.".
The solution was to set to 999 (or lower to 1000) the leading priority of the UIImageView.
I got this error in all sorts of circumstances (not necessarily tied to UICollectionView and friends as suggested by the correct answer here)..
So my way of dealing with it was simply clearing all the constraints then building them again (only this time i have no fear of my constraints colliding with these pre-created ones):
so in code:
UIView *parentView = [viewInQuestion superview];
[parentView clearConstraintsOfSubview:viewInQuestion];
where clearConstraintsOfSubview is a category method on UIView:
- (void)clearConstraintsOfSubview:(UIView *)subview
{
for (NSLayoutConstraint *constraint in [self constraints]) {
if ([[constraint firstItem] isEqual:subview] || [[constraint secondItem] isEqual:subview]) {
[self removeConstraint:constraint];
}
}
}
I was facing a similar issue and solved it with the following.
Environment: Swift 5.0, xcode 10.2.1, Setting views programmatically
Warning message: Unable to simultaneously satisfy constraints... 'UIView-Encapsulated-Layout-Width' UIView:0x0000000000.width == 0 (active)>"
)
Code with warning
override func loadView() {
view = UIView()
/// Adds the subviews to the view and sets their properties and constraints
setupViews()
}
Code that cleared warning
override func loadView() {
/// Needed to set the frame of the root view to the window frame.
let window = UIWindow()
view = UIView(frame: window.frame)
/// Adds the subviews to the view and sets their properties and constraints
setupViews()
}
Notes on the loadView() method: "If you use Interface Builder to create your views and initialize the view controller, you must not override this method. You can override this method in order to create your views manually. If you choose to do so, assign the root view of your view hierarchy to the view property. The views you create should be unique instances and should not be shared with any other view controller object. Your custom implementation of this method should not call super." - Apple documentation
Notes on the root view:
"If you prefer to create views programmatically ... you do so by overriding
your view controller’s loadView method. Your implementation of this method
should do the following:
Create a root view object. The root view contains all other views associated
with your view controller. You typically define the frame for this view to
match the size of the app window, which itself should fill the screen. However,
the frame is adjusted based on how your view controller is displayed. See “View
Controller View Resizing.”
You can use a generic UIView object, a custom view you define, or any other
view that can scale to fill the screen.
Create additional subviews and add them to the root view." - Old apple
documentation?
UIView-Encapsulated-Layout-* are the constraints used by table, collection, and stacks to layout elements as you told them to do it. If you later set constraints that create a change in size you’ll get a conflict. An example of this use case is cells that download images with variable size.
This happens because we are used to layoutIfNeeded() to refresh the layout, but once a collection goes through the rendering cycle the cell size is set with encapsulated constraints. You need to manually invalidate the index path you need to refresh, e.g.
let context = UICollectionViewLayoutInvalidationContext()
context.invalidateItems(at: [cellIndexPath])
collectionView.collectionViewLayout.invalidateLayout(with: context)
this will recursively create more invalidations to shift the positions of other cells and make space.
If you set, e.g. a different height constraint to change the size of the cell it will instantly crash and then recover after you invalidate and the cell is layout again. To avoid this crash you can lower the priority of the height constraint below .required, for instance UILayoutPriority(999).
In my case, I was inadvertently setting up my programmatic constraints twice. As soon as I removed the duplicate call, the conflicts went away.
I catch this problem when I use AL create tableviewHeader
I init tableview like below
let table = UITableView.init(frame: .zero, style: .grouped)
// set table constraint ...
then I create tableviewHeader with AutoLayout.
"<NSLayoutConstraint:0x600003d7d130 'UIView-Encapsulated-Layout-Width' UIView:0x7fe92bc55260.width == 0 (active)>"
symbolic breakpoint appear
After I refer #Yerk 's the answer.
I change the frame when I init tableView
let rect = CGRect(x: 0, y: 0, width: SCREEN_WIDTH, height: 0)
let table = UITableView.init(frame:rect , style: .grouped)
The problem seems to be solved
Finally I found solution for CollectionView! If You using storyBoard, like I am, it will help you!
Interface Builder / Storyboard
Go to storyBoard -> chose your CollectionView
ScreenShot CollectionView
Go to Size Inspector
Then set Estimate Size to None
ScreenShot Estimate Size
Thats All!
I was having a similar problem found from testing Split View on the iPad Pro, and DesignatedNerd's answer worked but I didn't need so much code. Here is what I used:
[self.tableView.tableHeaderView setTranslatesAutoresizingMaskIntoConstraints:NO];
NSLayoutConstraint *widthConstraint = [NSLayoutConstraint constraintWithItem:self.myTableHeaderView
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:self.tableView
attribute:NSLayoutAttributeWidth
multiplier:1
constant:0];
NSLayoutConstraint *yConstraint = [NSLayoutConstraint constraintWithItem:self.myTableHeaderView
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:self.tableView
attribute:NSLayoutAttributeTop
multiplier:1
constant:0];
[self.tableView addConstraints:#[widthConstraint, yConstraint]];
Note the addition of the Y Constraint, which binds the top of the tableHeaderView to the top of the tableView.
I had the same issue when adding constraints to a Table view header. It seem to occur when adding constraints with set constants when the bounds of the header was (0,0,0,0). I managed to fix this by only adding the constraints in the layout subviews method when the bounds of the header was not (0,0,0,0)
if self.bounds == CGRect.zero {
return
}
constraint UIView-Encapsulated-Layout-Height is created with value you set in tableView.estimatedSectionHeaderHeight
So I want to get rid of all “frames” in my code and completely switch to autolayout. But there is one case when I want the Y position of the view to be dependent on the content offset (bounds.origin.y) of a UIScrollView. What is the right way to do this using auto layout? One idea is to have a constant of the NSLayoutConstraint updated every time the scrollview content offset is changed, (but then what’s the point of using auto layout here?) so I was hoping there is better way to achieve that with “pure” autolayout.
I know that "Autolayout & UIScrollView” is frequently discussed here, so I want to stress that I do not want to use auto layout for UIScrollView subviews, I have a UIView with 2 subviews; a UIScrollView and a UIView objects, and I want to use auto layout to specify the relation between a UIView top coordinate and UIScrollView content offset.
OK, you can do this fairly easily. It's essentially just like you would do it with frames.
First you need to create your "variable" constraint and save it into a property.
You can do this in code...
self.heightConstraint = [NSLayoutConstraint constraintWithItem:self.theView
attribute:
relatedBy:
toItem:
attribute:
multiplier:
constant:];
// set up the rest
[self.view addConstraint:self.heightConstraint];
or do it in Interface Builder by creating the constraint and then ctrl drag to the property. (Like with any other IBOutlet).
Then when the scrollView scrolls...
self.heightConstraint.constant = calculatedValueFromTheOffset;
[self.view layoutIfNeeded];
This will then move the view just as if you had used frames but using auto layout constraints instead.
This seems to be a pretty common question but no matter which piece of code or change I make I cannot get it to work. I have a very simple example that I'm working on.
Storyboard is set to 4" portrait. UIScrollView 320px wide x 440px high. It contains a UIView which is 320px wide by 900px high. Application is locked to portrait only but needs to work on both 3.5" and 4" screens.
What do I need to do to get the UIView to scroll vertically within the UIScrollView? Do I need to set a contraint? What should Clip Subviews and Autoresize Subviews be set to and does it make a difference? What should View Mode be set to and does it make a difference? Do I need to programmatically reset the ContentSize of the UIScrollView? If so is this done on the ViewDidLoad event?
I've tried adding vertical contraints to the UIView, the bottom element on the UIView and the UIScrollView itself. I've programmatically set the content size to match the UIView.
I've built UI's in all sorts of other tools and this is just driving me insane for something so simple. If I turn off autolayout it works fine but that's not the solution I'm looking for.
Ended up getting this answer to work.
https://stackoverflow.com/a/16029013/1342779
Specifically my code in the viewDidLoad method:
[_scrollView addConstraint:[NSLayoutConstraint constraintWithItem:_innerView
attribute:NSLayoutAttributeBottom
relatedBy:NSLayoutRelationEqual
toItem:_scrollView
attribute:NSLayoutAttributeBottom
multiplier:1.0
constant:0]];
Take a look at this video. Also, you can try setting the content size of the scrollview problematically as well.
// For verical scrolling
scrollView.contentSize = CGSizeMake(viewToScroll.width, viewToScroll.height * numberOfPages);
// For horizontal scrolling
scrollView.contentSize = CGSizeMake(viewToScroll.width * numberOfPages, viewToScroll.height);
I'm experimenting with how to use UIScrollView. After much trouble, I finally got the hang of it. But now I've seem to hit another snag.
In this simple app, I have a scroll view with and in order for it to work, I have to set the view's bottom space to scrollview constraint to 0 as described here and it works fine. I'm doing it through the IB.
Now I've come across a scenario where I have to do that part programmatically. I've added the below code in the viewDidLoad method.
NSLayoutConstraint *bottomSpaceConstraint = [NSLayoutConstraint constraintWithItem:self.view
attribute:NSLayoutAttributeBottom
relatedBy:NSLayoutRelationEqual
toItem:self.scrollView
attribute:NSLayoutAttributeBottom
multiplier:1.0
constant:0.0];
[self.view addConstraint:bottomSpaceConstraint];
But it doesn't seem to work. It outputs the following message in the console window adn i don't know what to make of it.
Unable to simultaneously satisfy constraints.
Probably at least one of the constraints in the following list is one you don't want. Try this: (1) look at each constraint and try to figure out which you don't expect; (2) find the code that added the unwanted constraint or constraints and fix it. (Note: If you're seeing NSAutoresizingMaskLayoutConstraints that you don't understand, refer to the documentation for the UIView property translatesAutoresizingMaskIntoConstraints)
(
"",
""
)
Can someone please tell me how to do this? I've also attached a demo project here so that you can get a better idea on the issue.
UPDATE:
First off thanks for the responses. Using the ways mentioned in the answers I was able to get it working. However ina slightly different scenario its not. Now I'm trying to load a view onto a viewcontroller programatically.
If I may explain further. There are 2 view controllers. First one being a UITableViewController and the second one a UIViewController. Inside that a UIScrollView. Also There are multiple UIViews and some of those views' height exceeds the normal height of the screen.
The UITableViewController displays a list of options. Based on the user's selection, a particular UIView out of the lot will be loaded into the UIViewController with the UIScrollView.
In this scenario, the above method doesn't work. The scrolling isn't happening. Do I have to do something different since I'm loading the view separately?
I've uploaded a demo project here so that you can see it in action. Please see the Email.xib and select Email from the table view list.
Based upon a review of your code, a few comments:
You generally don't need to adjust constraints when a view appears. If you find yourself doing this, it often means that you haven't configured your storyboard correctly (or at least, not efficiently). The only time you really need to set/create constraints is either (a) you're adding views programmatically (which I'm suggesting is more work than it's worth); or (b) you need to do some runtime adjustment of constraints (see third bullet under point 3, below).
I don't mean to belabor it, but your project had a bunch of redundant code. E.g.
You were setting the frame for the scroll view, but that is governed by constraints, so that does nothing (i.e. when the constraints are applied, any manually set frame settings will be replaced). In general, in auto layout, don't try changing frame directly: Edit the constraints. But, no changing of constraints is needed at all anyway, so the point is moot.
You were setting the content size for the scroll view, but in auto layout, that, too, is governed by constraints (of the subviews), so that was unnecessary.
You were setting constraints for the scrollview (which were already zero), but then you weren't adding the view from the NIB into the scrollview, defeating any intent there, too. The original question was how to change the bottom constraint of the scroll view. But the bottom constraint for that is already zero, so I see no reason to set it to zero again.
I'd suggest a more radical simplification of your project:
You're making life much harder on yourself by storing your views in NIBs. It's much easier if you stay within the the storyboard world. We can help you do the NIB stuff if you really need to, but why make life so hard on yourself?
Use cell prototypes to facilitate the design of the cells in your table. You can also define the segues to go from the cells to the next scene. This eliminates any need to write any didSelectRowAtIndexPath or prepareForSegue code. Clearly, if you have something you need to pass to the next scene, by all means use prepareForSegue, but nothing you've presented thus far requires that, so I've commented it out in my examples.
Assuming you were looking for a practical example of programmatically changing constraints, I've set up the scene so that the text view will change its height programmatically, based upon the text in the text view. As always, rather than iterating through the constraints to find the one in question, when altering an existing constraint that IB created for me, I think it's far more efficient to set up an IBOutlet for the constraint, and edit the constant property for the constraint directly, so that's what I've done. So I set up the view controller to be the delegate of the text view, and wrote a textViewDidChange that updated the text view's height constraint:
#pragma mark - UITextViewDelegate
- (void)textViewDidChange:(UITextView *)textView
{
self.textViewHeightConstraint.constant = textView.contentSize.height;
[self.scrollView layoutIfNeeded];
}
Note, my text view has two height constraints, a mandatory minimum height constraint, and a medium priority constraint that I change above based upon the amount of text. The main point is that it illustrates a practical example of changing constraints programmatically. You shouldn't have to muck around with the scrollview's bottom constraint at all, but this is shows a real-world example of when you might want to adjust a constraint.
When you add a scrollview in IB, it will automatically get all the constraints you need. You probably don't want to be adding a constraint programmatically (at least not without removing the existing bottom constraint).
Two approaches might be simpler:
Create an IBOutlet for your existing bottom constraint, say scrollViewBottomConstraint. Then you can just do
self.scrollViewBottomConstraint.constant = 0.0;
Or create your view initially in IB where the bottom constraint is 0.0 and then you don't have to do anything programmatically at all. If you want to layout a long scrollview and it's subviews, select the controller, set it's simulated metrics from "inferred" to "free form". Then you can change the size of the view, set the scrollview's top and bottom constraints to be zero, layout everything you want inside the scroll view, and then when the view is presented at runtime, the view will be resized appropriately, and because you've defined the scrollview's top and bottom constraints to be 0.0, it will be resized properly. It looks a bit odd in IB, but it works like a charm when the app runs.
If you're determined to add a new constraint, you could either programmatically remove the old bottom constraint, or set the old bottom constraints' priority down as low as possible, and that way your new constraint (with higher priority) will take precedence, and the old, low-priority bottom constraint will gracefully not be applied.
But you definitely don't want to just add a new constraint.
It's possible to create outlets to represent layout constraints in your view controller. Just select the constraint you want in interface builder (e.g. via "select and edit" on the measurements pane of the view you are arranging). Then go to the outlets pane and drag a "New Referencing Outlet" to your code file (.h or .m). This will bind the constraint to an NSLayoutConstraint instance that you can access from your controller and adjust dynamically on the fly (generally via the constant property, which is poorly named because it's not a constant at all).
(Note that in XCode 6 you can double-click the constraint to select it for editing.)
Be careful when adjusting the layout in interface builder, however, as you may end up deleting the constraint and have to re-bind it to the outlet.
Looking at the console information, i feel that you are creating ambiguity when you add two same type of constraint.
So instead of creating and adding new constraint, try updating the previous constraint that is already in the constraints array.
for(NSLayoutConstraint *constraint in self.view.constraints)
{
if(constraint.firstAttribute == NSLayoutAttributeBottom && constraint.secondAttribute == NSLayoutAttributeBottom &&
constraint.firstItem == self.view && constraint.secondItem == self.scrollView)
{
constraint.constant = 0.0;
}
}
Hope this helps
Even Rob's answer will work!
You can use https://github.com/SnapKit/Masonry for adding constraints programmatically.
It is power of AutoLayout NSLayoutConstraints with a simplified, chainable and expressive syntax. Supports iOS and OSX Auto Layout.
UIView *superview = self.view;
UIView *view1 = [[UIView alloc] init];
view1.translatesAutoresizingMaskIntoConstraints = NO;
view1.backgroundColor = [UIColor greenColor];
[superview addSubview:view1];
UIEdgeInsets padding = UIEdgeInsetsMake(10, 10, 10, 10);
[superview addConstraints:#[
//view1 constraints
[NSLayoutConstraint constraintWithItem:view1
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:superview
attribute:NSLayoutAttributeTop
multiplier:1.0
constant:padding.top],
[NSLayoutConstraint constraintWithItem:view1
attribute:NSLayoutAttributeLeft
relatedBy:NSLayoutRelationEqual
toItem:superview
attribute:NSLayoutAttributeLeft
multiplier:1.0
constant:padding.left],
[NSLayoutConstraint constraintWithItem:view1
attribute:NSLayoutAttributeBottom
relatedBy:NSLayoutRelationEqual
toItem:superview
attribute:NSLayoutAttributeBottom
multiplier:1.0
constant:-padding.bottom],
[NSLayoutConstraint constraintWithItem:view1
attribute:NSLayoutAttributeRight
relatedBy:NSLayoutRelationEqual
toItem:superview
attribute:NSLayoutAttributeRight
multiplier:1
constant:-padding.right],]];
in just few lines
Heres the same constraints created using MASConstraintMaker
UIEdgeInsets padding = UIEdgeInsetsMake(10, 10, 10, 10);
[view1 mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(superview.mas_top).with.offset(padding.top); //with is an optional semantic filler
make.left.equalTo(superview.mas_left).with.offset(padding.left);
make.bottom.equalTo(superview.mas_bottom).with.offset(-padding.bottom);
make.right.equalTo(superview.mas_right).with.offset(-padding.right);
}];
Or even shorter
[view1 mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(superview).with.insets(padding);
}];
do your best is sort ;)