I'm getting the error:
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** +[NSLayoutConstraint constraintWithItem:attribute:relatedBy:toItem:attribute:multiplier:constant:]: Invalid pairing of layout attributes'
Any ideas?
Here is my code:
static func addViewConstraintsCenterTop(constrainObject : UIView, toSibling : UIView) {
constrainObject.setTranslatesAutoresizingMaskIntoConstraints(false)
var constraints:[NSLayoutConstraint] = []
var constW = NSLayoutConstraint(item: constrainObject, attribute: NSLayoutAttribute.CenterX, relatedBy: NSLayoutRelation.Equal, toItem: toSibling, attribute: NSLayoutAttribute.Width, multiplier: 1.0, constant: 0)
constraints.append(constW)
var constH = NSLayoutConstraint(item: constrainObject, attribute: NSLayoutAttribute.CenterY, relatedBy: NSLayoutRelation.Equal, toItem: toSibling, attribute: NSLayoutAttribute.Top, multiplier: 1.0, constant: 0)
constraints.append(constH)
constrainObject.addConstraints(constraints)
}
A constraint between sibling views cannot be installed on either sibling, but must be installed on a common ancestor.
From the Installing Constraints section of Apple's Auto Layout Guide:
The view that holds the constraint must be an ancestor of the views
the constraint involves, and should usually be the closest common
ancestor. (This is in the existing NSView API sense of the word ancestor,
where a view is an ancestor of itself.) The constraint is interpreted in the coordinate system
of that view.
If you want to retain this constraint, you would need to hold on to a reference to it, and manually add it to the correct superview when your view's superview changed, ideally in your view controller's updateViewConstraints() method.
I suspect you have to add the constraints to the super view, i.e. the view that contains constrainObject and toSibling.
Related
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.
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!
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.
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.
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