Thanks in advance for the help.
I want to create a new custom UIButton every time I click an ADD button.
Once I click the add button, I want the add button to slide over and make room for the new button. I want this to happen every time, then create a new row once the buttons fill up the horizontal space of the view
How would I got about doing this?
Note:
I definitely understand how to create a button programmatically, its the constraints/way of getting the buttons to animate sliding over, and spacing correctly that I don't understand
Thank you so much
The image is a quick idea of what I want it to look like. Top row is before I added several buttons, and bottom row is having so many buttons a new row is required
To be able to animate the views you have to set the constant attribute of your constraint variable(the value and direction depends of the attribute, of course), and later you have to call layoutIfNeeded() inside of an UIView animation block.
Code sample:
...
let newButton = UIButton()
containerView.addSubview(newButton)
...
// after adding the button
let horizontalSpace = ButtonWidth + horizontalSpace * 2
let newLeft = lastButtonX + 2 * (buttonWidth + horizontalSpace) + horizontalSpace
let newLeftConstraint = NSLayoutConstraint(item: newButton, attribute: .Left, relatedBy: .Equal, toItem: lastButton, attribute: .Right, multiplier: 0, constant: horizontalInset)
lastButton = newButton
if newLeft + addButtonWidth >= screenWidth {
containerView.layoutIfNeeded()
addButtonLeftConstraint.constant = horizontalSpace
addButtonTopConstraint.constant = buttonRowsNumber * rowHeight + verticalSpace
UIView.animateWithDuration(animationTime) {
containerView.layoutIfNeeded()
}
} else {
containerView.layoutIfNeeded()
addButtonLeftConstraint = newLeft
UIView.animateWithDuration(animationTime) {
containerView.layoutIfNeeded()
}
}
NOTES:
You'll need to keep a var for each constraint you want to animate later on. And depending of you layout behavior, you also need a var to the last button, so you can make the measurements of the positions.
the constant number 0 represents the initial state of the constraint, when it was added and created, so based in this initial state, the view will be moved from it's initial position(starting on the left, or right or whatever initial place you choose).
I suggest you to create the NSLayoutConstraint variables with the class constructor rather than using the visual language, as it generates an array of NSLayoutConstraints and this makes the detections of constraints harder for one specific constraint.
And the final note: I suggest one AL small library to manipulate the constraints more easily trough code, as you can see, constructing NSLayoutConstraints can be very boring and hard to maintain. As you're using Swift, please, take a look at this project: https://github.com/robb/Cartography
I've been using it in my projects, it's really helpful for those situations.
You can add constraint to any view by applying below code.
self.view.addConstraints(
NSLayoutConstraint.constraintsWithVisualFormat(
"H:|-[myLabel]-|", options: nil, metrics: nil, views: viewsDict))
self.view.addConstraints(
NSLayoutConstraint.constraintsWithVisualFormat(
"H:|-[myButton]-|",
options: nil, metrics: nil, views: viewsDict))
self.view.addConstraints(
NSLayoutConstraint.constraintsWithVisualFormat(
"V:|-[myLabel]-[myButton]-|", options: nil, metrics: nil,
views: viewsDict))
There is a good tutorial you can follow from :http://makeapppie.com/2014/07/26/the-swift-swift-tutorial-how-to-use-uiviews-with-auto-layout-programmatically/
Related
This question already has answers here:
Evenly space multiple views within a container view
(29 answers)
Closed 6 years ago.
I've just started working on this card game in Swift, and I was trying to figure out how to lay out a row of 6 cards horizontally near the top of the screen.
I've tried putting 6 imageViews in a stack, but my manual constraints ended up causing the last image to stretch to the edge:
Could someone show me how to set up a row of imageViews so that each of them has a fixed width and they're all centered? I'm kinda new to AutoLayout, so screenshots would be helpful.
I'd recommend using UIStackView. Have a look at the following Ray Wenderlich tutorial:
https://www.raywenderlich.com/114552/uistackview-tutorial-introducing-stack-views
However, before moving on to more complex views such as the aforementioned stack view; you should learn to use auto layout to avoid making any silly mistakes.
Here is another great tutorial from the same site:
https://www.raywenderlich.com/115440/auto-layout-tutorial-in-ios-9-part-1-getting-started-2
EDIT:
Improved answer:
UIStackView allows you to arrange elements with ease, in a row or in a column. This saves you a lot of time and makes your storyboard look a little bit cleaner as less constraints are needed.
The description of UIStackView on developer.apple.com:
The UIStackView class provides a streamlined interface for laying out a collection of views in either a column or a row. Stack views let you leverage the power of Auto Layout, creating user interfaces that can dynamically adapt to the device’s orientation, screen size, and any changes in the available space. The stack view manages the layout of all the views in its arrangedSubviews property. These views are arranged along the stack view’s axis, based on their order in the arrangedSubviews array. The exact layout varies depending on the stack view’s axis, distribution, alignment, spacing, and other properties.
UIStackViews functionality doesn't stop at the simplified view alignement. Indeed, you can also alter the properties that define the stack view.
The axis property determines the stack’s orientation, either
vertically or horizontally.
The distribution property determines the layout of the arranged views
along the stack’s axis.
The alignment property determines the layout of the arranged views
perpendicular to the stack’s axis.
The spacing property determines the minimum spacing between arranged
views.
The baselineRelativeArrangement property determines whether the
vertical spacing between views is measured from the baselines.
The layoutMarginsRelativeArrangement property determines whether the
stack view lays out its arranged views relative to its layout margins.
Despite the advantages mentioned above, UIStackView has limits.
The UIStackView is a nonrendering subclass of UIView; that is, it does
not provide any user interface of its own. Instead, it just manages
the position and size of its arranged views. As a result, some
properties (like backgroundColor) have no effect on the stack view.
Similarly, you cannot override layerClass, drawRect:, or
drawLayer:inContext:.
Note that UIStackView can't scroll. If you ever need it to scroll, embed a stack view within a UIScrollView.
Hope this helps!
I recommend pure coding, you learn more.
If you specify that all your cards are equal width and height, it will ensure the last card doesn't get stretched.
This is how I often build my UI:
import UIKit
class ViewController: UIViewController {
var container:UIView = UIView();
var card1:UIView! = nil;
var card2:UIView! = nil;
var card3:UIView! = nil;
var card4:UIView! = nil;
var card5:UIView! = nil;
var card6:UIView! = nil;
override func viewDidLoad() {
super.viewDidLoad()
self.initViews();
self.initConstraints();
}
func cardView() -> UIView
{
let card = UIView();
card.backgroundColor = UIColor.orangeColor();
return card;
}
func initViews()
{
self.card1 = self.cardView();
self.card2 = self.cardView();
self.card3 = self.cardView();
self.card4 = self.cardView();
self.card5 = self.cardView();
self.card6 = self.cardView();
self.container.addSubview(self.card1);
self.container.addSubview(self.card2);
self.container.addSubview(self.card3);
self.container.addSubview(self.card4);
self.container.addSubview(self.card5);
self.container.addSubview(self.card6);
self.view.addSubview(self.container);
}
func initConstraints()
{
self.container.translatesAutoresizingMaskIntoConstraints = false;
self.card1.translatesAutoresizingMaskIntoConstraints = false;
self.card2.translatesAutoresizingMaskIntoConstraints = false;
self.card3.translatesAutoresizingMaskIntoConstraints = false;
self.card4.translatesAutoresizingMaskIntoConstraints = false;
self.card5.translatesAutoresizingMaskIntoConstraints = false;
self.card6.translatesAutoresizingMaskIntoConstraints = false;
var views = [String: AnyObject]();
views["container"] = self.container;
views["card1"] = self.card1;
views["card2"] = self.card2;
views["card3"] = self.card3;
views["card4"] = self.card4;
views["card5"] = self.card5;
views["card6"] = self.card6;
self.view.addConstraint(NSLayoutConstraint(item: self.container, attribute: NSLayoutAttribute.CenterX, relatedBy: NSLayoutRelation.Equal, toItem: self.view, attribute: NSLayoutAttribute.CenterX, multiplier: 1.0, constant: 0.0));
self.view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|-50-[container]", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: views));
self.container.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|[card1(60)]-10-[card2(==card1)]-10-[card3(==card1)]-10-[card4(==card1)]-10-[card5(==card1)]-10-[card6(==card1)]|", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: views));
self.container.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|[card1(100)]|", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: views));
self.container.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|[card2(==card1)]|", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: views));
self.container.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|[card3(==card1)]|", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: views));
self.container.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|[card4(==card1)]|", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: views));
self.container.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|[card5(==card1)]|", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: views));
self.container.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|[card6(==card1)]|", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: views));
}
}
You end up with something like this:
My client’s requirement is as follows:
I am going to create registration form with around 15 textFields(Pre defined on server). Information of fields in registration fields are coming from server. There are chances that some fields are not required in some cases. So, I will get fields which are required for that particular case.
E.g. Suppose No. of predefined fields are 15 on server. i.e. FirstName, LastName, Phone no., Date of Birth, Picture, Email,Address,zipcode etc.
But there are chances that in some cases email id field is not required. so I will not get that field from server. So, application side I have to hide email textfield from predefined list of fields.
Also there are chances that multiple(any field) fields are not required. So I have to hide them too. I am trying to explain situation by image representation as follows.
Case 1:
Case 2:
In this case, what I am doing is(Application side) I create textFields for all predefined fields. then I am hiding textfield which is not required as per server response. i.e. email textfield or any other textField .
Now my question is, If any field is not required then the textfields should be repositioned and I am using auto layout in my application. I am hiding email textfield then How to set positions of next textfield like contact NO., Date of Birth etc.?
Yes, this is perfectly possible with Auto Layout.
The most important thing here is:
You do not need to reposition it manually, this job will done by Auto layout.
You must do the following things:
Use UIScrollView and add all UITextField in that. So that content size and changing position will be easily managed. Here content size of scrollView is also managed by Auto layout, So don't do it manually.
Don't assign frame manually for UITextField. e.g. By using CGRect when creating textField.
Use VFL (Visual Formatting Language). This is a powerful auto layout language which can gives you power to dynamic changes on runtime using code.
Add constraints for all UITextFields and initially set hidden property to No/False. From your server response and query make them visible and update auto layout constraints by simply calling below methods.
// Change hidden property based on your response
// ........
// ........
// Below methods will update auto layout
[textField layoutIfNeeded];
[self.view layoutIfNeeded];
You can put these method in animation block for ample UI experience.
Note: Remember VFL is not an easy part you must have to take care when you are using it.
Adding sample code which can helps you (But it is in Swift convert it in Objective C), Also there may be a variation in parameters and values of VFL because this code was fit as per my requirement. Have a look and change it as per your need.
Create UIScrollView using VFL:
// Create scrollview to display content
self.scrollView = UIScrollView()
self.scrollView.delegate = self
self.scrollView.setTranslatesAutoresizingMaskIntoConstraints(false)
self.view.addSubview(self.scrollView)
// Visual formatting constraints of scrollview horizontal-vertical alignment with respect to view
self.view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|[scrollView]|", options: NSLayoutFormatOptions(0), metrics: nil, views: ["scrollView" : scrollView]))
self.view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|[scrollView]|", options: NSLayoutFormatOptions(0), metrics: nil, views: ["scrollView" : scrollView]))
Add UITextField in UIScrollView:
func createRegistrationControls() {
var previousTextField : UITextField! = nil
// Remove all pervious views.
for view in self.scrollView.subviews {
view.removeFromSuperview()
}
for <YOUR NO OF TEXTFIELDS CONDITION> {
let textField : UITextField! = UITextField()
textField.borderStyle = UITextBorderStyle.RoundedRect
textField.font = UIFont.systemFontOfSize(14)
textField.clearButtonMode = UITextFieldViewMode.WhileEditing
textField.setTranslatesAutoresizingMaskIntoConstraints(false)
textField.delegate = self
self.scrollView.addSubview(textField)
// Left constraint
self.scrollView.addConstraint(NSLayoutConstraint(item: textField, attribute: NSLayoutAttribute.Left, relatedBy: NSLayoutRelation.Equal, toItem: self.scrollView, attribute: NSLayoutAttribute.Left, multiplier: 1.0, constant: 10))
// Right constraint
self.scrollView..addConstraint(NSLayoutConstraint(item: textField, attribute: NSLayoutAttribute.Right, relatedBy: NSLayoutRelation.Equal, toItem: self.scrollView, attribute: NSLayoutAttribute.Right, multiplier: 1.0, constant: -10))
// TOP Horizontal constraint
let metrices = ["width" : self.view.bounds.width]
self.scrollView.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|[textField(width)]", options: NSLayoutFormatOptions(0), metrics: metrices, views: ["textField" : textField]))
if previousTextField == nil {
// Top vertical constraint
self.scrollView.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|[textField]", options: NSLayoutFormatOptions(0), metrics: nil, views: ["textField" : textField]))
} else {
// Top constraint to previous view
self.scrollView.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:[previousTextField]-(10)-[textField]", options: NSLayoutFormatOptions(0), metrics: nil, views: ["textField" : textField, "previousTextField" : previousTextField]))
}
previousTextField = textField
}
if previousTextField != nil {
// This below constraints will increase UIScrollView content size
let constraint1 = NSLayoutConstraint.constraintsWithVisualFormat("H:[previousTextField]|", options: NSLayoutFormatOptions(0), metrics: nil, views: ["previousTextField" : previousTextField])
self.scrollView.addConstraints(constraint1)
let constraint2 = NSLayoutConstraint.constraintsWithVisualFormat("V:[previousTextField]|", options: NSLayoutFormatOptions(0), metrics: nil, views: ["previousTextField" : previousTextField])
self.scrollView.addConstraints(constraint2)
}
}
Just make the height constraint of those textFields to zero.
textFieldHeightConstraint.constant = 0;
I have tried to make a UILabel that is a certain width using preferredMaxLayoutWidth but no matter what I do it won't work. Can you help me? I have tries so many different combinations to make it work.
#IBAction func addBottomTextButton(sender: AnyObject) {
if addBottomTextField.text.isEmpty == false {
let halfScreenWidth = screenSize.width * 0.5
let bottomScreenPosition = screenSize.width
memeBottomText = addBottomTextField.text
fontName = "Impact"
let memeBottomTextCaps = memeBottomText.uppercaseString // --> THIS IS A STRING!
labelBottom.text = memeBottomTextCaps
labelBottom.textColor = UIColor.blackColor()
labelBottom.textAlignment = .Center
labelBottom.font = UIFont(name: fontName, size: 32.0)
labelBottom.sizeToFit()
labelBottom.userInteractionEnabled = true
labelBottom.adjustsFontSizeToFitWidth = true
labelBottom.numberOfLines = 1
labelBottom.preferredMaxLayoutWidth = screenSize.width
labelBottom.setTranslatesAutoresizingMaskIntoConstraints(true)
var r = CGFloat(halfScreenWidth)
var s = CGFloat(bottomScreenPosition)
labelBottom.center = CGPoint(x: r, y: s)
self.view.addSubview(labelBottom)
self.view.addConstraint(NSLayoutConstraint(item: labelBottom, attribute:
NSLayoutAttribute.Top, relatedBy: NSLayoutRelation.Equal, toItem: labelBottom,
attribute: NSLayoutAttribute.Bottom, multiplier: 1, constant: 0))
dismissKeyboard()
}
}
Judging by your code I'd say your problem was you haven't got your constraints setup correctly and you're mixing using NSLayoutConstraints with setting the position using center and setting the size using sizeToFit.
Firstly, in the constraint you've setup you're relating labelBottom (the item argument) to itself (the toItem argument). I'm not exactly sure what you were trying to achieve with that? I'd recommend having a look at some tutorials on AutoLayout if you're unfamiliar with its concepts. Here's a good one: http://www.raywenderlich.com/50317/beginning-auto-layout-tutorial-in-ios-7-part-1
Secondly, just a small point, on the line let memeBottomTextCaps = memeBottomText.uppercaseString you've written // --> THIS IS A STRING. An easier way to remind yourself of the variable type when looking back at your code could be to use: let memeBottomTextCaps: String = memeBottomText.uppercaseString.
Thirdly, preferredMaxLayoutWidth isn't used to set the width of a UILabel - that's what the frame is for (or NSLayoutConstraints if you're using AutoLayout).
Lets get on with it!
Here's an example of how to create a label that is pinned to the bottom edge of its container view and is not allowed to be wider than it's container: (Keep in mind that all this can be done in IB)
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let label = UILabel()
// 1.
label.setTranslatesAutoresizingMaskIntoConstraints(false)
// 2.
label.text = // Put your text here.
// 3.
self.view.addSubview(label)
// 4.
let pinToBottomConstraint = NSLayoutConstraint(item: label,
attribute: .Bottom,
relatedBy: .Equal,
toItem: self.view,
attribute: .Bottom,
multiplier: 1.0,
constant: -8.0)
// 5.
let horizontalConstraints = NSLayoutConstraint.constraintsWithVisualFormat("|-8-[label]-8-|",
options: .DirectionLeadingToTrailing,
metrics: nil,
views: ["label" : label])
// 6.
self.view.addConstraint(pinToBottomConstraint)
self.view.addConstraints(horizontalConstraints)
}
}
The following referrers to the commented numbers in the code above.
1. You need to set setTranslatesAutoresizingMaskIntoConstraints to false to stop constraints being created that would otherwise conflict with the constraints we're going to create later. Here's the what Apple have to say about it:
Because the autoresizing mask naturally gives rise to constraints that fully specify a view’s position, any view that you wish to apply more flexible constraints to must be set to ignore its autoresizing mask using this method. You should call this method yourself for programmatically created views. Views created using a tool that allows setting constraints should have this set already.
2. You need to make sure you put your own text here, otherwise the code won't run.
3. The label must be added to the view hierarchy before adding constraints between it and it's superview! Otherwise, in this case, you'll get a runtime error saying:
Unable to parse constraint format:
Unable to interpret '|' character, because the related view doesn't have a superview
|-8-[label]-8-|
This is due to our horizontalConstraints needing to know the label's superview (the superview is denoted by the "|") but the label doesn't have a superview.
4. The pinToBottomConstraint constraint does what it says. The constant of -8 just specifies that I want the label to be 8 points from the bottom of its container view.
We don't need to create a constraint to specify the label's size - that's an intrinsic property of the UILabel which is determined, for example, by the number of lines and font.
5. The horiontalConstraints are created using Visual Format Language. Here's a good tutorial: http://code.tutsplus.com/tutorials/introduction-to-the-visual-format-language--cms-22715 Basically, "|-8-[label]-8-|" creates constraints to pin the left and right edges of the label to the left and right edges of its superview.
6. Finally add the constraints!
This is what it looks like:
I hope that answers your question.
I think the property only work for multiline situation.
// Support for constraint-based layout (auto layout)
// If nonzero, this is used when determining -intrinsicContentSize for multiline labels
#available(iOS 6.0, *)
open var preferredMaxLayoutWidth: CGFloat
And it indeed true after my test. So, we need set multiline. It will work.
titleLabel.numberOfLines = 0
I don't why Apple limit it to only multiline. In fact, we often need to set max width on label easily by one property.
Finally, if we want set max width , we need set max constaint, like the following
if device.isNew == "1" {
self.title.mas_updateConstraints { (make) in
make?.width.lessThanOrEqualTo()(163.w)
}
self.newTag.isHidden = false
} else {
self.newTag.isHidden = true
self.title.mas_updateConstraints { (make) in
make?.width.lessThanOrEqualTo()(207.w)
}
}
I'd like to set a top constraint so that it has a relationship which is equal with another item's height.
I prepare it in interface builder so that item 1's top is equal to item 2's top.
Then, in code (because I don't think it can be done in Interface Builder), I try to set the constraint's secondAttribute property.
This seems logical to me based on a basic understanding of how constraints are composed (of two items, an attribute for each, a relationship type, and a constant), but it does not work:
#IBOutlet var fillTopToContainer: NSLayoutConstraint!
// ...
override func viewDidLoad() {
fillTopToContainer.secondAttribute = NSLayoutAttribute.Height
}
Swift compiler error:
Cannot assign to the result of this expression.
I have fiddled with the constant to make sure that topDistEqualsHeight contains the constraint I expect, and it does. The other values in the constraint are correct for my needs, I only want to change the attribute.
Is this a known limitation, a syntax issue, or a big piece of missing knowledge?
Update
I've also tried this, which throws a runtime error:
var pushTopDown = NSLayoutConstraint(item: self.view,
attribute: NSLayoutAttribute.Height,
relatedBy: NSLayoutRelation.Equal,
toItem: fillRect,
attribute: NSLayoutAttribute.Top,
multiplier: 1,
constant: -10)
self.view.addConstraint(pushTopDown)
This is the layout I'm trying to achieve. It's a scrollview which is exactly two screens tall, the bottom half has a fill color.
#P-double suggested that the fillRect match it's top to the bottom position of a full height object, which would work except you can't set the top of a grandchild relative to its grandparent in IB.
View Hierarchy:
frame (fills the screen, root view)
scrollView (fills the frame. Content size is determined by constraints of inside views)
fillRect (height==frame, bottom==scrollView.bottom, top==?)
Constraint's do not work in the way you are trying to use them, most notably constraints properties are all immutable apart from the constant property.
This pairing of constraints does not work, because one relates to an origin point (y-positon), and one relates to a size dimension. It's not clear what you are trying to achieve, but there will be other ways in which you can achieve your desired layout. If you want the second view to sit below the first (in the y-plane, it doesn't necessarily have to align centre-x positions), why not pin the bottom of the first to the top of the second? If you'd like to post some more details, I'll do my best to help.
EDIT
To achieve you desired layout, you should pin the top of the fillRect to the top of the scrollView, and give the constraints constant the value of the frame height. Such as this
var pushTopDown = NSLayoutConstraint(item: scrollView,
attribute: NSLayoutAttribute.Top,
relatedBy: NSLayoutRelation.Equal,
toItem: fillRect,
attribute: NSLayoutAttribute.Top,
multiplier: 1,
constant: self.view.height)
scrollView.addConstraint(pushTopDown)
Also notice that the constraint is added to the scroll view, not the view controllers view. You'll also want to the make the width of fillRect equal to the scrollViews frame width.
As #Rob points out, you'll need to make sure you haven't already added constraint for the top. Interface builder will complain though if the view is not fully constrained. The trick is to add a top constraint in interface builder, but to mark it as a design time constraint. To do this, select the constraint you want to replace in code, the open the attributes inspector on the right, and tick the 'Remove at build time' option. (See picture) This allows the xib/storyboard to compile without error, but doesn't actually add the constraint to the view.
Bottom line, if you try to define a constraint such that the "top" attribute of an item to be equal to the "height" attribute of another, you will receive an error that says:
Invalid pairing of layout attributes
Bottom line, you cannot define constraints between "top" and "height" attributes.
If you want to avoid using spacer views, the other technique to try when vertically spacing views is to set the .CenterYWithinMargins attribute with respect to the superview's .CenterYWithinMargins, applying the appropriate multiple. You can equally space views with judicious use of different multiple values for each item's respective .CenterY attribute.
A couple of observations regarding a few of your attempts: Notably, you cannot mutate secondAttribute of an existing constraint. Only the constant property may be modified after the constraint creation. Also, in your update to your question, you illustrate the attempt to create a new constraint, and you'll obviously want to make sure make sure you remove the old constraint (or define it with a lower priority) before you create a new one.
To illustrate the concept, here is a scroll view with a tinted view that is off screen all created programmatically (it's the most concise way to describe the constraints, methinks):
let scrollView = UIScrollView()
scrollView.setTranslatesAutoresizingMaskIntoConstraints(false)
view.addSubview(scrollView)
let tintedView = UIView()
tintedView.setTranslatesAutoresizingMaskIntoConstraints(false)
tintedView.backgroundColor = UIColor.orangeColor()
scrollView.addSubview(tintedView)
let views = ["scrollView" : scrollView, "tintedView" : tintedView]
// vfl for `frame` of scrollView
view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|[scrollView]|", options: nil, metrics: nil, views: views))
view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|[scrollView]|", options: nil, metrics: nil, views: views))
// vfl for `contentSize` of scrollView
scrollView.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|[tintedView]|", options: nil, metrics: nil, views: views))
scrollView.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:[tintedView]|", options: nil, metrics: nil, views: views))
// vfl for `frame` of tintedView
//
// (note, this can be integrated into the above constraints, reducing
// the extraneous VFL, but I implemented them as separate VFL to
// clearly differentiate between settings of the scrollView `contentSize`
// and the tintedView `frame`)
scrollView.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:[tintedView(==scrollView)]", options: nil, metrics: nil, views: views))
scrollView.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:[tintedView(==scrollView)]", options: nil, metrics: nil, views: views))
// offset tinted view
scrollView.addConstraint(NSLayoutConstraint(item: tintedView, attribute: .CenterY, relatedBy: .Equal, toItem: scrollView, attribute: .CenterY, multiplier: 3.0, constant: 0.0))
I'd like to create an "adding a new credit card viewController".
We don't want to aggravate users with all of the required fields presented at once.
This action contains several steps.
On each step the view-controller reveals a new subview (which contains one or more textfields) and collapses an old one (the current text field after it's text is validated).
I've created the ViewController on the storyboard. and placed all of its subviews one above the other.
I've created all of the constraints on the storyBoard, each subviews' clips to the above subview etc'.
i.e:
NSMutableArray *constraints = [[NSLayoutConstraint
constraintsWithVisualFormat:
#"V:|[titleView]-[subtitleView]-[amountView]-[cardNumView]-[cardsImagesView]-[mmYYCvvView]-[billingInfoView]-[buttomView]|"
options:NSLayoutFormatAlignAllTop | NSLayoutFormatAlignAllBottom
metrics:nil
views:variableBindings] mutableCopy];
Each of these subviews contain a height constraint.
In each step one of the height constraints are set to zero and another one is changed from zero to the required height.
i.e:
self.hgtCrtMMYYCvv.constant = showFields? 50 : 0;
self.hgtCrtBillingInfo.constant = showFields? 140 : 0;
self.mmYYCvvView.hidden = !showFields;
self.billingInfoView.hidden = !showFields;
I got two issues:
Without calling layoutIfNeeded the initial layout was valid but did not change after changing the height constraints.
Calling layoutIfNeeded did not clip the bottom view to the last visible one - placed it at the bottom of the view as if all the subviews appear at once, but since some are hidden a gap was created.
changing the height constraint of the subviews was applied on the screen but still the gap stayed.
Please advise.
Calling "layoutIfNeeded" did not clip the bottom view to the last visible one - placed it at the bottom of the view as if all the subviews appear at once
Look at your constraints. You have pinned the bottom of the bottom view to the bottom of its superview! So its bottom must appear at the bottom of the superview, since that is what you instructed it to do.
Indeed, I am surprised that your constraints work at all. You have basically overdetermined them. If you give every field a height and pin its top and bottom, for every field, then it will be impossible to satisfy your constraints unless you are very lucky. The height of the superview is fixed, so your constraints would have to add up perfectly to that height.
I'm going to suggest a complete alternative approach, which I think you will find easier. Instead of messing with individual constants, plan what the correct (not overdetermined) constraints would be for each possible situation, and store those constraints in properties. Now when you want to hide/reveal a field, you just remove all the constraints and swap in another set.
This will also solve the layoutIfNeeded problem.
It happens that I have an actual example showing how to do this. (It is written in Swift, but I'm sure you can compensate mentally.) In my example code, we have three rectangles; I then remove one rectangle and close the gap between the remaining two. The preparation of two sets of constraints is tedious but elementary:
let c1 = NSLayoutConstraint.constraintsWithVisualFormat("H:|-(20)-[v(100)]", options: nil, metrics: nil, views: ["v":v1]) as [NSLayoutConstraint]
let c2 = NSLayoutConstraint.constraintsWithVisualFormat("H:|-(20)-[v(100)]", options: nil, metrics: nil, views: ["v":v2]) as [NSLayoutConstraint]
let c3 = NSLayoutConstraint.constraintsWithVisualFormat("H:|-(20)-[v(100)]", options: nil, metrics: nil, views: ["v":v3]) as [NSLayoutConstraint]
let c4 = NSLayoutConstraint.constraintsWithVisualFormat("V:|-(100)-[v(20)]", options: nil, metrics: nil, views: ["v":v1]) as [NSLayoutConstraint]
let c5with = NSLayoutConstraint.constraintsWithVisualFormat("V:[v1]-(20)-[v2(20)]-(20)-[v3(20)]", options: nil, metrics: nil, views: ["v1":v1, "v2":v2, "v3":v3]) as [NSLayoutConstraint]
let c5without = NSLayoutConstraint.constraintsWithVisualFormat("V:[v1]-(20)-[v3(20)]", options: nil, metrics: nil, views: ["v1":v1, "v3":v3]) as [NSLayoutConstraint]
self.constraintsWith.extend(c1)
self.constraintsWith.extend(c2)
self.constraintsWith.extend(c3)
self.constraintsWith.extend(c4)
self.constraintsWith.extend(c5with)
self.constraintsWithout.extend(c1)
self.constraintsWithout.extend(c3)
self.constraintsWithout.extend(c4)
self.constraintsWithout.extend(c5without)
NSLayoutConstraint.activateConstraints(self.constraintsWith)
But the payoff comes when it is time to swap the middle view in or out of the interface: it's trivial. Just remove or insert it, and then remove all constraints and now insert the complete new set of constraints appropriate to the situation, which we have already prepared:
#IBAction func doSwap(sender: AnyObject) {
if self.v2.superview != nil {
self.v2.removeFromSuperview()
NSLayoutConstraint.deactivateConstraints(self.constraintsWith)
NSLayoutConstraint.activateConstraints(self.constraintsWithout)
} else {
self.view.addSubview(v2)
NSLayoutConstraint.deactivateConstraints(self.constraintsWithout)
NSLayoutConstraint.activateConstraints(self.constraintsWith)
}
}
The preparation of the multiple sets of constraints is tedious but can be done by rule, i.e. the constraints can be "machine-generated" in a loop (writing this is left as an exercise for you). Swapping constraints in and out is again according to a simple rule, since only one set will be right for the particular set of fields you wish to show/hide. So once this is set up it will be much simpler and more maintainable than what you are doing now.