I add a UIView in UIStoryboard and bind it to a custom UIView class called testView,next, I create a UIView called circelView in textView in require init function ,then I crate two UILabel in circelView
this is my step
initialization the circelView
add AutoLayout to circelView
create two UILabel and add them to circelView
add AutoLayout to two UILabel
then I run the app, I can find the subView in current position , But I can not find the two UILabel in screen ,what happen?
this is my code :
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
circelView = UIView()
self.addSubview(circelView)
circelView.snp_makeConstraints(closure: { (make) -> Void in
make.size.equalTo(80)
make.top.equalTo(self.snp_top)
make.right.equalTo(self.snp_right)
})
titleLabel = UILabel()
titleLabel.textColor = colorffffff
titleLabel.backgroundColor = UIColor.yellowColor()
titleLabel.font = font32
titleLabel.text = "hello"
titleLabel.numberOfLines = 1
titleLabel.backgroundColor = UIColor.yellowColor()
titleLabel.textAlignment = NSTextAlignment.Center
circelView.addSubview(titleLabel)
titleLabel.snp_makeConstraints { (make) -> Void in
make.center.equalTo(circelView.snp_center)
}
detailLabel = UILabel()
detailLabel.textColor = colorffffff
detailLabel.font = font24
detailLabel.text = "hello"
titleLabel.numberOfLines = 1
detailLabel.textAlignment = NSTextAlignment.Center
detailLabel.addSubview(titleLabel)
detailLabel.snp_makeConstraints { (make) -> Void in
make.top.equalTo(titleLabel.snp_bottom).offset(3)
make.centerX.equalTo(titleLabel.snp_centerX)
}
}
A few things could be causing this:
How big is your font? If your font is too large, and your view is too small (80x80 in this case), the text may not be rendered.
In Autolayout, it is optimal to have views have sizes based on their content, and not explicit (i.e. 80x80). By having your labels be pinned to their superview on all edges, you can have the size of your 'circelView' be dynamic to its content (the two labels). To maintain the 'circle' aspect of your 'circelView', you can constraint the height of your 'circelView' to be the same as its width.
At the end of the day, this needs more debugging. I advise examining the view hierarchy to see where your labels are. Check out the documentation here.
Your labels have 0 width and height. You don't set their frame anywhere in this code, and the constraints which apply to them relate only to their position, no size can be determined from them. You either need to Specify their frame or add constraints from which it could be determined by layouting engine.
Related
I want to create custom view like below.
As you see, it consists from title and price labels. Title can have million number of lines, but its top edge should be aligned with price label. It seems simple design, but it has hundreds of solution. I tried every of them, but my title label is not growing, by having dots at the end (numberOfLines = 0 doesn't help). Here is how I approached to create such a design:
I created titleLabel with top, leading, trailing to price label, bottom constraints. Also, I created price label with top and trailing constraints only in order to align their top edges. I assigned compression resistance and hugging priority to price label, because it is more important and should not be ruined. Here is code if you want:
addSubview(titleLabel)
addSubview(priceLabel)
titleLabel.snp.makeConstraints { make in
make.leading.equalToSuperview().offset(16)
make.trailing.lessThanOrEqualTo(priceLabel.snp.leading).offset(-8)
make.top.equalToSuperview()
make.bottom.equalToSuperview()
}
priceLabel.snp.makeConstraints { make in
make.trailing.equalToSuperview().offset(-16)
make.top.equalTo(titleLabel.snp.top)
}
I created separate custom view, because I want to use it inside StackView(spacing 8, distribution fill, vertical). Result of this approach: title label's is not growing. It has only one line with dots at the end, if it has big text.
Second approach was to create stackView (horizontally, spacing 8, distribution fill, alignment top). I set alignment top in order to align top edges of the labels. The result was the as in approach #1.
How to solve this problem? Where am I wrong? It seems I don't see something core in Auto Layout theory here.
Add a new height constraint as well for the title label with
relation: greater than equal to
contant: some constant(might be 20 or something based on your fontsize and content).
I hope this solves your issue
During development of your layout, it can be very helpful to use contrasting colors for element backgrounds... makes it really easy to see what's happening with their frames.
Give this a try...
Custom view class
class NeoCustomView: UIView {
let titleLabel = UILabel()
let priceLabel = UILabel()
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() -> Void {
titleLabel.translatesAutoresizingMaskIntoConstraints = false
titleLabel.numberOfLines = 0
titleLabel.font = .systemFont(ofSize: 17)
priceLabel.translatesAutoresizingMaskIntoConstraints = false
priceLabel.font = .boldSystemFont(ofSize: 17)
priceLabel.setContentHuggingPriority(.required, for: .horizontal)
priceLabel.setContentCompressionResistancePriority(.required, for: .horizontal)
addSubview(titleLabel)
addSubview(priceLabel)
titleLabel.snp.makeConstraints { make in
make.leading.equalToSuperview().offset(16)
make.trailing.lessThanOrEqualTo(priceLabel.snp.leading).offset(-8)
make.top.equalToSuperview()
make.bottom.equalToSuperview()
}
priceLabel.snp.makeConstraints { make in
make.trailing.equalToSuperview().offset(-16)
make.top.equalTo(titleLabel.snp.top)
}
// use some background colors so we can easily see the frames
backgroundColor = .red
titleLabel.backgroundColor = .yellow
priceLabel.backgroundColor = .green
}
}
Example view controller class - adds another label constrained 4-pts from the bottom of the custom view so we can see everything working:
class NeoViewController: UIViewController {
let testView = NeoCustomView()
let anotherLabel = UILabel()
override func viewDidLoad() {
super.viewDidLoad()
anotherLabel.translatesAutoresizingMaskIntoConstraints = false
anotherLabel.font = .systemFont(ofSize: 15)
anotherLabel.backgroundColor = .blue
anotherLabel.textColor = .white
anotherLabel.textAlignment = .center
anotherLabel.numberOfLines = 0
anotherLabel.text = "This label is constrained 4 points from the bottom of the custom view."
view.addSubview(testView)
view.addSubview(anotherLabel)
testView.snp.makeConstraints { make in
make.leading.trailing.equalTo(view.safeAreaLayoutGuide).inset(16)
make.top.equalTo(view.safeAreaLayoutGuide).offset(40)
}
anotherLabel.snp.makeConstraints { make in
make.top.equalTo(testView.snp.bottom).offset(4)
make.width.equalTo(testView.snp.width)
make.centerX.equalTo(testView.snp.centerX)
}
testView.titleLabel.text = "This is long text for the title label that will word wrap when it needs to."
testView.priceLabel.text = "300$"
}
}
Result (Red is custom view with title and price labels, Blue is a label added and constrained below the custom view):
I have to UILabel with dynamic height. I want to set it superview height equal to max of UILabel heights.
class ComponentCell: UIView {
private lazy var leftRow: UILabel = UILabel()
private lazy var rightRow: UILabel = UILabel()
init(leftValue: String, rightValue: String) {
super.init(frame: .zero)
leftRow.backgroundColor = .red
leftRow.numberOfLines = 0
leftRow.lineBreakMode = .byWordWrapping
leftRow.text = leftValue
rightRow.text = rightValue
rightRow.backgroundColor = .yellow
rightRow.numberOfLines = 0
rightRow.lineBreakMode = .byWordWrapping
self.addSubview(self.leftRow)
self.addSubview(self.rightRow)
leftRow.sizeToFit()
rightRow.sizeToFit()
leftRow.setContentHuggingPriority(.required, for: .vertical)
rightRow.setContentHuggingPriority(.required, for: .vertical)
self.translatesAutoresizingMaskIntoConstraints = false
self.leftRow.snp.makeConstraints { make in
make.top.equalToSuperview()
make.left.equalToSuperview()
make.width.equalToSuperview().dividedBy(2)
}
self.rightRow.snp.makeConstraints { make in
make.top.equalToSuperview()
make.right.equalToSuperview()
make.width.equalToSuperview().dividedBy(2)
}
self.layoutIfNeeded()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
If I set leftRow.botton.equalTo(superview.bottom) and rightRow.botton.equalTo(superview.bottom) it's working. But I think is not a good way. And I don't understand why setContentHuggingPriority not helped me to solve this problem.
Content Hugging
Content hugging leads more likely to squeeze your labels. What you want is the height of the labels to be more respected. So you'd rather use compression resistance priority. However you actually need neither of those.
Layout Constraints
Since you're setting your constraints programatically, you'll need to set translatesAutoresizingMaskIntoConstraints to false for your labels as well:
leftRow.translatesAutoresizingMaskIntoConstraints = false
rightRow.translatesAutoresizingMaskIntoConstraints = false
The bottom constraint is actually a good start, but you don't want to fit the height of the smaller label unnecessarily to the height of the bigger label. So you would want to add a constraint that is "less than or equal to the bottom anchor":
make.bottom.lessThanOrEqualTo(self.snp.bottom)
Lazy Variables
If you want to use lazy variables you'll have to change the way there being initialized. The way you've written it, it initializes the variables right away when initializing the class. But you only want them to be initialized when they're used the first time. For that you need to write it like this:
private lazy var leftRow: UILabel = {
return UILabel()
}()
private lazy var rightRow: UILabel = {
return UILabel()
}()
However in your case you don't need lazy loading, so you can initialize them directly:
private let leftRow = UILabel()
private let rightRow = UILabel()
Other
Since you're using layout constraints, you don't need to call sizeToFit on the labels. It doesn't do anything.
Calling layoutIfNeeded() within the init doesn't do anything either since it will be called anyway once you add ComponentCell as a subview to another view.
Im new to iOS development and Im a bit confused as to how to achieve this.
I have 2 UILabels added to a UIStackView like so:
let horizontalStackView1 = UIStackView(arrangedSubviews: [self.label1, self.label2])
and when I run the app it looks like this:
However, Id like the labels to be next to each other with no spacing in between something like this:
Ive tried setting horizontalStackView1.distribution, horizontalStackView1.alignment etc with no luck.
How do I achieve this?
Thanks in advance!
UPDATE:
The code looks like this (its a cell of a table by the way):
class ItemTableViewCell: UITableViewCell
{
...
let stateLabel = UILabel()
let effectiveDateLabel = UILabel()
...
var typeImage: UIImage?
{
...
}
var summary: String?
{
...
}
var effectiveDate: Date?
{
...
}
override init(style: UITableViewCellStyle, reuseIdentifier: String?)
{
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.accessoryType = .disclosureIndicator
...
let horizontalStackView1 = UIStackView(arrangedSubviews: [self.stateLabel, self.effectiveDateLabel])
let horizontalStackView2 = UIStackView(arrangedSubviews: [typeImageViewWrapper, self.titleLabel])
horizontalStackView2.spacing = 4
let verticalStackView = UIStackView(arrangedSubviews: [horizontalStackView1, horizontalStackView2, self.summaryLabel])
verticalStackView.axis = .vertical
verticalStackView.spacing = 4
self.contentView.addSubview(verticalStackView)
...
}
required init?(coder aDecoder: NSCoder)
{
fatalError()
}
}
That's because the UIStackView picks the first arrangedSubview with lowest content hugging priority and resizes it so the stackview's content takes up full width.
If you want to use UIStackView for this case, you can should change the content hugging priorities, eg.
label2.setContentHuggingPriority(.defaultLow, for: .horizontal)
label1.setContentHuggingPriority(.defaultHigh, for: .horizontal)
The stackviews distribution should be set to fillProportionally so every arranged subview keeps its proportions.
However, the remaining space is filled by the stackview automatically. To suppress this, you need to add an empty view at the end. This empty view needs a low content hugging priority so it can grow to fill up the space where the other views remain by their proportions.
Furthermore, the empty view needs an intrinsicContentSize for the stackview to compute the dimensions:
class FillView: UIView {
override var intrinsicContentSize: CGSize {
get { return CGSize(width: 100, height: 100) }
}
}
Now set your arranged subviews and put the fillView at the end
let fillView: UIFillView()
fillView.setContentHuggingPriority(priority: .fittingSizeLevel, for: .horizontal)
myStackView.addArrangedSubview(fillView)
Set the stackviews spacing to your needs to maintain the distance between the subviews.
I have a collectionView Cell with a StackView in it.
class OuterCollectionViewCell: UICollectionViewCell {
#IBOutlet weak var stackView: UIStackView!
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
for i in 1...30 {
let tick = UIView()
if i % 2 == 0 {
tick.backgroundColor = UIColor.red
} else {
tick.backgroundColor = UIColor.clear
}
tick.tag = i
tick.heightAnchor.constraint(equalTo: self.heightAnchor, constant: 0.0).isActive = true
self.stackView?.insertArrangedSubview(tick, at:i)
}
}
I want to draw 30 cells on the stackView alternating between red and clear . Right now I am setting the index of the subview as well as the height with a auto layout anchor constraint and hoping that the .fillEqually distribution will compute the width and X locations of each of the subviews. However right now, it crashes when I try and set the height anchor and simply doesn't show anything when I remove the height anchor. How do I lay these out such that they are full height, and right next to each other with no margins?
I think that you shouldn't be adding a height anchor here to items within your stackview. Automatically, things contained in the stack view are equal heights (or widths depending on stackview orientation). You shouldn't need to be setting sizes within the stackview directly. It defeats the purpose of the stackview in the first place.
On iOS 11 many of our layouts are breaking due to labels apparently misreporting their intrinsicContentSize.
The bug seems to manifest worst when a UILabel is wrapped in another view that attempts to implement intrinsicContentSize itself. Like so (simplified & contrived example):
class LabelView: UIView {
let label = UILabel()
override init(frame: CGRect) {
super.init(frame: frame)
self.setup()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func setup() {
self.label.textColor = .black
self.label.backgroundColor = .green
self.backgroundColor = .red
self.label.numberOfLines = 0
self.addSubview(self.label)
self.label.translatesAutoresizingMaskIntoConstraints = false
self.label.leadingAnchor.constraint(equalTo: self.leadingAnchor).isActive = true
self.label.trailingAnchor.constraint(lessThanOrEqualTo: self.trailingAnchor).isActive = true
self.label.topAnchor.constraint(equalTo: self.topAnchor).isActive = true
self.label.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
}
override var intrinsicContentSize: CGSize {
let size = self.label.intrinsicContentSize
print(size)
return size
}
}
The intrinsicContentSize of the UILabel is very distinctive and looks something like: (width: 1073741824.0, height: 20.5). This causes the layout cycle to give far too much space to the view's wrapper.
This only occurs when compiling for iOS 11 from XCode 9. When running on iOS 11 compiled on the iOS 10 SDK (on XCode 8).
On XCode 8 (iOS 10) the view is rendered correctly like so:
on XCode 9 (iOS 11) the view is rendered like this:
A Gist with full playground code demonstrating this issue is here.
I have filed a radar for this and have at least one solution to the problem (see answer below). I wonder if anyone else has had this problem or has alternative approached you might try.
So through experimenting on the playground I was able to come up with a solution that involves testing for the extremely large intrinsic content size.
I noticed that all UILabels that misbehave have numberOfLines==0 and preferredMaxLayoutWidth=0. On subsequent layout passes, UIKit sets preferredMaxLayoutWidth to a non-zero value, presumably to iterate onto the correct height for the label. So the first fix was to try temporarily setting numberOfLines when (self.label.numberOfLines == 0 && self.label.preferredMaxLayoutWidth == 0).
I also noticed that all UILabels that have these two properties as 0 do not necessarily misbehave. (i.e. the inverse isn't true). So this fix worked, but modified the label unnecessarily some of the time. It also has a small bug that when the label's text contains \n newlines, number of lines should be set to the number of lines in the string, not 1.
The final solution I came to is a little more hacky, but specifically looks for UILabel misbehaving and only kick's it then...
override var intrinsicContentSize: CGSize {
guard super.intrinsicContentSize.width > 1000000000.0 else {
return super.intrinsicContentSize
}
var count = 0
if let text = self.text {
text.enumerateLines {(_, _) in
count += 1
}
} else {
count = 1
}
let oldNumberOfLines = self.numberOfLines
self.numberOfLines = count
let size = super.intrinsicContentSize
self.numberOfLines = oldNumberOfLines
return size
}
You can find this as a Gist here.