How to set a constraint using percentage? - ios

Just a very direct question, but we had spent many hours trying to find a working solution but faild.
In Xcode, storyboard, how to set a constraint so one view can be located 30% of total window height from the top of the superview? And we need it to be that way for ALL supported iOS devices of all orientations.
Please see my illustration attached.

I demonstrate this below - you just have to change the value of multiplier.

Update
Sorry, I have misunderstood your problem.
You'll need to add the constraints from code like so (the xConstraint is totally arbitrary, but you must need to define x, y positions, width and height, for an unambiguous layout):
#IBOutlet weak var imageView: UIImageView!
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
let yConstraint = NSLayoutConstraint(item: imageView, attribute: .Top, relatedBy: .Equal, toItem: view, attribute: .Top, multiplier: 1, constant: view.bounds.height / 3)
let xConstraint = NSLayoutConstraint(item: imageView, attribute: .Leading, relatedBy: .Equal, toItem: view, attribute: .Leading, multiplier: 1, constant: 30)
NSLayoutConstraint.activateConstraints([yConstraint, xConstraint])
}
This way, the equation will be:
imageView.top = 1 * view.top + (view.width / 3)
Original answer
Auto Layout uses the following equation for constraints:
aView.property = Multiplier * bView.property + Constant
Based on this, you can simply add an equal width/height constraint, then add a multiplier:
So the equation will be:
view.height = 0.3 * superView.height + 0

You should calculate it.
1. Calculate how many percents are from top to center ImageView
2. Set Vertical center to ImageView
3. Configure multiplier in Vertical center constraint and set multiplier from 1
For example: multiplier 0.5 will be 25% from top to center ImageView. So your multiplier will be ~0.6
By the way, there is another way how to do it:
1. Create transparent view from top to your imageView
2. Set height equal to your subview
3. Set multiplier to 0.3 to this height constraint
4. Set bottom space from your imageView to this transparent view equal to zero

In the Equal Heights Constraint properties pane, you set the multiplier to "1:3" (i.e. 30% in division notation).

To avoid having to recalculate a constant each time after layoutSubviews, use UILayoutGuide.
Create a layout guide equal to 30% of the view's height, and then use that to align the top of the child view. No manual layout calculations necessary.
// Create a layout guide aligned with the top edge of the parent, with a height equal to 30% of the parent
let verticalGuide = UILayoutGuide()
parent.addLayoutGuide(verticalGuide)
verticalGuide.topAnchor.constraint(equalTo: parent.topAnchor).isActive = true
verticalGuide.heightAnchor.constraint(equalTo: parent.heightAnchor, multiplier: 0.3).isActive = true
// Align the top of the child to the bottom of the guide
child.topAnchor.constraint(equalTo: verticalGuide.bottomAnchor).isActive = true
UILayoutGuide can be laid out with constraints like any view, but doesn't appear in the view hierarchy.

Here is Simple Solution if you want to give Constraint according to Screen.
To Set Height Percentage
To Set Width Percentage
import UIKit
extension NSLayoutConstraint{
/// Set Constant as per screen Width Percentage
#IBInspectable var widthPercentage:CGFloat {
set {
self.constant = (UIScreen.main.bounds.size.width * newValue)/100
}
get {
return self.constant
}
}
/// Set Constant as per screen Height Percentage
#IBInspectable var heightPercentage:CGFloat {
set {
self.constant = (UIScreen.main.bounds.size.height * newValue)/100
}
get {
return self.constant
}
}
}

Related

Swift trailing constraint spacing depending on width of superview multiplied with a value (programmatically)

Let's say I have a UITableViewCell.
In the contentView of the cell, I have a subView. The trailing of the subView to the contentView is depending on the width of the contentView, multiplied with a value.
What I'm trying to achieve is:
let trailingSpacing: CGFloat = contentView.frame.size.width * 0.2
subView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -trailingSpacing).isActive = true
The piece of code above is what I was trying to do in layoutSubviews() of the UITableViewCell. It's not working and besides that, it doesn't feel right. Is there a more clean way to do this? I don't think you can do this in 1 constraint, but maybe I'm wrong.
The constraints are alle done in code, there is no storyboard/xib/nib.
Thanks in advance!
I'm not sure whether you're able to solve it with a single constraint, but you can add a spacer view to solve this. Anchor the spacer view to the trailing of your container, and the trailing of your "target" view to the leading of the spacer. Then, give the spacer a width that's a multiple of the container's width:
let constraint = NSLayoutConstraint(
item: spacer,
attribute: .width,
relatedBy: .equal,
toItem: container,
attribute: .width,
multiplier: 0.2, // <-- tweak this
constant: 0
)
container.addConstraint(constraint)
(You may need to switch space and container in the constraint, I always forget which is the one the multiplier is applied to.)
You can even do this solution in Interface Builder.

How to update Height of the view Programmatically?

One issue i am facing related to autolayouts. I am setting height of view containing image views to zero first via autolayouts. But if certain function is called I want that height updated to a constant value, but height of my view is not getting updated.
Here is the code, i have updated height programmatically inside the function but it is not working.
let heightContraint = NSLayoutConstraint(item: businessImageView, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: 40)
businessImageView.addConstraint(heightContraint)
First create IBOutlet of the height constraint.
You just need to change constant property of the constraint.
For e.g.:
self.consTblFilterHeight.constant = 100.0
self.view.layoutIfNeeded()
Replace self.view with the parent view of the view you are changing the height.
Create your constraint outlet and then set it like this :
self.heightConstraintOutlet.constant = newHeightValue
businessImageView.addConstraint(heightContraint) is not the code to update the constraint. It adds a constraint.
So as to update the height of parent view (which has images), you would need to update the constant for businessImageView's height constraint.
businessImageView.heightConstraint.constant = 40
self.view.layoutIfNeeded()
Approach
activate constraint
change constant value
Code
heightConstraint.isActive = true
heightConstraint.constant = 20

iOS constraints doesn't allow to use multiplier

I am trying to layout some custom views and when I try to activate the constraints, Xcode says that I can't use multiplier. Here is an example of the code:
class MenuView: UIView {
var addButton: AddButton!
var settingsButton: SettingsButton!
// ........
func setConstraints (withBarReference reference: NSLayoutYAxisAnchor) {
NSLayoutConstraints.activateConstraints([
// ........
addButton.centerXAnchor.constraintEqualToAnchor(self.centerXAnchor, multiplier: 0.5),
// ........
settingsButton.centerXAnchor.constraintEqualToAnchor(self.centerXAnchor, multiplier: 1.5)
])
}
}
The thing here is that Xcode gives a syntax error on the contraintEqualToAnchor: functions and says that I should replace "multiplier" to "constant".
Why can't I use the multiplier option with the X center anchors?
You can't set multiplier using helper functions, but you can set multiplier using NSLayoutConstraint initializer. Just got stuck by this myself, but found the answer.
Your code: addButton.centerXAnchor.constraintEqualToAnchor(self.centerXAnchor, multiplier: 0.5)
Correct code: NSLayoutConstraint(item: addButton, attribute: .centerX, relatedBy: .equal, toItem: self, attribute: .centerX, multiplier: 0.5, constant: 0)
Also, don't forget to activate this constraint by typing isActive = true
Previous answers work very weird now.
You can simply create UILayoutGuide with multiplier width/height with view and set guide.trailing equal to the centerX of your subview.
For example, if you need to place the addButton in the first 1/3 of a view and settingsButton in 2/3 you can simply set two layout guides
let addButtonGuide = UILayoutGuide()
self.addLayoutGuide(addButtonGuide)
addButtonGuide.widthAnchor.constraint(equalTo: self.widthAnchor, multiplier: 1/3).isActive = true
addButtonGuide.leadingAnchor.constraint(equalTo: self.leadingAnchor).isActive = true
addButton.centerXAnchor.constraint(equalTo: addButtonGuide.trailingAnchor).isActive = true
// same for settingsButton but using 2/3 for the multiplier
But the really best way is to use UIStackView and set its distribution property to equalCentering.
Another option is to use uncommon Auto Layout API to create NSLayoutDimension between two centerXAnchors and make constraint to self.widthAnchor:
addButton.centerXAnchor.anchorWithOffset(to: self.centerXAnchor)
.constraint(equalTo: self.widthAnchor, multiplier: 0.25).isActive = true
self.centerXAnchor.anchorWithOffset(to: settingsButton.centerXAnchor)
.constraint(equalTo: self.widthAnchor, multiplier: 0.25).isActive = true
It seems that in IB you can use the multiplier option with Center X and obtain the effect you're looking for (set the center of button1 at 1/4 the width of the view it's in, and the center of button2 at 2/3 of the width of the view it's in):
.
I tried to use it both in code and in IB, and in code I got the same error as you.
Surprisingly, in IB it worked, no errors, no warnings. (I am using Xcode 7, will try it in Xcode 8 to see if it still works).
You can't use multipliers on NSLayoutXAxisAnchor anchors - multiplying by a position along a line doesn't make sense in a way that the constraints system can understand. You can only use multipliers with NSLayoutDimension anchors, which measure lengths, like the width.
The layout you are trying to make would be better achieved using a stack view.

Adding vertical space constraint equal to superview height multiple

I want to achieve a very simple thing. I have a UIView, i want the vertical space between my UIView bottom and bottom layout guide to be 10% of the container height (in this case viewController.view). How can achieve this in storyboards?
So some thing like this
UIView.bottom = Height of superView * 0.1 + 0 from the Bottom layout guide
is there anyway to achieve this in storyboards. Currently i can just some constant magic number which will not work on iPhone 4s all the way till iPhone 6 plus.
Clicking on the constraint shows this properties, so how can i put something like superViewHeight * 0.1 in here. I understand that i can do this if i am setting the height of the view but how to do in this case.
Thanks
You need to invert the first and second item in this case. Simply click on first item dropdown and you will see the option.
Secondly, give a value of 0.9 in multiplier section. That will make the gap 10% of total height.
If I understand correctly, you want to create a constraint in proportion to the superview's height and not the height of the view itself.
You can do this programmatically by creating an NSLayoutConstraint and specify it's constant an run time.
let marginToBottomLayout = customView.superview!.frame.size.height * 0.1
let constraint = NSLayoutConstraint(item: customView,
attribute: .Bottom,
relatedBy: .Equal,
toItem: self.bottomLayoutGuide,
attribute: .Top, multiplier: 1.0,
constant: marginToBottomLayout)

preferredMaxLayoutWidth not working in Swift?

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)
}
}

Resources