How to add label programmatically and position it with auto layout programmatically - ios

I'm trying add a label to a UIScrollView. I'm adding a top constraint and a leading constraint.
The label is not appearing.
Here is my code.
self.label = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 40, 40)];
self.label.translatesAutoresizingMaskIntoConstraints = NO;
self.label.backgroundColor = [UIColor clearColor];
self.label.textAlignment = NSTextAlignmentCenter;
self.label.textColor=[UIColor whiteColor];
self.label.text = #"Hello";
[self.imageCarouselScrollView addSubview:self.label];
[self bringSubviewToFront:self.label];
self.titleLabelTopConstraint = [NSLayoutConstraint
constraintWithItem:self
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:self.imageCarouselScrollView
attribute:NSLayoutAttributeBottom
multiplier:1.0
constant:20];
NSLayoutConstraint *titleLeadingConstraint = [NSLayoutConstraint
constraintWithItem:self
attribute:NSLayoutAttributeLeading
relatedBy:NSLayoutRelationEqual
toItem:self.imageCarouselScrollView
attribute:NSLayoutAttributeTrailing
multiplier:1.0
constant:20];
[self addConstraints:#[self.titleLabelTopConstraint,titleLeadingConstraint]];

You have to include a constraint to the right (for horizontal scrolling) or bottom (for vertical scrolling) of the scroll view so that it can deduce the content size.

I try comment your code:
self.label = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 40, 40)]; //if 'translatesAutoresizingMaskIntoConstraints=NO' frame not work work anymore. Use CGRectZero instead
self.label.translatesAutoresizingMaskIntoConstraints = NO;
self.label.backgroundColor = [UIColor clearColor];
self.label.textAlignment = NSTextAlignmentCenter;
self.label.textColor=[UIColor whiteColor];
self.label.text = #"Hello";
[self.imageCarouselScrollView addSubview:self.label]; //Your self.label added on 'self.imageCarouselScrollView' but your constraints added on self
[self bringSubviewToFront:self.label]; // ??? Why?
self.titleLabelTopConstraint = [NSLayoutConstraint
constraintWithItem:self
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:self.imageCarouselScrollView
attribute:NSLayoutAttributeBottom
multiplier:1.0
constant:20]; //it constrain means: "self view top edge equal to self.imageCarouselScrollView bottom + offset 20 "
NSLayoutConstraint *titleLeadingConstraint = [NSLayoutConstraint
constraintWithItem:self
attribute:NSLayoutAttributeLeading
relatedBy:NSLayoutRelationEqual
toItem:self.imageCarouselScrollView
attribute:NSLayoutAttributeTrailing
multiplier:1.0
constant:20];//it constrain means: "self view Leading edge equal to self.imageCarouselScrollView Trailing + offset 20 "
[self addConstraints:#[self.titleLabelTopConstraint,titleLeadingConstraint]];
And now i have few questions:
Why constraints for self.label?? Because after translatesAutoresizingMaskIntoConstraints=no it frame equals 0; You didn't see label anymore.
You have 2 constraints what about other edges????
No question, council, try use 'constraintsWithVisualFormat:options:metrics:views:'

Related

Constraints programmatically with Objective C

I don't know what I'm doing wrong: I'm creating a UIView that occupies all the screen (it has already constraints) and then, programmatically I'm creating an UI Image View:
_panel = [[UIImageView alloc] initWithImage:[self loadImageForKey:#"registerPanel"]];
_panel.frame = CGRectMake(0, 0, 100, 100);
_panel.exclusiveTouch = YES;
_panel.userInteractionEnabled = YES,
[self.scrollView addSubview:_panel];
And here it comes the problem: I'm adding constraints to the panel I created but it crashes (I'm doing it on the ViewWillAppear):
NSLayoutConstraint *centreHorizontallyConstraint = [NSLayoutConstraint
constraintWithItem:_panel
attribute:NSLayoutAttributeCenterX
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeCenterX
multiplier:1.0
constant:0];
NSLayoutConstraint *centreVerticalConstraint = [NSLayoutConstraint
constraintWithItem:_panel
attribute:NSLayoutAttributeCenterX
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeCenterX
multiplier:1.0
constant:0];
[_panel addConstraint:centreHorizontallyConstraint];
[_panel addConstraint:centreVerticalConstraint];
Error message:
When added to a view, the constraint's items must be descendants of that view (or the view itself). This will crash if the constraint needs to be resolved before the view hierarchy is assembled. Break on -[UIView _viewHierarchyUnpreparedForConstraint:] to debug.
You can constrain a scrollView's subview to the scrollView's parent (self.view in this case), but that's probably not what you want.
Edit: For clarification, the reason you were getting the error was because you initialize your constraints:
toItem:self.view
and then you try to add them:
[_panel addConstraint:centreHorizontallyConstraint];
[_panel addConstraint:centreVerticalConstraint];
You want to add them to the toItem object:
[self.view addConstraint:centreHorizontallyConstraint];
[self.view addConstraint:centreVerticalConstraint];
Again, you probably don't want to center _panel in the main view, but this will compile and run:
#import "AddPanelScrollViewController.h" /// just default .h
#interface AddPanelScrollViewController ()
#property (strong, nonatomic) UIScrollView *scrollView;
#property (strong, nonatomic) UIImageView *panel;
#end
#implementation AddPanelScrollViewController
- (void)viewDidLoad {
[super viewDidLoad];
_scrollView = [UIScrollView new];
_scrollView.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:_scrollView];
[_scrollView.topAnchor constraintEqualToAnchor:self.view.topAnchor constant:20.0].active = YES;
[_scrollView.bottomAnchor constraintEqualToAnchor:self.view.bottomAnchor constant:-20.0].active = YES;
[_scrollView.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor constant:20.0].active = YES;
[_scrollView.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor constant:-20.0].active = YES;
_scrollView.backgroundColor = [UIColor blueColor];
_panel = [UIImageView new];
// required
_panel.translatesAutoresizingMaskIntoConstraints = NO;
[self.scrollView addSubview:_panel];
// frame will be ignored when using auto-layout / constraints
// _panel.frame = CGRectMake(0, 0, 100, 100);
_panel.exclusiveTouch = YES;
_panel.userInteractionEnabled = YES;
_panel.backgroundColor = [UIColor redColor];
// _panel needs width and height constraints
[_panel.widthAnchor constraintEqualToConstant:100.0].active = YES;
[_panel.heightAnchor constraintEqualToConstant:100.0].active = YES;
NSLayoutConstraint *centreHorizontallyConstraint = [NSLayoutConstraint
constraintWithItem:_panel
attribute:NSLayoutAttributeCenterX
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeCenterX
multiplier:1.0
constant:0];
NSLayoutConstraint *centreVerticalConstraint = [NSLayoutConstraint
constraintWithItem:_panel
attribute:NSLayoutAttributeCenterY
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeCenterY
multiplier:1.0
constant:0];
// if constraints are releated to "self.view" that's where they need to be added
[self.view addConstraint:centreHorizontallyConstraint];
[self.view addConstraint:centreVerticalConstraint];
}
First you can't create constraints between panel & self.view because there is no common parent , instead you want to create them with the scrollview
NSLayoutConstraint *centreHorizontallyConstraint = [NSLayoutConstraint
constraintWithItem:_panel
attribute:NSLayoutAttributeCenterX
relatedBy:NSLayoutRelationEqual
toItem:self.scrollView
attribute:NSLayoutAttributeCenterX
multiplier:1.0
constant:0];
NSLayoutConstraint *centreVerticalConstraint = [NSLayoutConstraint
constraintWithItem:_panel
attribute:NSLayoutAttributeCenterY
relatedBy:NSLayoutRelationEqual
toItem:self.scrollView
attribute:NSLayoutAttributeCenterY
multiplier:1.0
constant:0];
[_scrollView addConstraint:centreHorizontallyConstraint];
[_scrollView addConstraint:centreVerticalConstraint];
Also both constraints are centerX , you need also width & height , or better top , leading , trailing and bottom to scrollView ,,, with width and height static or proportional to self.view
//
Also for any view you want to add constraints programmatically you must set
[self.scrollView setTranslatesAutoresizingMaskIntoConstraints: NO];
[self.panel setTranslatesAutoresizingMaskIntoConstraints: NO];

navigation title overlaps bar button item after rotating back to portrait

Im trying to truncate the tail of a label when the text is too long. Everything works initially (screenshots attached). Problem arises when rotate to landscape. Navigation bar extends to cover left and right navigation bar items. Same problem when rotate back to portrait. I placed border around label and nav bar to help illustrate (red for nav bar, blue for label). Is there a way to prevent nav bar from overlapping left and right bar items?
UILabel *broadcastLabel = [[UILabel alloc] initWithFrame:CGRectZero];
broadcastLabel.text = BILocalizedString(#"Broadcast with long titleeeeeeeeeeee");
[broadcastLabel sizeToFit];
broadcastLabel.textColor = BIColorForPaletteKey(BIColorPrimaryText);
broadcastLabel.font = BIFontForStyle(BIFontFixedNavigationStyle);
broadcastLabel.translatesAutoresizingMaskIntoConstraints = NO;
CGSize navBarSize = self.navigationController.navigationBar.layer.frame.size;
UIView *titleView = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, navBarSize.width, navBarSize.height)];
[titleView addSubview:broadcastLabel];
self.navigationController.navigationItem.titleView = titleView;
titleView.layer.borderWidth = 1;
titleView.layer.borderColor = [UIColor redColor].CGColor;
broadcastLabel.layer.borderWidth = 1;
broadcastLabel.layer.borderColor = [UIColor blueColor].CGColor;
[titleView addConstraint: [NSLayoutConstraint constraintWithItem:broadcastLabel
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:titleView
attribute:NSLayoutAttributeTop
multiplier:1.f
constant:4.f]];
[titleView addConstraint: [NSLayoutConstraint constraintWithItem:broadcastLabel
attribute:NSLayoutAttributeCenterX
relatedBy:NSLayoutRelationEqual
toItem:titleView
attribute:NSLayoutAttributeCenterX
multiplier:1.f
constant:0.f]];
[titleView addConstraint: [NSLayoutConstraint constraintWithItem:broadcastLabel
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationLessThanOrEqual
toItem:titleView
attribute:NSLayoutAttributeWidth
multiplier:1.f
constant:0.f]];
if (navBarSize.height >= tallSize)
{
/* Two lines*/
UILabel *onewayLabel = [[UILabel alloc] init];
onewayLabel.text = BILocalizedString(#"one-way message");
[onewayLabel sizeToFit];
onewayLabel.textColor = BIColorForPaletteKey(BIColorPrimaryText);
onewayLabel.font = BIFontForStyle(BIFontFixedSmallStyle);
onewayLabel.translatesAutoresizingMaskIntoConstraints = NO;
[titleView addSubview:onewayLabel];
[titleView addConstraint: [NSLayoutConstraint constraintWithItem:onewayLabel
attribute:NSLayoutAttributeCenterX
relatedBy:NSLayoutRelationEqual
toItem:titleView
attribute:NSLayoutAttributeCenterX
multiplier:1.f
constant:0.f]];
[titleView addConstraint: [NSLayoutConstraint constraintWithItem:onewayLabel
attribute:NSLayoutAttributeBottom
relatedBy:NSLayoutRelationEqual
toItem:titleView
attribute:NSLayoutAttributeBottom
multiplier:1.f
constant:-4.f]];
[titleView addConstraint: [NSLayoutConstraint constraintWithItem:onewayLabel
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationLessThanOrEqual
toItem:titleView
attribute:NSLayoutAttributeWidth
multiplier:1.f
constant:0.f]];
}
all three screenshots

Using a custom UITableView containing an image view cell with UITableViewAutomaticDimension

In my new app, I am trying to adopt AutoLayout throughout. In one of my table views, I have an image view and a label. If I do this in my controller's viewDidLoad: method
self.tableView.rowHeight = UITableViewAutomaticDimension;
the row is not high enough to accommodate the image. (I have added fixed width and height constraints to the image view).
If I remove the statement, then the height of the cell is the height as set in IB.
Is there a method I have to implement in my UITableCell class to tell the AutoLayout system my minimum height requirement? Or did I do something wrong?
Well, I could never figure this out using IB. However, I did get success adding the constraints manually, so I thought it worth sharing my complete solution. (Remember: an image view, a label, dynamic text). I want my image to always be 80 by 80, no matter how big the content gets.
Enjoy.
-(id)initWithCoder:(NSCoder *)aDecoder {
if ( !(self = [super initWithCoder:aDecoder]) ) return nil;
// TODO: Add an imageview and populate it
_titleLabel = [[UILabel alloc] initWithFrame:CGRectMake(0,0,300,20)];
_titleLabel.font = [UIFont preferredFontForTextStyle:UIFontTextStyleHeadline];
_titleLabel.numberOfLines = 0;
[self.contentView addSubview:_titleLabel];
_albumArtImageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 80, 80)];
[_albumArtImageView setTranslatesAutoresizingMaskIntoConstraints:NO];
[self.contentView addSubview:_albumArtImageView];
NSMutableArray* constraints = [NSMutableArray new];
UIView* contentView = self.contentView;
[_titleLabel setTranslatesAutoresizingMaskIntoConstraints:NO];
// _titleLabel to contentView
[constraints addObject:[NSLayoutConstraint
constraintWithItem:_titleLabel
attribute:NSLayoutAttributeFirstBaseline
relatedBy:NSLayoutRelationEqual
toItem:contentView
attribute:NSLayoutAttributeTop
// as font grow, space from top grows
multiplier:1.8
constant:30]];
// similarly for bottom
[constraints addObject:[NSLayoutConstraint
constraintWithItem:contentView
attribute:NSLayoutAttributeBottom
relatedBy:NSLayoutRelationGreaterThanOrEqual
toItem:_titleLabel
attribute:NSLayoutAttributeLastBaseline
multiplier:1.3
constant:8]];
[constraints addObject:[NSLayoutConstraint
constraintWithItem:contentView
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationGreaterThanOrEqual
toItem:nil
attribute:0
multiplier:1.0
constant:80]];
// Now the imageView
[constraints addObject:[NSLayoutConstraint
constraintWithItem:_albumArtImageView
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:nil
attribute:0
multiplier:1.0
constant:80]];
[constraints addObject:[NSLayoutConstraint
constraintWithItem:_albumArtImageView
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:nil
attribute:0
multiplier:1.0
constant:80]];
// for horizontal
[constraints addObjectsFromArray:
[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-2-[_albumArtImageView]-5-[_titleLabel]-15-|"
options:0 metrics:nil views:NSDictionaryOfVariableBindings(_titleLabel,_albumArtImageView) ]];
[self.contentView addConstraints:constraints];
return self;
}

iOS 8 UILabel intrinsicContentSize breaking layout constraints

I have a fairly simple custom UITableViewCell subclass containing a UISwitch, UIImageView, and UILabel. All of these are subviews of the cells contentView and laid out using AutoLayout. All subviews are centered on Y and then I create a series of left and right edge constraints to arrange them horizontally. The UILabel is the rightmost item and should scale to fit between the right edge of the UIImageView to its left and the right edge of its superview. When the text it too long to fit, it should truncate. This works as expected when I test using the simulator for iOS 7.1. However, on iOS 8.1 when the text in the label is too long to fit in the allotted space, the label pushes the UIImageView to its left out of position. I have never had this issue with UILabel in the past.
Edit: I should have mentioned I get no auto layout errors when this runs.
Below are screenshots and my code:
Here is the correct behavior on iOS 7:
This what I get on iOS 8:
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
self.backgroundColor = [UIColor whiteColor];
self.contentView.backgroundColor = [UIColor whiteColor];
UISwitch *filterSwitch = [[UISwitch alloc] init];
_filterSwitch = filterSwitch;
filterSwitch.translatesAutoresizingMaskIntoConstraints = NO;
[self.contentView addSubview:filterSwitch];
[self.contentView addConstraint:[NSLayoutConstraint constraintWithItem:filterSwitch
attribute:NSLayoutAttributeCenterY
relatedBy:NSLayoutRelationEqual
toItem:self.contentView
attribute:NSLayoutAttributeCenterY
multiplier:1.0
constant:0.0]];
[self.contentView addConstraint:[NSLayoutConstraint constraintWithItem:filterSwitch
attribute:NSLayoutAttributeLeft
relatedBy:NSLayoutRelationEqual
toItem:self.contentView
attribute:NSLayoutAttributeLeft
multiplier:1.0
constant:10.0]];
UIImageView *filterImageView = [[UIImageView alloc] init];
_filterImageView = filterImageView;
filterImageView.translatesAutoresizingMaskIntoConstraints = NO;
filterImageView.backgroundColor = [UIColor redColor];
filterImageView.contentMode = UIViewContentModeScaleAspectFit;
[self.contentView addSubview:filterImageView];
[self.contentView addConstraint:[NSLayoutConstraint constraintWithItem:filterImageView
attribute:NSLayoutAttributeCenterY
relatedBy:NSLayoutRelationEqual
toItem:self.contentView
attribute:NSLayoutAttributeCenterY
multiplier:1.0
constant:0.0]];
[self.contentView addConstraint:[NSLayoutConstraint constraintWithItem:filterImageView
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:nil
attribute:NSLayoutAttributeNotAnAttribute
multiplier:1.0
constant:30.0]];
[self.contentView addConstraint:[NSLayoutConstraint constraintWithItem:filterImageView
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:filterImageView
attribute:NSLayoutAttributeHeight
multiplier:1.0
constant:0.0]];
[self.contentView addConstraint:[NSLayoutConstraint constraintWithItem:filterImageView
attribute:NSLayoutAttributeLeft
relatedBy:NSLayoutRelationEqual
toItem:filterSwitch
attribute:NSLayoutAttributeRight
multiplier:1.0
constant:8.0]];
UILabel *filterLabel = [[UILabel alloc] init];
_filterLabel = filterLabel;
filterLabel.translatesAutoresizingMaskIntoConstraints = NO;
filterLabel.font = [UIFont fontWithName:#"Avenir-Medium" size:14.0];
filterLabel.textColor = [UIColor colorWithWhite:0.25 alpha:1.0];
filterLabel.textAlignment = NSTextAlignmentLeft;
filterLabel.lineBreakMode = NSLineBreakByTruncatingTail;
[self.contentView addSubview:filterLabel];
[self.contentView addConstraint:[NSLayoutConstraint constraintWithItem:filterLabel
attribute:NSLayoutAttributeCenterY
relatedBy:NSLayoutRelationEqual
toItem:self.contentView
attribute:NSLayoutAttributeCenterY
multiplier:1.0
constant:0.0]];
[self.contentView addConstraint:[NSLayoutConstraint constraintWithItem:filterLabel
attribute:NSLayoutAttributeLeft
relatedBy:NSLayoutRelationEqual
toItem:filterImageView
attribute:NSLayoutAttributeRight
multiplier:1.0
constant:8.0]];
[self.contentView addConstraint:[NSLayoutConstraint constraintWithItem:filterLabel
attribute:NSLayoutAttributeRight
relatedBy:NSLayoutRelationEqual
toItem:self.contentView
attribute:NSLayoutAttributeRight
multiplier:1.0
constant:-10.0]];
return self;
}
Presumably, the horizontal compression resistance priorities of the switch and the label are the same. Therefore, the layout is ambiguous (which is not detected automatically, so no errors at run time). When both can't fit in the available width, it is arbitrary which will be compressed. So, you were just getting lucky (or unlucky, depending on how you look at it) on iOS 7.
Set the horizontal compression resistance of the label to be lower than that of the other two views so that is compresses first.
I just tried: if you set up the same situation — H:|-[switch]-[image(==30)]-[label]-| — in a view in IB and set the text of the label to be long enough, IB will tell you about the ambiguity problem.

how content inset affects layout

While working with UIScrollView, I noticed some weird behavior about auto layout and content inset; take the following code for example:
- (void) loadView
{
UIScrollView * scroller = [[UIScrollView alloc] init];
scroller.backgroundColor = [UIColor orangeColor];
UILabel * label = [[UILabel alloc] init];
label.backgroundColor = [UIColor blueColor];
label.translatesAutoresizingMaskIntoConstraints = NO;
[scroller addSubview:label];
[scroller addConstraint: [NSLayoutConstraint constraintWithItem:label attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:scroller attribute:NSLayoutAttributeLeft multiplier:1.0 constant:40]];
[scroller addConstraint: [NSLayoutConstraint constraintWithItem:label attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:scroller attribute:NSLayoutAttributeTop multiplier:1.0 constant:20]];
[scroller addConstraint: [NSLayoutConstraint constraintWithItem:label attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:240]];
[scroller addConstraint: [NSLayoutConstraint constraintWithItem:label attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:44]];
self.view = scroller;
scroller.contentInset = UIEdgeInsetsMake(44, 0, 0, 0); //this is the game changer
}
Without the last line that changes the content inset, the UILabel is at (40, 20) as expected. When altering the content inset, the label moved to (40, 108), instead of (40, 64); the top content inset is somehow doubled.
If the UILabel is created with set frame and without those constraints as:
- (void) loadView
{
UIScrollView * scroller = [[UIScrollView alloc] init];
scroller.backgroundColor = [UIColor orangeColor];
CGRect frame = CGRectMake(40, 20, 240, 44);
UILabel * label = [[UILabel alloc] initWithFrame:frame];
label.backgroundColor = [UIColor blueColor];
[scroller addSubview:label];
self.view = scroller;
scroller.contentInset = UIEdgeInsetsMake(44, 0, 0, 0);
}
Then it behave as expected (40, 20) and (40, 64) depending on the content inset.
I checked with the documents and tried flipping some switches, but still can't figure out why.
BTW, that was the result from the IOS(6) simulator, I did not try it on device yet.
P.S.
Things appear as expected on simulator with IOS7, except the difference of the new status bar behavior.

Resources