NSLayoutConstraint defined in code and bottom Layout Guide deprecated in iOS 11 - ios

I have a viewController holding a constraint like this one:
self.subviewConstraint = NSLayoutConstraint(item: self.subviewConstraint,
attribute: .bottom,
relatedBy: .equal,
toItem: self.bottomLayoutGuide,
attribute: .bottom,
multiplier: 1,
constant: 0)
and I'm being shown this warning:
'bottomLayoutGuide' was deprecated in iOS 11.0: Use view.safeAreaLayoutGuide.bottomAnchor instead of bottomLayoutGuide.topAnchor
I'm only finding examples for setting anchors instead of NSLayoutConstraints like this, and I don't fully understand this warning... "use view.safeAreaLayoutGuide.bottomAnchor instead of bottomLayoutGuide.topAnchor"? How is that the bottom anchor of the safeAreaLayoutGuide matches the top anchor of the of the bottomLayoutGuide? Where could I find a good graphical explanation of this?
How should I correctly rewrite my constraint to keep its current behaviour?

TopLayoutGuide and bottomLayoutGuide are deprecated since iOS 11.
You could update your code with this, taking advantage of the new NSLayoutAnchor:
self.subviewConstraint = self.subviewConstraint?.firstItem?.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor)
or you can use the initializer of NSLayoutConstraint as in your question:
self.subviewConstraint = NSLayoutConstraint(item: self.subviewConstraint?.firstItem as Any,
attribute: .bottom,
relatedBy: .equal,
toItem: view.safeAreaLayoutGuide,
attribute: .bottom,
multiplier: 1,
constant: 0)
Note that I've changed your self.subviewConstraint parameter to self.subviewConstraint.firstItem instead, within the NSLayoutConstraint initialization method, because you were using the NSLayoutConstraint as the item.
I suppose this is some kind of typo you made.
Here you can find some good graphical explanation of the new SafeArea behaviour in iOS 11 and above:
iOS Safe Area - Medium.com
Additionally, you said "I'm only finding examples for setting anchors instead of NSLayoutConstraints", but I want to make it clear that the constraint(equalTo:) method of NSLayoutAnchor returns an NSLayoutConstraint. (Apple NSLayoutAnchor documentation)

Related

Is there a difference between the constraints instantiated in these two ways?

I'm rather new to iOS programming.
I was wondering if there is any functional difference between making a constraint like this
cell_image_view.heightAnchor.constraint(equalToConstant: 60).isActive = true
As opposed to doing it this way
var my_constraint = NSLayoutConstraint(item: cell_image_view, attribute: NSLayoutConstraint.Attribute.height, relatedBy: NSLayoutConstraint.Relation.equal, toItem: nil, attribute: NSLayoutConstraint.Attribute.notAnAttribute, multiplier: 1, constant: 60)
cell_image_view.addConstraint(my_constraint)
Anyone have any insights into this problem? Thanks.
heightAnchor is a UIView property that returns an NSLayoutDimension anchor, which is a subclass of NSLayoutAnchor.
If you search in the Xcode docs under, you'll find the following:
NSLayoutAnchor: A factory class for creating layout constraint objects
using a fluent API.
Layout anchors are "Syntactic sugar" for creating NSLayoutConstraint objects. Assuming you have them set up to create the same constraints, the results are the same.

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

Swift4 issues : change Nslayoutconstraint programmatically

I would like to change position of UITextfield txtAmount NSLayoutConstraint programmatically from its top to bottom of collection view to bottom of image view. All views are embed in a ui view.
Old constraint is dragged and mapped from storyboard to view controller.
New constraint constr is to be created programmatically.
When it comes to implementation and execution, it says
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.
'Unable to install constraint on view. Does the constraint reference something from outside the subtree of the view? That's illegal.
Would you please tell me any guidelines for such modification ? I embed the UI elements in an embedded UIView because of scrollview I have used.
let constr = NSLayoutConstraint(item: txtAmount, attribute: .top, relatedBy: .equal, toItem: imageView, attribute: .bottom , multiplier: 1, constant: 0)
IETypeList.removeFromSuperview()
uiviewType.removeFromSuperview()
txtAmount.addConstraint(constr)
txtAmount.removeConstraint(constraintPo)
you are adding constraint to the txtAmount whereas you should've added it to the view which actually contains txtAmount and another view referenced in this constraint, i.e. imageView. Let's name this view which contains them superview.
let constr = NSLayoutConstraint(item: txtAmount, attribute: .top, relatedBy: .equal, toItem: imageView, attribute: .bottom , multiplier: 1, constant: 0)
IETypeList.removeFromSuperview()
uiviewType.removeFromSuperview()
txtAmount.removeConstraint(constraintPo)
superview.addConstraint(constr)
But this is not recommended way since iOS 8. As the docs say, you should set the constraint isActive (read this) property to true instead, and iOS will add them to relevant views:
IETypeList.removeFromSuperview()
uiviewType.removeFromSuperview()
constraintPo.isActive = false
constr.isActive = true

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.

Size classes iOS 7 compatibility issue

I am trying to use auto layout to to position two views like below picture. I know that with iOS 8 and size classes I can create layout for different layouts and it would work. However I am targeting iOS 7 and according to several posts such as https://stackoverflow.com/a/26841899/4170419, iPhone landscape mode of size classes will not work for earlier version. So, how can I position those two views on different orientation according to my picture? Thanks.
Compact width compact height size class will not work on iOS7. It will clash with compact width any height sadly.
Very good summary here: How can Xcode 6 adaptive UIs be backwards-compatible with iOS 7 and iOS 6?
For you particular case, I think you might have to resort to some manual work for iOS7. A possible approach would be:
1) Add a UIView between the label area and the right hand side pinned to the top, bottom, label area leading and trailing to superview with size 0. With no content this should end up 0 width.
2) Add a UIView between the label area and the bottom, pinned top to the label area, leading and trailing to the superview and to bottom with size 0. With no content this should end up 0 height.
3) Add IBOutlets for both views into your controller .h file.
4) In viewDidLoad, create and place the MKMapView into the appropriate view for the current orientation.
This gives you on startup the map view in the location you want.
5) Add orientation change detection to your controller. When the orientation changes, remove the MKMapView from its current view and add it to the view for the new orientation.
Not sure this will work, but it might give you what you want on iOS8 and iOS7 with one piece of code.
You may need to add constraints to the views for width/height or set the MKMapView frame to get the dimensions right for each orientation when layout occurs.
Hopefully somebody may come up with an easier solution, but seems size classes are unlikely to help in this case.
Finally I solved it :) First of all, both of my orientation show that mapview must be aligned to bottom and right of main view. These constraints are fixed. Then just to shut Xcode I set the constraints for portrait orientation.
Since iPad has enough screen state, I applied rotation fix to only iPhone. I am sure it can be written better way so if you have any comments please share. Thanks.
lazy var landscapeLeft: NSLayoutConstraint = {
return NSLayoutConstraint(item: self.myMap, attribute: .Leading, relatedBy: .Equal, toItem: self.idLabel, attribute: .CenterX, multiplier: 1.0, constant: 0)
}()
lazy var landscapeTop: NSLayoutConstraint = {
return NSLayoutConstraint(item: self.myMap, attribute: .Top, relatedBy: .Equal, toItem: self.idLabel, attribute: .Bottom, multiplier: 1.0, constant: 8)
}()
lazy var portraitLeft: NSLayoutConstraint = {
return NSLayoutConstraint(item: self.myMap, attribute: .Leading, relatedBy: .Equal, toItem: self.view, attribute: .Leading, multiplier: 1.0, constant: 8)
}()
lazy var portraitBottom: NSLayoutConstraint = {
return NSLayoutConstraint(item: self.myMap, attribute: .Top, relatedBy: .Equal, toItem: self.expectedDateLabel, attribute: .Bottom, multiplier: 1.0, constant: 8)
}()
func fixLayout() {
if (UIDevice.currentDevice().userInterfaceIdiom == UIUserInterfaceIdiom.Phone) && (UIDevice.currentDevice().orientation.isLandscape) {
self.view.removeConstraint(portraitLeft)
self.view.removeConstraint(portraitBottom)
self.view.addConstraint(landscapeLeft)
self.view.addConstraint(landscapeTop)
} else {
self.view.removeConstraint(landscapeLeft)
self.view.removeConstraint(landscapeTop)
self.view.addConstraint(portraitLeft)
self.view.addConstraint(portraitBottom)
}
}
override func updateViewConstraints() {
super.updateViewConstraints()
fixLayout()
}
UPDATE: I was so naive to think that the code I wrote before was compatible with iOS 7 just because it compiled to ios 7 target and worked on iOS 8 simulator. Thanks to #Rory McKinnel, I found out that I was using methods which are not available on iOS 7. This time, I tested on both iOS 7 and iOS 8 simulators and they worked. I hope that is the end of this problem.

Resources