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

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)
])
}

Related

Collapsed navigation bar title is unintentionally expanded to large title if any UI element is modified

To demonstrate the problem, I wrote a very simple demo app. If I scroll down on the scroll view, the navigation bar title collapses as desired. If the button on the bottom is pressed, the button image is changed. However, this also causes the navigation bar to show a large title again, which is not desired. The same problem also occurs if for example the visibility of a UI element is changed (button.isHidden = true), or if any other UI element is modified in a number of other ways.
Is there any possibility to avoid this undesired behaviour, i.e., for the title to remain in its collapsed state even if UI elements are modified?
Code:
import UIKit
class ViewController: UIViewController {
var buttonActivated = false
let scrollView: UIScrollView = {
let view = UIScrollView()
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = .clear
return view
}()
let cardView1 : UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = .red
view.layer.cornerRadius = 12.5
view.layer.shadowOffset = CGSize(width: 0, height: 5)
view.layer.shadowRadius = 5
view.layer.shadowOpacity = 0.3
return view
}()
let cardView2 : UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = .red
view.layer.cornerRadius = 12.5
view.layer.shadowOffset = CGSize(width: 0, height: 5)
view.layer.shadowRadius = 5
view.layer.shadowOpacity = 0.3
return view
}()
let cardView3 : UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = .red
view.layer.cornerRadius = 12.5
view.layer.shadowOffset = CGSize(width: 0, height: 5)
view.layer.shadowRadius = 5
view.layer.shadowOpacity = 0.3
return view
}()
let cardView4 : UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = .red
view.layer.cornerRadius = 12.5
view.layer.shadowOffset = CGSize(width: 0, height: 5)
view.layer.shadowRadius = 5
view.layer.shadowOpacity = 0.3
return view
}()
let button: UIButton = {
let button = UIButton()
button.translatesAutoresizingMaskIntoConstraints = false
button.setTitle("Test Button ", for: .normal)
button.setTitleColor(UIColor.label, for: .normal)
button.setImage(UIImage(systemName: "chevron.down"), for: .normal)
button.tintColor = UIColor.label
button.titleLabel?.font = .systemFont(ofSize: 12, weight: .regular)
button.contentHorizontalAlignment = .center
button.semanticContentAttribute = .forceRightToLeft
button.addTarget(self, action: #selector(buttonPressed), for: .touchUpInside)
return button
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(scrollView)
scrollView.addSubview(cardView1)
scrollView.addSubview(cardView2)
scrollView.addSubview(cardView3)
scrollView.addSubview(cardView4)
scrollView.addSubview(button)
NSLayoutConstraint.activate([
scrollView.topAnchor.constraint(equalTo: view.topAnchor),
scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
cardView1.topAnchor.constraint(equalTo: scrollView.topAnchor, constant: 20),
cardView1.leadingAnchor.constraint(equalTo: scrollView.readableContentGuide.leadingAnchor, constant: 20),
cardView1.trailingAnchor.constraint(equalTo: scrollView.readableContentGuide.trailingAnchor, constant: -20),
cardView1.heightAnchor.constraint(equalToConstant: 300),
cardView2.topAnchor.constraint(equalTo: cardView1.bottomAnchor, constant: 20),
cardView2.leadingAnchor.constraint(equalTo: scrollView.readableContentGuide.leadingAnchor, constant: 20),
cardView2.trailingAnchor.constraint(equalTo: scrollView.readableContentGuide.trailingAnchor, constant: -20),
cardView2.heightAnchor.constraint(equalToConstant: 300),
cardView3.topAnchor.constraint(equalTo: cardView2.bottomAnchor, constant: 20),
cardView3.leadingAnchor.constraint(equalTo: scrollView.readableContentGuide.leadingAnchor, constant: 20),
cardView3.trailingAnchor.constraint(equalTo: scrollView.readableContentGuide.trailingAnchor, constant: -20),
cardView3.heightAnchor.constraint(equalToConstant: 300),
cardView4.topAnchor.constraint(equalTo: cardView3.bottomAnchor, constant: 20),
cardView4.leadingAnchor.constraint(equalTo: scrollView.readableContentGuide.leadingAnchor, constant: 20),
cardView4.trailingAnchor.constraint(equalTo: scrollView.readableContentGuide.trailingAnchor, constant: -20),
cardView4.heightAnchor.constraint(equalToConstant: 300),
button.topAnchor.constraint(equalTo: cardView4.bottomAnchor, constant: 20),
button.leadingAnchor.constraint(equalTo: scrollView.readableContentGuide.leadingAnchor, constant: 20),
button.trailingAnchor.constraint(equalTo: scrollView.readableContentGuide.trailingAnchor, constant: -20),
button.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor, constant: -20),
])
}
#objc private func buttonPressed() {
if !buttonActivated {
button.setImage(UIImage(systemName: "chevron.up"), for: .normal)
}
else {
button.setImage(UIImage(systemName: "chevron.down"), for: .normal)
}
buttonActivated = !buttonActivated
}
}
Edit: The same problem is present if the UI change is performed from the main thread, e.g.:
DispatchQueue.main.async {
self.button.setImage(UIImage(systemName: "chevron.down"), for: .normal)
}

Swift - UIView moves when changing tab controller tabs

Inside the viewDidAppear I have a function that contains this code, in order to make a UIView:
let contentView = UIView()
func addSleepingView() {
contentView.backgroundColor = .systemYellow.withAlphaComponent(0.25)
view.addSubview(contentView)
contentView.translatesAutoresizingMaskIntoConstraints = false
contentView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
contentView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
// Anchor your view right above the tabBar
contentView.bottomAnchor.constraint(equalTo: (tabBarController?.tabBar.topAnchor)!).isActive = true
contentView.heightAnchor.constraint(equalToConstant: 50).isActive = true
let label = UILabel()
label.text = "Test"
label.frame = CGRect(x: 0, y: 0, width: 25, height: 34.0)
label.lineBreakMode = .byWordWrapping
label.numberOfLines = 0
label.textAlignment = .center
label.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(label)
label.centerXAnchor.constraint(equalTo: contentView.centerXAnchor).isActive = true
label.centerYAnchor.constraint(equalTo: contentView.centerYAnchor).isActive = true
let button = UIButton()
button.setImage(UIImage(systemName: "arrow.clockwise", withConfiguration: UIImage.SymbolConfiguration(scale: .large)), for: UIControl.State.normal)
button.tintColor = .systemGray
button.frame = CGRect(x: self.view.bounds.width-42, y: 8, width: 34, height: 34.0)
button.isUserInteractionEnabled = true
button.addTarget(self, action: #selector(wakeupFunction), for: .touchUpInside)
contentView.addSubview(button)
let button2 = UIButton()
button2.setImage(UIImage(systemName: "exclamationmark.triangle", withConfiguration: UIImage.SymbolConfiguration(scale: .large)), for: UIControl.State.normal)
button2.tintColor = .label
button2.frame = CGRect(x: 8, y: 8, width: 34, height: 34.0)
button2.isUserInteractionEnabled = false
contentView.addSubview(button2)
contentView.bringSubviewToFront(button)
}
This is what it looks like:
Now this is exactly how I want it. The problem comes when I change tab. For example go to the last tab, and back to the first tab again. Then it looks like this:
What am I doing wrong here?
You can just add your code of activating constraints inside the DispatchQueue.main block
Updated Line you can change and it will start working as you're expectation.
// Anchor your view right above the tabBar
DispatchQueue.main.async
{
self.contentView.bottomAnchor.constraint(equalTo: (self.tabBarController?.tabBar.topAnchor)!).isActive = true
self.contentView.heightAnchor.constraint(equalToConstant: 50).isActive = true
}

Why is space added between UIButtons?

I have the following code
let mostRead = UIButton(frame: CGRect(x: 0, y: tableView.frame.origin.y + tableView.frame.height, width: self.view.bounds.size.width, height: 160))
mostRead.setImage(UIImage.init(named: "mostread"), for: .normal)
mostRead.addTarget(self, action:#selector(self.goToMostRead(sender:)), for: .touchUpInside)
innerView.addSubview(mostRead)
let videoTitles = UIButton(frame: CGRect(x: 0, y: mostRead.frame.maxY, width: self.view.bounds.size.width, height: 160))
videoTitles.setImage(UIImage.init(named: "videotitles"), for: .normal)
videoTitles.addTarget(self, action:#selector(self.goToVideoTitles(sender:)), for: .touchUpInside)
innerView.addSubview(videoTitles)
let audioTitles = UIButton(frame: CGRect(x: 0, y: videoTitles.frame.maxY, width: self.view.bounds.size.width, height: 160))
audioTitles.setImage(UIImage.init(named: "audiotitles"), for: .normal)
audioTitles.addTarget(self, action:#selector(self.goToAudioTitles(sender:)), for: .touchUpInside)
innerView.addSubview(audioTitles)
Space gets added between the UI Buttons. If you notice in the picture, there's a lot of space between each image even though I specified no space between images. Does anyone know why that's happening?
You try to add it with stackView and auto layout like this:
first set your object under your controller class:
let buttonmostRead: UIButton = {
let button = UIButton()
button.backgroundColor = .red
button.setBackgroundImage(UIImage(named: "mostRead"), for: .normal)
button.layer.cornerRadius = 8
button.clipsToBounds = true
return button
}()
let buttonvideoTitles: UIButton = {
let button = UIButton()
button.backgroundColor = .red
button.setBackgroundImage(UIImage(named: "videoTitles"), for: .normal)
button.layer.cornerRadius = 8
button.clipsToBounds = true
return button
}()
let buttonaudiotitles: UIButton = {
let button = UIButton()
button.backgroundColor = .red
button.setBackgroundImage(UIImage(named: "audiotitles"), for: .normal)
button.layer.cornerRadius = 8
button.clipsToBounds = true
return button
}()
after that in viewDiLoad set staclView an constraints:
let stackView = UIStackView(arrangedSubviews: [buttonmostRead, buttonvideoTitles, buttonaudiotitles])
stackView.axis = .vertical
stackView.distribution = .fillEqually
stackView.spacing = 8
stackView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(stackView)
stackView.heightAnchor.constraint(equalToConstant: 466).isActive = true
stackView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -20).isActive = true
stackView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 20).isActive = true
stackView.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: 0).isActive = true
to set space between button, change stackView spacing...
and this is the result
I put stackView in the center of the view but you can place them wherever you want by changing the constraints...

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

Self-sizing UILabel with UIView Subclassing

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.

Resources