iOS - Pure AutoLayout and UIScrollView not scrolling - ios

This is my first time using UIScrollViews with a pure Autolayout approach. This is what the view hierarchy looks like
view
-scrollview
--view1
--view2
--view3
scrollview should contain view1|view2|view3 in that order.
I set the scrollviews width, height, centerx and bottom space to superview. The view1, view2 and view3 that are created all have their width and height constraints setup in their updateConstraints method. Additionally, some constraints are provided in code. What is the reason this scrollview is not scrolling from left to right? I have read literally all of the guides I can find online about creating and adding subviews to a UIScrollView programmatically with auto layout. I found some mention about having to provide four different constraints, leading, trailing, top and bottom for each view added as a subview to the scrollview. Are these the only NSLayoutAttributes that one can specify? How do attributes such as NSLayoutAttribueLeft or NSLayoutAttribueRight relate? I have read documentation on Apples website as well, specifically https://developer.apple.com/library/ios/technotes/tn2154/_index.html. I am attaching the setup I currently have. Everything is done via code.
- (void)viewDidLoad
{
[super viewDidLoad];
self.dataSource = #[ [[PCCGenericRating alloc] initWithTitle:#"Easiness"
andMessage:#"WHAT A JOKERRRR"
andVariatons:#[ #"very easy", #"easy", #"moderate", #"hard", #"very hard"]],
[[PCCGenericRating alloc] initWithTitle:#"Joker"
andMessage:#"WHAT A JOKERRRR"
andVariatons:#[ #"very easy", #"easy", #"moderate", #"hard", #"very hard"]],
[[PCCGenericRating alloc] initWithTitle:#"Difficulty"
andMessage:#"YOu are not difficult at all"
andVariatons:#[ #"very easy", #"easy", #"moderate", #"hard", #"very hard"]]
];
[self initView];
}
- (void)initView {
CGFloat navigationBarHeight = self.navigationController.navigationBar.frame.size.height;
CGFloat statusBarHeight = [[UIApplication sharedApplication] statusBarFrame].size.height;
CGFloat heightDifference = navigationBarHeight + statusBarHeight;
self.scrollView = [[UIScrollView alloc] init];
self.scrollView.delegate = self;
[self.scrollView setTranslatesAutoresizingMaskIntoConstraints:NO];
self.scrollView.backgroundColor = [UIColor greenColor];
[self.view addSubview:self.scrollView];
//setup constraints
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.scrollView attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeWidth
multiplier:1.0f
constant:0.0f]];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.scrollView attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeHeight
multiplier:1.0f
constant:-heightDifference]];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.scrollView attribute:NSLayoutAttributeCenterX
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeCenterX
multiplier:1.0f
constant:0.0f]];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.scrollView attribute:NSLayoutAttributeBottom
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeBottom
multiplier:1.0f
constant:0.0]];
[self.dataSource enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
PCCGenericRating *rating = (PCCGenericRating *)obj;
PCCGenericRatingView *ratingView = [self createViewWithRating:rating];
[self.scrollView addSubview:ratingView];
int multiplier = (idx == 0) ? 1 : (int) (idx + 1) ;
[self.scrollView addConstraint:[NSLayoutConstraint constraintWithItem:ratingView
attribute:NSLayoutAttributeCenterX
relatedBy:NSLayoutRelationEqual
toItem:self.scrollView
attribute:NSLayoutAttributeCenterX
multiplier:multiplier
constant:0.0f]];
[self.scrollView addConstraint:[NSLayoutConstraint constraintWithItem:ratingView
attribute:NSLayoutAttributeCenterY
relatedBy:NSLayoutRelationEqual
toItem:self.scrollView
attribute:NSLayoutAttributeCenterY
multiplier:1.0f
constant:0.0f]];
}];
}
- (PCCGenericRatingView *)createViewWithRating:(PCCGenericRating *)rating {
PCCGenericRatingView *view = [PCCGenericRatingView genericRatingViewWithTitle:rating.title andMessage:rating.message];
return view;
}
Upon printing out the scrollview constraints, they look okay to me:
po self.scrollView.constraints
<__NSArrayM 0x115b051f0>(
<NSLayoutConstraint:0x1145d9290 PCCGenericRatingView:0x114579880.centerX == UIScrollView:0x11458d4b0.centerX>,
<NSLayoutConstraint:0x1145d9410 PCCGenericRatingView:0x114579880.centerY == UIScrollView:0x11458d4b0.centerY>,
<NSLayoutConstraint:0x1145d9dd0 PCCGenericRatingView:0x1145d9560.centerX == 2*UIScrollView:0x11458d4b0.centerX>,
<NSLayoutConstraint:0x1145d9e40 PCCGenericRatingView:0x1145d9560.centerY == UIScrollView:0x11458d4b0.centerY>,
<NSLayoutConstraint:0x1145da6b0 PCCGenericRatingView:0x1145d9e90.centerX == 3*UIScrollView:0x11458d4b0.centerX>,
<NSLayoutConstraint:0x1145da730 PCCGenericRatingView:0x1145d9e90.centerY == UIScrollView:0x11458d4b0.centerY>
)
Here is a screenshot of what it looks like:
I find it odd that the last element in the datasource is the first view controller showing up in the scrollview, when it should be the last view. It also doesn't scroll left to right as it should.

Make sure your top_constraint for the view1 and bottom_constraint for view3 will be as per your scrollView's constraints. Otherwise scrollview's contentSize: {0, 0}.

Wherever you are printing your constraints, try printing scrollview.contentSize, it will likely be 0,0 and that is where your problem is. As far as I know, and as you mentioned in your post, you have to explicitly set the subviews of a scrollview to the scrollviews top bottom left and right constraints. Setting these automatically sets the contentSize of the scrollview which will enable it to scroll. It looks like you are only setting centerX and centerY constraints which will not set the scrollviews contentSize to what you need.
Try setting these programatically (this is pseudocode but you get the idea):
view1.topConstraint = scrollView.topConstraint
view1.leftConstraint = scrollView.leftConstraint
view3.bottomConstraint = scrollView.bottomConstraint
view3.rightConstraint = scrollView.rightConstraint
If you set all of those correctly, your scrollview will scroll properly. Just remember to check the contentsize, and if the contentsize is 0,0 then your constraints aren't properly set up.

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];

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.

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;
}

Center UITableView within a subView

I'm ultimately trying to vertically center my table view cells within a UITableView if the total amount of cells * row height < the height of the tableView
My problem is that whenever I try to reference the tableview height (which should be resized when added to its superview. It returns original height of the view as defined in Interface Builder.
Here is the method that adds the subView with the tableView:
- (void)addMissonListView {
missionView = [BSMissionListView loadMissionView:YES];
BSList *list = (BSList *)self.dataModel;
missionView.missionList = list.arrayMissions;
missionView.delegate = self;
missionView.heightofSuperView = [NSNumber numberWithFloat:viewBottomContainer.frame.size.height];
[viewBottomContainer addSubview:missionView];
[viewBottomContainer setTranslatesAutoresizingMaskIntoConstraints:NO];
[BSUtility setConstraintsOnView:viewBottomContainer relativeTo:missionView withConstant:0];
}
This is the setsConstraintsOnView method that's called:
+ (void)setConstraintsOnView:(UIView *)superView relativeTo:(UIView *)subView withConstant:(int)constant
{
[subView setTranslatesAutoresizingMaskIntoConstraints:NO];
[superView addConstraint:[NSLayoutConstraint constraintWithItem:subView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:superView attribute:NSLayoutAttributeBottom multiplier:1 constant:constant]];
[superView addConstraint:[NSLayoutConstraint constraintWithItem:subView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:superView attribute:NSLayoutAttributeTop multiplier:1 constant:constant]];
[superView addConstraint:[NSLayoutConstraint constraintWithItem:subView attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual toItem:superView attribute:NSLayoutAttributeRight multiplier:1 constant:constant]];
[superView addConstraint:[NSLayoutConstraint constraintWithItem:subView attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:superView attribute:NSLayoutAttributeLeft multiplier:1 constant:constant]];
}
The subview consists of a tableView inside of UIView. In Interface Builder, the size of the UIView is 320 X 586 and the tableView is 310 x 558.
viewBottomContainer's size in IB is 320 x 408
I'm trying to get the information about the view's and tableView's height in the configureGUI method. And once that works, I'll pass it to another method to create a contentOffset in the TableView. but the NSLog's keep returning the original values, not the resized values.
These are the methods from the BSMissionListView class:
- (void)awakeFromNib {
[super awakeFromNib];
[self configureGUI];
}
- (void)configureGUI {
[super configureGUI];
NSLog(#"Bounds height of the View is %f",self.bounds.size.height);
NSLog(#"Bounds height of the tableView is %f",self.tableViewMissionList.bounds.size.height);
[self setBackgroundColor:[UIColor clearColor]];
}
I'll be happy to post more code if that will be helpful. Thank you!
I was able to solve the problem by passing in the tableView's superView's height by ways of the method that the loaded the tableView nib.
-I added a CGFloat variable to the method that loads the nib in the BSMissionListView Class
-I passed in the viewBottomContainer's height and then used that to set the heightOfSuperView value in the newly modified method.
-heightOfSuperView's value is used to calculate to contentInset of the TableView to center it within the superView
Thank you to #Alex Renyolds for helping me out.
- (void)addMissonListView {
[viewBottomContainer setNeedsLayout];
[viewBottomContainer layoutIfNeeded];
[viewBottomContainer setTranslatesAutoresizingMaskIntoConstraints:NO];
NSLog(#"Height of bottomViewContrainer: %f", viewBottomContainer.bounds.size.height);
BSList *list = (BSList *)self.dataModel;
missionView = [BSMissionListView loadMissionView:YES heightOfSuperView:viewBottomContainer.bounds.size.height listArray:list.arrayMissions];
missionView.delegate = self;
[viewBottomContainer addSubview:missionView];
[viewBottomContainer setTranslatesAutoresizingMaskIntoConstraints:NO];
[BSUtility setConstraintsOnView:viewBottomContainer relativeTo:missionView withConstant:0];
}
BSMissionList
+ (BSMissionListView *)loadMissionView:(BOOL)isEditable heightOfSuperView:(CGFloat)height listArray:(NSMutableArray *)list {
BSMissionListView *missionListView = [[[NSBundle mainBundle] loadNibNamed:NSStringFromClass([BSMissionListView class]) owner:nil options:nil] lastObject];
missionListView.missionList = list;
missionListView.isEditable = isEditable;
missionListView.heightofSuperView = (height- 49);
[missionListView configureGUI];
return missionListView;
}

Position one UIlabel under another UILabel (top label has varying height)

I have two UILabels inside of a XIB, and I want to position one label underneath of another label. That said, the top label's height (descriptionLabel) varies. Does anyone know how I can go about doing this? I feel like I've tried everything.
Here is the code for my Labels so far; I want to position my second label (bodyLabel) about 25 pixels below descriptionLabel (regardless of how long descriptionLabel is):
CGRect frame = descriptionLabel.frame;
frame.origin.y=400;//pass the cordinate which you want
frame.origin.x= 12;//pass the cordinate which you want
descriptionLabel.frame= frame;
CGRect frame2 = bodyLabel.frame;
bodyLabel.frame= frame;
do this in viewDidLayoutSubviews;
-(void)viewDidLayoutSubviews{
[super viewDidLayoutSubviews];
/* set label1's frame first */
CGRect newFrame = _label2.frame;
newFrame.origin.y = CGRectGetMaxY(_label1.frame)+25;
_label2.frame = newFrame;
}
CGRectGetMaxY takes the frame's origin into account when returning a value. keep in mind that frames are not yet set for views if you're doing things in loadView or viewDidLoad, this could be why things keep ending up with a 0 origin - they are still 0 at that time.
Suppose you have two UILabels. Say, firstLabel and secondLabel. Suppose you have set the first frame like so:
firstLabel.frame = CGRectMake(0,0,50,50);
If your first frame dynamically changes its height, and if want your secondLabel to be always under the first, you can set the y coordinate of the secondLabel in such a way it is always under it. The code for it can be something like:
secondLabel.frame = CGRectMake(0,firstLabel.frame.size.height,50,50);
Using this, the y position of the secondLabel is dynamic and is dependent on the firstLabel's height.
In your case, the position of the bodyLabel can be :
CGRect bodyLabelFrame = CGRectMake(0,descriptionLabel.frame.size.height,50,50);
bodyLabel.frame = bodyLabelFrame;
Have you tried using autolayout? Using autolayout, this is how I might do this if the superview for the labels was superView:
// This is necessary to use autolayout
descriptionLabel.translatesAutoResizingMasksIntoConstraints = NO;
bodyLabel.translatesAutoResizingMasksIntoConstraints = NO;
NSDictionary *views = NSDictionaryOfVariableBindings(descriptionLabel, bodyLabel);
// this will pin the top of bodyLabel to the bottom of the descriptionLabel with a gap of 25px
[superView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:[descriptionLabel]-25-[bodyLabel]" options:nil metrics:nil views:views]];
You'll need to do it in code. I use auto-layout.
First, create two private NSLayoutContstraint variables for your two labels — you'll use these to adjust your label height when you set the text.
#interface CustomView ()
#property (strong, nonatomic) NSLayoutConstraint *firstLabelHeightCn;
#property (strong, nonatomic) NSLayoutConstraint *secondLabelHeightCn;
#end
Second, define the first labels X, Y, and width — the height will be set depending on the text you set in it.
NSLayoutConstraint *cnX;
NSLayoutConstraint *cnY;
NSLayoutConstraint *cnWidth;
// first label
_firstLabel = [[UILabel alloc] init];
_firstLabel.lineBreakMode = NSLineBreakByWordWrapping;
_firstLabel.numberOfLines = 0;
_firstLabel.translatesAutoresizingMaskIntoConstraints = NO;
[self addSubview:_firstLabel];
cnX = [NSLayoutConstraint constraintWithItem:_firstLabel attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeLeft multiplier:1.0 constant:H_MARGIN];
cnY = [NSLayoutConstraint constraintWithItem:_firstLabel attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeTop multiplier:1.0 constant:V_MARGIN];
cnWidth = [NSLayoutConstraint constraintWithItem:_firstLabel attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeRight multiplier:1.0 constant:-H_MARGIN];
_firstLabelHeightCn = [NSLayoutConstraint constraintWithItem:_firstLabel attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeHeight multiplier:0 constant:0];
[self addConstraints:#[ cnX, cnY, cnWidth, _firstLabelHeightCn ]];
Third, define the second labels X, Y, and width off the first labels properties. For the Y position you'll want to set the second labels TOP to the first labels BOTTOM (+ any margin).
// second label
_secondLabel = [[UILabel alloc] init];
_secondLabel.lineBreakMode = NSLineBreakByWordWrapping;
_secondLabel.numberOfLines = 0;
_secondLabel.translatesAutoresizingMaskIntoConstraints = NO;
[self addSubview:_secondLabel];
cnX = [NSLayoutConstraint constraintWithItem:_secondLabel attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:_firstLabel attribute:NSLayoutAttributeLeft multiplier:1.0 constant:0];
cnY = [NSLayoutConstraint constraintWithItem:_secondLabel attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:_firstLabel attribute:NSLayoutAttributeBottom multiplier:1.0 constant:V_MARGIN];
cnWidth = [NSLayoutConstraint constraintWithItem:_secondLabel attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual toItem:_firstLabel attribute:NSLayoutAttributeRight multiplier:1.0 constant:0];
_secondLabelHeightCn = [NSLayoutConstraint constraintWithItem:_secondLabel attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeHeight multiplier:0 constant:0];
[self addConstraints:#[ cnX, cnY, cnWidth, _secondLabelHeightCn ]];
Finally, create two methods to set the text for your two labels. These methods will take the incoming text, calculate the height, adjust your layout constraint constants, and then set the actual text in the label. Since you're using auto-layout once you change the text/height of the first label, the second label will automatically adjust.
- (void)setFirstText:(NSString *)firstText
{
_firstText = firstText;
if (_firstText.length) {
_firstLabelHeightCn.constant = [CustomView textHeight:_firstText width:self.bounds.size.width font:_firstLabel.font];
_firstLabel.text = _firstText;
} else {
_firstLabelHeightCn.constant = 0;
_firstLabel.text = nil;
}
}
- (void)setSecondText:(NSString *)secondText
{
_secondText = secondText;
if (_secondText.length) {
_secondLabelHeightCn.constant = [CustomView textHeight:_secondText width:self.bounds.size.width font:_secondLabel.font];
_secondLabel.text = _secondText;
} else {
_secondLabelHeightCn.constant = 0;
_secondLabel.text = nil;
}
}
Here is a real-life example:
ContextView.h
https://gist.githubusercontent.com/rosem/4fc7f9ed80c114ba45a0/raw/05f46c0340e1682823d6bbeb95f8b084ba4449d5/gistfile1.mm
ContextView.m
https://gist.githubusercontent.com/rosem/6d768776991569496ab6/raw/76ce4f47b3f86555ee4755e7d52d12511adcec27/gistfile1.m

Resources