iOS Autolayout keep distance from 2 views - ios

I'd like to have some views fixed on the top of the screen,
some other on the bottom and a single fixed size view in the equal distance between the top and bottom views.
I cannot figure out how to do this with Autolayout constraints. Do I need to add some spacer views to the UI, or calculate the desired position programatically?

You can do this with only one additional view. It'd look like this:
stuff_on_top
middle_view (with fixed size view inside)
stuff_on_bottom
There'd be vertical spacing constraints between stuff_on_top & middle_view and between middle_view & stuff_on_bottom. fixed size view would be centered horizontally and vertically in middle_view.
The other way of doing this would be two put two spacer views: between stuff_on_top & middle_view and between middle_view & stuff_on_bottom. Then you'd add a constraint that heights of spacing views are equal.

Check out this category: https://github.com/jrturton/UIView-Autolayout
Then you can do something as simple as this...
#import "UIView+AutoLayout.h"
...
- (void)viewDidLoad
{
[super viewDidLoad];
UIView *topView = [UIView autoLayoutView];
UIView *centralContainerView = [UIView autoLayoutView];
UIView *centralView = [UIView autoLayoutView];
UIView *bottomView = [UIView autoLayoutView];
topView.backgroundColor = [UIColor redColor];
bottomView.backgroundColor = [UIColor redColor];
centralView.backgroundColor = [UIColor greenColor];
[self.view addSubview:topView];
[self.view addSubview:centralContainerView];
[centralContainerView addSubview:centralView];
[self.view addSubview:bottomView];
//Pins the topView to the top, left and right edges of its superview (in iOS 7, it uses the topLayoutGuide)
[topView pinToSuperviewEdges:JRTViewPinTopEdge|JRTViewPinLeftEdge|JRTViewPinRightEdge inset:0 usingLayoutGuidesFrom:self];
//Constrains the height of topView to 75pts (if a value is passed as zero, no constrain is applied to that axis)
[topView constrainToSize:CGSizeMake(0, 75)];
//Pins the centralContainerView to the left and right edges of its superview
[centralContainerView pinToSuperviewEdges:JRTViewPinLeftEdge|JRTViewPinRightEdge inset:0];
//Pins the top of centralContainerView to the bottom of topView
[centralContainerView pinEdge:NSLayoutAttributeTop toEdge:NSLayoutAttributeBottom ofItem:topView];
//Pins the bottom of centralContainerView to the top of bottomView
[centralContainerView pinEdge:NSLayoutAttributeBottom toEdge:NSLayoutAttributeTop ofItem:bottomView];
//Centers centralView on the Y axis of its superview
[centralView centerInContainerOnAxis:NSLayoutAttributeCenterY];
//Pins the centralView to the left and right edges of its superview
[centralView pinToSuperviewEdges:JRTViewPinLeftEdge|JRTViewPinRightEdge inset:0];
//Constrains the height of topView to 100pts
[centralView constrainToSize:CGSizeMake(0, 100)];
//Pins the topView to the bottom, left and right edges of its superview (in iOS 7, it uses the bottomLayoutGuide)
[bottomView pinToSuperviewEdges:JRTViewPinBottomEdge|JRTViewPinLeftEdge|JRTViewPinRightEdge inset:0 usingLayoutGuidesFrom:self];
//Constrains the height of topView to 75pts
[bottomView constrainToSize:CGSizeMake(0, 75)];
}
And you get an output like this:
Edit:
I didn't see the interface-builder tag and just jumped to conclusions... An interface builder alternative would work similar to above.. You would need to have three main views, one pinned to the top and the other pinned to the bottom.. Then one with a flexible width that is pinned to the other two views.
You can then centre a fourth view with a fixed height in your middle view. This will then give you the result you are looking for

I code this
https://github.com/damienromito/UIView-AutoYPositioning
But I think a solution with AutoLayout exist...

Related

UIButton height change according to screen size

I have 10 UIButtons one below each other. I want to change its height according to the iPhone screen size. It should look bigger in iPhone 6 plus screen and smaller in iPhone 5s screen. How to do it using autolayout.
You first pick a UIView and set its constraints like top, bottom, leading and trailing, after that drag all UIButtons on the view and set all buttons constraints like top, bottom, leading, trailing and equal width and equal height constraints you can check these images
iPhone 7 Plus screen:-
and iPhone 5s screen
Xcode View
To do this, you must add the height of each button base on the percentage (%) of device screen size. so that button size can vary according to device(iPhone4s, 5s, 6 plus) screen size.
Now I’m going to add constraints programmatically using KVConstraintExtensionsMaster library. Try below code by calling below method from viewDidLoad of your ViewController.
- (void)configureScrollViewHierarchyAndApplyConstraint
{
CGFloat mainScreenHeight = [[UIScreen mainScreen] bounds].size.height;
// here baseScreenHeight is default screen height size for which we are implementing.
CGFloat baseScreenHeight = 667; // here default iPhone 6 height
// Note: try by changing baseScreenHeight via any iPhone screen height(480, 568, 667, 736) and see the changes in button height & space
// here fixed space and height are fixed size with respect to iPhone 6 height.
CGFloat fixedSpace = 28;
CGFloat fixedHeight = 150;
// ratio is responsible to increase or decrease button height depends on iPhone device size.
CGFloat ratio = mainScreenHeight/baseScreenHeight;
CGFloat baseSpace = fixedSpace * ratio;
CGFloat baseHeight = fixedHeight * ratio;
// prepare scrollView for autolayout
UIScrollView *scrollView = [UIScrollView prepareNewViewForAutoLayout];
scrollView.backgroundColor = [UIColor brownColor];
[self.view addSubview:scrollView];
// prepare containerView for autolayout
UIView *containerView = [UIView prepareNewViewForAutoLayout];
containerView.backgroundColor = [UIColor colorWithWhite:1 alpha:0.95];
[scrollView addSubview:containerView];
// To add Leading and Trailing constraint
[scrollView applyLeadingAndTrailingPinConstraintToSuperviewWithPadding:baseSpace];
// To add Top and Bottom constraint
[scrollView applyTopAndBottomPinConstraintToSuperviewWithPadding:baseSpace];
// To add Top and Bottom constraint of containerView
[containerView applyTopAndBottomPinConstraintToSuperviewWithPadding:0];
// To Define the containerView X Position by adding HorizontalCenter constraint
[containerView applyConstraintForHorizontallyCenterInSuperview];
// Here To Define the width
[containerView applyEqualWidthPinConstrainToSuperview]; // Or
//[containerView applyLeadingPinConstraintToSuperviewWithPadding:10];
NSInteger count = 20;
UIButton *previousContentButton = nil;
for (NSInteger i = 0; i < count; i++)
{
UIButton *contentButton = [UIButton prepareNewViewForAutoLayout];
if (i&1) {
[contentButton setBackgroundColor:[UIColor greenColor]];
}else{
[contentButton setBackgroundColor:[UIColor redColor]];
}
[contentButton setTag:i];
[containerView addSubview:contentButton];
// Define the contentButton Size
[contentButton applyLeadingAndTrailingPinConstraintToSuperviewWithPadding:baseSpace];
[contentButton applyHeightConstraint:baseHeight];
if (i == 0) // for first
{
// To add top constraint
[contentButton applyTopPinConstraintToSuperviewWithPadding:baseSpace];
}
else if (i == count-1) // for last
{
// To add vertical constraint between two buttons
[previousContentButton applyConstraintFromSiblingViewAttribute:NSLayoutAttributeBottom toAttribute:NSLayoutAttributeTop ofView:contentButton spacing:baseSpace];
// To add bottom constraint
[contentButton applyBottomPinConstraintToSuperviewWithPadding:baseSpace];
}
else
{
// To add vertical constraint between two buttons
[previousContentButton applyConstraintFromSiblingViewAttribute:NSLayoutAttributeBottom toAttribute:NSLayoutAttributeTop ofView:contentButton spacing:baseSpace];
}
previousContentButton = contentButton;
}
[containerView updateModifyConstraints];
}
a simple example:
drag a button from the object library to your viewcontroller's view in storyboard
ctrl drag from your button to your view (drag to the left or the right) and choose center vertically in container
ctrl drag from your button to your view (drag to the top or the bottom) and choose center horizontally in container
ctrl drag FROM the button TO the button (yes, the same button) and choose aspect ratio
in the size inspector check the button's aspect ratio constraint to have a multiplier of 1:1
ctrl drag from your button to your view (drag to the left or the right) and choose equal widths
7 .in the size inspector check the button's equal width constraint to have a multiplier of 1:3 (or whatever value you like - 1:3 means that that the button's width is one third of the view's width)
You can check this answer.
Just use Verticle UIStackView.

How to top align the text in UITableView cell?

I want to top align the text in tableView cell.I created a cell subclass and added the following in layout subview.However it doesn't work.What am I missing?
- (void)layoutSubviews {
[super layoutSubviews];
CGRect newFrame = self.textLabel.frame;
newFrame.origin.y = CGRectGetMinY (self.contentView.bounds);
[self.textLabel setFrame:newFrame];
}
Basically what you are doing is putting the UILabel at high place in the cell, but the labels height might still be big, and as a result the text is not at the top.
Vertical align in UILabel is a problem. By default a text in UILabel is center vertical aligned. If you want to see the text at the top you will have to change the label height to fit the size:
[self.textLabel sizeToFit];
// Will keep the same X and Y so label will look like it moved
// to the upper left corner of it's frame.

Autolayout Animation Keeping UILabel Centered

I'm writing an iOS 7 app and I have a rectangle with a label on it. The label is aligned center and is created to be the same size as the view (for simplicity when increasing the size).
The desired effect is when clicked, to animate the view to full size, with the label staying centered the whole time.
I currently have tried:
Setting the top, left, bottom, and right constraints on the label to 0
Setting the height and width of the label to the view at the initial size and animating it to the full size along with the view animation
Setting the label's top and left constraints to 0 and animating the size to the full size
None of these produce the desired output. Each time the label seems to just snap to its final size before the view even starts animating.
Here is my code:
_viewHeightConstraint.constant = self.view.frame.size.height;
_viewWidthConstraint.constant = self.view.frame.size.width;
_viewTopConstraint.constant = 0;
_viewLeftConstraint.constant = 0;
_labelWidthConstraint.constant = self.view.frame.size.width;
_labelHeightConstraint.constant = self.view.frame.size.height;
[self.view needsUpdateConstraints];
[UIView animateWithDuration:1.5f
animations:^(void) {
[self.myView layoutIfNeeded]; //perform relayout of view containing label before relayout of entire view
[self.view layoutIfNeeded];
}];
I'm not sure if I've provided everything necessary, as I'm still new to autolayout. However, the desired effect is a view that animates to full size while the label in the center stays centered.
Add these constraints to your label:
(UIViewAutoresizingFlexibleLeftMargin |
UIViewAutoresizingFlexibleRightMargin |
UIViewAutoresizingFlexibleTopMargin |
UIViewAutoresizingFlexibleBottomMargin)
Why do you call needsUpdateConstraints?
All the changes occur there. Just call:
...
_labelWidthConstraint.constant = self.view.frame.size.width;
_labelHeightConstraint.constant = self.view.frame.size.height;
[UIView animateWithDuration:1.5f
animations:^(void) {
[self.view layoutIfNeeded];
}];
On a side note, why do you set height and width constraints on the label? You can just add center horizontal and vertical constraints. I don't think you can set vertical text alignment for a label. But that was not your initial issue.

Auto layout UIScrollView with subviews with dynamic heights

I'm having troubles with UIScrollView using auto layout constraints.
I have the following view hierarchy, with constraints set through IB:
- ScrollView (leading, trailing, bottom and top spaces to superview)
-- ContainerView (leading, trailing, bottom and top spaces to superview)
--- ViewA (full width, top of superview)
--- ViewB (full width, below ViewA)
--- Button (full width, below ViewB)
The ViewA and ViewB have initial heights of 200 points, but it can be expended vertically to an height of 400 points by clicking on it. ViewA and ViewB are expanded by updating their height constraint (from 200 to 400). Here is the corresponding snippet :
if(self.contentVisible) {
heightConstraint.constant -= ContentHeight;
// + additional View's internal constraints update to hide additional content
self.contentVisible = NO;
} else {
heightConstraint.constant += ContentHeight;
// + additional View's internal constraints update to show additional content
self.contentVisible = YES;
}
[self.view setNeedsUpdateConstraints];
[UIView animateWithDuration:.25f animations:^{
[self.view layoutIfNeeded];
}];
My problem is that if both views are expanded, I need to be able to scroll to see the whole content, and right now the scroll is not working. How can I manage to update the scroll view using constraints to reflect the changes of ViewA and ViewB heights ?
The only solution I can think of so far is to manually set the height of the ContainerView after the animation, which will be the sum of the heights of ViewA + ViewB + Button. But I believe there is a better solution?
Thanks
I use pure structure like the following
-view
-scrollView
-view A
-view B
-Button
Make sure Button(THE LAST view) has a constraint(vertical spacing from its bottom to superview, which is the scrollview), in this case, no matter what changes for your view A and view B would be, scrollView's height will be changed accordingly.
I reference to this great online book site.
Just read the "Creating a scroll view" section, you should have an idea.
I had the similar problem that I was creating a detail view and using Interface Builder with Auto layout is such a good fit for the task!
Good luck!
(Additional resources:
Stack overflow discussion about the auto layout for scroll view.
iOS 6 has a Release Notes talking about Auto Layout support for UIScrollView.
Free online iOS book explanation about scroll view. This actually helped me a lot!
Let's say we have a hierachy like this (Label1 is a subview of ContentView; ContentView is a subview of ScrollView, ScrollView is a subiview of the viewcontroller's view):
ViewController's View
ScrollView
ContentView
Label1
Label2
Label3
ScrollView is constrained with autolayout in the normal way to the viewcontroller's view.
ContentView is pinned top/left/right/bottom to scrollview. Meaning you have constraints that make the ContentView's top/bottom/leading/trailing edges constrained to be equal to the same edges on the ScrollView. Here is a key: these constraints are for the contentSize of the ScrollView, not its frame size as shown in the viewcontroller's view. So it's not telling the ContentView to be the same frame size as the displayed ScrollView frame, it's rather telling Scrollview that the ContentView is its content and so if contentview is larger than the ScrollView frame then you get scrolling, just like setting scrollView.contentSize larger than scrollView.frame makes the content scrollable.
Here is another key: now you have to have enough constraints between ContentView, Label1-3, and anything else besides the Scrollview for the ContentView to be able to figure out it's width and height from those constraints.
So for example if you want a vertically scrolling set of labels, you set a constraint to make the ContentView width equal to the ViewController View's width, that takes care of the width. To take care of the height, pin Label1 top to ContentView top, Label2 top to Label1 bottom, Label3 top to Label2 bottom, and finally (and importantly) pin Label3's bottom to ContentView's bottom. Now it has enough information to calculate the ContentView's height.
I hope this gives someone a clue, as I read through the above posts and still couldn't figure out how to make the ContentView's width and height constraints properly. What I was missing was pinning the Label3's bottom to the ContentView's bottom, otherwise how could ContentView know how tall it is (as Label3 would just then be floating, and there would be no constraint to tell ContentView where it's bottom y position is).
This is an example of how I have laid out a pure autolayout UIScrollView with a container view. I've commented to make it clearer:
container is a standard UIView and body is a UITextView
- (void)viewDidLoad
{
[super viewDidLoad];
//add scrollview
[self.view addSubview:self.scrollView];
//add container view
[self.scrollView addSubview:self.container];
//body as subview of container (body size is undetermined)
[self.container addSubview:self.body];
NSDictionary *views = #{#"scrollView" : self.scrollView, #"container" : self.container, #"body" : self.body};
NSDictionary *metrics = #{#"margin" : #(100)};
//constrain scrollview to superview, pin all edges
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[scrollView]|" options:kNilOptions metrics:metrics views:views]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[scrollView]|" options:kNilOptions metrics:metrics views:views]];
//pin all edges of the container view to the scrollview (i've given it a horizonal margin as well for my purposes)
[self.scrollView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[container]|" options:kNilOptions metrics:metrics views:views]];
[self.scrollView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-margin-[container]-margin-|" options:kNilOptions metrics:metrics views:views]];
//the container view must have a defined width OR height, here i am constraining it to the frame size of the scrollview, not its bounds
//the calculation for constant is so that it's the width of the scrollview minus the margin * 2
[self.scrollView addConstraint:[NSLayoutConstraint constraintWithItem:self.container attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:self.scrollView attribute:NSLayoutAttributeWidth multiplier:1.0f constant:-([metrics[#"margin"] floatValue] * 2)]];
//now as the body grows vertically it will force the container to grow because it's trailing edge is pinned to the container's bottom edge
//it won't grow the width because the container's width is constrained to the scrollview's frame width
[self.container addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[body]|" options:kNilOptions metrics:metrics views:views]];
[self.container addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[body]|" options:kNilOptions metrics:metrics views:views]];
}
In my example 'body' is a UITextView, but it could be anything else. If you happen to be using a UITextView as well note that in order for it to grow vertically it must have a height constraint that gets set in viewDidLayoutSubviews. So add the following constraint in viewDidLoad and keep a reference to it:
self.bodyHeightConstraint = [NSLayoutConstraint constraintWithItem:self.body attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:nil multiplier:1.0f constant:100.0f];
[self.container addConstraint:self.bodyHeightConstraint];
Then in viewDidLayoutSubviews calculate the height and update the constraint's constant:
- (void)viewDidLayoutSubviews
{
[super viewDidLayoutSubviews];
[self.bodyHeightConstraint setConstant:[self.body sizeThatFits:CGSizeMake(self.container.width, CGFLOAT_MAX)].height];
[self.view layoutIfNeeded];
}
The second layout pass is needed to resize the UITextView.
Use this code. ScrollView setContentSize should be called async in main thread.
Swift:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
DispatchQueue.main.async {
var contentRect = CGRect.zero
for view in self.scrollView.subviews {
contentRect = contentRect.union(view.frame)
}
self.scrollView.contentSize = contentRect.size
}
}
Objective C:
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
dispatch_async(dispatch_get_main_queue(), ^ {
CGRect contentRect = CGRectZero;
for(UIView *view in scrollView.subviews)
contentRect = CGRectUnion(contentRect,view.frame);
scrollView.contentSize = contentRect.size;
});
}
At every moment the scroll view should know its content size. The content size is inferred from the scrollview's subviews. It is very handy to map controller properties to the constraints in the xib file describing heights of the subviews. Then in the code (an animation block) you can just change constants of these constraint properties. If you need to change the entire constraint, keep a reference to it, so that you can update it later in the parent container.
My variant for scroll view with !Dynamic! height:
1) Add scroll view to your UIView. Pin all (top, bottom, lead, trail) constraints.
2) Add UIView to Scroll View. Pin all (top, bottom, lead, trail) constraints. It will be your Content view. You can also rename it.
3) Control drag from Content view to Scroll view - Equal width
4) Add content to your UIView. Set needed constraints. And! At the lower item add bottom constraint NOT Greater or equal (>=)(Like most people talks) BUT Equal! Set it to 20 for example.
In my situation I have UIImageView in content. I have connected it's height to code. And if I change it to like 1000, scroll is visible. And all works.
Works like a charm for me. Any questions - welcome to comments.

UIView autoresizingMask for fixed left and right margin and flexible width

I can't figure it out how to resize width with fixed left and right margin.
I don't find any fixed led/right margin APIs.
In code, to get a view to have fixed left and right margins along with flexible width you can do the following:
UIView *parentView = self.view; // adjust as needed
CGRect bounds = parentView.bounds; // get bounds of parent view
CGRect subviewFrame = CGRectInset(bounds, 20, 0); // left and right margin of 20
UIView *subview = [[UIView alloc] initWithFrame:subviewFrame];
subview.autoresizingMask = UIViewAutoresizingFlexibleWidth;
[parentView addSubview:subview];
Adjust as needed to create your actual subview. Adjust the subviewFrame to match your desired margins.
As answered, this will give your subview fixed left and right margins of 20 points each and a flexible width. When setting the autoresizingMask, any component not set as flexible is automatically fixed (almost). This means the top margin and height are also fixed (since they are not set). The bottom margin is made implicitly flexible since the top margin and height are fixed. All three values going across or up/down can't be fixed at the same time for obvious reasons.

Resources