Snapkit and UILabel's rotation - ios

I have:
UIView (container)
UIView. subview of (1) - Dark blue in the image below
UIView. subview of (1) - Purple in the image below
UILabel. edges.equalToSuperview()
What I'm trying to accomplish:
The thing is, I want the UILabel to be rotated 3pi/2 (270°). Once I've done the rotation, it isn't placed correctly.
This is how it looks like by setting edges.equalToSuperview() and the 270°rotation:
I've tried this (but it leads to a crash):
myLabel.makeConstraints { make in
make.top.equalTo(containerView.snp.left)
make.right.equalTo(containerView.snp.top)
make.left.equalTo(containerView.snp.bottom)
make.bottom.equalTo(containerView.snp.right)
}
The crash description:
*** Terminating app due to uncaught exception 'NSInvalidLayoutConstraintException', reason: 'Constraint improperly relates anchors of incompatible types: <SnapKit.LayoutConstraint:0x6100000ad8c0#MyClass.swift#250 MyProject.MyLabel:0x7fcc2201ca80.top == UIView:0x7fcc2201bd30.left>'
Any ideas what I could do here?

I have done it using default autolayout and i like that much too. :)
Here is the function.
func makeLabel() {
//Creating stackview
let stackView = UIStackView()
view.addSubview(stackView)
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.alignment = .fill
stackView.distribution = .fillEqually
stackView.axis = .vertical
//Creating blueView
let blueView = UIView()
blueView.backgroundColor = UIColor.darkGray
blueView.translatesAutoresizingMaskIntoConstraints = false
stackView.addArrangedSubview(blueView)
blueView.widthAnchor.constraint(equalToConstant: 100).isActive = true
//Creating purpleView
let purpleView = UIView()
purpleView.backgroundColor = UIColor.purple
purpleView.translatesAutoresizingMaskIntoConstraints = false
stackView.addArrangedSubview(purpleView)
stackView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 0).isActive = true
stackView.topAnchor.constraint(equalTo: view.topAnchor, constant: 0).isActive = true
stackView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: 0).isActive = true
//Creating rotated label
let label = UILabel()
view.addSubview(label)
label.transform = CGAffineTransform.init(rotationAngle: -CGFloat.pi/2)
label.textColor = UIColor.white
label.text = "This is my Rotated Text"
label.font = UIFont.systemFont(ofSize: 25)
label.translatesAutoresizingMaskIntoConstraints = false
label.centerXAnchor.constraint(equalTo: stackView.centerXAnchor, constant: 0).isActive = true
label.centerYAnchor.constraint(equalTo: stackView.centerYAnchor, constant: 0).isActive = true
}
And here is the output.
Portrait:
Landscape

For anyone interested in elk_cloner's answer using Snapkit:
myLabel.snp.makeConstraints { make in
make.centerX.equalTo(containerView.snp.centerX)
make.centerY.equalTo(containerView.snp.centerY)
}

Related

Swift - constrain UITextView

I have a UITextView and I would like to constrain it the same way as I would a UILabel. But if I use the same constrains as I would with a UILabel I am getting a different result. I also do not really quite understand how UITextView.frame works because it doesn't really matter what I set height/width, the result stays the same.
In the picture below "LinkTest" is my UITextView. As you can see it is not lined up with the UILabels below even though I constrain it the same way.
UITextView:
let linkLabel: UITextView = {
let v = UITextView()
v.backgroundColor = .clear
v.text = "Link"
v.textColor = .lightGray
v.font = UIFont(name: "AvenirNext-Regular", size: 18)
v.textAlignment = .right
v.isSelectable = false
v.isScrollEnabled = false
v.frame = CGRect(x: 0, y: 0, width: 20, height: 20)
// v.attributedText = NSAttributedString(string: "", attributes: [.underlineStyle: NSUnderlineStyle.single.rawValue])
v.translatesAutoresizingMaskIntoConstraints = false
return v
}()
Constrains:
// constrain linkLabel
linkLabel.topAnchor.constraint(equalTo: linkImage.topAnchor).isActive = true
linkLabel.leadingAnchor.constraint(equalTo: linkImage.leadingAnchor, constant: 30).isActive = true
linkLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -20).isActive = true
// constrain priceLabel
priceLabel.topAnchor.constraint(equalTo: linkLabel.topAnchor, constant: 35).isActive = true
priceLabel.leadingAnchor.constraint(equalTo: linkImage.leadingAnchor, constant: 30).isActive = true
priceLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -20).isActive = true
// constrain noteLabel
noteLabel.topAnchor.constraint(equalTo: priceLabel.topAnchor, constant: 35).isActive = true
noteLabel.leadingAnchor.constraint(equalTo: linkImage.leadingAnchor, constant: 30).isActive = true
noteLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -20).isActive = true
I appreciate any help on this :)
Solution:
The problem was the UITextViews padding. Removing all padding solved the problem:
let padding = v.textContainer.lineFragmentPadding
v.textContainerInset = UIEdgeInsets(top: 0, left: -padding, bottom: 0, right: -padding)
When you set
v.translatesAutoresizingMaskIntoConstraints = false
then frame setting is ignored , btw you need a height
linkLabel.heightAnchor.constraint(equalToConstant: 30).isActive = true
You can set constraints using frame or using autolayout both can not work at same time. when you set v.translatesAutoresizingMaskIntoConstraints = false then frame setting doesn't affect and also you have to add height constraint
// constrain linkLabel
linkLabel.topAnchor.constraint(equalTo: linkImage.topAnchor).isActive = true
linkLabel.leadingAnchor.constraint(equalTo: linkImage.leadingAnchor, constant: 30).isActive = true
linkLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -20).isActive = true
linkLabel.heightAnchor.constraint(equalToConstant: 20).isActive = true

I cannot figure out what causes layout constraint warnings in my message bubble cell

I've been trying to solve this problem for a few days now and I haven't been able to understand what is wrong with my constraints, and warnings keep popping up in the console.
Here are my UI elements:
let messageText : UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.numberOfLines = 0
label.backgroundColor = .clear
label.clipsToBounds = false
label.font = UIFont.systemFont(ofSize: 17)
return label
}()
private let messageCard : UIView = {
let card = UIView()
card.translatesAutoresizingMaskIntoConstraints = false
card.layer.cornerRadius = 16
card.layer.masksToBounds = true
card.clipsToBounds = false
return card
}()
private let avatar : CachedImageView = {
var imageView = CachedImageView()
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.heightAnchor.constraint(equalToConstant: 15).isActive = true
imageView.heightAnchor.constraint(equalToConstant: 15).isActive = true
imageView.isAvatar = true
return imageView
}()
private let stackView : UIStackView = {
let stView = UIStackView()
stView.translatesAutoresizingMaskIntoConstraints = false
stView.backgroundColor = .clear
stView.axis = .vertical
return stView
}()
Here is my initialiser for the cell:
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.clipsToBounds = false
stackView.addArrangedSubview(messageText)
stackView.addArrangedSubview(avatar)
addSubview(messageCard)
addSubview(stackView)
let constraints = [
stackView.topAnchor.constraint(equalTo: topAnchor, constant: bubbleMargin + bubblePadding),
stackView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -(bubbleMargin + bubblePadding)),
stackView.widthAnchor.constraint(lessThanOrEqualToConstant: 300),
stackView.widthAnchor.constraint(greaterThanOrEqualToConstant: 30),
//stackView.heightAnchor.constraint(greaterThanOrEqualToConstant: 10), // ну хз
messageCard.topAnchor.constraint(equalTo: stackView.topAnchor, constant: -bubblePadding),
messageCard.bottomAnchor.constraint(equalTo: stackView.bottomAnchor, constant: bubblePadding),
messageCard.leadingAnchor.constraint(equalTo: stackView.leadingAnchor, constant: -bubblePadding),
messageCard.trailingAnchor.constraint(equalTo: stackView.trailingAnchor, constant: bubblePadding),
/*messageText.leadingAnchor.constraint(equalTo: stackView.leadingAnchor, constant: 20),
messageText.trailingAnchor.constraint(equalTo: stackView.trailingAnchor, constant: -20),*/
/*avatar.heightAnchor.constraint(equalToConstant: 15),
avatar.widthAnchor.constraint(equalToConstant: 15),
avatar.centerXAnchor.constraint(equalTo: stackView.centerXAnchor),
avatar.centerYAnchor.constraint(equalTo: stackView.centerYAnchor),*/
]
leftConstr = stackView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: bubbleMargin + bubblePadding)
rightConstr = stackView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -(bubbleMargin + bubblePadding))
leftConstr.isActive = true
for c in constraints {
c.isActive = true
}
}
Here is the method that fills the message bubble:
func setMessage() {
messageText.text = messageWrapper.message.text
messageText.textColor = .white
messageCard.backgroundColor = messageWrapper.message.out == 1 ? .darkGray : .lightGray
leftConstr.isActive = messageWrapper.message.out == 0
rightConstr.isActive = messageWrapper.message.out == 1
avatar.isHidden = messageWrapper.message.out == 1
if messageWrapper.message.out == 0 {
if let photo = messageWrapper.group?.photo50 {
avatar.setSource(url: photo)
}
if let photo = messageWrapper.profile?.photo100 {
avatar.setSource(url: photo)
}
}
}
I've also noticed that the warnings really only appear when I start scrolling, but the warning suggests that there is something wrong with a constraints that the setMessage() method doesn't ever touch.
screenshot of the warning, since stack overflow really wasn't having it as a code snippet
Looks like your stack view with needs to be <= 300 points, but the stack view width must be equal to message cell width - 13 - 13. Since your message cell width is 414, and 414 - 13 - 13 = 388, and 388 is not <= 300, you get the error.
You should either remove the stack view's width constraint, or change either the leading or trailing constraint to be more flexible.
There are many solutions. For example, you could remove these two lines:
messageCard.leadingAnchor.constraint(equalTo: stackView.leadingAnchor, constant: -bubblePadding),
messageCard.trailingAnchor.constraint(equalTo: stackView.trailingAnchor, constant: bubblePadding),
and instead center the stack view within messageCard.
But ultimately you need to not give it conflicting constraints.

Get height of uiview added by autolayout

I am added a view in viewDidAppear method using autolayout. In the end of viewDidAppear trying to find the height of view that I added , I am getting zero?
That view I am adding has a label , height of that label is dynamic
let viewToShowIn = self.view!
let bannerView = UIView()
bannerView.backgroundColor = UIColor.red
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
viewToShowIn.addSubview(bannerView)
bannerView.addSubview(label)
let margins = viewToShowIn.layoutMarginsGuide
bannerView.translatesAutoresizingMaskIntoConstraints = false
bannerView.leadingAnchor.constraint(equalTo: viewToShowIn.leadingAnchor, constant: 0).isActive = true
bannerView.trailingAnchor.constraint(equalTo: viewToShowIn.trailingAnchor, constant: 0).isActive = true
let bannerTopConstraint = bannerView.topAnchor.constraint(equalTo: margins.topAnchor, constant: 0)
bannerTopConstraint.isActive = true
label.font = UIFont.systemFont(ofSize: 20)
label.text = "kjafj kfj fk fjk dakjd k fdjakljf dkfjklsdjf dfsjlkj lkfsdjlkjl sjflksdjfljslf sdfljdslkjflsdjf sldfjlksj"
label.numberOfLines = 0
label.lineBreakMode = .byWordWrapping
label.textAlignment = .center
label.leadingAnchor.constraint(equalTo: bannerView.leadingAnchor, constant: 20).isActive = true
label.trailingAnchor.constraint(equalTo: bannerView.trailingAnchor, constant: -20).isActive = true
label.topAnchor.constraint(equalTo: bannerView.topAnchor, constant: 10).isActive = true
bannerView.bottomAnchor.constraint(equalTo: label.bottomAnchor, constant: 10).isActive = true
Declare your label and view as global variable like
var viewToShowIn = UIView()
let bannerView = UIView()
let label = UILabel()
Then set constraints in viewDidAppear or viewDidLoad
viewToShowIn = self.view!
bannerView.backgroundColor = UIColor.red
label.translatesAutoresizingMaskIntoConstraints = false
viewToShowIn.addSubview(bannerView)
bannerView.addSubview(label)
let margins = viewToShowIn.layoutMarginsGuide
bannerView.translatesAutoresizingMaskIntoConstraints = false
bannerView.leadingAnchor.constraint(equalTo: viewToShowIn.leadingAnchor, constant: 0).isActive = true
bannerView.trailingAnchor.constraint(equalTo: viewToShowIn.trailingAnchor, constant: 0).isActive = true
let bannerTopConstraint = bannerView.topAnchor.constraint(equalTo: margins.topAnchor, constant: 0)
bannerTopConstraint.isActive = true
label.font = UIFont.systemFont(ofSize: 20)
label.text = "kjafj kfj fk fjk dakjd k fdjakljf dkfjklsdjf dfsjlkj lkfsdjlkjl sjflksdjfljslf sdfljdslkjflsdjf sldfjlksj"
label.numberOfLines = 0
label.lineBreakMode = .byWordWrapping
label.textAlignment = .center
label.leadingAnchor.constraint(equalTo: bannerView.leadingAnchor, constant: 20).isActive = true
label.trailingAnchor.constraint(equalTo: bannerView.trailingAnchor, constant: -20).isActive = true
label.topAnchor.constraint(equalTo: bannerView.topAnchor, constant: 10).isActive = true
bannerView.bottomAnchor.constraint(equalTo: label.bottomAnchor, constant: 10).isActive = true
You will get height of your view or label in viewWillLayoutSubviews() or viewDidLayoutSubviews() method
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
// GET YOUR HEIGHT HERE
}
After adding all constraints to view just call self.view.layoutIfNeeded()
It will force auto layout engine to calculate view size.

In Swift, programmatically creating UIView and adding controls to it and using auto layout, causes the controls to appear on the view's parent

I am trying to write a simple composite component for iOS in Swift 3. It consists of a UILabel followed by an UITextField laid out horizontally followed by a line under them. But What happens is the UILabel disappears, UITextField appears on the parent view and line also disappears.
My design in sketch
What it actually looks like in the Storyboard
My component's constraints in the view controller
My intention was to use Auto Layout, anchor the label to top and leading anchors of the view, anchor the textfield to top of the view and trailing anchor of the label with a constant, so they would appear side by side.
I did do a lot of research on this, one site that looked pretty close to what I wanted was https://www.raywenderlich.com/125718/coding-auto-layout, and I think I am following more or less the same approach.
I am doing something obviously wrong, but can't figure out what. Any help is much appreciated, I have been at this for a few days now.
import UIKit
#IBDesignable
class OTextEdit: UIView {
#IBInspectable var LabelText: String = "Label"
#IBInspectable var SecureText: Bool = false
#IBInspectable var Color: UIColor = UIColor.black
#IBInspectable var Text: String = "" {
didSet {
edit.text = Text
}
}
fileprivate let label = UILabel(frame: CGRect(x: 0, y: 0, width: 200, height: 35))
fileprivate let edit = UITextField(frame: CGRect(x: 210, y: 0, width: 200, height: 35))
fileprivate let line: UIView = UIView()
override var intrinsicContentSize: CGSize {
return CGSize(width: 300, height: 100)
}
func setup() {
label.text = LabelText
label.textColor = Color
label.font = UIFont(name: "Avenir Next Condensed", size: 24)
edit.font = UIFont(name: "Avenir Next Condensed", size: 24)
edit.borderStyle = .roundedRect
edit.isSecureTextEntry = SecureText
line.backgroundColor = UIColor.white
self.addSubview(label)
self.addSubview(edit)
self.addSubview(line)
}
override func willMove(toSuperview newSuperview: UIView?) {
super.willMove(toSuperview: newSuperview)
setup()
setupConstaints()
}
func setupConstaints() {
label.translatesAutoresizingMaskIntoConstraints = false
edit.translatesAutoresizingMaskIntoConstraints = false
line.translatesAutoresizingMaskIntoConstraints = false
label.leadingAnchor.constraint(equalTo: leadingAnchor, constant: -10).isActive = true
label.topAnchor.constraint(equalTo: topAnchor)
edit.leadingAnchor.constraint(equalTo: label.leadingAnchor, constant: 10).isActive = true
edit.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -20).isActive = true
edit.topAnchor.constraint(equalTo: self.topAnchor)
line.heightAnchor.constraint(equalToConstant: 2.0).isActive = true
line.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 10).isActive = true
line.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -20).isActive = true
line.topAnchor.constraint(equalTo: label.bottomAnchor, constant: 1.0).isActive = true
}
}
You haven't got a series of constraints top to bottom, so auto layout can't determine the content size of your object. You have tried to set this via the initrinsicContentSize but you shouldn't need to do this.
You also need to set a horizontal hugging priority for your label to let auto layout know that you want the text field to expand:
I removed your override of intrinsicContentSize and changed your constraints to:
Constrain the bottom of the label to the top of the line
Constrain the bottom of the line to the bottom of the superview
Constrain the baseline of the label to the baseline of the text field
Remove the constraint between the top of the text field and the superview
Set the horizontal hugging priority of the label.
func setupConstraints() {
label.translatesAutoresizingMaskIntoConstraints = false
edit.translatesAutoresizingMaskIntoConstraints = false
line.translatesAutoresizingMaskIntoConstraints = false
label.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 10).isActive = true
label.setContentHuggingPriority(.defaultHigh, for: .horizontal)
label.topAnchor.constraint(equalTo: topAnchor)
label.bottomAnchor.constraint(equalTo: line.topAnchor, constant: -8).isActive = true
edit.leadingAnchor.constraint(equalTo: label.trailingAnchor, constant: 10).isActive = true
edit.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -20).isActive = true
edit.firstBaselineAnchor.constraint(equalTo: label.firstBaselineAnchor).isActive = true
line.heightAnchor.constraint(equalToConstant: 2.0).isActive = true
line.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 10).isActive = true
line.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -20).isActive = true
line.topAnchor.constraint(equalTo: label.bottomAnchor, constant: 1.0).isActive = true
line.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
}
I think it is pretty close to what you are after.

Swift iOS -How to set UIView's Height Anchor <= To A Label's Intrinsic Text Size? 'NSLayoutConstraint' is not convertible to 'Bool'

I have a programmatic view with a label inside of it that I'm pinning to the bottom of the navBar. There will be dynamic text inside of the label and I want the view the label is in to be the at least 64 pts or bigger if the height of text makes it smaller.
The intrinsic size of the text from this label sets the view at a noticeable height.
setViewAndLabel(dynamicText: "Unknown Error\nPlease try your request again\Error: 123")
However the intrinsic size of this text makes the height to small:
setViewAndLabel(dynamicText: "Message Deleted!")
The Message Deleted! should be more along the lines of:
I used some return keys to set it like that but I don't think that's the correct way to go because different messages will get generated:
setViewAndLabel(dynamicText: "\nMessage Deleted!\n")
I also tried:
if myView.heightAnchor.constraint(lessThanOrEqualToConstant: 64){
myView.heightAnchor.constraint(equalToConstant: 64).isActive = true
}
But I get the error:
'NSLayoutConstraint' is not convertible to 'Bool'
What's the best way to set the height for the view the label is in to a minimum height?
var myLabel: UILabel(){
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.textColor = UIColor.white
label.font = UIFont(name: "Helvetica-Regular", size: 19)
label.numberOfLines = 0
label.sizeToFit()
label.textAlignment = .center
return label
}
let myView:UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
override func viewWillAppear(_ animated: Bool)
super.viewWillAppear(animated){
setViewAndLabel(dynamicText: //some text will get set here)
}
func setViewAndLabel(dynamicText: String){
view.addSubView(myView)
myView.backgroundColor = UIColor.red
myView.topAnchor.constraint(equalTo: view.topAnchor, constant: 64).isActive = true
View.widthAnchor.constraint(equalTo: view.widthAnchor, constant: 0).isActive = true
myView.addSubView(myLabel)
myLabel.text = dynamicText
myLabel.topAnchor.constraint(equalTo: myView.topAnchor, constant: 0).isActive = true
myLabel.widthAnchor.constraint(equalTo: myView.widthAnchor, constant: 0).isActive = true
myView.bottomAnchor.constraint(equalTo: myLabel.bottomAnchor, constant: 0).isActive = true
//this if statement doesn't work
if myView.heightAnchor.constraint(lessThanOrEqualToConstant: 64){
viewForErrorLabel.heightAnchor.constraint(equalToConstant: 64).isActive = true
}
}
This is how you have to set up your constraints:
view.addSubview(myView)
myView.topAnchor.constraint(equalTo: topLayoutGuide.bottomAnchor).isActive = true
myView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
myView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
myView.heightAnchor.constraint(greaterThanOrEqualToConstant: 64).isActive = true
myView.addSubview(myLabel)
myLabel.topAnchor.constraint(equalTo: myView.topAnchor).isActive = true
myLabel.leadingAnchor.constraint(equalTo: myView.leadingAnchor).isActive = true
myLabel.trailingAnchor.constraint(equalTo: myView.trailingAnchor).isActive = true
myLabel.bottomAnchor.constraint(equalTo: myView.bottomAnchor).isActive = true
You do not need to check the label's height at all. You can simply always create that height greater than or equal constraint for myView and its height will never be smaller than 64pt (or whatever value you set it to) - even if the label contains a very short text.

Resources