I feel like it\'s a fairly common paradigm to show/hide UIViews
, most often UILabels
, depending on business logic. My question is, what is the best
UIStackView is probably the way to go for iOS 9+. Not only does it handle the hidden view, it will also remove additional spacing and margins if set up correctly.
In this case, I map the height of the Author label to an appropriate IBOutlet:
@property (retain, nonatomic) IBOutlet NSLayoutConstraint* authorLabelHeight;
and when I set the height of the constraint to 0.0f, we preserve the "padding", because the Play button's height allows for it.
cell.authorLabelHeight.constant = 0; //Hide
cell.authorLabelHeight.constant = 44; //Show
I'm surprised that there is not a more elegant approach provided by UIKit
for this desired behavior. It seems like a very common thing to want to be able to do.
Since connecting constraints to IBOutlets
and setting their constants to 0
felt yucky (and caused NSLayoutConstraint
warnings when your view had subviews), I decided to create an extension that gives a simple, stateful approach to hiding/showing a UIView that has Auto Layout constraints
It merely hides the view and removes exterior constraints. When you show the view again, it adds the constraints back. The only caveat is that you'll need to specify flexible failover constraints to surrounding views.
Edit
This answer is targeted at iOS 8.4 and below. In iOS 9, just use the UIStackView
approach.
My personal preference for showing/hiding views is to create an IBOutlet with the appropriate width or height constraint.
I then update the constant
value to 0
to hide, or whatever the value should be to show.
The big advantage of this technique is that relative constraints will be maintained. For example let's say you have view A and view B with a horizontal gap of x. When view A width constant
is set to 0.f
then view B will move left to fill that space.
There's no need to add or remove constraints, which is a heavyweight operation. Simply updating the constraint's constant
will do the trick.
My preferred method is very similar to that suggested by Jorge Arimany.
I prefer to create multiple constraints. First create your constraints for when the second label is visible. Create an outlet for the constraint between button and the 2nd label (if you are using objc make sure its strong). This constraint determines the height between the button and the second label when it's visible.
Then create another constraint that specifies the height between the button and the top label when the second button is hidden. Create an outlet to the second constraint and make sure this outlet has a strong pointer. Then uncheck the installed
checkbox in interface builder, and make sure the first constraint's priority is lower than this second constraints priority.
Finally when you hide the second label, toggle the .isActive
property of these constraints and call setNeedsDisplay()
And that's it, no Magic numbers, no math, if you have multiple constraints to turn on and off you can even use outlet collections to keep them organized by state. (AKA keep all the hidden constraints in one OutletCollection and the non-hidden ones in another and just iterate over each collection toggling their .isActive status).
I know Ryan Romanchuk said he didn't want to use multiple constraints, but I feel like this isn't micromanage-y, and is simpler that dynamically creating views and constraints programmatically (which is what I think he was wanting to avoid if I'm reading the question right).
I've created a simple example, I hope it's useful...
import UIKit
class ViewController: UIViewController {
@IBOutlet var ToBeHiddenLabel: UILabel!
@IBOutlet var hiddenConstraint: NSLayoutConstraint!
@IBOutlet var notHiddenConstraint: NSLayoutConstraint!
@IBAction func HideMiddleButton(_ sender: Any) {
ToBeHiddenLabel.isHidden = !ToBeHiddenLabel.isHidden
notHiddenConstraint.isActive = !notHiddenConstraint.isActive
hiddenConstraint.isActive = !hiddenConstraint.isActive
self.view.setNeedsDisplay()
}
}
Subclass the view and override func intrinsicContentSize() -> CGSize
. Just return CGSizeZero
if the view is hidden.