I have two UILabel's. One UILabel is on the top of other. What I want is, if there is no content in top one, the bottom one should take the origin of top.
I have to use Auto Layout on the screen. I tried using sizeToFit but that is not working. Bottom UILabel is still stuck at it's origin if there isn't no content in top UILabel.
Setting constraint for height will not work. The y position of second label will not get its correct Y axis value (It will shift upwards). I solve your problem by playing with vertical spacing constraint between two results.
Explanation:- Set the normal basic constraints for label plus vertical spacing constraint. When there is no text in upper label, then update the vertical spacing constraint as follows:-
verticalSpacingConstraintBetweenLabels.constant = -(KIntialVerticalSpacing + (KHeightOfLabel-KIntialVerticalSpacing));
I create a sample project for you. Source code is available at:-
https://www.dropbox.com/s/eq9hinnw4sdfltq/LabelSpacing.zip?dl=0
One alternative to that requirement is to make IBOutlet for the height of the upper label and set it to 0 whenever you don't want to show it. But keep in mind you have to set vertical spacing between upper and lower label. The lower label shouldn't have top margin to superview.
First drag both top constrains to UIViewController.
Second paste this code:
-(void) loadLabels{
_firstLabel.text=#"";
NSInteger heidFirstLabel = 20;
if(_firstLabel.text.length == 0){
_constrainTopFirstLabel.constant = 0 - heidFirstLabel;
_firstLabel.hidden = YES;
}else{
_constrainTopFirstLabel.constant = 8 ;
}
}
I always use this.
In iOS9 apple has introduces a new class UIStackView to solve this and other problems that we the developers were facing, using auto layout. If you are targeting iOS 9 and later, you should use this class.
If you are targeting versions before iOS 9, one of the many ways could be using intrinsic content size.
1.Subclass UILabel
2.Add a property that determines if this label is collapses or not
#property (nonatomic,assign) BOOL isLabelCollapsed;
3.Override its method intrinsicContentSize
- (CGSize)intrinsicContentSize {
if(self.isLabelCollapsed)
return CGSizeMake(0, 0);
else
return [super intrinsicContentSize];
}
4.Set you collapsable labels class as your custom class.
5.When needed, set your isLabelCollapsed property to YES and call invalidateIntrinsicContentSize on you collapsble label.
Related
I don't want to set a Y position constraint because I need the views to be relative to each other, since I have a UITextView that has to and should dynamically change its value based on how many lines of text are in it. It does not do this though. I can FORCE it to do it by calling sizeToFit() on the UITextView, but then it overlaps the views below.
Xcode nags me to reduce ambiguity and there are red lines all over the view controller, but it doesn't make any sense for me to manually have a Y position constraint if that has to be dynamic.
So my question is how do I
1. Make my UITextView resize its height after the number of lines of text increases
2. Make the views below it get pushed down automatically so the UITextView does not overlap them.
I've read multiple answers and have managed to do 1 but not the other.
Solution in Swift:
let sizeThatFitsTextView = textView.sizeThatFits(CGSizeMake(textView.frame.size.width, textView.frame.size.height))
textViewHeightConstraint.constant = sizeThatFitsTextView.height
1) Disable the scroll of Textview from storyboard or by coding.
2) Create an outlet for textview's height constraint. Change the height constraint programmatically from textView delegate method when text change occur based on calculation.
CGSize sizeThatFitsTextView = [TextView sizeThatFits:CGSizeMake(TextView.frame.size.width, MAXFLOAT)];
TextViewHeightConstraint.constant = sizeThatFitsTextView.height;
I have a KSSection UIView subclass that I'm trying to use to do collapsing / expanding of different sections. It has a child view (set by an IBOutlet) called content. The content's size is determined by a number of child views (UILabel, UIImageView, etc.) that are all variable size.
Currently I'm pinning the leading and trailing space of the content to the parent KSSection, aligning it centred vertically, and adding a remove at runtime constraint that the heights of content and section are equal. If I disable the remove at runtime everything works great - except that I can't collapse the view.
How can I calculate the size of the content to be used as the intrinsicContentSize of the KSection? So far I have the following snippet, but the call to intrinsicContentSize always returns UIViewNoIntrinsicMetric for both properties.
#implementation SKContainer
- (CGSize)intrinsicContentSize
{
if (self.collapsed) return CGSizeZero;
else return [self.content intrinsicContentSize];
}
- (void)layoutSubviews
{
[super layoutSubviews];
[self invalidateIntrinsicContentSize];
}
- (void)setCollapsed:(BOOL)collapsed
{
if (_collapsed != collapsed)
{
_collapsed = collapsed;
[self invalidateIntrinsicContentSize];
}
}
#end
Edit:
Sorry to clarify it is actually returning UIViewNoIntrinsicMetric for both dimensions of the CGSize.
Attaching a sample: http://cl.ly/2F3s3X3y2U1H
Okay, so my previous answer was on an unnecessary track. What I ended up doing was this:
First, I removed the section view entirely, leaving us with just the plain vanilla content view to play the role of the section. (The section view was just adding an extra layer of complication.) Then I lowered the priority of the section view height to 250, and ran the project. Presto! The section view now expands, all by itself, driven by the constraints of the labels within it.
Second, here's how I collapse and expand. I keep an outlet to two of the constraints: the section height constraint, and the last constraint in the height stack of the internal constraints. Then my expand/collapse code looks like this:
- (IBAction)toggleButtonSelector:(id)sender
{
self.collapsed = !self.collapsed;
if (self.collapsed) {
self.sectionHeightConstraint.constant = 10; // or whatever height you like
self.sectionHeightConstraint.priority = 999;
[NSLayoutConstraint deactivateConstraints:#[self.bottomInternalConstraint]];
} else {
self.sectionHeightConstraint.priority = 250;
[NSLayoutConstraint activateConstraints:#[self.bottomInternalConstraint]];
}
}
You see, we need to overcome the desire of the internal stack of constraints to keep us expanded, so we remove one of the constraints in order to collapse, and we set the height constraint to a small number and raise its priority. To expand, we reverse that: we restore the missing internal constraint, and lower the priority of the overall section height constraint once again.
EDIT A really cool byproduct of this implementation is that we can now animate the collapse/expand effect merely by appending these lines of code at the end of that method:
[UIView animateWithDuration:1 animations:^{
[self.view layoutIfNeeded];
}];
You are misusing -intrinsicContentSize, both in why you're calling it and in your attempt to implement it for your view class. That method is for returning a size which is "intrinsic" to its nature and contents. It can not depend on other views, other constraints, etc. It also has nothing to do with the view's current size, because a view can be compressed or stretched from its intrinsic size by other constraints.
You should use constraints to make your view depend on its collapsed state and its subview's size (resulting from other constraints in combination with descendant views' intrinsic sizes, if they have any).
For example, assuming you want your view to collapse in the vertical direction, you might have constraints which always pin the content subview to the top, leading, and trailing edges. If your view is collapsed, you would have a constraint to make its height zero. You would not constrain your view's bottom to the content view's bottom. The content view would have its normal height, but that would all be clipped out by virtue of the fact that its superview has zero height.
On the other hand, if your view is not collapsed, you would remove the height constraint on your view and add a constraint connecting your view's bottom to the content view's bottom.
Using solutions from #ken and #matt my final code (still using the SKContainer) is:
#interface SKContainer ()
#property (nonatomic, strong) IBOutlet NSLayoutConstraint *expandedLayoutConstraint;
#property (nonatomic, strong) IBOutlet NSLayoutConstraint *collapsedLayoutConstraint;
#end
#implementation SKContainer
- (void)setCollapsed:(BOOL)collapsed
{
if (_collapsed != collapsed)
{
_collapsed = collapsed;
[self removeConstraint:collapsed ? self.expandedLayoutConstraint : self.collapsedLayoutConstraint];
[self addConstraint:collapsed ? self.collapsedLayoutConstraint : self.expandedLayoutConstraint];
}
}
#end
Where expandedLayoutConstraint is a equal height constraint to the content view and collapsedLayoutConstraint is a height 0 priority 200 constraint.
I defined a view which contains an UImageView and a UILabel. I setted constraints for each elements.
In wanted to do a circle with the first view, so I did that in the code :
self.mainView.layer.cornerRadius = self.mainView.frame.size.width / 2;
self.mainView.clipsToBounds = YES;
So it works, I have a circle, BUT the UImageView and the UILabel seems don't follow the constraints setted in the storyboard.
For example, on my UILabel, I setted a margin left and right 5px to the mainView, but I can see my UILabel "out" the mainView....
I tried to do a "setNeedsUpdateConstraints", but it's not the solution.
So, what I need to do to have my UILabel correctly positioned ?
Thanks,
I think this is what you are looking for:
self.mainView.layer.masksToBounds = YES;
Your problem is that when changing the mainView's layer you are not actually changing the bounds of the view. The bounds of the view are still represented in a rectangle manner. what you need to do is change the width constraint of the UILable.
To do so just create a autolayout constrain to your UILable (is you don't have it already). Control-Drag it to your viewController and change it dynamically using the constant value in the constraint.
If you don't need to set it dynamically just set the left & right margins to a bigger margin
I am struggling with maybe a bit of a rookie issue. I have a UIView within which I display some price. I want the UIView to be of a dynamic width according to the price, if its 1 Euro, then it will be e.g. 20pt, if its 2300 Euro, then it will be like 50pt in width.
I was trying to use the storyboard's constraints but without luck. Is it possible to do it within storyboard or do I have to calculate the width of UILabel and then set the width of UIView programmatically?
Thank you in advance.
Yes, you can do this in the storyboard. Add a label to your view and pin it to the left and right edge (top and bottom if you want also). Give the view constraints to its superview in the x and y directions, but do not give it a width constraint (it will need a height constraint if you didn't pin the top and bottom of the label to it). The view should then expand with the label depending on its content.
In general, auto layout is performed in a top-down fashion. In other words, a parent view layout is performed first, and then any child view layouts are performed. So asking the system to size the parent based on the child is a bit like swimming upstream, harder to do, but still possible with some work.
One solution is to use the intrinsic size of a view.
For example, a UILabel has an intrinsic size based on the text in the label. If a UILabel has a leading constraint and a top constraint, but no other constraints, then its width and height are determined by its intrinsic size.
You can do the same thing with a custom view class that encloses a UILabel. By setting the intrinsic size of the custom view class based on the intrinsic size of the UILabel, you get a view that automatically resizes based on the text in the label.
Here's what the code looks like for the custom class. The .h file defines a single property text. The .m file has an IBOutlet to the child label. Setting and getting the text property simply sets or gets the text from the label. But there's one very important twist, setting the text invalidates the intrinsic size of the parent. That's what makes the system adjust the size of the parent view. In the sample code below the parent is sized to have an 8 pixel margin all around the UILabel.
SurroundView.h
#interface SurroundView : UIView
#property (strong, nonatomic) NSString *text;
#end
SurroundView.m
#interface SurroundView()
#property (weak, nonatomic) IBOutlet UILabel *childLabel;
#end
#implementation SurroundView
- (void)setText:(NSString *)text
{
self.childLabel.text = text;
[self invalidateIntrinsicContentSize];
}
- (NSString *)text
{
return( self.childLabel.text );
}
- (CGSize)intrinsicContentSize
{
CGSize size = self.childLabel.intrinsicContentSize;
size.height += 16;
size.width += 16;
return( size );
}
#end
Creating the IBOutlet to the childLabel can be a little tricky, so here's the procedure
drag out a UIView into the storyboard
use the Identity inspector to change the class to SurroundView
drag out a UILabel and add it as a subview of the SurroundView
select the label, and open the assistant editor
show SurroundView.m in the assistant
drag from the open circle to the label as shown below
All that's left is to get the constraints right. The constraints for the label should look like this
The constraints for the SurroundView should be as shown below. The key point is that the Intrinsic Size should be set to Placeholder to avoid the warnings about missing constraints.
Place the label inside the view and pin its TOP , BOTTOM , TRAILING and LEADING edges to the labels superview. Note that you do not specify the width constraint. Now add a height and width constraint to the view. Make an outlet to the width constraint and when the price changes set the view's width constraint's constant to your desired value. Since the label is pinned to the view it will expand too.
I have a UIButton and it can change the title at the runtime. Therefore, I want to increase the UIButton height depend on the title text for display full text by using AutoLayout.
I can increase the UILabel height by set the height constraint to "Greater than or Equal" but it not work with UIButton.
I have used [myButton sizeToFit] but it only increase the UIButon width (not increase height).
My current UIButton properties now is
- constraint height: 30
- leading : 15
- trailing: 15
- top: 5
- fontsize: 12
UPDATE
I created an IBOutlet for constraint height of UIButton for changing the height as #NSNood said.
Then I need to use \n in title text to split line.
But I don't know where should I put the \n?
Here is the Button that I want in portrait
Here is the Button that I want in landscape
How can I determine the place to put \n?
Please guide me how to achieve it with AutoLayout. Any help would be appreciated.
Sorry that I didn't follow the post, lately and thus am coming up with a real late solution. Still I'm writing the answer as a reference, if someone might find it useful in future.
First of all let's show the storyboard configuration for the button. Those are depicted in the following pictures:
The picture shows that I have added only left, top and right constraints for the button and nothing else. This allows the button to have some intrinsicContentSize for it's height but it's width is still determined by it's left and right constraints.
The next phase is to write some ViewController class that shall contain the button. In my VC, I have created an outlet for the button by name button:
#property(nonatomic,weak) IBOutlet UIButton* button;
and has attached it to the storyboard button. Now I have overridden two methods, namely, viewDidLoad and viewWillLayoutSubviews like below:
-(void)viewDidLoad {
[super viewDidLoad];
self.button.titleLabel.numberOfLines = 0;
self.button.titleLabel.lineBreakMode = NSLineBreakByWordWrapping;
}
-(void)viewWillLayoutSubviews {
[super viewWillLayoutSubviews];
[self.button setTitle:#"Chapter One\n "
"A Stop on the Salt Route\n "
"1000 B.C.\n "
"As they rounded a bend in the path that ran beside the river, Lara recognized the silhouette of a fig tree atop a nearby hill. The weather was hot and the days were long. The fig tree was in full leaf, but not yet bearing fruit." forState:UIControlStateNormal];
}
The viewDidLoad method ensures the titleLabel (the label that
holds button text) is multiline and if some large text comes to it,
it wraps the text by wrapping words.
The viewWillLayoutSubviews method ensures button layouting process
occurs whenever bounds of the main view change, e.g. due to the
change of interface orientation.
The final and the most effective part is to manually handle the layout process for the button. For this purpose, we need to subclass UIButton. I have written a subclass named MyButton that inherits from UIButton and you might use whatever name you like. Set this as the custom class for the button in Identity Inspector.
The subclass overrides two methods, namely, intrinsicContentSize and layoutSubviews. The class body looks something like the following:
#import "MyButton.h"
#implementation MyButton
-(CGSize)intrinsicContentSize {
return [self.titleLabel sizeThatFits:CGSizeMake(self.titleLabel.preferredMaxLayoutWidth, CGFLOAT_MAX)];;
}
-(void)layoutSubviews {
self.titleLabel.preferredMaxLayoutWidth = self.frame.size.width;
[super layoutSubviews];
}
#end
The UIButon subclass takes the ownership of the layout process by overriding layoutSubviews method. The basic idea here is to determine the button width, once it has been layout. Then setting the width as preferredMaxLayoutWidth (the maximum width for layouting engine, that a multiline label should occupy) of it's child titleLabel (the label that holds button text). Finally, returning an intrinsicContentSize for the button based on it's titleLabel's size, so that the button fully wraps it's titleLabel.
The overridden layoutSubviews is called when the button is already
layed out and it's frame size is determined. At it's first step,
button's rendered width is set as preferredMaxLayoutWidth of the
button's titleLabel.
The second step re-invokes the layouting engine by calling [super
layoutSubviews], so that the buttons intrinsicContentSize is
re-determined based on it's titleLabel's
preferredMaxLayoutWidth, which is set to buttons rendered width,
by now.
In the overridden intrinsicContentSize method we return the
minimum fitting size for the button that fully wraps it's
titleLabel with preferredMaxLayoutWidth set. We use
sizeThatFits fits method on the button's titleLabel and that
simply works as titleLabel doesn't follow any constraint based
layout.
The outcome should be something similar to that you might have required.
Feel free to let me know about any other clarification/concern.
Thanks.
Ayan Sengupta solution in Swift, with support for contentEdgeInsets (thanks Claus Jørgensen):
(You may also further customize the code to take titleEdgeInsets into account if needed)
Subclass your UIButton to take the ownership of the layout process:
/// https://stackoverflow.com/a/50575588/1033581
class AutoLayoutButton: UIButton {
override var intrinsicContentSize: CGSize {
var size = titleLabel!.sizeThatFits(CGSize(width: titleLabel!.preferredMaxLayoutWidth - contentEdgeInsets.left - contentEdgeInsets.right, height: .greatestFiniteMagnitude))
size.height += contentEdgeInsets.left + contentEdgeInsets.right
return size
}
override func layoutSubviews() {
titleLabel?.preferredMaxLayoutWidth = frame.size.width
super.layoutSubviews()
}
}
Use this class in your storyboard, and set constraints for Leading, Trailing, Top, Bottom. But don't set any Height constraint.
An alternative without subclassing is to add a wrapper view as suggested by Bartłomiej Semańczyk answer and Timur Bernikowich comment.
The point is that if you set sizeToFit property, then the text will always be in one line and the width of the button will increase unless you put a next-line sign \n to explicitly say that you want it to be several lines.
You put '\n' in the end of the first line like "line \n line" which represents
line
line
If you want to have two different string values (with \n positioned differently) for Portrait and Landscape you can check the orientation condition using UIDeviceOrientation (UIDevice.currentDevice.orientation) described here and set a string value depending on the orientation of the device
There is a way I always used:
Add another reference UILabel which lineNumber=0 and the same width with the target button.
Do not set height constraint for the ref-UILable, and should set a height constraint for the button to adjust its height
Set the same text to the ref UILabel with the button.titleLable, sizeTofit it and get its frame.size.height
Use the height value to the height constraint of the target button. (Of course, the button.titleLabel linenumber should be set to 0 or more lines)
Done. :)
PS1. This way can be used for the button and ref-label in a scrollview.
PS2. In some case, we can not get the correct height of the ref-label because it cannot gain a correct frame.width in scrollview, especially when we use the trailling constraint. We could consider to define a fixed width to the ref-label before sizeTofit and obtain the correct height for target button use.