Remove empty space of UILabel with autolayout - ios

I am trying to create a UIView in a library project and use it in my application. I have added auto layout constraints as follows:
But it produces the following result:
The labels have numOfLines as 0 but still, empty labels are displaying empty space.
I have only given the height of the white view in the center (56px)
Edit:
I am using the view from library as following:

One solution with Storyboard, where UILabel heights are dynamic based on its's text,
Or,
you can try using NSLayoutConstraint for the UILabel hights, for the ones you want to hide when the value is not there for the label,
#IBOutlet weak var errorLabelHeight: NSLayoutConstraint!
then,
if errorLabel.text.isEmpty {
errorLabelHeight.isActive = true
errorLabelHeight.constant = 0
} else {
errorLabelHeight.active = false
//works as usual
}
view.layoutIfNeeded()

Related

Swift - Remove width with condition?

I'm coding a social network, which can permit to like and comment pictures.
However, I have a problem with that part ...
If I have one (or more) like and one (or more) comment, it's ok:
If I have one (or more) comment but no like, the user interface is not very good ...
I would like to move the button "2 comments" on the left when there is no like. I thought to do that by giving 0 width to the "1 like" button. However, the width it's not all the time the same. If I have 1 like or 12543 likes, the width will be different.
I have no idea about how I could correct this problem.
Do you have any idea please ?
you can create a custom component that will have a view, and two uilabels as a subviews if the view. the first uilabel will contain the number and the second the title ("comments").
the constraints of the uilables should be as follow:
first table leading == view.leading, first label trailing == second label leading, second label.trailing == view.trailing. view.height == "some constant". In this case the width of the view will be according to the labels width.
Now you should add those custom views to your view. In your case it will be two or one custom components, one for the likes and one for the comments.
add those custom view to tmprorary array and do the following code:
var prevLayoutedView : UIView = self
for (index, customView) in custumViewsArr.enumerated()
{
customView.leadingAnchor.constraint(equalTo: prevLayoutedView.leadingAnchor)
if index == custumViewsArr.enumerated.count
{
customView.trailingAnchor.constraint(equalTo: prevLayoutedView.trailing)
}
}
you will need to adjust some constants and compression resistance and hugging priority but this should assist you to solve it
UPDATE:
you can use less generic solution and not using a custom component but just using uilable. and then use the same for loop
first easy solution is to keep single label for both if not clickable
otherwise with two labels,
my constraints for two label is,
1. Constraints for like label:
2. Constraints for comment label
#IBOutlet var cnLeadingToView: NSLayoutConstraint!
#IBOutlet var cnLeadingToLikelbl: NSLayoutConstraint!
assign this outlet in storyboard for leading constraints of comment label then do this
if cntLikes == 0 { // temp variable
//hide the like lable
self.lblLikes.isHidden = true
self.cnLeadingToLikelbl.isActive = false // deactivate the leading constraint with like label
self.cnLeadingToView.isActive = true // activate the leading constraint with view
self.cnLeadingToView.constant = 15 // give leading space constant
}else {
self.lblLikes.isHidden = false
self.cnLeadingToLikelbl.isActive = true
self.cnLeadingToView.isActive = false
}
As #dahiya_boy said, I had to change the like label relationship from equalTo to lessthanequalto.
I put 375 by default and 0 if the is no like. It was that simple.
Thank you very much everybody !

How to get resizing tableView cell with different content without deleting constraints in iOS

I have tableView cell with different content(views, labels, imageViews) in one cell. But in something cells content can be not full. How can i use resizing cells without removing and adding always constraints? Thanks.
One of possible solutions for this problem:
Add constraints for hidden state with priority 1000
Add extra constraints for resized state with lower priority (ex 750)
Save constraints that is ONLY for hidden state into IBOutlet collection
Save constraints that is ONLY for resized state into another IBOutlet collection
Code:
#IBOutlet var hiddenConstraints: [NSLayoutConstraint] = []
#IBOutlet var visibleConstraints: [NSLayoutConstraint] = []
func hide(_ hide: Bool) {
for hiddenConstraint in self.hiddenConstraints {
hiddenConstraint.isActive = hide
}
for visibleConstraint in self.visibleConstraints {
visibleConstraint.isActive = !hide
}
self.layoutIfNeeded()
}
There is faster solution:
Move content that can be hidden into container view
Set height constraint for container view
Change from code height constraint constant to 0 if hidden or to proper height if visible
Code:
#IBOutlet var heightConstraint: NSLayoutConstraint!
func hide(_ hide: Bool) {
self. heightConstraint.constant = hide ? 0 : 150 //Estimated height
self.layoutIfNeeded()
}
This is not a good approach, as it will lead to constraint crashes at runtime. So I prefer to use first one.
Also you will need to update your cell from table to move other cells up or down.
Ray Wenderlich has a fantastic tutorial on dynamic sizing of table cells that can be found here:
https://www.raywenderlich.com/129059/self-sizing-table-view-cells
TL;DR You need to make sure your cell's content is pinned on all four sides to the cell's content view, as well as setting as high priority vertical hugging, greater than or equal to height constraint on your label.

Adding UIStackView as a empty container in Storyboard

My wish is to have a UIStackView added in the Storyboard with 0 height that I reference in the code to add subviews to programatically. However, Storyboard is complaining about that it doesn't have a height (I haven't set it, so it is correct that it warns me). I only want it to act as a dynamic container for other views. The UIStackView I am talking about is added as a subview inside another UIStackView.
It is the stackview below "Name Label" it is all about.
Select your StackView which you want to set height 0
Select Size inspector
Select Intrinsic Size property of StackView
Change it to Placeholder from Default
Now change StackView Height as you wish!! even 0.
You can hide you inner stack view using the boolean .hidden property. Create an outlet to the inner stack view, say innerStackView, and hide it at initialization using innerStackView.hidden = true, e.g.:
#IBOutlet weak var innerStackView: UIStackView!
override func viewDidLoad() {
super.viewDidLoad()
// ...
innerStackView.hidden = true
}
This will hide the stack view even if it contains several other views; hence, it can act as your hidden dynamic container, and you needn't fiddle around with height properties. If you want to show the view again, simply bitswap the .hidden property to innerStackView.hidden = false.

Xcode; Set a autolayout constraint to a defined variable value

Is it possible to define a variable, and set a constraints constant value to that variable?
Thereby making it possible to change many constraints by just changing the variable. I think I saw someone do this directly from interface builder ones?
EDIT:
There is a constraint between each label. I need a method to change all of these constraints, so they get a the same value. Is this possible?
If I use a outlet collection, I will have to iterate through all the constraints, and change the value for each. I'm looking for a method like this:
// SEUDO!!
lineSeperationWidth = 31 // changes all 4 constraints.
The NSLayoutContraints are all separate objects, and Xcode provides no way to set the constant value of multiple constraints with the same variable. The closest you can get is to use an #IBOutlet collection and then use the didSet property observer for your variable holding the common space value to update each of the constraints in the collection.
Here is an example I created which spaces out the labels based upon a random value that I set to the space property each time the Go button is pressed:
class ViewController: UIViewController {
#IBOutlet var spacers: [NSLayoutConstraint]!
var space: CGFloat = 20.0 {
didSet {
spacers.forEach { $0.constant = space }
}
}
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func spaceOut(sender: AnyObject) {
// set space to a random value
space = CGFloat(arc4random_uniform(30)) + 20.0
}
}
Each of the vertical space constraints between the labels is connected to the #IBOutlet collection spacers. Here is what the Connections Inspector shows:
Here it is in action:
Yes it is! You can use the document outline view to find the constraint you want to use as a variable. Once you have it, CTRL + Drag from the constraint in the document outline view to your code to make the outlet. Then you can change the constraints in code by using self.constraint.constant = 31.
To space all the views out equally, you can put UILayoutGuides in between each of the labels and constrain all their heights to be equal. Then you can change the height of one of the layout guides and they will all change to match it.

How to easily collapse vertical space around label when its text is nil?

Suppose I have three labels that are laid out below each other in a column. The uppermost label's top edge is pinned to the superview's top edge. All following labels' top edges are pinned to the preceding label's bottom edge. The leading and trailing edges of all labels are pinned to the leading and trailing edge of the superview. Here's what it looks like in Interface Builder (I added a blue background on every label to visualize its extent).
In the simulator the result looks like this.
All labels are connected to outlets in a view controller.
#IBOutlet weak var label1: UILabel!
#IBOutlet weak var label2: UILabel!
#IBOutlet weak var label3: UILabel!
When I set the text of label2 to nil
label2.text = nil
the label itself collapses.
However, the top and bottom spaces around the label do not collapse. This is evident by the fact that there is no blue background on the middle label in the last screenshot. As a result, the space between label1 and label3 is double the space of the layout in the first screenshot.
My question is - on iOS8 - what is the easiest way to collapse either the middle label's top or bottom space so that the two remaining labels still use the vertical spacing defined in the original layout? To be clear, this is the result I want to achieve.
Options I've found so far:
Bottom/Top Spacing Constraint Outlet
Define an outlet for the middle label's top or bottom spacing constraint.
#IBOutlet weak var spacingConstraint: NSLayoutConstraint!
Store the constraint's initial constant into a variable (e.g. in awakeFromNib or viewDidLoad).
private var initialSpacing: CGFloat!
override func viewDidLoad() {
initialSpacing = spacingConstraint.constant
...
Set the constraint's constant to zero whenever the text is set to nil or back to its initial value when the text is not nil.
spacingConstraint.constant = label2.text == nil ? 0 : initialSpacing
This approach feels a bit clumsy since it requires two additional variables.
Height Constraint Outlet
Set the vertical spacing around the middle label to zero and increase its height by the same amount. Define an outlet for the height constraint and proceed as above, setting the height to zero when the text is nil and back to it's initial value when the height is not nil.
This is still as clumsy as the previous approach. In addition, you have to hardcode the spacing and cannot use the built-in default spacings (blank fields in Interface builder).
UIStackView
This is not an option since UIStackView is only available on iOS 9 and above.
I'm using this UIView category for this purpose.
It extends UIView by adding two more property named fd_collapsed and fd_collapsibleConstraints using objective-c runtime framework. You simply drag constraints that you want to be disabled when fd_collapsed property set to YES. Behind the scene, it captures the initial value of these constraints, then set to zero whenever fd_collapsed is YES. Reset to initial values when fd_collapsed is NO.
There is also another property called fd_autocollapsed
Not every view needs to add a width or height constraint, views like UILabel, UIImageView have their Intrinsic content size when they have content in it. For these views, we provide a Auto collapse property, when its content is gone, selected constraints will collapse automatically.
This property automatically sets fd_collapsed property to YES whenever specified view has content to display.
It's really simple to use. It's kinda shame that there is no builtin solution like that.
Your solutions are good enough for me and I'd do Bottom/Top Spacing Constraint Outlet solution but since you want something different. You can use this third party: https://github.com/orta/ORStackView It has iOS7+ support and do exactly what you need.
This is low-key a pain all perfectionist devs learn about when trying to stack a bunch of labels. Solutions can get too verbose, annoying to folow, and really annoying to implement (ie. keeping a reference to the top constraint... gets annoying once you do it multiple times, or just change the order of the labels)
Hopefully my code below puts an end to this:
class MyLabel: UILabel {
var topPadding: CGFloat = 0
override func drawText(in rect: CGRect) {
var newRect = rect
newRect.origin.y += topPadding/2
super.drawText(in: newRect)
}
override var intrinsicContentSize: CGSize {
var newIntrisicSize = super.intrinsicContentSize
guard newIntrisicSize != .zero else {
return .zero
}
newIntrisicSize.height += topPadding
return newIntrisicSize
}
}
Usage:
let label = MyLabel()
label.topPadding = 10
// then use autolayout to stack your labels with 0 offset
Granted, its only for top padding, but that should be the only thing you need to layout your labels properly. It works great with or without autolayout. Also its a big plus not needing to do any extra mental gymnastics just to do something so simple. Enjoy!

Resources