Center custom UIView vertically and horizontally using Auto Layout - ios

I'm trying to build a rather simple animated custom UI using the Auto Layout API newly available iOS 6. The custom view I'm building has a circle that I want to be both vertically and horizontally centered.
Unfortunately I can't figure out why my constraints appear to work fine for UIButton, and UILabel elements but yield weird results when I use a custom view with and custom CALayer (in this case a circle, that will eventually be animated).
To be clear I don't want my view to expand to fill the whole screen, but rather to have dynamic "padding" so that the view is vertically centered both on the iPhone 4 and 5. I should also note that I'm very new to Cocoa and UIKit.
RootViewController.m:
...
- (void)viewDidLoad {
[super viewDidLoad];
// Create Circle View
CGRect circle_view_rect = CGRectMake(0, 0, 100, 100);
UIView *circle_view = [[UIView alloc] initWithFrame:circle_view_rect];
// Create Circle Layer
CircleLayer *circle_layer = [[CircleLayer alloc] init];
circle_layer.needsDisplayOnBoundsChange = YES;
circle_layer.frame = circle_view.bounds;
[circle_view.layer addSublayer:circle_layer];
// Enable Auto Layout
[circle_view setTranslatesAutoresizingMaskIntoConstraints:NO];
[self.view addSubview:circle_view];
// Center Vertically
NSLayoutConstraint *centerYConstraint =
[NSLayoutConstraint constraintWithItem:circle_view
attribute:NSLayoutAttributeCenterY
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeCenterY
multiplier:1.0
constant:0.0];
[self.view addConstraint:centerYConstraint];
// Center Horizontally
NSLayoutConstraint *centerXConstraint =
[NSLayoutConstraint constraintWithItem:circle_view
attribute:NSLayoutAttributeCenterX
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeCenterX
multiplier:1.0
constant:0.0];
[self.view addConstraint:centerXConstraint];
}
...
CircleLayer.m:
...
- (void)drawInContext:(CGContextRef)context {
CGContextAddArc(context, 50, 50, 50, 0.0, 2*M_PI, 0);
CGContextSetFillColorWithColor(context, [UIColor yellowColor].CGColor);
CGContextFillPath(context);
}
...
Basically the constraints I've implemented are:
center vertically inside parent view
center horizontally inside parent view
And this is the result I get:
Any help would be greatly appreciated, I've been pondering this one for a few days now.
Thanks

Try adding a height and width constraint to your circle_view. I couldn't even get just a pain square view to appear at all without adding those (using your code, minus the layer stuff).
NSLayoutConstraint *heightConstraint =
[NSLayoutConstraint constraintWithItem:circle_view
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:nil
attribute:NSLayoutAttributeNotAnAttribute
multiplier:1.0
constant:100.0];
[circle_view addConstraint:heightConstraint];
NSLayoutConstraint *widthConstraint =
[NSLayoutConstraint constraintWithItem:circle_view
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:nil
attribute:NSLayoutAttributeNotAnAttribute
multiplier:1.0
constant:100.0];
[circle_view addConstraint:widthConstraint];

Just to add to rdelmar's answer:
The core issue is that as soon as you go the NSLayoutConstraint route, and specify setTranslatesAutoresizingMaskIntoConstraints:NO, the frame you made with CGRectMake is rendered irrelevant for AutoLayout purposes. That's why it didn't use the info from the frame's height and width.

Related

Add Constraint to ScrollView

I am trying to add constraints to UIScrollView with a label (subview of scrollview)
but the scrollview wouldn't scroll and trailing never work.
float y = self.navigationController.navigationBar.frame.size.height + 30;
self.scrollView = [[UIScrollView alloc]initWithFrame:CGRectMake(0,0, 0,0)];
[self.scrollView setBackgroundColor:[UIColor blueColor]];
[self.scrollView setScrollEnabled:YES];
[transparentImageView addSubview:self.scrollView];
self.scrollView.translatesAutoresizingMaskIntoConstraints = NO;
[transparentImageView addConstraint:[NSLayoutConstraint constraintWithItem:self.scrollView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:transparentImageView attribute:NSLayoutAttributeBottom multiplier:1.0f constant:-120.0]];
//leading
[transparentImageView addConstraint:[NSLayoutConstraint constraintWithItem:self.scrollView attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:transparentImageView attribute:NSLayoutAttributeLeading multiplier:1.0f constant:20.0f]];
//trailing
[transparentImageView addConstraint:[NSLayoutConstraint constraintWithItem:self.scrollView attribute:NSLayoutAttributeTrailing relatedBy:NSLayoutRelationEqual toItem:transparentImageView attribute:NSLayoutAttributeTrailing multiplier:1.0f constant:-20.0]];
[transparentImageView addConstraint:[NSLayoutConstraint constraintWithItem:self.scrollView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:transparentImageView attribute:NSLayoutAttributeTop multiplier:1.0f constant:y]];
self.shineText = [[RQShineLabel alloc]initWithFrame:CGRectMake(0,0, 0, 0)];
[self setupText];
[self.shineText setBackgroundColor:[UIColor redColor]];
[self.scrollView addSubview:self.shineText];
self.shineText.translatesAutoresizingMaskIntoConstraints = NO;
self.scrollView.contentSize = CGSizeMake(self.scrollView.frame.size.width, self.scrollView.frame.size.height);
//bottom
[self.scrollView addConstraint:[NSLayoutConstraint constraintWithItem:self.shineText attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self.scrollView attribute:NSLayoutAttributeBottom multiplier:1.0f constant:-10.0f]];
//leading
[self.scrollView addConstraint:[NSLayoutConstraint constraintWithItem:self.shineText attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:self.scrollView attribute:NSLayoutAttributeLeading multiplier:1.0f constant:10.0f]];
//trailing
[self.scrollView addConstraint:[NSLayoutConstraint constraintWithItem:self.shineText attribute:NSLayoutAttributeTrailing relatedBy:NSLayoutRelationEqual toItem:self.scrollView attribute:NSLayoutAttributeTrailing multiplier:1.0f constant:-500.0f]];
[self.scrollView addConstraint:[NSLayoutConstraint constraintWithItem:self.shineText attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.scrollView attribute:NSLayoutAttributeTop multiplier:1.0f constant:20]];
If you have this line self.scrollView.contentSize = CGSizeMake(self.scrollView.frame.size.width, self.scrollView.frame.size.height); then your scroll view never make scroll at all because your content size is the same as the scroller frame, the content size of your scrollView must be greater than the scrollView frame in order to scroll
Try with something like this
self.scrollView.contentSize = CGSizeMake(self.scrollView.frame.size.width, 1000);
then your scrollView must scroll
I hope this helps you
Generally, while working with UIScrollView and auto layouts, we need to add one UIView as child view on scroll view with same size as scroll view.
Here is tutorial which explains this in details.
But this tutorial is for using scroll view in .nib file.
For adding scroll view programatically, go through Apple developer technical note.
It suggest that:
Use constraints to lay out the subviews within the scroll view, being sure that the constraints tie to all four edges of the scroll view and do not rely on the scroll view to get their size.
try this tricky way to work with scrollview in storyboard. I created a small video tutorial to show how easy to work with scroll view. You should have a small knowledge of autolayout.
step 1 : change your viewcontroller size as your need
step 2 : add a scroll view and setup constarints (top, lead, trail and top) to the conatainer view.
step 3 : then add another view on to scroll view(this is your child view where you add your all other IBOutlets)
step 4 : then add constraints to it. (top, lead, trail and top to scroll view) + (width and height to it self)
step 5 : create an outlet to the child view width to your controller and give the width programatically( the device width)
this small tutorial shows you how to do it..
part one : less thant 5 min
part two : less than 5 min
hope this will help to you.

how to add multiple images with each of its constraints using For/While loop

What i want to achieve is set buttons/images Horizontally Centered with main view, including its width set to 75% of the screen width.
I want to fit like 7 such images/buttons vertically on the screen (line-by-line).
I am using the following code, which is working perfect:
UIImageView *l1 = [[UIImageView alloc] initWithFrame:CGRectMake(20, 20, 100, 100)];
[l1 setImage:[UIImage imageNamed:#"level-1"]];
[l1 setContentMode:UIViewContentModeScaleAspectFit];
l1.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:l1];
NSLayoutConstraint *c1 = [NSLayoutConstraint
constraintWithItem:l1
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeTop
multiplier:1.0f
constant:20.f
];
[self.view addConstraint:c1];
NSLayoutConstraint *c1b = [NSLayoutConstraint
constraintWithItem:l1
attribute:NSLayoutAttributeCenterX
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeCenterX
multiplier:1.0f
constant:0.f
];
[self.view addConstraint:c1b];
NSLayoutConstraint *c1c = [NSLayoutConstraint
constraintWithItem:l1
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeWidth
multiplier:0.75f
constant:0.f
];
[self.view addConstraint:c1c];
Instead of repeating the same code for different images, i want to use some iteration process and increment the image name [UIImage imageNamed:#"level-xxx"]] and bind top position to the bottom position of the last added item.
How it would be possible? Thx
iOS 9 introduces a new class called UIStackView that allows you to do this :
https://www.hackingwithswift.com/read/31/2/uistackview-by-example
There are even back-ports of this to iOS 7 on Github.
Some people might downvote me for this but sometimes , when it comes to autolayout I just say , $##& this I'll write the layout code myself. And this is one of those cases (especially when UIStackView is not available).
First create all your views and store them in an array in viewDidLoad.
Override viewDidLayoutSubviews on your view controller and loop through the array , set the frames and lay them out one by one.
-(void)viewDidLayoutSubviews
{
NSArray* imageList = self.imageViewList
// calculate it if necessary
CGPoint startPoint = CGPointMake(200,200)
CGFloat spacing = 50
for(UIView *view in imageList) {
view.center = startPoint
startPoint = CGPointMake(startPoint.x , startPoint.y + spacing)
}
}
This is way more obvious , way easier to debug than a mess of constraints created in code.

UISlider bar disappears using auto layout programmatically

I created a UISlider programmatically, and after adding two auto layout constraints the slider bar no longer appears. I am seeing the nub / circle for the slider, but nothing else.
// create slider programmatically
_sliderFrame = CGRectMake(10.0f, 10.0f, 250.0f, 400.0f);
_sliderCalibrate = [[UISlider alloc] initWithFrame:_sliderFrame];
_sliderCalibrate.minimumValue = 1.0f;
_sliderCalibrate.maximumValue = 100.0f;
_sliderCalibrate.value = 50.0f;
// manually specify Auto Layout constraints in code
[_sliderCalibrate setTranslatesAutoresizingMaskIntoConstraints:NO];
// add slider to view
[self.view addSubview:_sliderCalibrate];
NSLayoutConstraint *centerSliderX = [NSLayoutConstraint constraintWithItem:_sliderCalibrate
attribute:NSLayoutAttributeCenterX
relatedBy:NSLayoutRelationEqual toItem:_sliderCalibrate.superview
attribute:NSLayoutAttributeCenterX
multiplier:1.0
constant:0.0];
NSLayoutConstraint *centerSliderY = [NSLayoutConstraint constraintWithItem:_sliderCalibrate
attribute:NSLayoutAttributeCenterY
relatedBy:NSLayoutRelationEqual toItem:_sliderCalibrate.superview
attribute:NSLayoutAttributeCenterY
multiplier:1.0
constant:0.0];
[_sliderCalibrate.superview addConstraints:#[centerSliderX, centerSliderY]];
You shouldn't set any frame when using auto layout, so just create the slider with [UISlider new]; Add either a width constraint, or delete the centerX constraint, and add constraints to both edges of the superview to give the slider a non-zero width.
By calling setFrame:​ for initializing an object, you don't need to call setTranslatesAutoresizingMaskIntoConstraints:​ with the argument NO on views that you place manually.
Just remove this line of code.

UIProgressView displaying issues in iOS 7

I need UIProgressView as part of UITableView cell for one iOS 7 application. I have added UIProgressView from code on the right side of cell like this:
_progressBar = [[UIProgressView alloc] initWithProgressViewStyle:UIProgressViewStyleDefault];
_progressBar.translatesAutoresizingMaskIntoConstraints = NO;
[self.contentView addSubview:_progressBar];
[self.contentView addConstraint:[NSLayoutConstraint constraintWithItem:_progressBar attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self.contentView attribute:NSLayoutAttributeCenterY multiplier:1.0 constant:0.0]];
[self.contentView addConstraint:[NSLayoutConstraint constraintWithItem:_progressBar attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual toItem:self.contentView attribute:NSLayoutAttributeRight multiplier:1.0 constant:-10.0]];
[_progressBar addConstraint:[NSLayoutConstraint constraintWithItem:_progressBar attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:60.0]];
[_progressBar addConstraint:[NSLayoutConstraint constraintWithItem:_progressBar attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:12.0]];
However, displaying UIProgressView is a bit strange. When progress is zero, UIProgressView object looks like this:
When I set progress value to 0.5, UIProgressView becomes rectangle on the left and remains rounded rectangle on the right:
And when I set progress to 0.98, looks like this:
Any idea how to fill UIProgressView by simply setting progress value and that rounded rectangle (or rectangle) form of element are kept the entire time? Or is this impossible without adding custom graphics or overriding UIProgressView?
Many thanks in advance and best regards.
Had the same problem today and fix is not that hard :)
In interface builder add a UIView with the exact same size as the UIProgressView. Add your UIProgressView in this new UIView.
Do the following for your UIView if you want corners:
1) Set cornerradius to the half of the height (e.g. 30px height progressbar means 15px corner radius UIVIew))
myView.layer.cornerRadius = 15;
2) Set mask to bounds
myView.layer.masksToBounds = TRUE;
3) Set clip to bounds so nothing can come out of the UIView
myView.clipsToBounds = TRUE;
And now you have a rounded UIProgressBar with the height you want.
If you don't want rounded corners just add a UIView as the same way but only change the background color to the same grey as the UIProgressbar tracktint.
Goodluck!
-Sjoerd
For future reference: I've solved this issue by setting the corner radius like
_progressView.layer.cornerRadius = 2.0;
And then:
_progressView.clipsToBounds = YES;
No need for a container for the UIProgressView.

Draw and Center a Custom UIView as Background Using Auto Layout

I have a BoardViewController (UIViewController) and need to draw centered coordinate lines into its background. For these coordinate lines I created a custom UIView class CoordinateView which are added as subView. The coordinateView should be centered and fill the whole screen even when changing the device orientation.
To do this I'd like to use Auto Layout implemented in code. Here's my current setup:
In the CoordinatesView (UIView) class a custom draw method for the coordinate lines
- (void)drawRect:(CGRect)rect {
[super drawRect:rect];
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetStrokeColorWithColor(context, [UIColor whiteColor].CGColor);
CGContextSetLineWidth(context, 1.0);
CGContextMoveToPoint(context, self.bounds.size.width/2,0);
CGContextAddLineToPoint(context, self.bounds.size.width/2,self.bounds.size.height);
CGContextStrokePath(context);
CGContextMoveToPoint(context, 0,self.bounds.size.height/2);
CGContextAddLineToPoint(context, self.bounds.size.width,self.bounds.size.height/2);
CGContextStrokePath(context);
}
Initializing this coordinatesView object in the BoardViewController
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
...
coordinatesView = [[CoordinatesView alloc]initWithFrame:self.view.frame];
[coordinatesView setBackgroundColor:[UIColor redColor]];
[coordinatesView clipsToBounds];
[coordinatesView setTranslatesAutoresizingMaskIntoConstraints:NO];
[self.view addSubview:coordinatesView];
[self.view sendSubviewToBack:coordinatesView];
...
}
Adding the auto layout magic to the coordinateView in the BoardViewController's viewWillAppear function
-(void)viewWillAppear:(BOOL)animated{
...
NSLayoutConstraint *constraintCoordinatesCenterX =[NSLayoutConstraint
constraintWithItem:self.view
attribute:NSLayoutAttributeCenterX
relatedBy:NSLayoutRelationEqual
toItem:coordinatesView
attribute:NSLayoutAttributeCenterX
multiplier:1.0
constant:1];
NSLayoutConstraint *constraintCoordinatesCenterY =[NSLayoutConstraint
constraintWithItem:self.view
attribute:NSLayoutAttributeCenterY
relatedBy:NSLayoutRelationEqual
toItem:coordinatesView
attribute:NSLayoutAttributeCenterY
multiplier:1.0
constant:1];
[self.view addConstraint: constraintCoordinatesCenterX];
[self.view addConstraint: constraintCoordinatesCenterY];
...
}
Note: This approach worked for me using an UIImageView Image as coordinates but it doesn't work with the custom UIView coordinateView.
How do I make it work again? As soons as I apply the Auto Layout/NSLayoutConstraint my coordinatesView UIView seems disappears
Is this actually a good approach to add a background drawing to a UIViewController or is it better to directly draw into the UIViewController. (If so how would that look like?)
I appreciate your help with this.
Your view disappears because you set no size constraints -- you generally need 4 constraints to fully express the position and size of a view. Some views, like buttons have an intrinsic content size, so you don't need to explicitly set the size. The same is true for image views, which get their size from the image they display.
So, in your case, you can set the width and height equal to the width and height of the superview. This is something that I do often, so I've created a category on UIView that contains various constraint methods, so I don't have to write this over and over. I have one method constrainViewEqual: that does what you want to do:
#import "UIView+RDConstraintsAdditions.h"
#implementation UIView (RDConstraintsAdditions)
-(void)constrainViewEqual:(UIView *) view {
[view setTranslatesAutoresizingMaskIntoConstraints:NO];
NSLayoutConstraint *con1 = [NSLayoutConstraint constraintWithItem:self attribute:NSLayoutAttributeCenterX relatedBy:0 toItem:view attribute:NSLayoutAttributeCenterX multiplier:1 constant:0];
NSLayoutConstraint *con2 = [NSLayoutConstraint constraintWithItem:self attribute:NSLayoutAttributeCenterY relatedBy:0 toItem:view attribute:NSLayoutAttributeCenterY multiplier:1 constant:0];
NSLayoutConstraint *con3 = [NSLayoutConstraint constraintWithItem:self attribute:NSLayoutAttributeWidth relatedBy:0 toItem:view attribute:NSLayoutAttributeWidth multiplier:1 constant:0];
NSLayoutConstraint *con4 = [NSLayoutConstraint constraintWithItem:self attribute:NSLayoutAttributeHeight relatedBy:0 toItem:view attribute:NSLayoutAttributeHeight multiplier:1 constant:0];
NSArray *constraints = #[con1,con2,con3,con4];
[self addConstraints:constraints];
}
Then, in your main code, you can call it like this:
[self.view constrainViewsEqual:coordinatesView];

Resources