IOS SafeAreaLayoutGuide anchor for landscape screen - ios

I've been following Paul Hudsons' Hacking with Swift tutorials and I'm up to project 6 where he uses layout constraint programmatically. I've been doing this kind of task solely using Interface Builder, but I'm keen to learn on how to do it programmatically.
From his tutorial, we have the following code that add 5 UILabels to the main controller's view.
let label1 = UILabel()
label1.translatesAutoresizingMaskIntoConstraints = false
label1.text = "THESE"
label1.backgroundColor = #colorLiteral(red: 0.5725490451, green: 0, blue: 0.2313725501, alpha: 1)
label1.sizeToFit()
// do the same with label2, label3, label4, label5
view.addSubview(label1)
view.addSubview(label2)
view.addSubview(label3)
view.addSubview(label4)
view.addSubview(label5)
and then I can add constraints manually:
let dictionary = [
"label1": label1,
"label2": label2,
"label3": label3,
"label4": label4,
"label5": label5
]
let metrics=[ "labelHeight":80]
view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|[label1(labelHeight#999)]-[label2(label1)]-[label3(label1)]-[label4(label1)]-[label5(label1)]-(>=10)-|", options: [], metrics: metrics, views: dictionary))
for label in dictionary.keys {
view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|[\(label)]|", options: [], metrics:nil, views: dictionary))
}
as you can see, I'm setting the first label's height to be 80. Then set label1 to have priority of 999, and make the remaining labels to follow label1's height constraint.
This is working fine, both in portrait and landscape mode.
Now i'm converting the code to use anchor.
let heightConstraint = label1.heightAnchor.constraint(equalToConstant: 88)
heightConstraint.priority = UILayoutPriority(rawValue: 999)
heightConstraint.isActive = true
for label in [label2, label3, label4, label5] {
label.heightAnchor.constraint(equalTo: label1.heightAnchor, multiplier: 1).isActive = true
}
var previousLabel : UILabel?
for label in [label1, label2, label3, label4, label5] {
label.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 0).isActive = true
label.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: 0).isActive = true
if let previousLabel = previousLabel {
label.topAnchor.constraint(equalTo: previousLabel.bottomAnchor, constant: 10).isActive = true
} else {
label.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 0).isActive = true
}
previousLabel = label
}
label5.bottomAnchor.constraint(greaterThanOrEqualTo: view.safeAreaLayoutGuide.bottomAnchor, constant: 10.0).isActive = true
I think I'm missing something here, because
when the app is in portrait mode, it is trying to fill the entire screen
when the app is in landscape mode, label5 is chopped off.
I think i'm missing something here when using anchor? I'm guessing it is this bit:
-(>=10)-
But i'm not sure how to do it with anchor mode. Any assistance would be greatly appreciated!

For label5 you should change a bottom constraint to:
label5.bottomAnchor.constraint(lessThanOrEqualTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -10.0).isActive = true
It should be a negative number since you fix it to the anchor, which is below (in contrast to previous anchors where you fix it to the label above your current one). And for negative numbers you need to use lessThanOrEqualTo.
For the vertical layout it works fine too since constraint is lessThanOrEqualTo:

This question is pretty old but I am also following too Paul Hudsons' Hacking with Swift tutorials and I have difficulties in Project 6 about same issue. But I figured out and it might be helpful to any other person.
For trailing and bottom constraints, value should be negative both view.safeAreaLayoutGuide.bottomAnchor and view.trailingAnchor. As far as I understand, dimension is determined always to rightward and downward.
label5.bottomAnchor.constraint(greaterThanOrEqualTo: view.safeAreaLayoutGuide.bottomAnchor, constant: 10.0).isActive = true
With this code, chopping off is pretty normal because by entering a positive value (+10.0), you already initialize the contsraint in the way that your label5's bottomAnchor will be under the safeAreaLayoutGuide bottomAnchor's by 10 units.
So, why does label5 seem in the view, entirely? Because label1 is attached to safeAreaLayoutGuide, your labels have determined vertical space between each other. Probably when you run the code, XCode will show some error and ignore some constraint. You may try to not determine a specific height. You can use:
label1.heightAnchor.constraint(greaterThanOrEqualToConstant: 80).isActive = true
And for the safeAreaLayoutGuide:
label5.bottomAnchor.constraint(lessThanOrEqualTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -10.0).isActive = true

Related

Setting a bottom constraint to a UITextView if needed

I'm trying to fix an issue with a UItextview which I placed at the bottom of a viewcontroller programmatically and sometimes it can clip through the bottom of the view if I don't set a constraint like so.
https://i.stack.imgur.com/YfyPi.png
Whenever I try to constraint the textview to the bottom of the safe area, the text needlessly expands too much if there's less text.
https://i.stack.imgur.com/8w1v2.png
Here's the relevant code snippets from the textview and the constraints respectively:
private let summaryTextView: UITextView = {
let summaryTextView = UITextView()
summaryTextView.textColor = .label
summaryTextView.backgroundColor = .customWhite
summaryTextView.textAlignment = .center
summaryTextView.font = UIFont.systemFont(ofSize: 24)
summaryTextView.clipsToBounds = true
summaryTextView.layer.cornerRadius = 20
summaryTextView.layer.masksToBounds = true
summaryTextView.isSelectable = false
summaryTextView.isEditable = false
summaryTextView.isScrollEnabled = false
summaryTextView.translatesAutoresizingMaskIntoConstraints = false
return summaryTextView
}()
private func setupConstraints() {
NSLayoutConstraint.activate([
imageContainerView.topAnchor.constraint(equalTo: view.topAnchor, constant: 120),
imageContainerView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 10),
imageContainerView.heightAnchor.constraint(equalToConstant: 280),
imageContainerView.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.4),
summaryTextView.topAnchor.constraint(equalTo: imageContainerView.bottomAnchor,constant: 15),
summaryTextView.leadingAnchor.constraint(equalTo: view.leadingAnchor,constant: 10),
summaryTextView.trailingAnchor.constraint(equalTo: view.trailingAnchor,constant: -10),
summaryTextView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor)
])
backgroundImage.fillSuperView(to: view)
bookCover.fillSuperView(to: imageContainerView)
}
Any help would be appreciated!
You need to set the height constraint for UITextView(). Because you are giving fixed top anchor and and bottom anchor so it stretches the textview.

Set constraint on label from below collection view programmatically

Set constraint lbl_Title from bottom to collectionView.
On setting the bottom constraint 60, the label goes below the collection view, after setting it to -60 then it's adjusted to location.
How to set constraints based on collection?
func setCollectionViewConstraints() -> Void {
collectionView.translatesAutoresizingMaskIntoConstraints = false
collectionView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: 10).isActive = true
collectionView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 0).isActive = true
collectionView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: 0).isActive = true
collectionView.heightAnchor.constraint(greaterThanOrEqualToConstant: 60).isActive = true
}
func setRecentJobLabelConstraints() -> Void {
lbl_Title.translatesAutoresizingMaskIntoConstraints = false
lbl_Title.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -60).isActive = true
lbl_Title.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 20).isActive = true
lbl_Title.heightAnchor.constraint(greaterThanOrEqualToConstant: 20).isActive = true
}
Here the issue is fixed if the constraint is set to -60, I think it's the wrong way.
Setting -60 is the right way. The coordinate system for CocoaTouch is a bit strange because it's (0,0) is in the top-left corner of the device, compared to the coordinated in Cocoa which starts from bottom-left. You'll get used to this once you do more auto-layout programmatically.
Note: Also, you need to give negative values when trying to constraint sub-views to super-views from right.
Different Approach: Another approach would be to constraint the super-view to the sub-view this way it's more readable and self-explanatory. Constraint the bottomAnchor of super-view to sub-view's bottomAnchor with a padding of 60 points.
bottomAnchor.constraint(equalTo: lbl_Title.bottomAnchor, constant: 60).isActive = true
It is not a wrong way , calling the constant while using bottom & trailing constraints should be with a minus value , you can use the below extension i created rather than repeating the same autolayout lines over & over
// MARK: - Anchors Method
extension UIView {
func anchors (top:NSLayoutYAxisAnchor? , leading:NSLayoutXAxisAnchor? , bottom : NSLayoutYAxisAnchor? , trailing: NSLayoutXAxisAnchor? , padding : UIEdgeInsets = .zero){
translatesAutoresizingMaskIntoConstraints = false
if let top = top {
topAnchor.constraint(equalTo: top , constant: padding.top).isActive = true
}
if let leading = leading {
leadingAnchor.constraint(equalTo: leading , constant: padding.left).isActive = true
}
if let bottom = bottom {
bottomAnchor.constraint(equalTo: bottom , constant: -padding.bottom).isActive = true
}
if let trailing = trailing {
trailingAnchor.constraint(equalTo: trailing , constant: -padding.right).isActive = true
}
}
}
and call it like below this :
YourUIView.anchors(top: View.topAnchor , leading: View.leadingAnchor , bottom: View.bottomAnchor , trailing: View.trailingAnchor , padding: .init(top: 10, left: 10, bottom: 10, right: 10))
A quick advice , there is no need to assign a void as a return since the function is not returning something .

Programmatic constraints cut the bottom of my label off

I have a Nib file with a root UITableViewCell and child UILabel that I anchor at run time using programmatic constraints
lblAccountItemTitle.translatesAutoresizingMaskIntoConstraints = false
lblAccountItemTitle.topAnchor.constraint(lessThanOrEqualTo: self.topAnchor, constant: 16).isActive = true
lblAccountItemTitle.bottomAnchor.constraint(lessThanOrEqualTo: self.bottomAnchor, constant: -16).isActive = true
lblAccountItemTitle.leadingAnchor.constraint(equalTo: imgAccountItemLeft.trailingAnchor, constant: 16).isActive = true
lblAccountItemTitle.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -32).isActive = true
lblAccountItemTitle.centerYAnchor.constraint(equalTo: self.centerYAnchor).isActive = true
Also, I've noticed that the shorter I make my bottom anchor, the less clipped the text is
How can I get rid of the clipping while still maintaining the equal 16 vertical padding?
change the both image and label bottom anchor from equalTo to lessThanOrEqualTo
lblAccountItemTitle.bottomAnchor.constraint(lessThanOrEqualTo: self.bottomAnchor, constant: -16).isActive = true
I was programmatically pinning the label to the cell and not to the Content View
self.topAnchor
should've been
self.contentView.topAnchor

How to programmatically place two labels in center side by side with spacing using auto layout in ios swift?

I am very new to swift and IOS and I want to place two labels side by side horizontally center programmatically. Below is my code
let secondLabel : UILabel = {
let label = UILabel()
label.text = "1234"
label.textAlignment = .center
label.translatesAutoresizingMaskIntoConstraints = false
label.backgroundColor = UIColor.blue
return label
}()
let thirdLabel : UILabel = {
let label = UILabel()
label.text = "5678"
label.textAlignment = .center
label.translatesAutoresizingMaskIntoConstraints = false
label.backgroundColor = UIColor.red
return label
}()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
setupLayout()
}
private func setupLayout(){
view.addSubview(secondLabel)
//secondLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
secondLabel.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
secondLabel.widthAnchor.constraint(equalToConstant: 100).isActive = true
secondLabel.heightAnchor.constraint(equalToConstant: 100).isActive = true
view.addSubview(thirdLabel)
//thirdLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
thirdLabel.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
thirdLabel.widthAnchor.constraint(equalToConstant: 100).isActive = true
thirdLabel.heightAnchor.constraint(equalToConstant: 100).isActive = true
self.view.addConstraint(NSLayoutConstraint(
item: thirdLabel,
attribute: .left,
relatedBy: .equal,
toItem: secondLabel,
attribute: .right,
multiplier: 1.0,
constant: 10
))
}
But this is how it looks now
Please help me to move the labels to the center
You can use UIStackView to solve your problem. Please update the following code in your setupLayout method.
private func setupLayout(){
let stackview = UIStackView()
stackview.axis = .horizontal
stackview.spacing = 10
stackview.translatesAutoresizingMaskIntoConstraints = false
stackview.addArrangedSubview(secondLabel)
secondLabel.widthAnchor.constraint(equalToConstant: 100).isActive = true
secondLabel.heightAnchor.constraint(equalToConstant: 100).isActive = true
stackview.addArrangedSubview(thirdLabel)
thirdLabel.widthAnchor.constraint(equalToConstant: 100).isActive = true
thirdLabel.heightAnchor.constraint(equalToConstant: 100).isActive = true
self.view.addSubview(stackview)
stackview.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
stackview.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
}
Output:-
Well UIStackView will be the best option and #manikandan has answered properly.
But if you are not a big fan of UIStackView, you can achieve it using UIView too. A bit more lines of code and it will work.
private func setupLayout() {
let sampleBackgroundView = UIView()
sampleBackgroundView.translatesAutoresizingMaskIntoConstraints = false
sampleBackgroundView.addSubview(secondLabel)
secondLabel.topAnchor.constraint(equalTo: secondLabel.superview!.topAnchor).isActive = true
secondLabel.bottomAnchor.constraint(equalTo: secondLabel.superview!.bottomAnchor).isActive = true
secondLabel.leadingAnchor.constraint(equalTo: secondLabel.superview!.leadingAnchor, constant: 10).isActive = true
secondLabel.widthAnchor.constraint(equalToConstant: 100).isActive = true
secondLabel.heightAnchor.constraint(equalToConstant: 100).isActive = true
sampleBackgroundView.addSubview(thirdLabel)
thirdLabel.topAnchor.constraint(equalTo: secondLabel.superview!.topAnchor).isActive = true
thirdLabel.bottomAnchor.constraint(equalTo: secondLabel.superview!.bottomAnchor).isActive = true
thirdLabel.trailingAnchor.constraint(equalTo: secondLabel.superview!.trailingAnchor, constant: 10).isActive = true
thirdLabel.widthAnchor.constraint(equalToConstant: 100).isActive = true
thirdLabel.heightAnchor.constraint(equalToConstant: 100).isActive = true
sampleBackgroundView.addConstraint(NSLayoutConstraint(
item: thirdLabel,
attribute: .left,
relatedBy: .equal,
toItem: secondLabel,
attribute: .right,
multiplier: 1.0,
constant: 10
))
view.addSubview(sampleBackgroundView)
sampleBackgroundView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
sampleBackgroundView.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
}
Please go through these solutions and you will have a better idea to implement constraints programmatically:
How to add constraints programmatically using Swift
https://theswiftdev.com/2018/06/14/mastering-ios-auto-layout-anchors-programmatically-from-swift/
https://theswiftdev.com/2017/10/31/ios-auto-layout-tutorial-programmatically/
While a UIStackView is one way to go (and easier many times), there are also times that you want to do what you are trying. Truth is, you're almost there.
private func setupLayout(){
view.addSubview(secondLabel)
secondLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor, constant: -55).isActive = true
secondLabel.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
secondLabel.widthAnchor.constraint(equalToConstant: 100).isActive = true
secondLabel.heightAnchor.constraint(equalToConstant: 100).isActive = true
view.addSubview(thirdLabel)
thirdLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor, constant: 55).isActive = true
thirdLabel.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
thirdLabel.widthAnchor.constraint(equalToConstant: 100).isActive = true
thirdLabel.heightAnchor.constraint(equalToConstant: 100).isActive = true
}
Basically, it looks like you have two 100x100 UILabels that you want 10 points apart. So:
Get rid of that last constraint.
Comment back in those two centerXAnchor constraints. This will move both labels on top of each other, centered horizontally.
Now you just need to add a constant property to each constraint. A negative value move to the left and a positive value moves to the right. Since your widths are 100, a value of 50should make the labels be right next to each. For 10 points in between them, make it 55.
There're two options for you to work on that (or maybe more).
Create Additional containerView(UIView) to addSubView your 2 labels. Then centerYAnchor that containerView to your baseView.
Using UIStackView. Add those 2 labels into your Horizontal StackView and centerYAnchor to your baseView
Simple explanation hopes this helps.
Using a container view (either UIView, or UIStackView) might be the best approach, as it would allow you to easier build other UI components around the group of two labels. But if you don't need this flexibility, or want to avoid using an extra view, you can achieve the desired layout quite nicely via layout anchors:
NSLayoutConstraint.activate([
// set the size
secondLabel.widthAnchor.constraint(equalToConstant: 100),
secondLabel.heightAnchor.constraint(equalToConstant: 100),
// center vertically in the parent view
secondLabel.centerYAnchor.constraint(equalTo: view.centerYAnchor),
// align to the left of the parent view center
secondLabel.rightAnchor.constraint(equalTo: view.centerXAnchor, constant: -5),
// set the size
thirdLabel.widthAnchor.constraint(equalToConstant: 100),
thirdLabel.heightAnchor.constraint(equalToConstant: 100),
// center vertically in the parent view
thirdLabel.centerYAnchor.constraint(equalTo: view.centerYAnchor),
// align to the right of the parent view center
thirdLabel.leftAnchor.constraint(equalTo: view.centerXAnchor, constant: 5)
])
The spacing between the two is automatically achieved via the offsets from the X center.
As a general note, Apple recommends activating constraints in batches, by using activate(), for performance reasons:
The effect of this method is the same as setting the isActive property of each constraint to true. Typically, using this method is more efficient than activating each constraint individually.

UIStackView - Three arranged subviews with relative widths

I have an outer UIStackView, call it outerStackView with the following properties:
outerStackView.axis = .Horizontal
outerStackView.distribution = .Fill
outerStackView.spacing = 10
The outerStackView has three arranged subviews (think of them as 3 columns) which also happen to be UIStackViews.
I want to set the width of the three arranged subviews (columns) to be a relative multiplier of the outerStackView's width.
So I tried doing this:
leftColumnStackView = UIStackView()
middleColumnStackView = UIStackView()
rightColumnStackView = UIStackView()
outerStackView.addArrangedSubview(leftColumnStackView)
outerStackView.addArrangedSubview(middleColumnStackView)
outerStackView.addArrangedSubview(rightColumnStackView)
leftColumnStackView.widthAnchor.constraintEqualToAnchor(outerStackView.widthAnchor, multiplier: 0.4, constant: 0).active = true
middleColumnStackView.widthAnchor.constraintEqualToAnchor(outerStackView.widthAnchor, multiplier: 0.4, constant: 0).active = true
rightColumnStackView.widthAnchor.constraintEqualToAnchor(outerStackView.widthAnchor, multiplier: 0.2, constant: 0).active = true
This throws a bunch of autolayout errors.
How would I make these three column UIStackViews have a relative width to their parent view?
I solved this by giving the left and middle columns relative widths (taking into consideration the spacing between them), and the right column simply filled in the space.
leftColumnStackView.widthAnchor.constraintEqualToAnchor(outerStackView.widthAnchor, multiplier: 0.38, constant: -20).active = true
middleColumnStackView.widthAnchor.constraintEqualToAnchor(outerStackView.widthAnchor, multiplier: 0.38, constant: -20).active = true

Resources