Dynamically set view top margin and height using autolayout - swift3 - ios

I am working on a UI where I have 3 labels. All are arranged vertically-
Label 1
Label 2
Label 3
List item At a time any two Label will be shown to user.
For example if Label 1 and Label 3 are shown then I want to shift Label3 up below the label1 and make Label2 height to 0.
If Label2 and Label3 are shown we have to shift both label up and set label1 height to 0.
So everytime topmst label should have same top margin from superview. It can be either Label1, Label2 or Label3
Should I use stack view to achieve this requirement?
What is the best way to do it using auto layout?

You can group your different scenarios constraints in a IBOutletCollection(NSLayoutConstraint) property (one per scenario/ group of constraints) like:
#property (strong, nonatomic) IBOutletCollection(NSLayoutConstraint) NSArray *scenarioNConstraints;
And then based on your conditions you can Activate or Deactivate the desire constraints by doing something like
for (NSLayoutConstraint *constraint in scenarioNConstraints) {
[constraint setActive:YES]; //or [constraint setActive:NO];
}

Related

Make UIScrollView with dynamic height subviews work automatically with Auto Layout

I have the following view structure with its constraints:
UIView "parent"
UIScrollView (leading, trailing, top and bottom to superview)
- UIView "container" (leading, trailing, top and bottom to UIScrollView,
equal width and height so parent UIView)
- UIView "A" (leading, trailing, top to UIScrollView, height of 200)
- UIView "B" (top, leading and trailing to UIView A, height of 140)
- UIView "C" (top, leading and trailing to UIView B, height >= 88 "rest
of the screen until bottom", bottom to UIView "container")
"A" and "B" UIView's do not change its size but "C" does. Inside it, I am adding programmatically n "labels containers" UIView that have different heights depending on the content of m UILabel that they host.
Right now, I am calculating the size of the n UILabel with boundingRectWithSize: and I am sizing the height of their parent "labels containers" UIView that it is being added inside "C" UIView setting its height constraint to the sum of all UILabel.
Then, I resize "C" UIView height constraint so that it is equal to the sum of all added UIView.
This is working perfectly on all different screen sizes, portrait and landscape. The UIScrollView is showing all three "A", "B" and "C" subviews, having "C" n UIView that host m UILabel.
But now I am having troubles when rotating the device. I face the problem that I have to recalculate the size of all UILabel to change the height constraint of all the "labels container" UIView and change the "C" UIView height constraint so that it can fit everything without large blank spaces between all views.
So my question is: how can I achieve the same behaviour using exclusively Auto Layout?
Now I have to recalculate sizes and change height constraints so that everything adapts, but I would love that all UILabel resize themselves automatically and fit their content, then the "labels container" UIView resize to fit all the UILabel and then "C" UIView resizes automatically to fit the content.
Thank you in advance!
As per my knowledge you need to add TableView for your dynamic labels and to achieve this you have to follow below steps:
1step : Add ScrollView in your ViewController.
2step : Add ContainerView in ScrollView to manage scroll constrains.
3step : Add your "A" view with fixed size lets say 150px.
4step : Add your "B" view with fixed size lets say 150px.
5step : Add TableView for dynamic labels with fixed size 0px. At this step make Outlet of your tableView height Constrains we will use it in next step.
6step : Call your service and add labels in your tableView.
in this 6th step while you are adding labels in your table view means while you reload your tableView do below code for dynamic heigh constrains.
7step : Now as per your Row height adjust height of table view as mention below.
arrListArray = #[#"One",#"Two",#"Three",#"Four",#"Five",#"Six",#"Seven",#"Eight",#"Nine",#"Ten"];
int rowHeight = 44;
self.tblHeightConstraint.constant = (rowHeight * arrListArray.count);
now reload your constrains with below method:
[UIView animateWithDuration:0.1
animations:^{
// Called on parent view
[self.view layoutIfNeeded];
}];
it will increase your scrollView contain size automatically.
Show sample images :
View Constrains Stack :
Now as per your comment you have to add CollectionView in place of tableView and add TableView in CollectionViewCell with your specific design.
Hope this will helps!
Ok, I was doing three things wrong:
1st: I was calculating the height of the labels to set then the size of the "labels container" UIView using a height constraint. Height constraints not needed as Auto Layout manages everything.
2nd: I was modifying the "C" UIView constraint height manually by adding the height value of each "labels container" UIView added.
3rd: After fixing the first two steps, I forgot to set the last added UIView constraint with its parent view...
Now everything works as expected.

Arrange UILabels in UIstackView Dynamically

I am adding labels as subview to UIstackview using arrangedSubView property.
what happens is that if I keep Stackview axis horizontal,
I get one horizontal strip of UILabels
label1 label2 label3 label4.....
if I keep it vertical, I get a vertical strip of UILabels
like below
label1
label2
label3
and so on
But what I am trying to achieve is this
label1, label2(if it fits) otherwise take it below
label3, label4
label5 and so on
presently my code is this
uiLabel1.numberOfLines = 1
uiLabel1.backgroundColor = THEMECOLOR
uiLabel1.font = UIFont.preferredFontForTextStyle(UIFontTextStyleCaption1)
uiLabel1.textColor = topicColor
uiLabel1.layer.cornerRadius = (CGFloat)(5.0)
uiLabel1.layer.masksToBounds = true
uiLabel1.sizeToFit()
uiLabel1.userInteractionEnabled = true
self.dynamicStackView.addArrangedSubview(uiLabel1)
I believe Flow Layout of the UICollectionView is what you need. Have a look at the link here

AutoLayout setting UILabel height to zero

I have a UILabel for item description amongst other views, all laid out using constraints in Interface Builder - you can see all relevant constraints in the image below. The number of lines is also set to 0.
I haven't set the height constraint because I want the UILabel to resize based on the text it contains. Instead, what happens is right after
[self.view layoutIfNeeded];
is called, the height of the UILabel gets set to 0. Even if I don't set other text to the UILabel, it has a default value of Item description set in Interface Builder.
The item title label above is set the same way, but that one doesn't get squashed to 0, so I'm a bit confused.
Anyone had any experience with such behaviour?
I managed to solve it by setting the UILabel's vertical compression resistance priority to 1000 (default 750) in Interface Builder.
Since my views are embedded in another view, and the parent view's bottom is dependent on the bottom of the lowest child view, I only speculate that the UILabel without a height constraint was getting squeezed in the process of laying out the views. Probably playing with priorities of other constraints somewhere down the chain would have yielded the same result, but I wasn't able to do it successfully. The solution above, however, worked, which is good enough in my case.
Hope this helps someone.
Set 3 constraint
1.Leading space to superview
2.Trailing space to superview
3.Top space to superview
then
#property (nonatomic, strong) IBOutlet UILabel *lbl;
- (void) viewDidLoad{
[self.lbl sizeToFit];
}
ctrl drag from the label to itself > select height > set the constant of the height to 0 and change equal (==) to greater than or equeal (>=)
I think you need to set 5 constraints on your label :
Leading space to superview
Trailing space to superview
Vertical space to "Item"
Vertical space to "Name"
Height
Then add an IBOutlet in your controller on the constraint height (let's say labelHeight).
So in your viewDidLoad you will be able to set this constraint value:
#property (weak, nonatomic) IBOutlet UILabel *label;
#property (weak, nonatomic) IBOutlet NSLayoutConstraint *labelHeight;
- (void) viewDidLoad{
[self.label sizeToFit];
labelHeight.constant = self.label.frame.size.height;
}
AutoLayout in this UIViewController can't satisfy all the constraints you have set, therefore it dismiss those on your UILabel, resulting in a compressed state. You should have a look at the other constraints in your UIViewController, and set the priority of the height contraint to a higher number.

Updating width of uilabel when subview not

I have UILabel in a UITableViewCell whose neighboring subview is UIControlSwitch. When the UIControlSwitch is hidden, the UILabel's width is expected to grow. Below is my custom class implementation:
- (void)setBounds:(CGRect)bounds
{
[super setBounds:bounds];
self.contentView.frame = self.bounds;
}
- (void)layoutSubviews
{
[super layoutSubviews];
[self.contentView updateConstraintsIfNeeded];
[self.contentView layoutIfNeeded];
self.numberLabel.preferredMaxLayoutWidth = CGRectGetWidth(self.numberLabel.frame);
self.quoteLabel.preferredMaxLayoutWidth = CGRectGetWidth(self.quoteLabel.frame);
}
#end
I have uploaded a sample code to explain the constraints on the storyboard.
The UILabel's width is not changing. If I set a trailing space between the UILabel and the UIControlSwitch, the control is partially hidden beyond the screen bounds. Hence the constraint for the UILabel has trailing space to the superview instead of the UIControlSwitch.
When you hide a view, it still takes part in the layout process. So simply hiding your UISwitch will not cause anything to change.
So you either have to add/remove constraints, remove the UISwitch from its containing view or change how you are doing the constraints.
Option 1: Instead of hide/show do remove/add.
If you put the UISwitch in its own view, you can pin the UILabel to the switches containing view. Instead of hiding the UISwitch, remove it from its containing view. The result will be that the containing view collapses and your UILabel will stretch as long as the label is pinned to the switches view and the switches view to the edge.
Instead of show, add the UILabel back into the container view. The view will stretch and your UILabel will shrink.
Option 2: Use simple trailing constraint for UILabel.
Make your UILabel have a trailing constraint to the superview and CTRL drag that constraint into your code so you can set its value. Or you could add it manually if that is what you are doing.
Do not pin the UILabel to the UISwitch at all. Pin the UILabel to the right h§and side so it has the required position.
So your UILabel is now pinned to the right edge with an offset of your choosing. If you set this small, the UILabel will be over/under the switch. If set to the width of the switch plus left and right margin it will appear to stretch up to the switch.
In your code, you can find the width of the UISwitch by looking at its frame.
1) When the UISwitch is to be visible, set the trailing pin constraint of the UILabel to be the margin you want + width of UISwitch + margin to left of UISwitch you want.
2) When the UISwitch is to be hidden, set the trailing pin constraint of the UILabel to be the margin you want to the superview.
This should then cause the UILabel to extend to the switch when not hidden, or to the edge when the switch is hidden.

iOS how to make dynamic width / autolayout of UIView according to UILabel within it

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.

Resources