UIButton not render exactly as XIB's contentVerticalAlignment value - ios

I want to button 's top is align to the button's titleLabel's top , so I set the content vertical alignment to top in xib . this is works well in xib , but after build , the titleLabel seems still layout with center vertical alignment . What did I miss?

First, a comment: What you see in Storyboard / Interface Builder:
is not always exactly what UIKit renders in the Simulator
which is not always exactly what UIKit renders on a Device
This is why we test, test, test... on different simulators and devices.
So, what's going on here?
UIKit uses the frame of the button's .titleLabel to determine the button's intrinsic size.
For a default button, with no explicit width or height set, UIKit insets the title label by 6-pts on the Top and Bottom.
Here are 2 buttons - both with no Height constraint. Button 1 is the default Content Alignment of center/center, Button 2 is set to Left/Top. The button title label background is cyan, so we can easily see its frame.
Storyboard / IB:
Runtime:
Debug View Hierarchy (note the label frame vs the button frame):
So, with an 18-pt system font, the title label height is 21.0 ... add 6-pts top and bottom and the button frame height is 33-pts.
It doesn't matter whether you set the Control Alignment to Top / Center / Bottom or Fill ... UIKit still takes the label height and adds 6-pts Top and Bottom "padding."
What to do to get actual Top alignment? Let's look at a couple approaches.
Here are 6 buttons in a stack view:
Button 1 is at the default center/center, with no Height constraint.
Button 2 as we've seen, has Control Alignment Left / Top ... but has no effect on the vertical alignment.
Button 3 is also Left/Top, but let's give it an explicit Height (we'll use 80 to make things obvious). Looks better - but there is still 6-pts of "padding" added to the top of the title label.
Now, you may have seen this at the top of the Size Inspector panel:
This looks promising! Let's set the Title Insets Top to Zero!
Whoops -- it's already Zero?!?!?!?
Same thing with the Content Insets!
Turns out, if the the edge insets are at the default, the padding is added anyway.
We could try setting the Title Inset Top to -6 and, because labels center the text vertically, we'll also have to set the Bottom inset to 6. This works... but we may not want to rely on that value of 6 to be accurate in future iOS versions.
Button 4 - lets try Content Insets -> Bottom: 1 ... and it looks like we're on our way! The label is now top-aligned with the button frame!
So...
Button 5 - remove the Height: 80 constraint so we can use the default button height. D'oh! The button frame height is now title-label-height plus zero-top plus one-bottom...
Button 6 - finally, we'll use Content Insets -> Bottom: 1 with an explicit Height constraint of 33 (the default button height).
So... what if we don't want to set an explicit Height? Perhaps we're going to change the title label's font size later? Or we run into some other issue?
You could use a custom UIButton subclass.
Here's an example, marked as #IBDesignable so we can see its layout in Storyboard / IB:
#IBDesignable
class MyTopLeftAlignedButton: UIButton {
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
commonInit()
}
func commonInit() {
self.contentVerticalAlignment = .top
self.contentHorizontalAlignment = .left
// if we want to see the title label frame
//titleLabel?.backgroundColor = .cyan
}
override var bounds: CGRect {
didSet {
if let v = titleLabel {
var t = titleEdgeInsets
let h = (bounds.height - v.frame.height) * 0.5
t.top = -h
t.bottom = h
titleEdgeInsets = t
}
}
}
}
Edit - response to comment...
If your goal is to match the "Button 5" style - where the button height matches the title label height - best bet is probably...
Select the button, then in the Size Inspector panel use these settings:
The 0.1 Top and Bottom values will override the default 6-pt Top/Bottom "padding," reducing it to effectively Zero.

Related

UILabel not resizing after scaling font using minimumScaleFactor

I have a UILabel inside a UITableViewCell that I want to dynamically adjust its font and size based on content to always maintain the largest font that it can support without truncating. I am most of the way there using adjustsFontSizeToFitWidth, but the label has extra vertical space that it does not need.
In this screenshot running on the iPhone SE simulator, the label correctly scaled the font down from 55 to fit, but there is this extra space at the top of the label. I want that space to go away!
Here is my label code:
private lazy var label: UILabel = {
let label = UILabel()
label.font = .systemFont(ofSize: 55, weight: .ultraLight)
label.adjustsFontSizeToFitWidth = true
label.minimumScaleFactor = 0.25
label.backgroundColor = .purple
return label
}()
Here is my layout code:
self.addSubview(self.labelsContainerView)
self.labelsContainerView.snp.makeConstraints { (make) in
make.leading.equalTo(leftView.snp.trailing).offset(20)
make.trailing.equalTo(rightView.snp.leading).offset(-20)
make.top.bottom.equalToSuperview()
}
self.labelsContainerView.addSubview(self.middleLabel)
self.middleLabel.snp.makeConstraints { (make) in
make.leading.trailing.centerY.equalToSuperview()
}
self.labelsContainerView.addSubview(self.topLabel)
self.topLabel.snp.makeConstraints { (make) in
make.leading.equalToSuperview()
make.bottom.equalTo(self.middleLabel.snp.top)
make.top.equalToSuperview().offset(8)
}
self.labelsContainerView.addSubview(self.bottomLabel)
self.bottomLabel.snp.makeConstraints { (make) in
make.leading.equalToSuperview()
make.top.equalTo(self.middleLabel.snp.bottom)
make.bottom.equalToSuperview().offset(-8)
}
In short, I have a UIView pinned to the top and bottom of my UITableViewCell. Inside that view, I have 3 labels: top, middle, and bottom. The labels are pinned top-to-bottom.
I want middleLabel to always be the largest size it can be to satisfy layout and use the largest font that will fit inside that size without truncating. It seems almost like the intrinsic content size isn't being updated when the label's font changes. I have tried all kinds of calls to setNeedsLayout() and layoutIfNeeded() but they never helped.
It will take some extra work to get the label's Height to change, but if you only need the text vertically centered, add this to your label configuration:
label.baselineAdjustment = .alignCenters
Here's the difference... top uses default .alignBaselines bottom uses .alignCenters:

UILabel can not be wrapped and aligned at the same time

I cannot wrap and align at the same time an UILabel displayed in a UITableViewCell.
I want some UILabels (displayed below with a white background) to be right aligned
and word wrapped if the text is too long. To clarify the sreenshots below:
UILabel with a white background are the labels I am talking about
I am using two different types of cell (respectively with blue and orange background)
The UITableView has a something-like-pink background
The ViewController in which the UITableView is displayed has a light gray background
Either is the alignment correct but the text is not wrapped (Actually the text "Long.. " is long, please see the second screenshot)
Or the text is correctly wrapped but it is not right aligned:
My code is based on this tutorial: How to build a Table View with multiple cell types
Inside the code for the cell displayed with an orange background:
class AttributeCell: UITableViewCell {
#IBOutlet weak var titleLabel: UILabel?
#IBOutlet weak var valueLabel: UILabel?
let orange = UIColor(red: 1, green: 165/255, blue: 0, alpha: 1)
var item: AttributeLabelLabel? {
didSet {
titleLabel?.backgroundColor = .white
titleLabel?.setLabel(contentText: (item?.attributeNameFromLocalizable)!, alignmentText: .right)
valueLabel?.backgroundColor = .green
valueLabel?.setLabel(contentText: (item?.attributeValue)!, alignmentText: .left)
self.backgroundColor = orange
}
}
static var nib:UINib {
return UINib(nibName: identifier, bundle: nil)
}
static var identifier: String {
return String(describing: self)
}
}
I added an extension to UILabel to set the alignment and text of the two labels displayed on cell, the way how the text should be wrapped is the same for all labels.
With the extension below the label is aligned but not wrapped (see first screenshot above).
extension UILabel{
func setLabel(contentText: String, alignmentText: NSTextAlignment){
self.text = contentText
self.textAlignment = alignmentText
self.numberOfLines = 0
self.lineBreakMode = .byWordWrapping // inefficient alone
}
}
If I want to have the text to be wrapped then I have to add a call to sizeToFit() but then short label (see label with the text "Short") is not right aligned (see second screenshot above).
extension UILabel{
func setLabel(contentText: String, alignmentText: NSTextAlignment){
self.text = contentText
self.textAlignment = alignmentText
self.numberOfLines = 0
self.lineBreakMode = .byWordWrapping
self.sizeToFit() // allow text to be effectivly wrapped
}
}
Why do I need to specify self.sizeToFit() on the documentation I have found only the use of lineBreakMode is mentionned to wrap a text ?
As I can not handle word wrapping and text alignement, I had the idea to compare the width of the UILabel with its text, and depending on the comparaison handling the alignment (for a text short enough) or the wrapping (if the text is too long). But I did not find how to get the UILabel´s width.
Another idea would be to create a custom UILabel and set all constraint, compression and resistance in code. For now there are no constraints:
Has someone already dealt with such problems?
Is it possible to handle text wrapping and text alignement at the same time ?
Note:
On the second screenshot the UILabel with a wrapped text overlapped the cell boundaries. It is not the first problem and I can live with that for now but if someone has an hint about that...
I actually use the following code to deal with cell with different heights:
cell?.estimatedRowHeight = 200
cell?.rowHeight = UITableView.automaticDimension
You are missing a few constraints.
To get a multiline label to wrap, it must have its width limited (how else would it know the text is too long?).
To get auto layout to adjust the cell's height, you need constraints on the content of the cell to "push down" the bottom of the cell.
So...
Constrain your top-left label to Leading: 0, Top: 0, Width: 77 (I'm using 77 as the width, based on your images).
Constrain your top-right label to Leading: 8 (to top-left label's trailing), Top: 0, Trailing: 0
Constrain your bottom-left label to Leading: 0, Top: 8 (to top-left label's bottom), Width: 77 (or, width equal to top-left label)
Constrain your bottom-right label to Leading: 8 (to bottom-left label's trailing), Top: 8 (to top-right label's bottom, or Top: 0 to top of bottom-left label), Trailing: 0
then, add Bottom constraints of >= 0 to each of the bottom labels.
I'm guessing either bottom label may wrap to multiple lines, so set each one to Number of Lines: 0
The layout:
the result:
A UILabel can definitely be right-aligned and wrap on multiple lines. Here is an example:
Actually, the label content is misleading as it wraps on four lines! ;-)
This can be achieved through AutoLayout constraints and the right settings on the UILabel. This particular UILabel is constrained as follows:
Vertically centred
Leading edge to super view
Width
Here are the constraints, as shown in Interface Builder:
Finally, to have a UILabel line-wrap to multiple lines, its numberOfLines property needs to be set to 0, either through Interface Builder or code.
You can also right-align the text using the textAlignment property, setting it to .right, again through Interface Builder or code.

UILabel breaks early inside UIStackView

Given the view hierarchy:
UIStackView
--UILabel
--UISwitch
The label breaks too early, even if it can be fit to a single line.
Setting numberOfLines = 1 forces the label to be laid out correctly.
How to make UILabel perform line break only when needed?
Code:
private lazy var title: UILabel = {
let v = UILabel()
v.numberOfLines = 0
return v
}()
private lazy var toggle = UISwitch()
private lazy var stack = UIStackView(axis: .horizontal,
distribution: .equalSpacing,
alignment: .center,
views: [title,
toggle])
func setupConstraints() {
stack.snp.makeConstraints { (make) in
make.edges.equalTo(contentView.layoutMarginsGuide)
}
}
Result:
Setting numberOfLines = 1 gets me what I'd like to achieve, but the label looses its multi-line functionality:
How to force the desired behavior without losing support for multi-line labels?
When there is a lot of horizontal space, the label gets laid out correctly no matter of the numberOfLines property:
Set your UISwitch's content hugging and resistance priority to 1000.
And stack view distribution and alignment to fill.
Extra Note - If you want label and switch to be top aligned, then set alignment to top.
In your stack view you can give a constraint to your label and switch...
1) give your label leading, top , trailing and bottom constraint. Don't give Width constraint. In trailing constraint take second item Switch.
2) give your switch trailing, top, bottom and Fix width.
Hope it Will work.
Add label inside another stack view.
UIStackView
--UIStackView
--UILabel
--UISwitch

Add "constant" value in xib in auto layout ios

I have successfully set AutoLayout for iPhone8+,iPhone X,iphone 7 but there is problem for iPhone SE i.e Reduced the space between two labels, so I need to change "constant value" in constraints but I don't want to add outlet add to constant value is there any possibility for add constant value for iPhone SE ?
storyboard solution:
screen width (or super view's width)
⬇︎
base view's leading
⬇︎
target view's spacing between base view.
use left button as the base reference, set right or target button's trailing constraint with left button's leading(or something else horizontal), and set target button's
constraint with multiplier.
change multiplier value based on storyboard's virtual view, that's easy.
set left button's leading based on its' super view's trailing, also with multiplier, so left button's leading based on super view's width, and the right
button based on super view's width too, the spacing between them will grow or shrink with the actual screen size.
case all these iPhone devices are (wC, hR), so, so can not set size classes in storyboard, but, you can still check screen size and make your own logic to layout based on screen sizes in code.
hope this post may help you. :)
Create a class and following code in it.
import UIKit
class DynamicVerticalConstraint: NSLayoutConstraint {
override init() {
super.init()
updateConstant()
}
override func awakeFromNib() {
super.awakeFromNib()
updateConstant()
}
//It will send contraint constant according to devices. For this you have to set your contraint according to iPhone6
func updateConstant(){
self.constant = self.constant * (UIScreen.main.bounds.height / 667)
}
}
class DynamicHorizontalConstraint: NSLayoutConstraint {
override init() {
super.init()
updateConstant()
}
override func awakeFromNib() {
super.awakeFromNib()
updateConstant()
}
//It will send contraint constant according to devices. For this you have to set your contraint according to iPhone6
func updateConstant(){
self.constant = self.constant * (UIScreen.main.bounds.height / 375)
}
}
Now set your constraint's constant value according to iphone 6.
After that you have to assign class to specific contrant.

Which Constraint we need to make dynamic View height in iOS

As I am new to iOS. So forgive me if it is duplicate or very basic question.
I am taking one View. Approx below is the size .
x : 5 y : 5
Width : 590 Height : 100
and I set constraint it
Top to superView 5
Trailing to superView 5
Leading to superView 5
Now I have one Label which have dynamic Text and the Text is too large.
And the Label Constraint is below
Top to superView 5
Trailing to superView 5
Leading to superView 5
and when i set the background color of the View the color is not set. If the Text is to Long. So how to set the Height of the View and also set background so that it looks clear.
Code :
public override void ViewDidLoad()
{
base.ViewDidLoad();
lbl_one.Text = "This is a long label which have long text inside the writing. This is a long label which have long text inside the writing. This is a long label which have long text inside the writing. This is a long label which have long text inside the writing";
lbl_one.LineBreakMode = UILineBreakMode.WordWrap;
lbl_one.Lines = 0;
view_main.BackgroundColor = UIColor.Red;
}
If I give fix Height then it look like this .
Output :
1. Give the below constraints to your view, height is according to your need. here I'm giving 80.
2. Change the height relationship.
3. Add aUILabel in your above UIview, and give below constraints.
--> leading, top, bottom, trailing to uiview and height i.e. 80.
4. set height relationship as you do with UIView.
5. Change the property of UILabel , Lines to zero
6. Now enjoy with your constraints.
EDIT: Add bottom constraint to your view instead of height constraint.
I don't see and bottom constraint added to UIView, so the view height will be 0.
If you have added the height constraint to UIView, there is a probability that UILablel might be overlapping the UIView, so you are not able to see the background color.
Set the UIView height constraint this will solve your problem
You can also add height or bottom constraint to your UIView.

Resources