Creating UILabel programmatically with constraints? - swift

What I want to do is create a new UILabel programmatically every time a certain action occurs in my code. I know the x, y, and height that I want to give the label, but I don't want to give it a set width. I want to constrain the sides so that the UILabel width is equal to the width of the screen, and so that the label width will change if the orientation is flipped.
I have considered using:
CGRect(x:, y:, width:, height:)
However, I would have to give it a set width if I use this, so I don't think it will work.
I also tried using:
CGPoint(x:, y:)
Then setting leading, trailing and height anchors, however, this doesn't seem to work either as even though it does compile, I get an error when I try creating a new UILabel.
I'm kind of new to programming in Swift so I'm not sure if there is an obvious fix to this.

Like you said, we already have x, y and height available for the label, i.e.
let x: CGFloat = 0
let y: CGFloat = 0
let height: CGFloat = 50
Let's create a label using the above details. Also set the width of the label as UIScreen.main.bounds.width as per your requirement.
let label = UILabel(frame: CGRect(x: x, y: y, width: UIScreen.main.bounds.width, height: height))
label.text = "This is a sample text"
Don't forget to set label's translatesAutoresizingMaskIntoConstraints as false and add it to whatever subview you want.
label.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(label)
Add the relevant constraints of label with its superview - top, bottom, leading, height. You can definitely add bottom constraint if required. That totally depends upon your UI.
I'm adding the label to the top of the viewController's view.
NSLayoutConstraint.activate([
label.heightAnchor.constraint(equalToConstant: height),
view.topAnchor.constraint(equalTo: label.topAnchor),
view.leadingAnchor.constraint(equalTo: label.leadingAnchor),
view.trailingAnchor.constraint(equalTo: label.trailingAnchor)
])

You can use following code to create UILabel programmatically.
private let label: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.numberOfLines = 0
label.text = "Hello World"
return label
}()
Then inside your viewDidLoad()
addSubview(label)
NSLayoutConstraint.activate([
topAnchor.constraint(equalTo: label.topAnchor),
bottomAnchor.constraint(equalTo: label.bottomAnchor),
leadingAnchor.constraint(equalTo: label.leadingAnchor),
trailingAnchor.constraint(equalTo: label.trailingAnchor)
])

Related

Swift 5: centerXAnchor won't center my frame in the view

I'm stuck at the Consolidation IV challenge from hackingwithswift.com.
Right now I'm trying to create a hangman game. I thought to place placeholder labels based on the length of the answer word. These placeholder labels would be placed inside a frame, which then would be placed in the center of the main view.
Unfortunately, the leading edge of the frame is placed centered. In my opinion, this is not a problem of constraints, but rather a problem of me creating the frame wrong.
My current code is
import UIKit
class ViewController: UIViewController {
var answer: String!
override func viewDidLoad() {
super.viewDidLoad()
// MARK: - declare all the labels here
let letterView = UIView()
letterView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(letterView)
// MARK: - set constraints to all labels, buttons etc.
NSLayoutConstraint.activate([
letterView.topAnchor.constraint(equalTo: view.layoutMarginsGuide.topAnchor),
letterView.centerXAnchor.constraint(equalTo: view.layoutMarginsGuide.centerXAnchor)
])
// MARK: - populate the letterView
// set the size of the placeholder
answer = "Atmosphäre"
let height = 60
let width = 25
// var width: Int
for placeholder in 0..<answer.count {
// create new label and give it a big font size
let placeholderLabel = UILabel()
placeholderLabel.text = "_"
placeholderLabel.font = UIFont.systemFont(ofSize: 36)
// calculate frame of this label
let frame = CGRect(x: placeholder * width, y: height, width: width, height: height)
placeholderLabel.frame = frame
// add label to the label view
letterView.addSubview(placeholderLabel)
}
}
}
The simulator screen looks just like this:
I already searched for answers on stackoverflow, but wasn't successful. I think I don't know what I'm exactly looking for.
The main problem, is that the letterView has no size, because no width or height constraints are applied to it.
To fix your code make the letterView big enough to contain the labels you've added as subviews by adding height and width constraints after the for loop:
for placeholder in 0..<answer.count {
...
}
NSLayoutConstraint.activate([
letterView.widthAnchor.constraint(equalToConstant: CGFloat(width * answer.count)),
letterView.heightAnchor.constraint(equalToConstant: CGFloat(height))
])
I'm not sure if you've covered this in your course yet, but a better way to go about this (which would take much less code), is to use a UIStackView as your letterView instead.
An extra thing to consider:
If you give the letterView a background color, you'll see that the labels are actually aligned outside of its bounds:
That's because you're setting each label's y position to be height, when it should probably be zero:
let frame = CGRect(x: placeholder * width, y: 0, width: width, height: height)
Correcting this places the labels within the bounds of the letterView:

Setting UIImageView Width, Height, Aspect Ratio & Constraints X, Y

I've set a UIImageView in my view controller and assigned constraints x & Y which works I'm trying to set the image width, height & aspect ratio but it doesn't seem to be working.
I've tried many answers already on here but can seem to get it working.
import UIKit
class GuestLoginViewController: UIViewController {
let headerImage = UIImageView()
override func viewDidLoad() {
super.viewDidLoad()
setHeaderImage()
}
func setHeaderImage() {
view.addSubview(headerImage)
headerImage.frame = CGRect(x: 0, y: 0, width: super.view.frame.width, height: 95)
headerImage.translatesAutoresizingMaskIntoConstraints = false
headerImage.mask?.contentMode = UIView.ContentMode.redraw
headerImage.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
headerImage.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
headerImage.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
headerImage.image = UIImage(named: "header")
view.sendSubviewToBack(headerImage)
}
}
As soon as you set headerImage.translatesAutoresizingMaskIntoConstraints = false, the frame is ignored. You need to set some constraint to establish the height of your UIImageView. Unfortunately, the image contents does not affect the height of the UIImageView.
Set either:
an absolute height constraint
an offset from the bottom of the superView
height relative to the width with a multiplier (an "aspect ratio constraint")
Based on my comment, you turned off headerImage.translatesAutoresizingMaskIntoConstraints = false and it worked.
This gives you extra constraints (your 3 plus the 4 that are generated from the frame), but luckily they aren't conflicting.
Instead, I would suggest you leave the translatesAutoresizingMaskIntoConstraints set to false and set a constraint for the height:
headerImage.heightAnchor.constraint(equalToConstant: 95).isActive = true

How to use frame to set x position in loop on Swift

I have an array that count is between 1 to 3 varies.
I want to frame them in the middle of the screen with a certain distance
Also the width of the labels is fixed
let array = ["some1", "some2", "some3"]
func setLabel(){
var i = -1
for text in array{
i += 1
let label = UILabel()
label.fram = CGRect(x: screenWidth/2 - (CGFloat(i)*50) + 25, y: 100, width: 50 , height: 20)
label.text = text
addSubview(label)
}
}
You can use stackview as suggested by #matt. If you don't want to use stackview you can calculate the x position by array count and label index like this
let array = ["some1", "some2", "some3"]
func setLabel(){
var i:CGFloat = 0
for text in array{
i += 1
let label = UILabel()
label.backgroundColor = .red
label.frame = CGRect(x: (view.bounds.width/CGFloat(array.count+1))*i-25, y: 100, width: 50 , height: 20)
label.text = text
view.addSubview(label)
}
}
The really easy way to do this is to put the labels (one, two, or three of them) into a centered UIStackView. You would still need to know the desired fixed width of a label and apply a width constraint to each label, but everything else will just take care of itself.
Just as a demonstration, I used an arbitrary width of 100, with the text for all three labels being just "UILabel" as in your image. Here's what it looks like with one label:
Here's what it looks like with three labels:
Here's the code I used (sv is the stack view, which has already been configured in the storyboard; n is how many labels we want):
for _ in 1...n {
let lab = UILabel()
lab.text = "UILabel"
lab.textAlignment = .center
lab.translatesAutoresizingMaskIntoConstraints = false
lab.widthAnchor.constraint(equalToConstant: 100).isActive = true
sv.addArrangedSubview(lab)
}
So all you really need to do is get the right text from your array as we loop, and you're done. The point is that you don't need to think about frames at all! The UIStackView imposes the correct the frames for us.
NOTE Although I said above that the stack view was "configured in the storyboard", that has nothing to do with the answer. You can create and configure the stack view in code just as well, without changing anything about my answer. The point is that the stack view already knows how to receive an arbitrary number of views, space them out evenly, and center them as a whole. That is what it is for. So if, as your question implies, you can't manage the arithmetic to assign the views a frame yourself, why not let the stack view do it for you?

How to add a UILabel to a scrollview in order to show all the text in the label

I'm trying to add 2 UILabels to a scrollview programatically. The text within the UILabel is dynamic, and I dont know what the text will be since it's loaded from a database. Therefore I cant know what size I should set my UILabel. Im using a scrollview to display all the text within each label.
How do I go about doing this? Currently, I'm using the following code;
let textArray = [job.title, job.description]
for stage in 0...1{
let label = UILabel()
label.font = UIFont(name: "CenturyGothic", size: 20)
label.textColor = #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1)
label.textAlignment = .center
label.lineBreakMode = .byWordWrapping
label.numberOfLines = 0
label.text = textArray[stage]
scrollView.addSubview(label)
}
with the title and description being dynamic and pulled from a database. However, this doesnt display any text, let alone my scrollview. Any help would be appreciated.
Did you think about using a UITextView?
If you want to use UILabels I'd advise to put a vertical UIStackView inside your UIScrollView to put the UILabels in.
Set the the top, bottom, trailing and leading anchors to zero for scrollview to superview and for stackview to scrollview. Also set equal width constraints for superview to stackview. The vertical stack will calculate it's height automatically based on its content, but the width needs to be set from outside. The scroll view will become scrollable as soon as its content height exceeds its height.
Also don't forget to set the number of lines for the labels to zero to allow them to display an endless amount of line.
You can set all of this up programmatically or set up everything but the labels in storyboards which is way faster and reduces the amount of boilerplate code in your ViewController.
You have to set the frame of the label as well.
let label = UILabel(frame: CGRect(x: self.view.frame.size.width/2 - 75, y: self.view.frame.size.height-100, width: 150, height: 35))
label.autoresizingMask = [.flexibleWidth, .flexibleHeight]
label.textColor = UIColor.white
label.textAlignment = .center;
lLabel.font = UIFont(name: "Montserrat-Light", size: 12.0)
label.text = message
label.alpha = 1.0
self.scrollView.addSubview(label)

UIScrollView wrong offset

I have several labels inside an scrollview. Each label is an "page". But the offset isn't right. And I can't find the problem.
My code:
let quantity = 10;
var width : CGFloat = 0.0;
override func viewDidLoad() {
super.viewDidLoad()
width = self.scrView.frame.width;
for i in 0..<quantity {
let xPos = width * CGFloat(i);
let label = UILabel(frame: CGRect(x: xPos, y: 0, width: width, height: self.scrView.frame.height))
label.textAlignment = .center
label.font = UIFont(name: "Helvetica Neue", size: 50)
label.textColor = UIColor.white
label.text = "\(i + 1)"
label.backgroundColor = .randomColor()
scrView.addSubview(label);
}
scrView.contentSize.width = width * CGFloat(quantity + 1);
}
The pink color on the left side is from the previous "page".
And again, note the purple color on the left side from the previous (number 8) page.
The first page seems fine. However the higher the number the greater the offset. I want to cover the whole visible area for 1 label.
Does somebody knows an solution?
Your code works fine for me.
Note that the size of the scrollView can change after viewDidLoad as a result of Auto Layout. A better place to set up your scrollView would be in an override of viewDidLayoutSubviews. The size of the scrollView will be established by then. But be careful, because viewDidLayoutSubviews will run multiple times, so make sure you only set up your scrollView once.
This is not your bug, but your contentSize.width is too big. You don't need to add 1 to quantity since there are exactly quantity labels in your scrollView content area.
scrView.contentSize.width = width * CGFloat(quantity)
Also, Swift doesn't need the ; at the ends of lines, so delete those.