Which time to add constraint to superView? - ios

I wanna add constraints toAview on Aview.superView without Xib and Storyboard, i add constraint code in the function -(void)didMoveToSuperview, it worked. The second time i add Aview to cell.contentView, when i start to scroll , it crashed;
Here is the error info :
A multiplier of 0 or a nil second item together with a location for the first attribute creates an illegal constraint of a location equal to a constant. Location attributes must be specified in pairs
But i am sure all the params of the multiplier is not 0 or nil .
It seems that the second item(the superView) is nil , so i write the constraint code in a custom function like this :
[cell.contentView addSubview:view];
[view setConstraint];
Then it works;
I just wanna know is there a good way to add constraint in the Aview?In which function ?

Related

Update widthAnchor on Device orientation

I have this constraint in my controller for a view like this:
someView.widthAnchor.constraint(equalToConstant: view.bounds.width).isActive = true
Now I've added this constraint in willLayoutSubviews to update it on device rotation.
But it doesn't seem to update, even more like it adds another width constraint, which of course conflicts with the old width constraint.
Now I don't really know a proper solution to update this width constraint, but it seems to me like I need to remove the constraint first and then set it again.
Which if I test this like this:
someView.constraints.forEach {
someView.removeContraint($0)
}
This works like expected, only it deletes of course some constraints I don't want to delete... so also not a solution.
Sol1
hook the width constraint as IBOutlet and change it's constant in code like this
self.widthCon.constant = //value
self.view.layoutIfNeeded()
Sol2
delete the constraint with identifier
someView.constraints.forEach {
if $0.identifier == "set_id" {
someView.removeConstraint($0)
someView.widthAnchor.constraint(equalToConstant: view.bounds.width).isActive = true
}
}
Alternate Solution:
If you have a reference to the superview of someView then you can simply assign equal width constraints like so:
someView.widthAnchor.constraint(equalTo: superView.widthAnchor)
This way you can let the Autolayout engine update the width automatically when the device orientation changes.

How to access Constraint Data Programatically?

I would like to access firstAttribute constraint property programatically. so that i can able to find if a constraint is height constraint,width constraint,topMargin constraint etc...
when i try,
print(constraint.firstAttribute)
i get the general result as
"NSLayoutAttribute"
but what i want more specific result to treat the constraints differently based on it attribute(height,width,topMargin etc..) type.
The property on the constraint that you're looking for is firstAttribute (or secondAttribute). However, there's really no reason you should need to "guess" which constraint you're looking at - just store the constraints as properties and name them accordingly.
EDIT: to tell which type of attribute you have, switch over it:
switch myConstraint.firstAttribute {
case .height:
// height constraint
case .leading:
// leading constraint
case .topMargin:
// top margin constraint
// etc
}
The solution for this is to store the constraint as a property. You can do this with an outlet from a storyboard or nib (just like a button or label) or in code.
That way, you don’t have to dive into the view to try and find the constraint. You just access it like...
buttonHeightConstraint.constant = 50
Etc...
For me firstAttribute didn't work at all, and also, it's not compatible with versions prior to iOS 9.0
So I finally solved by assigning an identifier to the NSLayoutConstraint object. Then, you can iterate though the constraints of your view and compare the identifier to retrieve the desired constraint.
yourConstraint.identifier = "yourString"
So:
for tmpConst in self.view.constraints {
if tmpConst.identifier == "yourString" {
return tmpConst
}
}
Will return your constraint.

Constraint to view after nearest neighbour removed

Sorry for the title if it's not the best but I really don't know how to explain this in a few words.
So, what I have is a view with a label and a image. There are two casses. One in which I don't need the image, just the label(the most ussual one). And one where I need both the image and the label and at some point I have to remove the image through an animation(this one is handled).
Now. for the one where I need just the label I was thinking to play with the constraints. I want to have the spacing for the left 15 and the constraint to go to the superview, but also when there is a image I want the constraint to go to the image. I'll add a image to make it more clear. How can I accomplish this?
Actually you can do the same thing from the XIB also.
Select the leadingConstraint outlet from the XIB and create the outlet for the constraint.
It will create a property of type NSLayoutConstraint, then set the value to 0 in case where you don't want to show ImageView
self.imageViewLeadingConstraint.constant = 0;
Repeat the same process for ImageView Width also.
For that you should bind (Take #IBOutlet) UIImageView's width constrains AND leading constrains and you need to manage it in cellForRowAtIndexPath like
if isOnlyLabel { // "isOnlyLabel" it's just for understanding
// Here you have only label not image
// Set image with constraint = 0 and leading = 0
}
else {
// Here you have label + image
// Set image with constraint = 60 and leading constraint = 15 Or as you want
}

Swift: Views not adjusting to programmatic constraints

I have a view that I am making hidden at the bottom of the screen, and want the scrollView above it to adjust and fill the void space.
The view at the bottom of the screen is a GADBannerView and has a fixed height of 50 (bannerHeight). The scroll view above it has a constraint to the bottom of the container that equals 50 (scrollConstraint). See photo.
In viewDidLoad is am setting these constraints to the following:
bannerHeight.constant = 0
scrollConstraint.constant = 0
This is causing the bannerView did disappear but the scroll view is staying in it's original position and not filling the void space.
You can force the superview to take into account the change of the constraint because this does not happen automatically. Add your code to viewDidLayoutSubviews() instead or simply call view.layoutIfNeeded() after you set the constants to 0 in the viewDidLoad().
If this does not work, you can try this alternative approach:
Go to your Storyboard and click on the scroll view's bottom constraint (the blue line that gives the scroll view its bottom constraint of 50). In the Attributes Inspector you should be able to see details about your constraint, it should look something like this
In the field that asks for an Identifier, give it the name "ScrollViewBottom" or whatever name you like.
Now loop over all the constraints that make up your scroll view and use the identifier name to find the correct one and change it's constant as follows
for constraint in yourScrollView.superview!.constraints {
if constraint.identifier == "ScrollViewBottom" {
constraint.constant = 0
}
}
Finally, force the view to take into account of this change by calling the following straight after
view.layoutIfNeeded()

Why can't Interface Builder set a negative multiple for a constraint, even though [NSLayoutConstraint constraintWithItem:] can?

I have an iOS 9 & 10 app which has a little "tray" UIView along the bottom of the window that slides in and out to display (or hide) certain controls. By default the tray is fully displayed ("slide out") so the user can see everything. There is a button on the far right that they can tap to make the tray "slide in" to the left so that the tray is mostly hidden because it's now off-screen to the left. (The right-most 20 points are the "thumb" that the user can tap to slide in/out, so the thumb is the only part of the tray that remains visible when the tray is slid in.)
The way I'd like to do this is to have two different constraints defined via Interface Builder, and I simply toggle them so that only one is active at a time. For example, the two constraints could be:
trayOutConstraint:
trayView.leading = (superview.leading * 1) + 0 // result == 0
trayInConstraint:
trayView.leading = (superview.trailing * -1) + 20 // result == -300 (for example)
It turns out that I can create constraint for trayInConstraint programmatically, like so (edit: expanded to show how I'm disabling the storyboard's default constraint and adding my new constraint):
// Disable the default constraint (in the storyboard)
[[self trayTripOutLeadingConstraint] setActive:NO];
// Create the new constraint (add to an array by itself)
NSLayoutConstraint *newConstraint = [NSLayoutConstraint constraintWithItem:(id)trayView attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:(id)[trayView superview] attribute:NSLayoutAttributeTrailing multiplier:-1 constant:20];
NSArray *newTrayConstraintsArray = [[NSArray alloc] initWithObjects:newConstraint, nil];
// Remove any existing constraints array and use the new one
[[self view] removeConstraints:[self tripTrayConstraints]];
[self setTripTrayConstraints:newTrayConstraintsArray];
[[self view] addConstraints:newTrayConstraintsArray];
But if I try to create it in Interface Builder, I can't set the multiplier to anything other than 1.0. (Typing any other value simply forces it to reset to "1".)
I see in Apple docs https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/AutolayoutPG/AnatomyofaConstraint.html :
You cannot use a nonidentity multiplier (a value other than 1.0) with
location attributes.
But why can I create it in code but not IB?
(Unrelated aside that might be useful to someone else: I also wanted to create a constraint where the multiple was 0 but couldn't do that programmatically or in IB. I found that I could work around the inability to use "0.0" as the multiplier by instead using something very small such as 0.000001.)
Select "Reverse First And Second Item" and re-enter the absolute value of the multiplier => This way (by switching first and second item) the multiplier behaves like it is negative.
First Item: View.Leading
Second Item: Superview.Trailing
First Item: Superview.Trailing
Second Item: View.Leading
You can assign negative values to leading, trailing, top and bottom constraints simply. But You can not assign negative values to any multiplier either from code or in Interface builder. If you are adding any negative value in your code as multiplier that it will be considered as +1 by the compiler and you may get the unexpected result. (In your case , you may got the expected result unexpectedly)

Resources