Determining which of two layout constraints with different priorities is determining the value of the dimension/anchor they affect - ios

Say I have two NSLayoutConstraints with different priorities that would both affect the height of some view (middleView).
Below, middleView is pinned to the bottom of topView and its height is 500, unless the top of bottomView would force it to be smaller.
// topView and bottomView have well-defined constraints, and middleView has well defined x-axis/width constraints not shown here
middleView.topAnchor.constraint(isEqual: topView.bottomAnchor).isActive = true
let heightConstraint = middleView.heightAnchor.constraint(isEqualToConstant: 500)
heightConstraint.priority = UILayoutPriority.high
heightConstraint.isActive = true
let bottomConstraint = middView.bottomAnchor.constraint(isLessThanOrEqualTo: bottomView.topAnchor, constant: someMargin)
bottomConstraint.isActive = true // priority is .required by default
Besides just checking if the height of my view is 500, is there some other way to determine which of these two constraints is "in effect"? Say bottomView is in such a position that if forces middleView's height to be less than 500, does heightConstraint have some property to determine that it has been bypassed by a constraint with a greater priority than its own?
I would like to use heightConstraint as some sort of 'switch' that triggers something when it is satisfied.

This can't be answered because both may have some effect. Even when a non-rquired constraint cannot be satisfied, it can influence layout. From the Auto Layout Guide:
Even if an optional constraint cannot be satisfied, it can still influence the layout. If there is any ambiguity in the layout after skipping the constraint, the system selects the solution that comes closest to the constraint. In this way, unsatisfied optional constraints act as a force pulling views towards them.
Auto layout uses a constraint solver to calculate its values. (I believe it's still using Cassowary, or something close to it.) Constraint solvers are basically linear algebra engines. You put a bunch of linear equations into a matrix and you try to solve it for some vector that makes everything "best." One trade-off of that approach is that everything is solved together, and it's not always obvious why a particular value was chosen. It's what it is because it's part of the total solution. (In this way, CSP systems are very similar to machine learning systems. They give answers that are "correct," but they don't always provide a step-by-step of their reasoning they way branching-if logic can. It's why they can be so challenging to debug.)
To your question, I would base your logic off the thing you actually care about. "A constraint fired" is never the actual thing you cared about (especially since constraints don't "fire"). You generally care about something in the result (such as the final hight), so you should check that.

Looks like it's not possible. The alternative would be to compare the dimension/anchor constant in question to the possible values.

Related

Does Adding Calculations in Constraints Reduce System Performance?

I'm a beginner and just started out learning swift and have a question in layout constraints.
If I were to type out a constraint for an imageView in a Table View Cell like so:
func setConstraints(){
//Method 1
NSLayoutConstraint.activate([
clientPhoto.leftAnchor.constraint(equalTo: baseView.leftAnchor,constant: 12),
clientPhoto.topAnchor.constraint(equalTo: baseView.topAnchor,constant: 2),
clientPhoto.widthAnchor.constraint(equalToConstant: 20),
clientPhoto.heightAnchor.constraint(equalToConstant: 23)
])
//OR
//Method 2
NSLayoutConstraint.activate([
clientPhoto.leftAnchor.constraint(equalTo: baseView.leftAnchor,constant: 12),
clientPhoto.topAnchor.constraint(equalTo: baseView.topAnchor,constant: 2),
clientPhoto.widthAnchor.constraint(equalToConstant: UIscreen.main.bounds.width/2),
clientPhoto.heightAnchor.constraint(equalToConstant: UIScreen.main.bounds.height/4)
])
}
}
Of the methods 1 & 2 written above, which is faster in execution and by how much? I've been told that having calculations in the constraints slows down the performance of the system.
If the way of constants with numbers is better, how do I account for the changes in screen width and height when trying to display the UI with same ratio in different devices(iPhone SE - XS Max)?
The short answer is "No, there will be no significant difference between this two ways".
And here's why: you actually calculate the values once and set the values. No more caluclations needed. It's very very fast.
I'm not sure what've you heard about the layout peformance, but the constraints in a nutshell is a complex system of equations, so every time system needs layout any of subview – it recalculates all the equatations. And this is what takes significant time to do – to solve all the equatations. So you ease the system work if you set a simple constraints with a constants (like you do in your question).
"A constraint with a calculation" you've heard about is probably a constraints with equatation like "the width BINDED to the half of height of another view".

How many minimum constraints are required for one UI object ? (iOS)

Most of the interviews common question is "How many minimum constraints are required for one UI object" Can someone please clarify?
Regardless of what are the constraints (how would you determine the appropriate constraints), basically, the minimum required constraints are the constraints that should determine the size and the origin of the component (height, width, x and y).
Keep in mind it is not only required to specify each property by its own literal constraint, for example, you could determine what is the width of the component by setting leading and trailing constraints to it instead of equals constant width. Furthermore, components with intrinsic content size should not have always fixed size (height and width), hence determining their origins (x and y) would be sufficient, unless there is a need to setup their constants.
To be short, excluding the case of an UIScrollView where the content size is involved as well, you need 2 distinct constraints per axis (horizontal and vertical).
It's also important to note that if an UI element has an intrinsic size, that counts as a constraint for that axis already, even though it can be overridden with constraints that have higher priority.

How do I make a constraint "5-53" space between views?

when I want to make a constraint saying that the layout system can have minimum 5 and maximum 53 points between two views, such as
ViewA.Leading is greater or equal ViewB.Trailing 5 (pri 1000 multiplier 1)
ViewA.Leading is less or equal ViewB.Trailing 53 (pri 1000 multiplier 1)
Interface Builder always gets upset with me, giving me the "Inequality Constraint Ambiguity" error. If this is not the way to make such a constraint, how should it be instead?
(please, I insist on expressing the constraints in a storyboard through Interface Builder)
Cheers
Nik
The ambiguity is that the auto layout system doesn't know exactly how much space to put. You have successfully placed limits on the range of allowed space, but there are still any number of solutions. For example, 20 points works, but so does 40, and so does 31.2875.
The system needs additional information to pick a specific distance. How much space, specifically, would you prefer if all other constraints allow flexibility?
You could, for example, set a constraint ViewA.Leading equal to ViewB.Trailing plus 20 but at a lower priority. The lower priority would allow it to be overridden by higher priority constraints and things such as content hugging or compression resistance priority. But, all else being equal, the system will try for 20 or as close to 20 as possible. That removes the ambiguity.
But, ultimately, you need to decide how the system should resolve things when there are remaining degrees of freedom and give the system the corresponding constraints to eliminate that freedom, so it can come up with one right answer.

Understanding multiplier in auto layout to use relative positioning

I am trying to understand how one can utilize Auto Layout to position items relative to other views percentage-wise.
For example, I recently learned that you can specify a view's bottom should lie 4% higher than its superview's bottom by using this:
self.view.addConstraint(NSLayoutConstraint(item: label, attribute: .Bottom, relatedBy: .Equal, toItem: self.view, attribute: .Bottom, multiplier: 0.96, constant: 0))
This makes sense to me because a multiplier of 1 would align it right at the view's bottom, so you can decrease that amount by 4 percent by changing the multiplier to 0.96.
But how can you do the same in the other direction? You want to specify a label's top should begin 4% down from the superview's top. If you use that same line but change the attributes to .Top, that means it would be 4% higher than the superview's top (but it actually doesn't move it off screen). You can't have a negative multiplier I don't think, and I don't believe a value greater than 1 does anything when constant is 0. So how do you get that set up?
I have the same question for implementing leading and trailing. Trailing is easy. If you want it 10% from the right:
self.view.addConstraint(NSLayoutConstraint(item: label, attribute: .Trailing, relatedBy: .Equal, toItem: self.view, attribute: .Trailing, multiplier: 0.9, constant: 0))
It makes sense because you dial it back 0.1 or 10% instead of aligning fully at 1.0. But how do you do the same for leading? I thought you might be able to set the label's leading relative to the view's trailing, then set the multiplier to 0.1. In my mind that would mean the label would start at the very far right but then be dialed back 90%, therefore obtaining the desired 10% from the left. But that's not the case, I'm not sure why.
Can you explain how this is works, how to properly use multiplier to obtain these relative layouts?
To make it easy, let's say you'd like to create a label that has top and bottom 10% of the superview's height, and trailing and leading 10% of the superview's width. On an iPhone in portrait there's going to be more padding above and below the label than there is padding to the left and right of it, like so (yes it's drawn to scale):
But let's say on the iPad it's going to be shown in a view that's a perfect square. Therefore the padding will be the same amount all around, like so:
The question is how do you define such constraints to be dynamic in value, as opposed to setting a fixed value for a constant. I already know how to do bottom and trailing, but top and leading has me stumped. I'm hoping to understand how to use multiplier to do more advanced layouts, for example, specifying a label's top should lie 10% beneath another label's bottom, as opposed to setting it to a fixed constant.
There are a couple ways to do this. In the simplest case, you've already almost got it: if you want the horizontal boundaries to be at 10% and 90%, then you need to specify both constraints with respect to the trailing edge of the superview -- so Subview.Trailing locks to Superview.Trailing with a multiplier of 0.9, as you say, but then Subview.Leading also locks to Superview.Trailing, just with a multiplier of 0.1:
(and similarly for top / bottom)
On the other hand, the case you mention at the end is a little more complicated: "specifying a label's top should lie 10% beneath another label's bottom." For that you probably won't be able to use fixed percentage insets like the previous case. Instead, you can create an invisible spacer view between them: add a constraint with Spacer.Height = 0.1 * Superview.Height and then attach it between the two labels. (You can also handle the previous case with these spacer views, but for that case it isn't really necessary.)
In my opinion, "You can't have a negative multiplier I don't think, and I don't believe a value greater than 1 does anything when constant is 0" exposed your comprehending deviation.
The rule underneath the hood is just a linear equation:
FirstItem.Attribute1 = (SecondItem.Attribute2 * Multiplier) + Constant
All measured in points. As you see, multiplier(a property of NSLayoutConstraint) is not the multiplier of constant. Follow the equation, what you don't understand will be clear.
As to your specific example, #Archaeopterasa presented a great solution, another is shown below:
Based on the fact you know how to do bottom and trailing, I suppose you've done these two. Then add another two constraints, the effect will be what you want:
At last, if you want to specify a label's top lie 10% beneath another label's bottom, it seems that you cannot implement it without writing a line of code. You have to use code to set the constant of the NSLayoutConstraint object connecting the FirstItem and the SecondItem after the superview's height is known in runtime.
Firstly, control drag from one label to the other and choose "Vertical Spacing".(Or you can do this in other ways)
Secondly, a referencing outlet is needed:
#IBOutlet weak var tenPercentOfSuperview: NSLayoutConstraint!
Then, do this in a appropriate place(for example, in viewDidLoad())
let heightOfSuperview = self.view.bounds.height
tenPercentOfSuperview.constant = heightOfSuperview * 0.1
Everything is OK now.
If you want to know more about this topic, Apple's document is recommended:https://developer.apple.com/library/ios/recipes/xcode_help-IB_auto_layout/chapters/EditingConstraintAttributesintheAttributesInspector.html
Here's the
INCREDIBLY SIMPLE
way to do it.
Just add 'helper' or "measure' views:
The 'calculation helper' views are yellow in the example.
The general technique is so simple and obvious - it doesn't need any explantion, you can get it from the image.
It's one of those "elephant in the room" things they "don't teach you about" in iOS. But it is used constantly in all layouts.
(Indeed, Apple should have made a special UIView subclass "measures" for exactly this purpose - and indeed, many large teams do just that, with the obvious features.)
Notice icon #1 is centered on the right end of the "View 1/4" helper view.
Notice icon #3 is centered on the left end of the "View 3/4" helper view.
You're done, have a Chardonnay.
Conveniently, in the helper views, just set the multiplier to anything you want, depending on the feel wanted. It's incredibly easy to then change those in your code, use IBInspectable, animate, and so on and on...

Autolayout ambiguity with inequalities

I'm confused about how Cocoa's Autolayout determines whether a layout is ambiguous. Here's a simple example:
The observed behavior is as follows. The spacers to the left and right of the green rectangle are always the same width. When you stretch the superview horizontally outwards, the spacers stick to 80 while the rectangle expands. When you shrink the superview horizontally, the rectangle sticks to 398 while the spacers shrink to 10, after which the rectangle continues to shrink. At no point is the layout labeled ambiguous by IB.
However, you'll notice that the horizontal layout is defined almost entirely by inequalities! From what I can see, when the rectangle has a width of > 398, there's no reason for the spacers to have a width of 80. They could have a width of anywhere from 10 to 80 and still satisfy each horizontal inequality. That sounds ambiguous to me, but IB clearly does not agree.
There must be some implicit rule I'm missing. Please help me out!
Auto layout has, coincidentally, ambiguous documentation. Therefore, the behavior you observed from your application is technically undefined. Here is exactly what the documentation says:
Constraints have a priority level. Constraints with higher priority levels are satisfied before constraints with lower priority levels. The default priority level is required (NSLayoutPriorityRequired), which means that the constraint must be satisfied exactly. The layout system gets as close as it can to satisfying an optional constraint, even if it cannot completely achieve it.
The requirements are therefore
The layout system gets as close as it can to satisfying an optional constraint
Constraints with a priority of 1000 must be satisfied exactly
Higher priority constraints are satisfied before constraints with lower priority levels
However, as you have noticed
What causes the spacer to greedily expand to 80, versus the green rect expanding maximally on account of it having a higher priority?
Auto layout does not define "close as it can" and "Higher priory constraints are satisfied before constraints" in a unambiguous manner. The Auto layout documentation mentions these two dimensions: priority and closeness; but these documents do not say how the optimization of these dimensions interact.
With that being said, here is one theory of how auto layout could operate based on the fact that auto layout is probably a multi-variable linear decomposition solver.
Let's consider your corner cases
When you stretch the superview horizontally outwards the spacers stick to 80 while the rectangle expands
When you shrink the superview horizontally, the rectangle sticks to 398 while the spacers shrink to 10
after which the rectangle continues to shrink
These corner cases can be reduced to
Case A - superview > 398+80+80 (Spacers fixed at 80, rectangle expands from 398)
Case B - superview > 398+10+10 (Rectangle fixed at 398, spacers contract from 80 to 10)
Case C - superview <= 398+10+10 (Spacers fixed at 10, rectangle contracts from 398)
Notice how the numbers never jump from case to case. There is a smooth connection from Case A to Case B in the spacers, going from 80 -> 80 at the critical points. The same occurs for the rectangle from Case B to Case C. The algorithm used by auto-layout always results in a smooth solution when dealing with critical points.

Resources