How can I change constraints priority in run time - ios

I have a view which has dynamic height and I am trying to change this view height priority in run time.
Here is my part of code;
if (index == 0) {
surveyViewHeightConstraint.constant = 0;
surveyViewHeightConstraint.priority = 1000;
} else if (index == 1) {
surveyViewHeightConstraint.constant = 163;
surveyViewHeightConstraint.priority = 500;
}
I am changing index with a button action. When I run this code, I am getting this error:
*** Assertion failure in -[NSLayoutConstraint setPriority:], /SourceCache/Foundation/Foundation-1141.1/Layout.subproj/NSLayoutConstraint.m:174
What is my mistake in here?

As stated in NSLayoutConstraint class reference:
Priorities may not change from nonrequired to required, or from required to nonrequired. An exception will be thrown if a priority of NSLayoutPriorityRequired in OS X or UILayoutPriorityRequired in iOS is changed to a lower priority, or if a lower priority is changed to a required priority after the constraints is added to a view. Changing from one optional priority to another optional priority is allowed even after the constraint is installed on a view.
Use priority 999 instead of 1000. It won't be absolutely required technically speaking, but it'll be a higher priority than anything else.

I want to make a small addition to Cyrille's answer.
If you are creating constraint in code, make sure to set its priority before making it active. For instance:
surveyViewHeightConstraint = [NSLayoutConstraint constraintWithItem:self
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:self.superview
attribute:NSLayoutAttributeHeight
multiplier:1
constant:0];
surveyViewHeightConstraint.active = YES;
surveyViewHeightConstraint.priority = 999;
This would result in run-time exception.
Mutating a priority from required to not on an installed constraint (or vice-versa) is not supported.
Correct order is:
surveyViewHeightConstraint.priority = 999;
surveyViewHeightConstraint.active = YES;
For Swift version 4+
constraintName.priority = UILayoutPriority(rawValue: 999)

Swift version:
myContraint.priority = UILayoutPriority(999.0)

The way we have always handled this is by not changing the constraint constant, just the priority. For example, in your situation have two height constraints.
heightConstraintOne (who's height is set in the storyboard at 150, and priority set at 750)
heightConstraintTwo (who's height is set in the storyboard at 0, and priority set at 250)
if you want to hide the view, you:
heightConstraintTwo.priority = 999;
likewise, if you want to show the view:
heightConstraintTwo.priority = 250;

I hope this scenario help someone:
I had two constraints, that they were opposite to each other, I mean they couldn't be satisfied in the same time, in interface builder one of them had 1000 priority and other had less than 1000 priority. when I changed them in the code, I had this error:
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Mutating a priority from required to not on an installed constraint (or vice-versa) is not supported. You passed priority 250 and the existing priority was 1000.'
then I just initialized them in viewDidLoad function with .defaultHigh and .defaultLow values and this fixed my problem.

I was facing the same issue. As some of the answer mentioned above in iOS 13 changing priority will work fine, However in iOS 12 it will lead to crash.
I was able to fix this problem by creating IBOutlet NSLayoutConstraint to that specific constraint in Storyboard retaining its priority to 1000, below is the code for fix.
if (Condition) {
surveyViewHeightConstraint.constant = 0;
surveyViewHeightConstraint.isActive = false;
} else if (index == 1) {
surveyViewHeightConstraint.constant = 163;
surveyViewHeightConstraint.isActive = True;
}
Hope this helps!!! Cheers

According to NSLayoutConstraints class inside UIKit Module
If a constraint's priority level is less than
UILayoutPriorityRequired, then it is optional. Higher priority
constraints are met before lower priority constraints.
Constraint satisfaction is not all or nothing. If a constraint 'a == b' is optional, that means we will attempt to minimize
'abs(a-b)'.
This property may only be modified as part of initial set up or when optional. After a constraint has been added to a view, an
exception will be thrown if the priority is changed from/to
NSLayoutPriorityRequired.
Example:- UIButton constraints with various priorities -
func setConstraints() {
buttonMessage.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint(item: buttonMessage, attribute: .bottom, relatedBy: .equal, toItem: view, attribute: .bottom, multiplier: 1.0, constant: -10).isActive = true
let leading = NSLayoutConstraint(item: buttonMessage, attribute: .leading, relatedBy: .equal, toItem: view, attribute: .leading, multiplier: 1.0, constant: 10)
leading.isActive = true
let widthConstraint = NSLayoutConstraint(item: buttonMessage, attribute: NSLayoutAttribute.width, relatedBy: NSLayoutRelation.equal, toItem: nil, attribute: NSLayoutAttribute.notAnAttribute, multiplier: 1, constant: 100)
let heightConstraint = NSLayoutConstraint(item: buttonMessage, attribute: NSLayoutAttribute.height, relatedBy: NSLayoutRelation.equal, toItem: nil, attribute: NSLayoutAttribute.notAnAttribute, multiplier: 1, constant: 50)
let trailingToSuperView = NSLayoutConstraint(item: buttonMessage, attribute: .trailing, relatedBy: .equal, toItem: view, attribute: .trailing, multiplier: 1, constant: 0)
trailingToSuperView.priority = 999
trailingToSuperView.isActive = true
//leading.isActive = false//uncomment & check it will align to right of the View
buttonMessage.addConstraints([widthConstraint,heightConstraint])
}

Now you can , just take the IBOutlet connection for your constraints , then use this code .
self.webviewHeight.priority = UILayoutPriority(rawValue: 1000)

Related

Constrain UIView's proportionally

I want to position my view's in relative terms. I want do use percentages and not pixels. Say I want to position a button's center at 25% of its super view's height. How would I do this?
One way to do it would be:
button.topAnchor.constraint(
equalTo: superView.topAnchor,
constant: /* compute constant "dynamically")
.isActive = true
But this feels silly because we have a multiplier parameter in many of the Layout Margin API functions.
But yet if I type button.centerYAnchor.constraint, none of the suggested completions show any functions that take a multiplier argument except for one's with systemSpacing in the name. I've looked into "system spacing" but cannot figure out what it does.
If I have this constraint:
button.topAnchor.constraintEqualToSystemSpacingBelow(rootView.topAnchor, multiplier: 0.2).isActive = true
The view is just position directly under its superView's top.
I haven't found a way to do it with the layout anchors, but here is how to do it by creating the NSLayoutConstraint directly:
NSLayoutConstraint(item: button, attribute: .centerY,
relatedBy: .equal, toItem: superView, attribute: .bottom,
multiplier: 0.25, constant: 0).isActive = true

Could not find relevant edges for attributes

I get the following error:
Could not resolve symbolic constant for constraint, because: Could not
find relevant edges for attributes: centerX and centerX.
Use a symbolic
breakpoint at
NSLayoutConstraintFailedToFindDefaultResolvedValueForSymbolicConstant
to debug.
If I add a breakpoint at NSLayoutConstraintFailedToFindDefaultResolvedValueForSymbolicConstant it stops at this line:
[self.customNavigationBar.widthAnchor constraintEqualToAnchor:self.view.widthAnchor].active = YES;
This line is called within the viewDidLoad of the view controller. customNavigationBar is a UIView loaded from a nib which already have been added as subview to self.view.
If I try to print out the anchors I am using everything seems ok:
(lldb) po self.customNavigationBar.widthAnchor
<NSLayoutDimension:0x17446cc80 "UIView:0x10115c160.width">
(lldb) po self.view.widthAnchor
<NSLayoutDimension:0x170667080 "UIView:0x1012ae550.width">
This error comes from your choice of constructor for the NSLayoutConstraint.
You probably have something like this:
view.topAnchor.constraint(equalToSystemSpacingBelow: otherView.centerYAnchor, multiplier: 0.25).isActive = true
But you should construct it like this:
let constraint = NSLayoutConstraint(item: view, attribute: .top, relatedBy: .equal, toItem: otherView, attribute: .centerY, multiplier: 0.25, constant: 1)
constraint.isActive = true

How to change size of an MKMapView in Swift?

I can't seem to change the size of my MKMapView in Swift. How would one go on about it?
I've tried two different methods but without any luck:
var rect: CGRect = self.view.frame;
rect.origin.y = 0;
self.mapView.frame = rect;
and one where I used constraints and autolayout but it made the app crash. Any ideas?
EDIT:
When I write this code it doesn't crash but writes some warnings in the output:
let height = UIScreen.mainScreen().bounds.height
let width = UIScreen.mainScreen().bounds.width
let widthConstraint = NSLayoutConstraint(item: mapView, attribute: NSLayoutAttribute.Width, relatedBy: NSLayoutRelation.Equal, toItem: nil, attribute: NSLayoutAttribute.NotAnAttribute, multiplier: 1, constant: width)
self.view.addConstraint(widthConstraint)
let heightConstraint = NSLayoutConstraint(item: mapView, attribute: NSLayoutAttribute.Height, relatedBy: NSLayoutRelation.Equal, toItem: nil, attribute: NSLayoutAttribute.NotAnAttribute, multiplier: 1, constant: height)
self.view.addConstraint(heightConstraint)
The output says:
2016-03-09 18:43:02.697 Map[19782:3177712] Unable to simultaneously satisfy constraints.
Probably at least one of the constraints in the following list is one you don't want.
Try this:
(1) look at each constraint and try to figure out which you don't expect;
(2) find the code that added the unwanted constraint or constraints and fix it.
(
"",
""
)
Will attempt to recover by breaking constraint
Make a symbolic breakpoint at UIViewAlertForUnsatisfiableConstraints
to catch this in the debugger. The methods in the
UIConstraintBasedLayoutDebugging category on UIView listed in
may also be helpful. Message from debugger:
Terminated due to signal 15
I don't really understand this, any ideas?
In this case you should use
self.mapView.addConstraint(widthConstraint)
and
self.mapView.addConstraint(heightConstraint)
You are trying to add a constraint that belongs to the mapView to its superview, that's not the way it's supposed to be. If you were adding a vertical distance between the mapview and another view you would add it to the superview, but not in this case.
For the constraint warning you try to add constraint without disabling the autorisizing mask into constraint translation.
Try adding this line:
mapView.translatesAutoresizingMaskIntoConstraints = false
Regards

Xcode swift view wrap content

I'm an Android developer trying my hand at Xcode and it's been unpleasant so far. What I'm trying to do is have a custom view that has three sub views:
UIImageView (for an icon)
UILabel (for the title)
UILabel (for the content)
I want it such that the content label's height grows and shrinks to wrap the text it contains (like Android's wrap_content). And then, I want the custom view to also grow and shrink to wrap all three sub views.
However, I cannot, for the life of me, figure out how these auto layouts/constraints work.
01) How would I make my UILabel's height grow/shrink to match its contained text?
02) How would I make my custom view's height grow/shrink to match its contained sub views?
override func layoutSubviews() {
super.layoutSubviews()
img_icon = UIImageView()
txt_title = UILabel()
txt_content = UILabel()
img_icon.backgroundColor = Palette.white
img_icon.image = icon
txt_title.text = title
txt_title.textAlignment = .Center
txt_title.font = UIFont(name: "Roboto-Bold", size:14)
txt_title.textColor = Palette.txt_heading1
txt_content.text = content
txt_content.textAlignment = .Center
txt_content.font = UIFont(name: "Roboto-Regular", size:12)
txt_content.textColor = Palette.txt_dark
txt_content.numberOfLines = 0
txt_content.preferredMaxLayoutWidth = self.frame.width
txt_content.lineBreakMode = NSLineBreakMode.ByWordWrapping
self.backgroundColor = Palette.white
addSubview(img_icon)
addSubview(txt_title)
addSubview(txt_content)
/*snip img_icon and txt_title constraints*/
let txt_content_x = NSLayoutConstraint(item: txt_content, attribute: .CenterX, relatedBy: .Equal, toItem: self, attribute: .CenterX, multiplier: 1, constant: 0)
let txt_content_y = NSLayoutConstraint(item: txt_content, attribute: .Top, relatedBy: .Equal, toItem: self, attribute: .Top, multiplier: 1, constant: 80)
let txt_content_w = NSLayoutConstraint(item: txt_content, attribute: .Width, relatedBy: .Equal, toItem: self, attribute: .Width, multiplier: 1, constant: 0)
let txt_content_h = NSLayoutConstraint(item: txt_content, attribute: .Height, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1, constant: 40)
txt_content.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activateConstraints([
txt_content_x,
txt_content_y,
txt_content_w,
txt_content_h
])
}
I understand that, in the above code I've tried, I have the height set to a constant 40. This is only because I don't know how to achieve what I want.
[EDIT]
I've tried setting the height constraint to greater than or equal to but it just crashes Xcode.
[EDIT]
It crashes Xcode if I try to view it but works perfectly fine in the simulator. Question now is, why?
My height constraint is now:
let txt_content_h = NSLayoutConstraint(item: txt_content, attribute: .Height, relatedBy: .GreaterThanOrEqual, toItem: nil, attribute: .NotAnAttribute, multiplier: 1, constant: 40)
It works in the simulator and has the desired behaviour. However, if I open the storyboard that contains the view, it crashes. It's definitely that line of code because changing it back to .Equal resolves the crash.
[EDIT]
My temporary fix is:
#if TARGET_INTERFACE_BUILDER
//use .Equal for height constraint
#else
//use .GreaterThanOrEqual for height constraint
#endif
This way, it doesn't crash Xcode and still renders the way I want it on the simulator.
[EDIT]
I removed the pre-processor check because I realized there's no actual thing like that defined and it still works now. I swear I've changed nothing else.
I am this close to giving up on iOS development because the interface builder keeps crashing Xcode without a reason when everything works in the simulator. Then, I do some nonsense edits and it works fine again.
01) How would I make my UILabel's height grow/shrink to match its contained text?
Just set top, left and right-constraint to the labels superview. Set the property number of lines to 0. Then it will start wrapping text.
02) How would I make my custom view's height grow/shrink to match its contained sub views?
By using interface builder this is much easier to achieve.
My suggestion to you is to start with your constraints in storyboard. You will not need to compile your code to see what the constraints will result in. Also you will get warnings and errors directly in the interface builder.
If you WANT to use programmatic constraints, my suggestion is to start using a framework for it. For example: https://github.com/SnapKit/SnapKit
You can use a trick with constraints to achieve wrap-content. For example :
let maximumWidth = frame / 4 //For example
yourView.widthAnchor.constraint(lessThanOrEqualToConstant: maximumWidth).isActive = true
The "maximumWidth" depends on your UI and your design and you can change it.
Also, you should set "lineBreakMode" in StoryBoard or in code like :
yourBtn.titleLabel?.lineBreakMode = .byCharWrapping //For UIButton or
yourTxt.textContainer.lineBreakMode = .byCharWrapping //For UITextView
Often clean will do a lot of good when code jams for no reason ar all, cmd-shift-k if i remember correctly
I understand there is no direct application of wrap content in iOS just like we have in Android and thats a big problem, I resolved it through manual anchors like this.
create a function with where in you calculate the height of the view using
mainView.contentSize.height
and then set anchors based on the total height to the enclosing view, call this function inside
override func viewWillLayoutSubviews()
And this would work, the viewWillLayoutSubviews() is a lifecycle method and whenever you override you have to do
super.viewWillLayoutSubviews()
This worked in my case, might work with yours too, if there is a better approach please do comment.

How do I retrive the value of the constant of a constraint in Swift?

I have a NSLayoutConstraint constraint that looks like this:
var myConstant:CGFloat = 20
var myConstraint = NSLayoutConstraint (item: image,
attribute: NSLayoutAttribute.Width,
relatedBy: NSLayoutRelation.Equal,
toItem: nil,
attribute: NSLayoutAttribute.NotAnAttribute,
multiplier: 1,
constant: myConstant)
self.view.addConstraint(myConstraint)
When an UIButton is pressed, the image gets resized, therefore the constant of the image changes.
self.image.transform = CGAffineTransformMakeScale(0.8, 0.8)
Though, when I print the value of the constant after the transformation finished, the constant stays the same as it was before:
println(constraintImageCharacterHomeLeading.constant) // 20
Why is that happening? I resized the image, hence the constraint must vary.
Transforms and Auto Layout don't work well together. If you set the transform of a view, it is expected behavior for it to NOT update the constraint.
myConstraint.constant
It will do the job for you.

Resources