Reduce space around a textfield using auto layout - swift

I'm currently using a stackView that contains a few vertically aligned elements. I'm doing everything in code.
I've marked every stack view's subview. Here's a screenshot:
This is the code for the stackview:
func setUpVerticalLayoutStackView() {
stackViewForVerticalLayout = UIStackView(arrangedSubviews: [viewTitleButton, stackViewForTheTwoPersons, owedButton, amountTextField, doneButton])
stackViewForVerticalLayout.axis = .vertical
stackViewForVerticalLayout.distribution = .equalCentering
stackViewForVerticalLayout.alignment = .center
stackViewForVerticalLayout.spacing = 20
stackViewForVerticalLayout.translatesAutoresizingMaskIntoConstraints = false
self.addSubview(stackViewForVerticalLayout)
}
My question is: why does the textfield occupy so much space? Can I reduce it somehow?
Don't mind the "r" at the center of the view, it's just something I mistakenly added in an image editor.

Change stackViewForVerticalLayout.distribution to .equalSpacing or .fillEqually depending on what you want. This will reduce this size of the field

Related

How do I make a Swift UIStackView size dynamically based on content?

I have added the following UIStackView to my ViewController. As the views change the values of textOneLabel and textTwoLabel change.
With the following code the initial StackView is centered and the portions filled proportionally. However with subsequent text combinations the bounds of the StackView don't change, leaving the content off center. How can I change the StackView properties so it will adapt to the content and always stay centered?
headerStackView.axis = .horizontal
headerStackView.distribution = .fillProportionally
headerStackView.spacing = 8
headerStackView.layer.borderWidth = 1
headerStackView.layer.borderColor = UIColor.red.cgColor
headerStackView.translatesAutoresizingMaskIntoConstraints = false
headerStackView.topAnchor.constraint(equalTo: timerHeader.topAnchor, constant: 4).isActive = true
headerStackView.centerXAnchor.constraint(equalTo: timerHeader.centerXAnchor).isActive = true
headerStackView.addArrangedSubview(textOneLabel)
headerStackView.addArrangedSubview(textTwoLabel)
First, forget you ever heard of .fillProportionally...
it doesn't do what you think it does
you'll encounter very unexpected layout issues if your stack view has spacing greater than Zero
if your stack view has no width (neither width anchor nor leading/trailing anchors), .fillProportionally doesn't do anything
So, change your .distribution to .fill.
Add these lines to control what auto-layout does with your labels:
textOneLabel.setContentHuggingPriority(.required, for: .horizontal)
textOneLabel.setContentCompressionResistancePriority(.required, for: .horizontal)
textTwoLabel.setContentHuggingPriority(.required, for: .horizontal)
textTwoLabel.setContentCompressionResistancePriority(.required, for: .horizontal)
Now, your stackView FRAME will remain centered.

Determining when a view created programmatically did appear on screen

I have a textField which, when tapped, pops up a vertical stackView created programatically. The stackView is a child of the textField. textField.addSubview(stackView) places the stackView's origin at the textField's origin. I wish to move the stackView's origin up vertically by an amount equal to the height of the stackView. If I do:
func textFieldDidBeginEditing(_ textField: UITextField) {
stackView = UIStackView() // var stackView: UIStackView!
stackView.axis = .vertical
stackView.alignment = .leading
stackView.distribution = .fillEqually
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.spacing = 0
stackView.backgroundColor = .white
stackView.layer.borderWidth = 1
stackView.layer.borderColor = UIColor.gray.cgColor
for title in titleList {
stackView.addArrangedSubview(createButton(title))
}
textField.addSubview(stackView)
stackView.transform = CGAffineTransform(translationX: 0, y: -stackView.frame.height)
}
It doesn't work. The stackView's origin remains at the textField's origin. But if I use a constant:
textField.addSubview(stackView)
stackView.transform = CGAffineTransform(translationX: 0, y: -144)
it works. I have confirmed through debugging that stackView.frame.height is 0 at the time the CGAffineTransform is performed. If I check its value after it has exited the function, I do see that its height is 144. That means the stackView hasn't actually appeared yet within the function.
Is there a way to catch when the programmatically-created stackView appears (sort of like the way viewDidAppear(_:) works) so that I can then set its position on the screen?
I don't think there is a canonical way to know when an individual view has appeared on screen. You might be able to hook into the draw method (which is hacky), but a stack view doesn't draw anything itself, so I don't think that works.
You could probably use UIViewController.viewDidLayoutSubviews, but you might need to add the stack view to the root view for that to work.
In any case, adding the UIStackView as a subview of the UITextField is bad practice. You probably want to add it to the parent view instead. And yes, using AutoLayout is the way to go here. Let the framework handle the layout, don't try to do it using transforms.

UIStackView and a placeholder view in another UIStackView problem

There is a problem if you have a UIStackView(testStack) and a placeholder UIView(testView) inside another UIStackView(mainStack). It is meant that if there is no content in the testStack it will collapse, and the testView will take all the space. There is even a content hugging priority set to maximum for the testStack so it should collapse its height to 0 when there are no subviews. But it does not. How to make it collapse when there is no content?
PS If there are items in the testStack, everything works as expected: testView takes all available space, testStack takes only the space to fit its subviews.
class AView: UIView {
lazy var mainStack: UIStackView = {
let stack = UIStackView()
stack.axis = .vertical
stack.backgroundColor = .gray
stack.addArrangedSubview(self.testStack)
stack.addArrangedSubview(self.testView)
return stack
}()
let testStack: UIStackView = {
let stack = UIStackView()
stack.backgroundColor = .blue
stack.setContentHuggingPriority(.init(1000), for: .vertical)
return stack
}()
let testView: UIView = {
let view = UIView()
view.backgroundColor = .red
return view
}()
init() {
super.init(frame: .zero)
backgroundColor = .yellow
addSubview(mainStack)
mainStack.translatesAutoresizingMaskIntoConstraints = false
mainStack.topAnchor.constraint(equalTo: topAnchor).isActive = true
mainStack.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
mainStack.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true
mainStack.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
When auto-layout arranges subviews in a UIStackView, it looks at:
the stack view's .distribution property
the subviews' height constraints (if given)
the subviews' Intrinsic Content Size
Since you have not specified a .distribution property, mainStack is using the default of .fill.
A UIStackView has NO Intrinsic Content Size, so auto-layout says "testStack has a height of Zero"
A UIView has NO Intrinsic Content Size, so auto-layout says "testView has a height of Zero"
Since the distribution is fill, auto-layout effectively says: "the heights of the arranged subviews are ambiguous, so let's give the last subview a height of Zero, and fill mainStack with the first subview.
Setting .setContentHuggingPriority will have no effect, because there is no intrinsic height to "hug."
If you set mainStack's .distribution = .fillEqually, you will get (as expected) testStack filling the top half, and testView filling the bottom half.
If you set mainStack's .distribution = .fillProportionally, you will get the same result... testStack filling the top half, and testView filling the bottom half, because .fillProportionally uses the arranged subviews' Intrinsic Content Sizes... in this case, they are both Zero, so "proportional" will be equal.
If you set mainStack's .distribution = .equalSpacing or .distribution = .equalCentering, you won't see either testStack or testView ... auto-layout will give each of them a height of Zero, and fill the rest of mainStack with (empty) "spacing."
If your goal is to have testStack "disappear" if it is empty, you can either:
set it hidden, or
subclass it and give it an intrinsic height

iOS - Constraint to UIImage with centrally aligned UILabel's text

I have UITableViewCell that has a UIlabel aligned center I'm setting image in default imageView property of the UITableViewCell but since text is aligned center there is a gap between text and the image.
I want image then little space then text all center to UITableViewCell I have tried following code,
cell.imageView?.image = image
cell.imageView?.translatesAutoresizingMaskIntoConstraints = false
cell.imageView?.centerYAnchor.constraint(equalTo: label.centerYAnchor).isActive = true
let rect: CGRect = label.textRect(forBounds: label.bounds, limitedToNumberOfLines: 1)
cell.imageView?.leadingAnchor.constraint(equalTo: label.leadingAnchor, constant: rect.origin.x - padding).isActive = true
That works for me but when I switch device from iPhone 11 Max Pro to iPhone 8 image overlaps the text because label.textRect always brings the same text irrespective of screen size
I have also tried using range of the first later and using it's rect but same problem of not being changed per screen size.
Can this be achieved without putting custom UIImageView in UITableViewCell?
You could use a stackView that you center inside your cell and add your imageView and your label as arranged subViews. Note that you would need to create a custom cell.
Create your stackView:
let stackView: UIStackView = {
let stackView = UIStackView()
stackView.axis = .horizontal
stackView.alignment = .center
stackView.distribution = .fill
stackView.spacing = 10 // You can set the spacing accordingly
return stackView
}()
Layout as follows:
contentView.addSubview(stackView)
// Swap these two lines if instead you want label then image
stackView.addArrangedSubview(image)
stackView.addArrangedSubview(label)
// StackView
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.centerYAnchor.constraint(equalTo: self.contentView.centerYAnchor).isActive = true
stackView.centerXAnchor.constraint(equalTo: self.contentView.centerXAnchor).isActive = true

Swift, hide overflowing text

I have a label with a single character that I wish to be bigger than the UIView that it's a subview of. However, I need to hide the overflowing parts of the character.
For an example, this is the result I'm trying to achieve:
Where the character/Icon is the tags in lighter green in the background
Like the above example I'm using the font called "fontawesome" and their icon set. However in Swift I havn't been able to find any options to hide the overflowing parts.
Here's the current code:
var actionBox = UIView()
var actionLabel = UILabel()
var actionIcon = UILabel()
// #actionBox
actionBox.translatesAutoresizingMaskIntoConstraints = false
actionBox.backgroundColor = UIColor.formulaGreenColor()
cellView.addSubview(actionBox)
actionIcon.translatesAutoresizingMaskIntoConstraints = false
actionIcon.font = UIFont(name: "fontawesome", size: 80)
actionIcon.text = ""
actionIcon.textColor = UIColor.colorWithHex("#13E6A7")
actionBox.addSubview(actionIcon)
I know I can technically make another 2 UIViews, with a background color, and layer those on top. But that's not exactly an elegant solution.
So how can I hide the rest of the label, that's overflowing it's superView?
Any help would be greatly appreciated!
You are searching for clipsToBounds property:
view.clipsToBounds = true
Where view is your container view.