StackView constraints with UIViews - ios

I'm trying to setup a stackview in the middle of the screen with a padding of 20 to the left an right. Inside, I want to place two custom UIViews, but I don't quit understand how to do it. I tried giving the UIViews their respective Height's and Width's but I got nothing.
I believe the stack view has all the correct constraints. Here's the code:
func setupTeamViews() {
view.addSubview(teamsStackView)
teamsStackView.distribution = .fill
teamsStackView.axis = .vertical
teamsStackView.spacing = 20
teamsStackView.alignment = .fill
NSLayoutConstraint.activate([
teamsStackView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
teamsStackView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
teamsStackView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
teamsStackView.heightAnchor.constraint(equalToConstant: 420)
])
let height = CGFloat((teamsStackView.frame.height / 2) - 20)
let width = CGFloat((teamsStackView.frame.width - 20))
firstTeamView = ATTeamView(width: width, height: height)
firstTeamView.changeColor(color: .lightBlue)
firstTeamView.setTeamName(name: "Tobias")
firstTeamView.setNewPoints(points: "0")
secondTeamView = ATTeamView(width: width, height: height)
secondTeamView.changeColor(color: .white)
secondTeamView.setTeamName(name: "Valen")
secondTeamView.setNewPoints(points: "0")
teamsStackView.addArrangedSubview(firstTeamView)
teamsStackView.addArrangedSubview(secondTeamView)
}
How does a stackview work with UIview's? As far I understand, UIViews don't have intrinsicContentSize, but I don't know how to deal with that.

In stead of doing it programmatically you can add two container views attach them to your view controller, and then active them with the alpha.
This will make constraints easier and you can still program anything extra you would like
myView1.alpha = 1 // activate
myView2.alpha = 0 // deactivate

My code wasn't working because I've forgotten to set teamsStackView.translatesAutoresizingMaskIntoConstraints = false

Related

How can I get UIView's (x, y) when using AutoLayout if initial frame was misconfigured?

Perhaps it is a misuse of a UIView's frame property by my setup code, for auto laid-out views, wherein, because I assume the view's initial frame property values won't matter once AutoLayout constraints are added, that I can use the frame.origin to cache values that aren't really the view's origin in coordinate space.
What I mean by "misusing" the frame property is: I am setting the frame.origin to values to be parameter values in my constraint setup function calls, because I like like the succinctness and convenience for tweaking constraint offsets by the CGRect, in my code. But perhaps it's backfiring? Not sure why though...
For example:
let view = UIView(frame: CGRect(x: 10, y: 10, width: 100, height: 100))
NSLayoutConstraint.activate([
view.leadingAnchor.constraint(equalTo: leftSideView.trailingAnchor,
constant: view.frame.origin.x)
.
.
.
])
Initially it seems fine, views are placed as expected on the screen, but after views have been laid-out by AutoLayout, the frame.origin seems to reflect the original misappropriation by me... E.g. after AutoLayout frame.origin is still (10, 10)... anyway after AutoLayout, frame.origin are not the correct absolute values in the coordinate system one generally refers to the frame property to get.
After I do this:
view.setNeedLayout()
view.layoutIfNeeded()
When I print the frame.origin values, they aren't the view's actual position.
Why wouldn't they be?
So, how can I find the right values?
Perhaps I need to call one of UIView's superview coordinate translation functions?
UPDATE: The view that is not having its origin set is actually the leftSideView, which I configured using the same approach as the view I'm constraining. I did run setNeedsLayout()/layout, but now I'm thinking that AutoLayout doesn't have enough data yet to calculate the frame so I have a chicken-egg problem where I'm trying to add constraints based on constraints that are unresolved yet, which won't work.
You are asking the wrong view to layout. There is nothing much for view to layout, other than its own size. It doesn't know how to lay itself out relative to leftSideView, because leftSideView is not one of its subviews.
To layout view relative to leftSideView, find their common parent, and ask it to layout. Suppose view.superview is the common parent, do:
view.superview!.layoutIfNeeded()
Then view.frame will be the values you'd expect.
Alternatively, you can override viewDidLayoutSubviews in UIViewController or layoutSubviews in the common parent of the views you want to layout, to detect when layout has finished, without calling layoutIfNeeded. In there, frame will have the correct values too.
Minimal example:
class MyViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let v = UIView(frame: .zero)
view.addSubview(v)
v.backgroundColor = .blue
v.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
v.topAnchor.constraint(equalTo: view.topAnchor, constant: 10),
v.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 30),
v.widthAnchor.constraint(equalToConstant: 100),
v.heightAnchor.constraint(equalToConstant: 100)
])
let u = UIView(frame: .init(x: 10, y: 10, width: 100, height: 100))
view.addSubview(u)
u.backgroundColor = .red
u.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
u.topAnchor.constraint(equalTo: v.bottomAnchor, constant: 10),
u.leftAnchor.constraint(equalTo: v.leftAnchor),
u.widthAnchor.constraint(equalToConstant: 100),
u.heightAnchor.constraint(equalToConstant: 100)
])
// before any layout
print(u.frame)
// laying out u, doesn't change anything
u.layoutIfNeeded()
// note that the above could change the frame of u depending on the frame
// of u and its size constraints, but I've deliberately chosen these
// values and constraints so that it matches your behaviour
print(u.frame)
// laying out u, and v, making u.frame what you expect
view.layoutIfNeeded() // view is the common parent between u and v
print(u.frame)
}
}

iOS UiView layout issue

My code is as follows:
let payBackView = UIView(frame: CGRect.sizeMake(x: 10, y: UIScreen.main.bounds.size.height - 86, width: 369, height: 52))
self.view.bringSubviewToFront(payBackView)
payBackView.layer.cornerRadius = 21
payBackView.layer.masksToBounds = true
payBackView.backgroundColor = .white
self.view.addSubview(payBackView)
iOS simulator iPhone 12Pro is what I want, but simulator iphone11 pro Max is abnormal.I want to know why and what should I do ?enter image description hereenter image description here
You really should be taking advantage of auto-layout / constraints.
Take a look at this:
let payBackView = UIView()
payBackView.layer.cornerRadius = 21
payBackView.layer.masksToBounds = true
payBackView.backgroundColor = .white
// we'll be using auto-layout constraints
payBackView.translatesAutoresizingMaskIntoConstraints = false
// add payBackView to view
self.view.addSubview(payBackView)
// respect safe-area
let g = self.view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
// we want 10-pts on each side
payBackView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 10.0),
payBackView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -10.0),
// aligned to the bottom
payBackView.bottomAnchor.constraint(equalTo: g.bottomAnchor),
// Height: 52-pts
payBackView.heightAnchor.constraint(equalToConstant: 52.0),
])
You are using a fixed width for payBackView and This is why your view on a bigger screen such as 11 pro max will be smaller. Try this:
let payBackView = UIView(frame: CGRect.sizeMake(x: 10, y: UIScreen.main.bounds.size.height - 86, width: UIScreen.main.bounds.size.width - 40, height: 52))
UIScreen is a bad idea some iPhone has a bezel and some don't. To make sure all your views stay at the visible "zone" you have to use constraints and relate them to "Safe Area". "Safe Area" is where all kinds of iPhones with or without a bezel acts the same.
You may lose the bezel part in your design.

ios correct way to use constraintLessThanOrEqualToSystemSpacingAfter for trailingAnchor

I'd like to programmatically layout a UILabel that should fit the width of the screen, with the system spacing as left and right insets. Here's my code:
statusLabel.font = UIFont.systemFont(ofSize: UIFont.smallSystemFontSize)
statusLabel.numberOfLines = 0
statusLabel.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(statusLabel)
statusLabel.topAnchor.constraint(equalTo: otherView.bottomAnchor, constant: 0),
statusLabel.leadingAnchor.constraintEqualToSystemSpacingAfter(view.safeAreaLayoutGuide.leadingAnchor, multiplier: 1),
statusLabel.trailingAnchor.constraintLessThanOrEqualToSystemSpacingAfter(view.safeAreaLayoutGuide.trailingAnchor, multiplier: 1),
statusLabel.bottomAnchor.constraint(equalTo: someOtherView.topAnchor, constant: 0)
Here is the result:
the label is laid out using the system spacing as the left inset, as intended, but its trailingAnchor seems to be equal to the superview's trailingAnchor rather than adding a system spacing between the two.
I've tried using constraintEqualToSystemSpacingAfter and constraintGreaterThanOrEqualToSystemSpacingAfter but got the same results.
Any ideas on how to get the system spacing between the label and its superview's trailing anchors?
Reverse the order Like this
view.safeAreaLayoutGuide.trailingAnchor.constraintEqualToSystemSpacingAfter(statusLabel.trailingAnchor, multiplier: 1).isActive = true
view first & statusLabel next.

Using NSLayoutAnchor to create constraints between two labels?

I'm creating a view programmatically and need to set constraints between two labels. I recently just discovered NSLayoutAnchor and feel it would be a good choice to use it but I'm unsure how to create constraints between two different things (ie labels, imageViews, etc). I know a general setup will look something like this:
let codedLabel:UILabel = UILabel()
codedLabel.frame = CGRect(x: 100, y: 100, width: 200, height: 200)
codedLabel.textAlignment = .center
codedLabel.text = alertText
codedLabel.numberOfLines=1
codedLabel.textColor=UIColor.red
codedLabel.font=UIFont.systemFont(ofSize: 22)
codedLabel.backgroundColor=UIColor.lightGray
self.contentView.addSubview(codedLabel)
codedLabel.translatesAutoresizingMaskIntoConstraints = false
codedLabel.heightAnchor.constraint(equalToConstant: 200).isActive = true
codedLabel.widthAnchor.constraint(equalToConstant: 200).isActive = true
codedLabel.centerXAnchor.constraint(equalTo: codedLabel.superview!.centerXAnchor).isActive = true
codedLabel.centerYAnchor.constraint(equalTo: codedLabel.superview!.centerYAnchor).isActive = true
How would you set up constraints in between two labels?
Let's say you have two UIViews you wish to lay out horizontally in one of two ways:
View #1 is 20 points from the leading edge of the superview's margin or safe area and View #2 is another 20 points from view #1. (Think of a left-justified row of buttons.)
Both views are to be centered, with equal spacing before/between/after each view. (Think of two buttons spaced equally apart and centered.)
For example #1 the code would be:
// #1
let view1 = UIView()
view1.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(view1)
let view2 = UIView()
view2.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(view2)
// #2
let margins = view.layoutMarginsGuide
view.addLayoutGuide(margins)
view1.leadingAnchor.constraint(equalTo: margins.leadingAnchor, constant: 20).isActive = true
// #3
view2.leadingAnchor.constraint(equalTo: view1.trailingAnchor, constant: 20).isActive = true
// #4
view1.widthAnchor.constraint(equalToConstant: 100).isActive = true
view2.widthAnchor.constraint(equalToConstant: 100).isActive = true
Comment #1: Note that you do not need to give each view a frame, but you do need to set the auto-resizing mask to false. This is a mistake many new coders forget.
Comment #2: All UIViewController main views have a layoutMarginGuide that yield standard margins both vertically and horizontally (and in iOS 11, particularly for iPhone X, there is a new safeAreaLayoutGuide for vertical alignment). I've set the leading edge of view1 to be the leading edge of the margin with an additional constant of 20 points from it.
Comment #3: Just like I related view1 to the margin, I'm relating view2 to view1, but this time the leading edge ofview2is 20 points from theview1` trailing edge.
Comment #4: The last thing you need to do for horizontal placement is to give each view a width. In this case I wanted both to be 100 points.
Example #2 pretty much uses the same code as example #1, so I'll note the key differences only:
let view1 = UIView()
view1.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(view1)
let view2 = UIView()
view2.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(view2)
let margins = view.layoutMarginsGuide
view.addLayoutGuide(margins)
// #1
let spacer1 = UILayoutGuide()
view.addLayoutGuide(spacer1)
let spacer2 = UILayoutGuide()
view.addLayoutGuide(spacer2)
let spacer3 = UILayoutGuide()
view.addLayoutGuide(spacer3)
// #2
spacer.leadingAnchor.constraint(equalTo: margins.leadingAnchor).isActive = true
view1.leadingAnchor.constraint(equalTo: spacer1.trailingAnchor).isActive = true
spacer2.leadingAnchor.constraint(equalTo: view1.trailingAnchor).isActive = true
view2.leadingAnchor.constraint(equalTo: spacer2.trailingAnchor).isActive = true
spacer3.leadingAnchor.constraint(equalTo: view2.trailingAnchor).isActive = true
spacer3.trailingAnchor.constraint(equalTo: margins.trailingAnchor).isActive = true
// #3
view1.widthAnchor.constraint(equalToConstant: 100).isActive = true
view2.widthAnchor.constraint(equalToConstant: 100).isActive = true
spacer1.widthAnchor.constraint(equalTo: spacer2.widthAnchor).isActive = true
spacer2.widthAnchor.constraint(equalTo: spacer3.widthAnchor).isActive = true
Comment #1: In iOS 9 Apple introduced the concept of UILayoutGuides. Before this, to create "spacers" you had to actually create an invisible UIView and add it as a subview (with all the overhead associated with it). Layout guides "act" like views but do not have that overhead.
Comment #2: The horizontal sequence if "margin...spacer1...view1...spacer2...view2...spacer3...margin". Note that I'm not using any constants, as I wish to let the layout engine give equal spacing.
Comment #3: While I am giving width values for both views, I am not with the spacers. Instead, I am declaring their widths to be equal.
Please note that I've only worked with horizontal constraints. For auto layout to work, you also need to declare vertical constraints too. Most of the concepts are the same... instead of leading/trailing, centerX, and width anchors you have top/bottom, centerY, and height ones. BUT! Starting with iOS 11 you now have a safeAreaLayoutGuide that you should be using instead of layoutMarginsGuide. This only applies to vertical! That's why I separated things. Here's the code I use to work with vertical alignment:
let layoutGuideTop = UILayoutGuide()
let layoutGuideBottom = UILayoutGuide()
view.addLayoutGuide(layoutGuideTop)
view.addLayoutGuide(layoutGuideBottom)
if #available(iOS 11, *) {
let guide = view.safeAreaLayoutGuide
layoutGuideTop.topAnchor.constraintEqualToSystemSpacingBelow(guide.topAnchor, multiplier: 1.0).isActive = true
layoutGuideBottom.bottomAnchor.constraintEqualToSystemSpacingBelow(guide.bottomAnchor, multiplier: 1.0).isActive = true
} else {
layoutGuideTop.topAnchor.constraint(equalTo: topLayoutGuide.bottomAnchor).isActive = true
layoutGuideBottom.bottomAnchor.constraint(equalTo: bottomLayoutGuide.topAnchor).isActive = true
}
Now you have layoutGuideTop and layoutGuideBottom that should work regardless of what iOS version is running.

Swift 3 UILabel get actual height (intrinsicContentSize)

I'm trying to simulate a kind of "Please wait..." UILabel. The label's text must be regularly updated. So far everything works as expected. However, I need to get the intrinsic content height of the label to be able to position its container view (UIView).
The label is the one with the red background, whereas the one with the white background is its container.
I've tried a few different approaches, unfortunately, all in vain. Any help would be greatly appreciated.
private func createBusyLabel(labelText: String) -> CGFloat {
self.busyViewContainer.addSubview(self.busyLabel)
self.busyLabel.backgroundColor = UIColor.red
self.busyLabel.numberOfLines = 0
self.busyLabel.lineBreakMode = NSLineBreakMode.byWordWrapping
self.busyLabel.sizeToFit()
//set the constraints, but skip height constraints
self.busyLabel.translatesAutoresizingMaskIntoConstraints = false
self.busyLabel.horizontalLeft(toItem: self.busyViewContainer, constant: 60)
self.busyLabel.horizontalRight(toItem: self.busyViewContainer, constant: -10)
self.busyLabel.topConstraints(toItem: self.busyViewContainer, constant: 10)
self.busyLabel.text = labelText
//calculate height with margin
let height: CGFloat = self.busyLabel.intrinsicContentSize.height + 20
return height
}
Also, the line counting function, from a previously asked and already answered question, delivers only 1
Here is what it look like after I set the bottom constraint:
A million Thanks to ozgur, who changed my approach. Ozgur, your code works perfect, but unfortunately not for me, as I faced problems with bottomLayoutGuide part. The reason for this is that the label and its container are created in an external class.
Previously I tried to set bottom constraint to the label, which did not return the expected result. However, inspired by ozgur's answer, this time I simply set the bottom constraint to its container and not the label, giving in expected result, like following:
self.busyViewContainer.bottomConstraints(toItem: self.busyLabel, constant: 10)
Thanks to all who put in their precious efforts.
private func createBusyLabel(labelText: String) -> Void {
self.busyLabel.text = labelText
self.busyLabel.font = UIFont.getGlobalFont(size: _textSizeSmall, type: "bold")
self.busyLabel.backgroundColor = UIColor.red
// handle multiline problem
self.busyLabel.numberOfLines = 0
self.busyLabel.lineBreakMode = NSLineBreakMode.byWordWrapping
self.busyLabel.sizeToFit()
self.busyViewContainer.addSubview(self.busyLabel)
self.busyLabel.translatesAutoresizingMaskIntoConstraints = false
self.busyLabel.horizontalLeft(toItem: self.busyViewContainer, constant: 60)
self.busyLabel.horizontalRight(toItem: self.busyViewContainer, constant: -10)
self.busyLabel.topConstraints(toItem: self.busyViewContainer, constant: 10)
// the following line made the difference
self.busyViewContainer.bottomConstraints(toItem: self.busyLabel, constant: 10)
}
The simple fix is adding the bottom constraint between 'self.busyViewContainer' and its superview.
Following your code and syntax it can be something like this:
self.busyLabel.bottomConstraints(toItem: self.busyViewContainer, constant: 10)
It is a common problem with 'Unsatisfiable Constraints'. The autolayout should ensure it satisfies horizontal axis layout so it needs to have both top and bottom constraints in this case.
Apple doc - Unsatisfiable Constraints
Apple doc - Logical Errors
UPD: In this case, the layout of the superview can define a height with intrinsicContentSize:
var intrinsicContentSize: CGSize { return ... }
Where the height of the parent view will be calculated based on the label one.

Resources