Self-sizing UILabel with UIView Subclassing - ios

I have a sketch and I'm trying to achieve the concept programmatically.
Here's my Sketch:
Achievement:
My code:
let luna = UIView()
let descStrWidth = messageDescription.stringWidth // Using String Extension i made.
let descStrHeight = messageDescription.stringHeight // Using String Extension i made.
let titleStrHeight = messageTitle.stringHeight // ..
let titleStrWidth = messageTitle.stringWidth // ..
let titleLabel = UILabel()
titleLabel.text = messageTitle
titleLabel.font = UIFont(name: "avenirnext-demibold", size: 13)
titleLabel.textColor = UIColor(hexString: "4A4A4A")
titleLabel.numberOfLines = 0 // whole idea of self-sizing
let titleDesc = UILabel()
titleDesc.text = messageDescription
titleDesc.font = UIFont(name: "avenirnext-regular", size: 9)
titleDesc.textColor = UIColor(hexString: "4A4A4A")
titleDesc.numberOfLines = 0
// My mainView
luna.frame = CGRect(x: 16, y: 40, width: screenWidth - 30, height: titleStrHeight + descStrHeight)
luna.center.x = luna.center.x
luna.backgroundColor = .white
luna.addShadow(radius: 11, opacity: 0.2) // Some Shadow
luna.layer.cornerRadius = 10
titleDesc.frame = CGRect(x: luna.frame.minX + 3, y: titleLabel.frame.maxY + titleStrHeight, width: luna.frame.width, height: descStrHeight)
titleLabel.frame = CGRect(x: luna.frame.minX + 3, y: 8, width: titleStrWidth, height: titleStrHeight)
luna.addSubview(titleLabel)
luna.addSubview(titleDesc)
self.view.addSubview(luna)
Getting provided string width and height:
extension String {
var stringWidth: CGFloat {
let constraintRect = CGSize(width: UIScreen.main.bounds.width, height: .greatestFiniteMagnitude)
let boundingBox = self.trimmingCharacters(in: .whitespacesAndNewlines).boundingRect(with: constraintRect, options: [.usesLineFragmentOrigin, .usesFontLeading], attributes: [NSAttributedStringKey.font: UIFont.systemFont(ofSize: 14)], context: nil)
return boundingBox.width
}
var stringHeight: CGFloat {
let constraintRect = CGSize(width: UIScreen.main.bounds.width, height: .greatestFiniteMagnitude)
let boundingBox = self.trimmingCharacters(in: .whitespacesAndNewlines).boundingRect(with: constraintRect, options: [.usesLineFragmentOrigin, .usesFontLeading], attributes: [NSAttributedStringKey.font: UIFont.systemFont(ofSize: 14)], context: nil)
return boundingBox.height
}
}
This is what I achieved so far. This might be easier with constraints, right?

Here's a solution by using auto layout and stack views.
let luna = UIView()
let titleLabel = UILabel()
titleLabel.text = "Title"
titleLabel.font = UIFont(name: "avenirnext-demibold", size: 13)
//titleLabel.textColor = UIColor(hexString: "4A4A4A")
titleLabel.numberOfLines = 0 // whole idea of self-sizing
let titleDesc = UILabel()
titleDesc.text = "Description"
titleDesc.font = UIFont(name: "avenirnext-regular", size: 9)
//titleDesc.textColor = UIColor(hexString: "4A4A4A")
titleDesc.numberOfLines = 0
// My mainView
luna.backgroundColor = .white
//luna.addShadow(radius: 11, opacity: 0.2) // Some Shadow
luna.layer.cornerRadius = 10
let verticalStackView = UIStackView(arrangedSubviews: [titleLabel, titleDesc])
verticalStackView.axis = .vertical
let okButton = UIButton()
okButton.setTitleColor(.blue, for: .normal)
okButton.setTitle("Okay", for: .normal)
okButton.setContentHuggingPriority(.defaultHigh, for: .horizontal) // to stretch the okay button horizontally
let horizontalStackView = UIStackView(arrangedSubviews: [verticalStackView, okButton])
horizontalStackView.axis = .horizontal
luna.addSubview(horizontalStackView)
view.addSubview(luna)
horizontalStackView.translatesAutoresizingMaskIntoConstraints = false // when using autolayout from code, this property must be false, otherwise constraint won't work
luna.translatesAutoresizingMaskIntoConstraints = false
// This method activates all constraint (when you create a constraint with anchors, by default they are disabled)
NSLayoutConstraint.activate([
horizontalStackView.topAnchor.constraint(equalTo: luna.topAnchor, constant: 8),
horizontalStackView.bottomAnchor.constraint(equalTo: luna.bottomAnchor, constant: -8),
horizontalStackView.leadingAnchor.constraint(equalTo: luna.leadingAnchor, constant: 8),
horizontalStackView.trailingAnchor.constraint(equalTo: luna.trailingAnchor, constant: -8),
luna.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 30),
luna.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 30),
luna.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -30)
])
You can change distances between views by changing spacing property of stack view. (i.e. horizontalStackView.spacing = 16 to put a space of 16 points between the two label and the okay button)

You can try this
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let luna = UIView()
let titleLabel = UILabel()
titleLabel.text = "messageTitle"
titleLabel.font = UIFont(name: "avenirnext-demibold", size: 13)
titleLabel.textColor = UIColor.blue
titleLabel.numberOfLines = 0 // whole idea of self-sizing
let titleDesc = UILabel()
titleDesc.text = "messageDescriptionmessageDescriptionmessageDescriptionmessageDescriptionmessageDescriptionmessageDescriptionmessageDescriptionvmessageDescriptionmessageDescriptionmessageDescription"
titleDesc.font = UIFont(name: "avenirnext-regular", size: 9)
titleDesc.textColor = UIColor.red
titleDesc.numberOfLines = 0
let btn = UIButton()
btn.setTitle("Okay", for: .normal)
btn.setTitleColor(UIColor.blue, for: .normal)
luna.translatesAutoresizingMaskIntoConstraints = false
titleLabel.translatesAutoresizingMaskIntoConstraints = false
titleDesc.translatesAutoresizingMaskIntoConstraints = false
btn.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(luna)
luna.layer.cornerRadius = 10
luna.addSubview(titleLabel)
luna.addSubview(titleDesc)
luna.addSubview(btn)
luna.layer.borderColor = UIColor.green.cgColor
luna.layer.borderWidth = 2
btn.setContentCompressionResistancePriority(UILayoutPriority(rawValue: 1000), for: .horizontal)
NSLayoutConstraint.activate([
luna.topAnchor.constraint(equalTo: view.topAnchor, constant: 30),
luna.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
luna.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
titleLabel.topAnchor.constraint(equalTo: luna.topAnchor, constant: 20),
titleLabel.leadingAnchor.constraint(equalTo: luna.leadingAnchor, constant: 20),
titleLabel.trailingAnchor.constraint(equalTo: btn.leadingAnchor, constant: -10),
titleDesc.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 10),
titleDesc.leadingAnchor.constraint(equalTo: luna.leadingAnchor, constant: 20),
titleDesc.trailingAnchor.constraint(equalTo: btn.leadingAnchor, constant: -10),
titleDesc.bottomAnchor.constraint(equalTo: luna.bottomAnchor, constant: -20),
btn.centerYAnchor.constraint(equalTo: luna.centerYAnchor, constant: 0 ),
btn.trailingAnchor.constraint(equalTo: luna.trailingAnchor, constant: -20),
])
}

As people are saying I would suggest you look into auto layout for the future but for now this should do what you want:
let luna = UIView()
let titleLabel = UILabel()
titleLabel.text = messageTitle
titleLabel.font = UIFont(name: "avenirnext-demibold", size: 13)
titleLabel.textColor = UIColor(hexString: "4A4A4A")
titleLabel.numberOfLines = 0 // whole idea of self-sizing
let titleDesc = UILabel()
titleDesc.text = messageDescription
titleDesc.font = UIFont(name: "avenirnext-regular", size: 9)
titleDesc.textColor = UIColor(hexString: "4A4A4A")
titleDesc.numberOfLines = 0
// My mainView
// The margins of the labels inside the luna view.
let margins = UIEdgeInsetsMake(8, 4, 8, 4)
// The vertical space between the two labels.
let labelSpace: CGFloat = 4
let titleDescSize = titleDesc.sizeThatFits(CGSize(width: screenWidth - 30 - margins.left - margins.right, height: .greatestFiniteMagnitude))
let titleLabelSize = titleLabel.sizeThatFits(CGSize(width: screenWidth - 30 - margins.left - margins.right, height: .greatestFiniteMagnitude))
luna.frame = CGRect(x: 16, y: 40, width: screenWidth - 30, height: titleLabelSize.height + titleDescSize.height + margins.top + margins.bottom + labelSpace)
luna.center.x = luna.center.x
luna.backgroundColor = .white
luna.layer.borderWidth = luna.addShadow(radius: 11, opacity: 0.2) // Some Shadow
luna.layer.cornerRadius = 10
titleLabel.frame = CGRect(x: margins.left, y: margins.top, width: titleLabelSize.width, height: titleLabelSize.height)
titleDesc.frame = CGRect(x: margins.left, y: titleLabel.frame.origin.y + titleLabel.frame.size.height + labelSpace, width: titleDescSize.width, height: titleDescSize.height)
luna.addSubview(titleLabel)
luna.addSubview(titleDesc)
self.view.addSubview(luna)
Note: You don't even need to use you string extension as the UILabels are capable of calculating the size they need based on their current settings.

You are using avenirnext-demibold and avenirnext-regular custom font. But when you are calculating string width and Height you're using system font UIFont.systemFont(ofSize: 14). You're not getting the correct width and height of the string. titleLabel and titleDesc both labels width should be less then luna width for Padding.

Related

Set Constraints to Labels So That One is At Leading Side and The Other at Trailing Side in Swift

I am programmatically adding a label and a button in Swift. I want the label to be at leading side and the button at trailing side. This is what I did so far:
let view = UIView.init(frame: CGRect.init(x: 0, y: 0, width: view.frame.width, height: 50))
view.clipsToBounds = true
view.translatesAutoresizingMaskIntoConstraints = false
let label = UILabel()
label.text = "Top-rated experts near you"
label.font = UIFont.systemFont(ofSize: 18, weight: .medium)
label.textColor = .black
label.contentMode = .left
let button = UIButton()
button.backgroundColor = .clear
button.setTitle("See All", for: .normal)
button.setTitleColor(.black, for: .normal)
button.contentMode = .right
button.addTarget(self, action: #selector(buttonTapped(sender:)), for: .touchUpInside)
button.frame = CGRect.init(x: 100, y: 0, width: view.frame.width-25, height: view.frame.height-25)
label.frame = CGRect.init(x: 10 , y: 0, width: view.frame.width-10, height: view.frame.height-10)
view.addSubview(label)
view.addSubview(button)
And this results in:
My desired result is:
I want see all to be at trailing side and the top-rated label at the leading side. How can I achieve it in Swift?
Question is a little unclear, I think you are asking how to achieve the same layout with constraints instead of using frames?
Here is a rough sample of how to do it, you might need to tweak the constants:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
let view = UIView(frame: CGRect.init(x: 0, y: 0, width: view.frame.width, height: 50))
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.text = "Top-rated experts near you"
label.font = UIFont.systemFont(ofSize: 18, weight: .medium)
label.textColor = .black
label.contentMode = .left
let button = UIButton()
button.translatesAutoresizingMaskIntoConstraints = false
button.backgroundColor = .clear
button.setTitle("See All", for: .normal)
button.setTitleColor(.black, for: .normal)
button.contentMode = .right
button.addTarget(self, action: #selector(buttonTapped(sender:)), for: .touchUpInside)
view.addSubview(label)
view.addSubview(button)
NSLayoutConstraint.activate([
label.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 16),
label.trailingAnchor.constraint(equalTo: button.leadingAnchor, constant: 16),
label.topAnchor.constraint(equalTo: view.topAnchor, constant: 16),
label.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: 16),
button.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: 16),
button.topAnchor.constraint(equalTo: view.topAnchor, constant: 16),
button.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: 16)
])
self.view.addSubview(view)
}
override func viewDidLoad() {
super.viewDidLoad()
let label = UILabel()
label.text = "Top-rated experts near you"
label.font = UIFont.systemFont(ofSize: 21, weight: .medium)
label.textColor = .black
label.contentMode = .left
let button = UIButton()
button.backgroundColor = .clear
button.setTitle("See All", for: .normal)
button.setTitleColor(.black, for: .normal)
button.contentMode = .right
button.addTarget(self, action: #selector(buttonTapped(sender:)), for: .touchUpInside)
let stack = UIStackView(arrangedSubviews: [label, button])
stack.axis = .horizontal
stack.distribution = .fill
stack.alignment = .center
stack.spacing = 16
stack.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(stack)
let padding: CGFloat = 16
let viewHeight: CGFloat = 50
NSLayoutConstraint.activate([
stack.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: padding),
stack.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: padding),
stack.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: 1-padding),
stack.heightAnchor.constraint(equalToConstant: viewHeight)
])
}

Swift: Programmatic Autolayout broken by UNavigationController

I have inherited a UIKit app where my predecessor has written all the interface code by hand. I have a view controller, a simple login screen that works just fine, but when I add it to a UINavigationController it is oddly stretched. This doesn't make a lot of sense to me.
I feel like I must be missing some simple flag, or what do I need to do to make the Navcontroller play nice with this programmatic autolayout (which is hopefully my last ever)
// this a view controller extension
func apply(constraints: [NSLayoutConstraint]) {
translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate(constraints)
}
There's my handy dandy, seemingly sane utility for applying constraints. As an example, the nice little oval thing making my password textfield looks nice has these constraints set up.
// these constraints are being setup in the view controller
emailCapsuleView.apply(constraints: [
emailCapsuleView.topAnchor.constraint(equalTo: subBigTitle.bottomAnchor, constant: 24.0),
emailCapsuleView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
emailCapsuleView.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.8),
emailCapsuleView.heightAnchor.constraint(equalToConstant: Dimensions.inputFieldHeight)
])
Outside of a UINavigationController everything is fine,
but in a UINavigationController everythhing is super wide and broken (simulator shot)
The width is not the problem here - if I use a constant for the width, my subview is still off center despite having it's centerX sets to the view controller's view's center X.
Configure your navigation controller and after that try like this:
set your objects:
let emailTextfield: UITextField = {
let tf = UITextField()
tf.backgroundColor = .white
tf.layer.borderWidth = 1
tf.layer.borderColor = UIColor.lightGray.cgColor
tf.setPadding(left: 10, right: 10)// use my extension below
tf.attributedPlaceholder = NSAttributedString(string: "Email address", attributes: [.foregroundColor: UIColor.lightGray])
tf.layer.cornerRadius = 14
tf.clipsToBounds = true
tf.translatesAutoresizingMaskIntoConstraints = false
return tf
}()
let passTextfield: UITextField = {
let tf = UITextField()
tf.backgroundColor = .white
tf.layer.borderWidth = 1
tf.layer.borderColor = UIColor.lightGray.cgColor
tf.setPadding(left: 10, right: 10)// use my extension below
tf.attributedPlaceholder = NSAttributedString(string: "Password", attributes: [.foregroundColor: UIColor.lightGray])
tf.layer.cornerRadius = 14
tf.clipsToBounds = true
tf.translatesAutoresizingMaskIntoConstraints = false
return tf
}()
let loginButton: UIButton = {
let b = UIButton(type: .system)
b.backgroundColor = .black
b.setTitle("Save Image", for: .normal)
b.titleLabel?.font = .systemFont(ofSize: 16, weight: .semibold)
b.setTitleColor(.white, for: .normal)
b.layer.cornerRadius = 14
b.clipsToBounds = true
b.translatesAutoresizingMaskIntoConstraints = false
return b
}()
let subBigTitle: UILabel = {
let l = UILabel()
l.text = "Your Big Title"
l.font = .systemFont(ofSize: 30, weight: .regular)
l.textColor = .black
l.textAlignment = .center
l.translatesAutoresizingMaskIntoConstraints = false
return l
}()
Now in viewDidLoad set stackView, bigTitle label and constraints:
view.addSubview(subBigTitle)
subBigTitle.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
subBigTitle.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor).isActive = true
subBigTitle.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor).isActive = true
subBigTitle.heightAnchor.constraint(equalToConstant: 60).isActive = true
let stackView = UIStackView(arrangedSubviews: [emailTextfield, passTextfield, loginButton])
stackView.axis = .vertical
stackView.distribution = .fillEqually
stackView.spacing = 10
stackView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(stackView)
stackView.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.8).isActive = true
stackView.heightAnchor.constraint(equalToConstant: 170).isActive = true
stackView.topAnchor.constraint(equalTo: subBigTitle.bottomAnchor, constant: 24).isActive = true
stackView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
to add text padding to your textfield use my extension:
extension UITextField {
func setPadding(left: CGFloat, right: CGFloat){
let paddingView = UIView(frame: CGRect(x: 0, y: 0, width: left, height: self.frame.size.height))
self.leftView = paddingView
self.leftViewMode = .always
let paddingViewRight = UIView(frame: CGRect(x: 0, y: 0, width: right, height: self.frame.size.height))
self.rightView = paddingViewRight
self.rightViewMode = .always
}
}
and this is the result:

SetImage() method removes titleLabel for UIButton

My wish is to make centered image(left) and next to it(right) the label.
Without setting an image, there was a perfectly centered titleLabel:
btnWhatsapp.titleLabel?.adjustsFontSizeToFitWidth = true
btnWhatsapp.setTitle("WhatsApp", for: .normal)
Then I added this code to add an image:
btnWhatsapp.setImage(UIImage(named: "phoneIcon"), for: .normal)
btnWhatsapp.imageView?.layer.transform = CATransform3DMakeScale(0.5, 0.6, 0.5)
btnWhatsapp.imageView?.contentMode = .scaleAspectFit
, and this iswhat I got then:
, so the title disappeared.
Maybe the problem is that image uses more space than its actual size(the size shouldnt take more widht and height than the icon size). I saw this when changed images background(should be this much grey color):
btnWhatsapp.imageView?.backgroundColor = .gray
I tried to use the imageEdgeInsets but it is very hard to calculate it to fit perfectly on every iPhone.
This is the Attributes inspector of the button:
You can't set title and image at once by default, nor position them as you describe.
If you need to have a UIButton, I'd recommend to make a UIView (or possibly horizontal UIStackView) with UIImage and UILabel inside, position them with autolayout, then you can add this view to the UIButton as a subview.
let button = UIButton(type: .custom)
button.frame = viewFrame // This is the desired frame of your custom UIView or UIStackView
button.addSubview(customView)
You will be able to position the views easily for all sizes with this approach, but you will probably want to use autolayout in real word app, instead of hardcoded frames.
Example:
override func viewDidLoad() {
super.viewDidLoad()
let image = UIImageView(frame: CGRect(x: 0, y: 0, width: 50, height: 50))
let label = UILabel(frame: CGRect(x: 0, y: 0, width: 50, height: 50))
label.text = "text"
let stack = UIStackView(arrangedSubviews: [image, label])
stack.frame = CGRect(x: 0, y: 0, width: 100, height: 50)
stack.distribution = .fillEqually
let button = UIButton()
button.frame = CGRect(x: 0, y: 0, width: 100, height: 50)
button.addSubview(stack)
view.addSubview(button)
self.view.addSubview(button)
}
Set your button under your controller class like this:
let imageButton: UIButton = {
let b = UIButton(type: .custom)
b.backgroundColor = #colorLiteral(red: 0.3411764801, green: 0.6235294342, blue: 0.1686274558, alpha: 1)
b.layer.cornerRadius = 12
b.clipsToBounds = true
b.translatesAutoresizingMaskIntoConstraints = false
let imageV = UIImageView()
imageV.image = UIImage(named: "yourImage")?.withRenderingMode(.alwaysTemplate)
imageV.tintColor = .white
imageV.contentMode = .scaleAspectFill
imageV.translatesAutoresizingMaskIntoConstraints = false
imageV.widthAnchor.constraint(equalToConstant: 30).isActive = true
let label = UILabel()
label.text = "WhatsApp"
label.textColor = .white
label.font = .systemFont(ofSize: 16, weight: .regular)
let stack = UIStackView(arrangedSubviews: [imageV, label])
stack.distribution = .fill
stack.spacing = 4
stack.axis = .horizontal
stack.translatesAutoresizingMaskIntoConstraints = false
b.addSubview(stack)
stack.heightAnchor.constraint(equalToConstant: 30).isActive = true
stack.widthAnchor.constraint(equalToConstant: 120).isActive = true
stack.centerXAnchor.constraint(equalTo: b.centerXAnchor).isActive = true
stack.centerYAnchor.constraint(equalTo: b.centerYAnchor).isActive = true
return b
}()
Now in viewDidLoad add button and set constraints in your view (in my case on top)
view.addSubview(imageButton)
imageButton.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 20).isActive = true
imageButton.heightAnchor.constraint(equalToConstant: 50).isActive = true
imageButton.widthAnchor.constraint(equalToConstant: 200).isActive = true
imageButton.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
This is the result:

Can we restrict the width of a NSMutableAttributedString?

I have a string Ex: Arya said "We must find a solution" yesterday. Here I want to restrict the width of the text in bold ("We must find a solution") to 100, but others must not be shortened. So I added it as 3 different NSMutableAttributedStrings appended together and set this to a UILabel. I want to know if I can restrict the width of one of these strings alone.
I tried the following :
let mutableString = "We must find a solution"
mutableString.draw(in: CGRect(x: 0, y: 0, width: 100, height: 30))
mutableString.draw(with: CGRect(x: 0, y: 0, width: 10, height: 10), options: .truncatesLastVisibleLine, context: .none)
mutableString.boundingRect(with: CGSize(width: 100, height: 40), options: [.truncatesLastVisibleLine,.usesFontLeading], context: .none)
But none of them worked. I want to reproduce the UILabel .lineBreakMode = .byTruncatingTail for a NSMutableAttributedString. Am I doing something wrong here, is there a way to achieve this?
Here is a solution using three UILabels in a UIStackView:
override func viewDidLoad() {
super.viewDidLoad()
let label1 = UILabel()
label1.text = "Arya said"
label1.font = UIFont.italicSystemFont(ofSize: 20.0)
let label2 = UILabel()
label2.text = "\"We must find a solution\""
label2.font = UIFont.boldSystemFont(ofSize: 20.0)
label2.lineBreakMode = .byTruncatingTail
let label3 = UILabel()
label3.text = "yesterday"
label3.font = UIFont.italicSystemFont(ofSize: 20.0)
let stack = UIStackView(arrangedSubviews: [label1, label2, label3])
stack.axis = .horizontal
stack.spacing = 4
label2.translatesAutoresizingMaskIntoConstraints = false
stack.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
label2.widthAnchor.constraint(equalToConstant: 100)
])
self.view.addSubview(stack)
NSLayoutConstraint.activate([
stack.centerXAnchor.constraint(equalTo: self.view.centerXAnchor),
stack.centerYAnchor.constraint(equalTo: self.view.centerYAnchor)
])
}
output:

UIView setting constraints

I am having trouble creating a UIView instance programmatically. I have a function that returns the view so that I can use it in an NSAttachment inside a UITextView.
This is what I am trying to achieve:
This is what I am getting in the simulator:
Code Below:
let fullView = UIView()
let firstButton = UIButton()
let secondButton = UIButton()
let thirdTextView = UITextView()
fullView.frame = CGRect(x: 0, y: 0, width: textView.frame.width, height: 90)
fullView.backgroundColor = UIColor(red:0.82, green:0.83, blue:0.85, alpha:1.0)
firstButton.setTitle(text1, for: .normal)
firstButton.setTitleColor(.black, for: .normal)
firstButton.titleLabel?.font = UIFont.systemFont(ofSize: 15, weight: UIFont.Weight.medium)
firstButton.contentHorizontalAlignment = .left
secondButton.setTitle("Button2", for: .normal)
secondButton.setTitleColor(.black, for: .normal)
secondButton.titleLabel?.font = UIFont.systemFont(ofSize: 15, weight: UIFont.Weight.medium)
secondButton.contentHorizontalAlignment = .right
thirdTextView.text = text2
let descriptionBarStackView = UIStackView()
descriptionBarStackView.axis = .horizontal
descriptionBarStackView.alignment = .fill
descriptionBarStackView.distribution = .fillProportionally
descriptionBarStackView.addArrangedSubview(firstButton)
descriptionBarStackView.addArrangedSubview(secondButton)
let viewWithStackViews = UIStackView()
viewWithStackViews.axis = .vertical
viewWithStackViews.alignment = .fill // .leading .firstBaseline .center .trailing .lastBaseline
viewWithStackViews.distribution = .fillEqually
viewWithStackViews.addArrangedSubview(descriptionBarStackView)
viewWithStackViews.addArrangedSubview(thirdTextView)
fullView.addSubview(viewWithStackViews)
descriptionBarStackView.translatesAutoresizingMaskIntoConstraints = false
thirdTextView.translatesAutoresizingMaskIntoConstraints = false
viewWithStackViews.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
viewWithStackViews.topAnchor.constraint(equalTo: fullView.topAnchor, constant: 5),
viewWithStackViews.leadingAnchor.constraint(equalTo: fullView.leadingAnchor, constant: 5),
viewWithStackViews.trailingAnchor.constraint(equalTo: fullView.trailingAnchor, constant: 5),
viewWithStackViews.bottomAnchor.constraint(equalTo: fullView.bottomAnchor, constant: 5),
])
Edit:
Check this updated method
func customView(){
let fullView = UIView()
let firstButton = UIButton()
let secondButton = UIButton()
let thirdTextView = UITextView()
fullView.frame = CGRect(x: 0, y: 0, width: mainView.frame.width, height: mainView.frame.height)
fullView.backgroundColor = UIColor(red:0.82, green:0.83, blue:0.85, alpha:1.0)
firstButton.setTitle("Button1", for: .normal)
firstButton.setTitleColor(.black, for: .normal)
firstButton.titleLabel?.font = UIFont.systemFont(ofSize: 15, weight: UIFont.Weight.medium)
firstButton.contentHorizontalAlignment = .left
firstButton.backgroundColor = .clear
secondButton.setTitle("Button2", for: .normal)
secondButton.setTitleColor(.black, for: .normal)
secondButton.titleLabel?.font = UIFont.systemFont(ofSize: 15, weight: UIFont.Weight.medium)
secondButton.contentHorizontalAlignment = .right
secondButton.backgroundColor = .clear
thirdTextView.text = "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum."
thirdTextView.backgroundColor = .clear
let descriptionBarStackView = UIStackView()
descriptionBarStackView.axis = .horizontal
descriptionBarStackView.alignment = .fill
descriptionBarStackView.distribution = .fillProportionally
descriptionBarStackView.isLayoutMarginsRelativeArrangement = true
descriptionBarStackView.directionalLayoutMargins = NSDirectionalEdgeInsets(top: 0, leading: 5, bottom: 0, trailing: 5)
descriptionBarStackView.addArrangedSubview(firstButton)
descriptionBarStackView.addArrangedSubview(secondButton)
let viewWithStackViews = UIStackView()
viewWithStackViews.axis = .vertical
viewWithStackViews.alignment = .fill // .leading .firstBaseline .center .trailing .lastBaseline
viewWithStackViews.distribution = .fill
viewWithStackViews.addArrangedSubview(descriptionBarStackView)
viewWithStackViews.addArrangedSubview(thirdTextView)
fullView.addSubview(viewWithStackViews)
descriptionBarStackView.translatesAutoresizingMaskIntoConstraints = false
thirdTextView.translatesAutoresizingMaskIntoConstraints = false
viewWithStackViews.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint(item: descriptionBarStackView, attribute: NSLayoutConstraint.Attribute.height, relatedBy: NSLayoutConstraint.Relation.equal, toItem: nil, attribute: NSLayoutConstraint.Attribute.notAnAttribute, multiplier: 1, constant: 30).isActive = true
NSLayoutConstraint.activate([
viewWithStackViews.topAnchor.constraint(equalTo: fullView.topAnchor, constant: 0),
viewWithStackViews.leadingAnchor.constraint(equalTo: fullView.leadingAnchor, constant: 0),
viewWithStackViews.trailingAnchor.constraint(equalTo: fullView.trailingAnchor, constant: 0),
viewWithStackViews.bottomAnchor.constraint(equalTo: fullView.bottomAnchor, constant: 0),
])
fullView.layer.cornerRadius = 5
mainView.addSubview(fullView)
}
here i have listed changes
mainView.frame.height full height based on main text view
firstButton.backgroundColor = .clear
secondButton.backgroundColor = .clear
thirdTextView.backgroundColor = .clear
margin for button
descriptionBarStackView.isLayoutMarginsRelativeArrangement = true
descriptionBarStackView.directionalLayoutMargins = NSDirectionalEdgeInsets(top: 0, leading: 5, bottom: 0, trailing: 5)
NSLayoutConstraint constraint zero to all in full view
fullView.layer.cornerRadius = 5
thats it here is result..
Tried to preserve your layout structure
class VisualTestViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
let fullView = UIView()
fullView.backgroundColor = UIColor(red:0.82, green:0.83, blue:0.85, alpha:1.0)
fullView.translatesAutoresizingMaskIntoConstraints = false
let firstButton = UIButton()
firstButton.translatesAutoresizingMaskIntoConstraints = false
let secondButton = UIButton()
secondButton.translatesAutoresizingMaskIntoConstraints = false
let thirdTextView = UITextView()
thirdTextView.translatesAutoresizingMaskIntoConstraints = false
thirdTextView.text = "lorem ipsumlorem ipsumlorem ipsumlorem ipsumlorem ipsumlorem ipsumlorem ipsumlorem ipsumlorem ipsum"
let text1 = "Button1"
firstButton.setTitle(text1, for: .normal)
firstButton.setTitleColor(.black, for: .normal)
firstButton.titleLabel?.font = UIFont.systemFont(ofSize: 15, weight: UIFont.Weight.medium)
firstButton.contentHorizontalAlignment = .left
secondButton.setTitle("Button2", for: .normal)
secondButton.setTitleColor(.black, for: .normal)
secondButton.titleLabel?.font = UIFont.systemFont(ofSize: 15, weight: UIFont.Weight.medium)
secondButton.contentHorizontalAlignment = .right
let descriptionBarStackView = UIStackView(arrangedSubviews: [firstButton, UIView() ,secondButton])
descriptionBarStackView.translatesAutoresizingMaskIntoConstraints = false
descriptionBarStackView.axis = .horizontal
descriptionBarStackView.alignment = .center
let viewWithStackViews = UIStackView(arrangedSubviews: [descriptionBarStackView, thirdTextView])
viewWithStackViews.translatesAutoresizingMaskIntoConstraints = false
viewWithStackViews.axis = .vertical
viewWithStackViews.layoutMargins = UIEdgeInsets(top: 5, left: 5, bottom: 5, right: 5)
viewWithStackViews.isLayoutMarginsRelativeArrangement = true
fullView.addSubview(viewWithStackViews)
fullView.leadingAnchor.constraint(equalTo: viewWithStackViews.leadingAnchor).isActive = true
fullView.trailingAnchor.constraint(equalTo: viewWithStackViews.trailingAnchor).isActive = true
fullView.topAnchor.constraint(equalTo: viewWithStackViews.topAnchor).isActive = true
fullView.bottomAnchor.constraint(equalTo: viewWithStackViews.bottomAnchor).isActive = true
view.addSubview(fullView)
fullView.widthAnchor.constraint(equalTo: view.widthAnchor, constant: -10).isActive = true
fullView.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
fullView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
thirdTextView.isScrollEnabled = false
thirdTextView.backgroundColor = .clear
}
}
You are setting
viewWithStackViews.distribution = .fillEqually
Because of .fillEqually the StackView will allocate equal height to the subviews. Thats why you are seeing buttons in half of viewWithStackViews and thirdTextView in remaining half.
So change it to:
viewWithStackViews.distribution = .fillProportionally
One more thing: your thirdTextView is not getting its height according to its content.
Try this to allow you thirdTextView to get height automatically. In case the text might be too long, set scrolledEnabled to true and set height contraint for the textView.
thirdTextView.translatesAutoresizingMaskIntoConstraints = true
thirdTextView.sizeToFit()
thirdTextView.scrollEnabled = false

Resources