NSLayoutConstraint won't work - swift

I want to move 2 button to center of width of screen.
It's should looks like: |<-(100)FirstButton(50)->SecondButton(100)->|
I started from first button.
var const = NSLayoutConstraint(item: firstButton,
attribute: NSLayoutAttribute.Left,
relatedBy: NSLayoutRelation.Equal,
toItem: view.superview,
attribute: NSLayoutAttribute.Left,
multiplier: 1.0,
constant: 100)
Why it doesn't work?

It seem that firstButton have not set the property translatesAutoresizingMaskIntoConstraints to false
Apple document description of translatesAutoresizingMaskIntoConstraints:
/* By default, the autoresizing mask on a view gives rise to constraints that fully determine
the view's position. This allows the auto layout system to track the frames of views whose
layout is controlled manually (through -setFrame:, for example).
When you elect to position the view using auto layout by adding your own constraints,
you must set this property to NO. IB will do this for you.
*/
#available(iOS 6.0, *)
open var translatesAutoresizingMaskIntoConstraints: Bool // Default YES
So you must set firstButton's translatesAutoresizingMaskIntoConstraints to false when you adding your own constraints

Related

change views height in Keyboard extension (iPhone X)

I have following problem: I build a keyboard extension, a alternative keyboard to the system keyboard. In all older iPhone models it works fine, but at the iPhone X it doesn't. My problem is, the height of the keyboard's view is decreasing from 216px (iPhone 8 ,8+ , ...) to 141px (iPhone X). Because lower height, my keyboard buttons are now smaller, to small for good user usability.
I did used a .xib file to create the UI, I add all UI - Items programmatically.
my Question
Is it posible to get more space (height) for the keyboard extension view (specially for the iPhone X) ?
I solve my problem. I found following paragraph in the Apple documentation:
You can adjust the height of your custom keyboard’s primary view using Auto Layout. By default, a custom keyboard is sized to match the system keyboard, according to screen size and device orientation. A custom keyboard’s width is always set by the system to equal the current screen width. To adjust a custom keyboard’s height, change its primary view's height constraint.
The following code lines show how you might define and add such a constraint:
CGFloat _expandedHeight = 500;
NSLayoutConstraint *_heightConstraint =
[NSLayoutConstraint constraintWithItem: self.view
attribute: NSLayoutAttributeHeight
relatedBy: NSLayoutRelationEqual
toItem: nil
attribute: NSLayoutAttributeNotAnAttribute
multiplier: 0.0
constant: _expandedHeight];
[self.view addConstraint: _heightConstraint];
Swift 4
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
let heightConstraint = NSLayoutConstraint(item: self.view, attribute: NSLayoutAttribute.height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 0.0, constant: 300)
self.view.addConstraint(heightConstraint)
}
Adding the constraints to ViewDidLoad method solves the issue.
override func viewDidLoad() {
super.viewDidLoad()
let heightConstraint = NSLayoutConstraint(item: self.view, attribute: NSLayoutAttribute.height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 0.0, constant: 220)
self.view.addConstraint(heightConstraint)
setupKeyboard()
Note: Creating a subview generates a memory warning.

is there more than one layer to constrain on a button in Xcode?

I'm attempting to remove a UIImageView from my viewController in a swift Xcode project, if a certain condition exists (if a user has purchased "remove ads"). I am able to do so by setting the images' .image to nil and the constraint that sets it's size to nil. however when I do this, the buttons go to the correct location at the bottom of the view, but the button's pressable area is no longer the entire button. I can press it if I touch the bottom left corner of it.
if I am able to present the appearance of the button in the proper place, how do I affect the functional area it?
You should check the button new frame or any view in front of the button that catch the touch event.
Normally, UIButton touch area is its frame area.
Here is the code I used, instead of just nil for the image and banner view.
self.adBannerView.removeFromSuperview()
self.bottomGreyArea.removeFromSuperview()
self.view.addConstraint(NSLayoutConstraint(item: nextButton, attribute: .bottom, relatedBy: .equal, toItem: self.bottomLayoutGuide, attribute:.top, multiplier: 1, constant: 0))
self.view.addConstraint(NSLayoutConstraint(item: backButton, attribute: .bottom, relatedBy: .equal, toItem: self.bottomLayoutGuide, attribute:.top, multiplier: 1, constant: 0))
self.nextButton.translatesAutoresizingMaskIntoConstraints = false
self.backButton.translatesAutoresizingMaskIntoConstraints = false
removing from the superview cleared out all the constraints, so I then rebuilt the relevant constraints for the buttons (which previously depended on the bottomGreyArea. all is well in the world again!

How can I have multiple labels the same size within Auto Layout programmatically?

Imagine the following layout within a UITableView. The red area is a UILabel and the blue area is a UITextField.
I have the following constraints to get me started:
var allConstraints:[NSLayoutConstraint] = []
allConstraints.append(NSLayoutConstraint(item: self.textFieldLabel!, attribute: .leading, relatedBy: .equal, toItem: self.textFieldLabel!.superview, attribute: .leadingMargin, multiplier: 1.0, constant: 0.0))
allConstraints.append(NSLayoutConstraint(item: self.textFieldLabel!, attribute: .centerY, relatedBy: .equal, toItem: self.textFieldLabel!.superview, attribute: .centerY, multiplier: 1.0, constant: 0.0))
allConstraints.append(NSLayoutConstraint(item: self.textField!, attribute: .leading, relatedBy: .equal, toItem: self.textFieldLabel!, attribute: .trailingMargin, multiplier: 1.0, constant: 0.0))
allConstraints.append(NSLayoutConstraint(item: self.textField!, attribute: .trailing, relatedBy: .equal, toItem: self.textField!.superview, attribute: .trailingMargin, multiplier: 1.0, constant: 0.0))
allConstraints.append(NSLayoutConstraint(item: self.textField!, attribute: .centerY, relatedBy: .equal, toItem: self.textField!.superview, attribute: .centerY, multiplier: 1.0, constant: 0.0))
NSLayoutConstraint.activate(allConstraints)
However the outcome is not desired. The red label takes all of the whitespace. I am having a lot of trouble grasping how auto-layout works can anyone help me out here?
EDITED 6 APR 2017
When I add content hugging with the following line self.textFieldLabel!.setContentHuggingPriority(UILayoutPriorityDefaultHigh, for: .horizontal) I will get the following result. As you can see somehow the labels get cut off.
At a glance your constraints look reasonable and I suspect what you're missing is the content hugging priority of your label and text field. Content hugging defined how important it is that the intrinsic content size of a view match the minimum required to fit it's contents.
As is both views probably have the same content hugging priority (the default of 250) and that leads to an ambiguous layout. The intrinsic content size of your labels will be whatever width is required to fit your text. The intrinsic content size of your empty text field will be 0. Your constraints require that these two views together span their entire superview but since it is equally important for them both to hug their content their are two valid layouts:
The label is only as wide as needed and the text field fills the rest of the space.
The text field is only as wide as needed and the label fills the rest of the space.
You're seeing layout #2 but that could switch at any time because there's no way to reliably choose which content hugging size takes precedence since they have the same priority.
You could fix this by increasing the content hugging priority of your labels. If the content hugging priority of your label is greater than the priority of your text field then it is unambiguous which view should grow first to fill the available space; i.e. we're telling autolayout that it is more important to keep the view with the higher content hugging priority small.
See Anatomy of a Constraint for more.
UILabels have an intrinsic size, basically meaning if you only define leading/trailing anchors to adjacent controls, their widths will be automatically adjusted to only fit the content inside it.
Apple Doc on Intrinsic Size.
One way you can do what you want is to set an explicit width for each UILabel.
Another way (nearly the same) is to set the width for one UILabel and set the remaining UILabel left/right anchors to the first UILabel's left/right anchors.
There's likely more - including using UIStackViews, etc., but since you already have most of your auto layout done, the first two are the easiest.

Updating existing constraints does not work, wrong use of .active property?

I have a storyboard that contains :
A "tab bar" on the left with 5 tabs
A container on the right side of the tab bar that contains 6 image views that share the same space so it looks like this :
Each image view is configured to occupy 1/3 of the container's width and 1/2 of its height.
However, different ratios can be provided at runtime (from a JSON file) so that for example, the 1st image view's height become 70% of its container's height and 50% of its width (therefore, the 2nd and 3rd image views widths occupy 25% of the container's width and the 4th image view, 2nd line column 1 has a height of 30% of the image view).
To do so here is what I tried :
-create 2 arrays of outlets (width and height constraints from my image views) :
// Height constraints
#IBOutlet var heightConstraints: [NSLayoutConstraint]!
// Width constraints
#IBOutlet var widthConstraints: [NSLayoutConstraint]!
-create an outlet for the container of the image views
// Drawings - Container
#IBOutlet weak var drawingView: UIView!
-create stored properties to update my constraints (these are not outlets)
// Drawing height property
var drawingHeightConstraint: NSLayoutConstraint?
// Drawing width property
var drawingWidthConstraint: NSLayoutConstraint?
And here comes the job : I've overridden updateViewConstraints(), is this a mistake ? It seemed the best place to update constraint but I saw people use ViewWillAppear...
This method is called every time I click on a tab (and then load new drawings with new ratios)
public override func updateViewConstraints() {
super.updateViewConstraints()
// Activate default height and width constraints
PapooHomePageViewController.activateConstraint(constraints: heightConstraints)
PapooHomePageViewController.activateConstraint(constraints: widthConstraints)
// Deactivate temporarily created new height and width constraints
drawingHeightConstraint?.active = false
drawingWidthConstraint?.active = false
// Display drawings
let drawingElements:[(String, Double, Double)] = papooBrain.getDrawings(forSection: currentSection)
for (index, (drawing, heightRatio, widthRatio)) in drawingElements.enumerate() {
drawingViews[index].image = UIImage(named: drawing)
// update height constraint if ratio is different than defaut ratio of 1/2
if heightRatio != Double(heightConstraints[index].multiplier) {
heightConstraints[index].active = false
drawingHeightConstraint = NSLayoutConstraint(item: drawingViews[index], attribute: .Height, relatedBy: .Equal, toItem: drawingView, attribute: .Height, multiplier: CGFloat(heightRatio), constant: 0)
drawingHeightConstraint!.active = true
}
if widthRatio != Double(widthConstraints[index].multiplier) {
widthConstraints[index].active = false
drawingWidthConstraint = NSLayoutConstraint(item: drawingViews[index], attribute: .Width, relatedBy: .Equal, toItem: drawingView, attribute: .Width, multiplier: CGFloat(widthRatio), constant: 0)
drawingWidthConstraint!.active = true
}
}
// Which one should I or should i NOT call, in what order ?
drawingView.setNeedsUpdateConstraints()
drawingView.setNeedsLayout()
drawingView.layoutIfNeeded()
}
-Here is the code of the little helper to activate my constraints. NOTE: This my be a problem. I try to activate a constraint previously deactivated (it comes from my array of outlets) but I don't want it to be duplicated
class func activateConstraint(constraints constraints: [NSLayoutConstraint]) {
for constraint in constraints {
constraint.active = true
}
}
-Finally, here is a piece of my JSON so you see what I parse...
"drawings": [
{
"image": "01-01-drawing.png",
"height-ratio": "0.5",
"width-ratio": "0.33",
etc.
The problem(s)
If I change my configuration file (json) to say "Okay, image view 1's height ratio is O.7 and so image view 4's height ratio is 0.3" : I have conflicting constraints (it seems that after all, the "active" property did not deactivate properly my constraint
So when I am debugging, I see all my constraints of width and height getting duplicated, causing a nightmare.
The same happens for the height etc.
Many questions here
Did I use correctly (in terms of lifecycle etc.) updateViewConstraints() ? Is this good to call the super at the beginning ?
What is the correct use of setNeedsLayout, layoutIfNeeded, setNeedsUpdateConstraints...
When I set an outlet NSLayoutConstraint's active property to false. Then active = true afterwards, did it get the correct reference to my default height/width from the storyboard ?
Thank you for reading

Treat NSLayoutConstraint constant as multiplier?

I have three horizontally aligned UIViews within a container UIView. These three views should span the entire width of the above UIImageView. The problem, however, is that sometimes only one or two of the child views should be shown.
I set up my view hierarchy like so:
Since the child views are set to be equal to the width of the first child (which will always be shown), I simply set the width of the first child to be a fraction of the UIImageView width. So if three child views should be shown, the first child view would have a multiplier of 1/3 the width of the UIImageView. If two child views should be shown, the multiplier would be 1/2. If just one, the multiplier would be 1.
This seemed like a perfect solution, however the multiplier property is read only. My first attempt to solve this was by creating three different NSLayoutConstraints attached to the first child view, all with a different multiplier with 2/3 of them turned off. Then, on runtime, I would enable the appropriate constant with the appropriate multiplier based off of the number of views I wanted to show.
This resulted in a lot of ugly errors, and so did my second solution:
var constraint = NSLayoutConstraint(item: color1, attribute:NSLayoutAttribute.Width, relatedBy: NSLayoutRelation.Equal, toItem: imageView, attribute: NSLayoutAttribute.Width, multiplier: 1/2, constant: 0)
view.addConstraint(constraint)
Where I would add a new constraint to the view based on the multiplier I wanted. This, of course, resulted in an error:
When added to a view, the constraint's items must be descendants of that view (or the view itself). This will crash if the constraint needs to be resolved before the view hierarchy is assembled.
My question, therefore, is if I can treat the constant property like the multiplier property. My fear with doing this, however, is that if I set the constant for the width of the first child view, it would not update its width when the phone rotates.
Is there a better solution for all of this?
Firstly, In your question your were using IB but you seemed to suggest there may be a different number of views each time you moved to the view controller which is why I decided to create the NSLayoutConstraints programatically.
Secondly, my solution is fine provided you didn't intend to change the number of views whilst you were on the view controller. If you did, then this needs a bit more work.
In your view controller:
var viewWidthConstraints : [NSLayoutConstraint] = []
override func viewDidLoad() {
super.viewDidLoad()
let numberOfViews = 3
var previousView: UIView = self.view
for i in 0..<numberOfImages {
let myView = UIView()
myView.setTranslatesAutoresizingMaskIntoConstraints(false)
myView.backgroundColor = UIColor.grayColor().colorWithAlphaComponent(CGFloat(i) * (1/CGFloat(numberOfImages)) + 0.1)
self.view.addSubview(myView)
let heightConstraint = NSLayoutConstraint.constraintsWithVisualFormat("V:|[view]|",
options: .DirectionLeadingToTrailing,
metrics: nil,
views: ["view" : myView])
let widthConstraint = NSLayoutConstraint(item: myView,
attribute: .Width,
relatedBy: .Equal,
toItem: nil,
attribute: .NotAnAttribute,
multiplier: 1.0,
constant: self.view.frame.width / CGFloat(numberOfImages))
let attribute: NSLayoutAttribute = (i == 0) ? .Left : .Right
let leftConstraint = NSLayoutConstraint(item: myView,
attribute: .Left,
relatedBy: .Equal,
toItem: previousView,
attribute: attribute,
multiplier: 1.0,
constant: 0.0)
self.view.addConstraints(heightConstraint)
self.view.addConstraint(leftConstraint)
myView.addConstraint(widthConstraint)
viewWidthConstraints.append(widthConstraint)
previousView = myView
}
}
func updateWidthConstraints() {
if viewWidthConstraints.count > 0 {
let width = self.view.frame.width / CGFloat(viewWidthConstraints.count)
for constraint in viewWidthConstraints {
constraint.constant = width
}
}
}
override func viewWillLayoutSubviews() {
updateWidthConstraints()
}
In viewDidLoad you add the UIViews to the view and set up their constraints. The vertical constraint you could change to make the UIViews appear underneath your UIImageView. And change numberOfViews to increase or decrease the number of views.
Then in viewWillLayoutSubviews you update the width of each view using their width constraint. This will make sure, if the device is rotated, each view takes up the correct proportion of the screen.
This is what is looked like with horizontal orientation.
And vertical orientation.