Auto layout of right-aligned view not working - ios

I am trying to adapt this tableview section header view to auto layout so it will adapt for iPhone 6 and 6 Plus. This is the desired result
which has 3 views named stringView, favoriteCount and heartImage. The following screenshots will have a grey background set on each view to make it easier to see what is going on.
What I don't understand is that the following
#"|-(15.0)-[stringView]-(>=20.0)-[favoriteCount(44.0)]-[heartImage(22.0)]-(10.0)-|"
produces
with the count and heart views nowhere to be seen. So it feels as if the containing view is far wider than it needs to be, but I'm not aware that I have any control over that as the returned view from this method should automatically be sized to the tableview width shouldn't it?
BTW This is all happening inside the tableView:viewForHeaderInSection: method which looks like
- (UIView *)tableView:(UITableView*)tableView viewForHeaderInSection:(NSInteger)section {
if (section == 0) {
// My Favourites menu
UIView *myFavoritesMenuView = [[UIView alloc] init];
[myFavoritesMenuView setBackgroundColor:[UIColor colorWithWhite:0.2 alpha:1.0]];
UILabel *stringView = [[UILabel alloc] init];
[stringView setTranslatesAutoresizingMaskIntoConstraints:NO];
[stringView setText:#"My Favourites"];
[stringView setTextColor:[UIColor whiteColor]];
[stringView setFont:[UIFont fontWithName:#"OpenSans" size:16.0]];
[stringView setBackgroundColor:[UIColor grayColor]];
[myFavoritesMenuView addSubview:stringView];
NSDictionary *favoriteArticles = [[NSUserDefaults standardUserDefaults] dictionaryForKey:#"FavoriteArticles"];
UILabel *favoriteCount = [[UILabel alloc] init];
[favoriteCount setTranslatesAutoresizingMaskIntoConstraints:NO];
[favoriteCount setFont:[UIFont fontWithName:#"OpenSans" size:16.0]];
[favoriteCount setTextColor:[UIColor whiteColor]];
[favoriteCount setTextAlignment:NSTextAlignmentRight];
[favoriteCount setBackgroundColor:[UIColor grayColor]];
[myFavoritesMenuView addSubview:favoriteCount];
if (favoriteArticles && favoriteArticles.count > 0) {
[favoriteCount setText:[NSString stringWithFormat:#"%lu", (unsigned long)favoriteArticles.count]];
} else {
[favoriteCount setText:#""];
}
UIImageView *heartImage = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"heart-button-on"]];
[heartImage setTranslatesAutoresizingMaskIntoConstraints:NO];
[heartImage setBackgroundColor:[UIColor grayColor]];
[myFavoritesMenuView addSubview:heartImage];
// Create the views dictionary
NSDictionary *views = NSDictionaryOfVariableBindings(stringView, favoriteCount, heartImage);
// Horizontal layout - note the options for aligning the vertical center of all views
NSString *horizontalFormat = #"|-(15.0)-[stringView]-(>=20.0)-[favoriteCount(44.0)]-[heartImage(22.0)]-(10.0)-|";
[myFavoritesMenuView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:horizontalFormat
options:NSLayoutFormatAlignAllCenterY
metrics:nil
views:views]];
// Vertical layout - we only need one "column" of information because of the alignment options used when creating the horizontal layout
NSString *verticalFormat = #"V:|-[stringView]-|";
[myFavoritesMenuView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:verticalFormat
options:0
metrics:nil
views:views]];
return myFavoritesMenuView;
}
// handle other section headers here
}
Incidentally, I've also tried this by creating a UIView as separate xib, containing just the text label aligned left and heart image aligned to trailing edge of superview and am seeing exactly the same issue with the heart not visible (presumably off screen to the right).

The width of the entire table view is incorrect (the table view is square for some reason rather than fitting to the screen) so the missing subviews are off screen to the right. Discovered it when I changed to run in iPhone 6 in landscape view and the subviews appeared...

Related

Autoresizing not working for UIView added programatically

I have followed this SO link for autoresizing, but Autoresizing not working.
How to set frame programmatically with autoresizing?
I have set frame for iPhone 4
UIView *box = [[UIView alloc]initWithFrame:CGRectMake(0, 0, 320, 120)];
[box setBackgroundColor:[UIColor redColor]];
[box setAutoresizingMask:UIViewAutoresizingFlexibleHeight];
[box setAutoresizingMask:UIViewAutoresizingFlexibleWidth];
[box setAutoresizingMask:UIViewAutoresizingFlexibleTopMargin];
[box setAutoresizingMask:UIViewAutoresizingFlexibleLeftMargin];
[box setAutoresizingMask:UIViewAutoresizingFlexibleRightMargin];
[self.view addSubview:box];
But it is not working in iPad or iPhone 6
First thing first : As per open suggestion, you should use constraints.
Add autoresizing mask after adding it to view.
UIView *box = [[UIView alloc]initWithFrame:CGRectMake(0, 0, 320, 120)];
[box setBackgroundColor:[UIColor redColor]];
[self.view addSubview:box];
[box setAutoresizingMask:UIViewAutoresizingFlexibleHeight];
[box setAutoresizingMask:UIViewAutoresizingFlexibleWidth];
[box setAutoresizingMask:UIViewAutoresizingFlexibleTopMargin];
[box setAutoresizingMask:UIViewAutoresizingFlexibleLeftMargin];
[box setAutoresizingMask:UIViewAutoresizingFlexibleRightMargin];
If I understand you question correctly you're trying to set red view on top with height = 120
ADD EXPLANATION
You could achieve it with using constraints:
UIView *box = [[UIView alloc] init];
// Prevent creating constraints from masks automatically
box.translatesAutoresizingMaskIntoConstraints = NO;
box.backgroundColor = [UIColor redColor];
[self.view addSubview:box];
// Define metrics (constants) which will be used to create constraints.
// Key #"boxSize" - name which will be used in constraints, Value - constant
NSDictionary *metrics = #{#"boxSize" : #(120)};
// Define views that will participate in auto layout constraints.
// Key #"readBox" - name which will be used in constraints, Value - real UIView object
NSDictionary *views = #{ #"redBox" : box };
// Here we create constraints. For Vertical, and for Horizontal
// I'm using Visual language format (you can find it in Apple Documentation
// In a few words:
// H:|-0-[redBox]-0-|
// "H" - means horizontal
// "|" - short cut for parent view (in our case it is UIViewController.view)
// "[redBox]" - view name from view's dictionary
// "-0-" - gap between views (you could set number), in our case it is "|" and "[redBox]"
// "[redBox(boxSize)]" - means that view (redBox) size should be qual to "boxSize" from metrics' dictionary
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-0-[redBox]-0-|" options:NSLayoutFormatAlignAllCenterY metrics:metrics views:views]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-0-[redBox(boxSize)]" options:NSLayoutFormatAlignAllCenterY metrics:metrics views:views]];
Apple Documentation
You are setting a UIView with a frame (0, 0, 320, 120). This frame will fit the iPhone 4 screen, as the phone screen width is 320 pixels. But you cant expect same when you run the code in iPhone 6/6s. Setting Autoresizing will not handle this. You need to use constraints/autolayout for that.
Autoresizing masks describe how a subview will resize or move when its superview is resized.
So after adding this view, if you change the phone orientation, this will resize the view in position accordingly. But you need to set the frame according to the superview first. You can set the width dynamically, like: (0, 0, self.view.frame.size.width, 120).

Custom TableViewCell with dynamic height and full width image using Autolayout

I'm really stuck using autolayout and a custom tableviewcell.
I want my tableviewcell to have a dynamic height. The table cell should contain an image with the same width as the tableviewcell and the height should adjust based on an aspect ratio of 0.6
This is what it should look like
But this is what I get
To give you a clear look of what is going on I gave the tableViewCell and it's subviews some pretty background colors…
Green = thumbnailView
Blue = self
Red = contentView
Purple = hotspotNameLabel
Yellow = categoryLabel
This is what my code looks like:
Initializer:
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier{
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
self.contentView.backgroundColor = [UIColor redColor];
self.backgroundColor = [UIColor blueColor];
CGFloat imageAspectRatio = 720/432;
CGFloat imageHeight = self.contentView.bounds.size.width / imageAspectRatio;
CGRect imageFrame = CGRectMake(0, 0, self.contentView.bounds.size.width,imageHeight);
self.thumbnailView = [[UIImageView alloc] initWithFrame:imageFrame];
[self.thumbnailView setBackgroundColor:[UIColor greenColor]];
[self.thumbnailView setContentMode:UIViewContentModeScaleAspectFill];
[self.thumbnailView.layer setMasksToBounds:true];
[self.contentView addSubview:self.thumbnailView];
self.hotspotNameLabel = [[UILabel alloc] initWithFrame:CGRectMake(40, 15, self.contentView.bounds.size.width - 80, 20)];
[self.hotspotNameLabel setAutoresizingMask:UIViewAutoresizingFlexibleWidth];
[self.hotspotNameLabel setTextColor:[UIColor colorWithWhite:(32/255.0) alpha:1]];
[self.hotspotNameLabel setLineBreakMode:NSLineBreakByWordWrapping];
[self.hotspotNameLabel setNumberOfLines:0]; // Unlimited Lines
[self.hotspotNameLabel setBackgroundColor:[UIColor purpleColor]];
[self.hotspotNameLabel setFont:[UIFont fontWithName:#"AvenirNext-Medium" size:20]];
[self.contentView addSubview:self.hotspotNameLabel];
// Category Label
self.categoryLabel = [[CategoryLabel alloc] initWithFrame:CGRectMake(40, 15, self.contentView.bounds.size.width - 80, 10)
categoryId:22 categoryName:#"Food"];
[self.contentView addSubview:self.categoryLabel];
// Constraints
[self.contentView setTranslatesAutoresizingMaskIntoConstraints:false];
[self.hotspotNameLabel setTranslatesAutoresizingMaskIntoConstraints:false]; // Enables autosizing
[self.categoryLabel setTranslatesAutoresizingMaskIntoConstraints:false];
[self.thumbnailView setTranslatesAutoresizingMaskIntoConstraints:false];
[self.contentView addConstraint:[NSLayoutConstraint constraintWithItem:self.thumbnailView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem: self.contentView attribute:NSLayoutAttributeWidth multiplier:1.0f constant:0]];
[self.contentView addConstraint:[NSLayoutConstraint constraintWithItem:self.thumbnailView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem: self.contentView attribute:NSLayoutAttributeWidth multiplier:0.6f constant:0]];
[self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-0-[nameLabel]-0-|" options:0 metrics:nil views:#{#"nameLabel": self.hotspotNameLabel}]];
[self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-0-[thumbnail]-20-[nameLabel]-10-[categoryLabel]-0-|" options:0 metrics:nil views:#{#"thumbnail": self.thumbnailView,#"nameLabel": self.hotspotNameLabel, #"categoryLabel":self.categoryLabel}]];
}
return self;}
Layout subviews:
-(void)layoutSubviews {
[super layoutSubviews];
[self.contentView setNeedsLayout];
[self.contentView layoutIfNeeded];
self.hotspotNameLabel.preferredMaxLayoutWidth = CGRectGetWidth(self.hotspotNameLabel.bounds);
}
tableView:HeightForRowAtIndexPath:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
return [self heightForLargeCellAtIndexPath:indexPath];
}
- (CGFloat)heightForLargeCellAtIndexPath:(NSIndexPath *)indexPath {
LargeHotspotCell *sizingCell = [[LargeHotspotCell alloc] init];
[self configureLargeCell:sizingCell atIndexPath:indexPath];
[sizingCell setNeedsLayout];
[sizingCell layoutIfNeeded];
CGFloat height = [sizingCell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;
return height + 1 ;
}
Here you are my little tutorial with explaining basic concepts of autolayout.
Your first step, should be subclassing UITableviewCell, I named my new cell DynamicTableViewCell and assigned it in the identity inspector.
I dragged and dropped UILabel and UIImageView into contentView.
Below a screenshot with UILabel constraints, I added simultaneously leading and trailing space constraints, in order to make my label equal to contentView width. And it is aligned to the bottom of the cell.
Now, it is time to assign constraints to UIImageView. I am adding aspect ratio 0.6 (imageHeight/imageWidth = 0.6) and adding leading and trailing space to contentView. Feel free to play with constants to achieve your desired purposes.
Final results in iphone5, iphone6 simulator
Your image height (green color) is 60pt in 5s screen, 70pt in 6 screen and width resized properly:
For your better understanding, I am publishing Working Sample Project on github

Get frame of a view after autolayout

I have a method:
- (void)underlineTextField:(UITextField *)tf {
CGFloat x = tf.frame.origin.x-8;
CGFloat y = tf.origin.y+tf.frame.size.height+1;
CGFloat width = self.inputView.frame.size.width-16;
UIView *line = [[UIView alloc] initWithFrame:(CGRect){x,y,width,1}];
line.backgroundColor = [UIColor whiteColor];
[self.inputView addSubview:line];
}
That underlines an input UITextField; The textfield has a width that changes depending on the screen width (nib autolayout).
I have tried using
[self.view setNeedsLayout];
[self.view layoutIfNeeded];
and
[self.inputView setNeedsLayout];
[self.inputView layoutIfNeeded];
before I call this method with no change in result. the resulting line is much wider than the UITextField (it matches the original size in the Nib).
I just want the resulting frame of the UITextField in question after being processed by the autolayout
SOLUTION: (using 'Masonry' Autolayout)
- (UIView *)underlineTextField:(UITextField *)tf {
UIView *line = [[UIView alloc] initWithFrame:CGRectZero];
line.backgroundColor = [UIColor whiteColor];
[self.inputView addSubview:line];
[line mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerX.equalTo(tf.mas_centerX);
make.width.equalTo(tf.mas_width).with.offset(16);
make.height.equalTo(#1);
make.top.equalTo(tf.mas_bottom);
}];
return line;
}
Your underline view has a static frame, it is not connected to the textField through constraints. Instead of setting the frame, add constraints to self.inputView
- (void)underlineTextField:(UITextField *)tf {
UIView *line = [[UIView alloc] init];
line.backgroundColor = [UIColor whiteColor];
[line setTranslatesAutoresizingMaskIntoConstraints:NO];
[self.inputView addSubview:line];
// Vertical constraints
[self.inputView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:[line(==1)]-(-1)-|" options:0 metrics:nil views:#{ #"line": line}]];
// Horizontal constraints
[self.inputView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-(-8)-[line]-8-|" options:0 metrics:nil views:#{ #"line": line}]];
[self.inputView layoutIfNeeded];
}
After the layoutIfNeeded call, the frame for your view should be right. I hoped I got the constants right. Because the line appears one unit under the textView make sure to unset Clip Subviews in the storyboard for the textField
I hope this works for you. Let me know if you have questions!
You could adjust the frame of the underline in viewDidLayoutSubviews which is called after auto layout has set all the frames.
Note that you're creating a new view and calling addSubview: each time. You should do that once when you create the text view, outside of this method. Otherwise you'll create a new UIView every time your user rotates the device.

Implementing auto layout for views generated programmatically

I have an app whose views are generated programmatically. Example:
-(void)loadView
{
[super loadView];
// SET TOP LEFT BTN FOR NEXT VIEW
UIBarButtonItem *topLeftBtn = [[UIBarButtonItem alloc] initWithTitle:#"Back" style:UIBarButtonItemStyleBordered target:nil action:nil];
self.navigationItem.backBarButtonItem = topLeftBtn;
[topLeftBtn release];
// programmatically set up the view for cart tableView
CGRect iouTableViewFrame = CGRectMake(0, 0, 320, 348);
iouTableView = [[UITableView alloc]initWithFrame:iouTableViewFrame style:UITableViewStylePlain];
[[self iouTableView] setDelegate:self];
[[self iouTableView] setDataSource:self];
[[self view] addSubview:iouTableView];
// set up the summary label
CGRect summaryTableFrame = CGRectMake(0, 348, 320, 18);
UILabel *summaryTableLabel = [[UILabel alloc] initWithFrame:summaryTableFrame];
[summaryTableLabel setFont:[UIFont fontWithName:#"Helvetica" size:14]];
[summaryTableLabel setText:#" Summary"];
UIColor *labelColor = UIColorFromRGB(MiddleBlueColor);
[summaryTableLabel setBackgroundColor:labelColor];
[summaryTableLabel setTextColor:[UIColor whiteColor]];
[[self view] addSubview:summaryTableLabel];
// set up the summary table
CGRect summaryTableViewFrame = CGRectMake(0, 366, 320, 44);
summaryTableView = [[UITableView alloc]initWithFrame:summaryTableViewFrame style:UITableViewStylePlain];
[summaryTableView setScrollEnabled:NO];
[[self summaryTableView] setDelegate:self];
[[self summaryTableView] setDataSource:self];
[[self view] addSubview:summaryTableView];
}
Yes. I will update to NIBs and use interface builder and storyboard in the future but I have not done ios programming in a year.
With the new iPhone 5 having a different screen size, the app just does not look good and I need to implement auto layout of some sort. Is there a way to do it programmatically for now instead of using IB?
Thanks much!
Yes there is, by using two methods in NSLayoutConstraint
-(NSArray*)constraintsWithVisualFormat:options:metrics:views:
-(NSLayoutConstraint*)constraintWithItem:attribute:relatedBy:toItem:attribute:
multiplier:constant:
The visual format language is all packaged up into an NSString
So I'll take your iouTableView for example.
[self.view addConstraints:[NSLayoutConstraint
constraintsWithVisualFormat:#"|[iouTableView]|"
options:0
metrics:nil
views:NSDictionaryOfVariableBindings(iouTableView)]];
The pipe symbol "|" represents the superview's edge.
The [] represent a view.
So what we did there was we hooked the iouTableView's left and right edge to the left and right edge of its superview.
Another example of the visual format:
Let's hook your table view, summary label and summary table vertically.
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:
#"V:|[iouTableView(348)][summaryTableLabel(18)][summaryTableView(44)]"
options:NSLayoutFormatAlignAllLeft
metrics:nil
views:NSDictionaryOfVariableBindings(iouTableView, summaryTableLabel, summaryTableView)]];
Now this links up all three views vertically on each of their edges, NSLayoutFormatAlignAllLeft tells all the views to align left and they'll do so based on other constraints, in this case, the previous constraint.
The ()s are used to specify the size of the views.
There's a bit more like inequalities and priorities as well as the "-" spacer symbol but check out the apple docs for that
Edit: Corrected the examples to use constraintsWithVisualFormat as shown in the method signature.
In addition to Aplle provided methods you can use Parus lib for operating with AutoLayout from code.
For example you will be able to specify:
PVVFL(#"[view1]-20-[view2]").fromRightToLeft.withViews(views).asArray
Instead of
[NSLayoutConstraint constraintsWithVisualFormat:#"[view1]-20-[view2]"
options:NSLayoutFormatDirectionRightToLeft
metrics:nil
views:views]
Also you will be able to group layouts settings, mix VFL and not VFL constraints.
Parus able to prevent common mistakes, differentiate location and parameters constriaints, and provide great auto-completion support.

How to center UITableView Section Footer with Support for Orientation?

EDIT:
After messing with this for days the real questions I have are the following:
1. Does UITableView take up the entire view?
2. If so, how does it set the bounds of the cells to that it looks like it only takes up part of the view.
3. How do I get the bounds of the cells - or more accurately how do I know the bounds of the visible area that the cells are taking up. self.tableView.bounds.size.width does not help because it returns the width of the view.
Thanks.
Leaving the previous info below in case it helps make my question clearer.
Can this be possible?
I have read the apple docs and trolled the forums here and elsewhere and can't find and answer to this.
Does the footer in a UITableVIew actually take up the entire view no matter what you do? Does it not have a concept of the table width?
Example:
- (UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section {
UIView *footerView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)];
[footerView setBackgroundColor:[UIColor redColor]];
return footerView;
}
This code will create a red line from one edge to the other. No matter what boundaries you give it the line will take up the entire view. The problem with this is that if you want to center a label in that footer you don't have any way to know where center is if you are supporting orientation changes.
For instance in an iPad app I am trying to do the following:
if ([footerText length] > 0) {
UIView *customView = [[[UIView alloc]initWithFrame:CGRectMake(0.0, 0.0, 0, 0.0)] autorelease];
[customView setBackgroundColor:[UIColor grayColor]];
UILabel *footerLabel = [[UILabel alloc] initWithFrame:CGRectZero];
footerLabel.numberOfLines = 2;
footerLabel.adjustsFontSizeToFitWidth = NO;
[footerLabel setTextAlignment:UITextAlignmentCenter];
[footerLabel setBackgroundColor:[UIColor colorWithRed:1.0 green:1.0 blue:1.0 alpha:0.5]];
[footerLabel setOpaque:NO];
[footerLabel setTextColor:[UIColor whiteColor]];
[footerLabel setShadowColor:[UIColor lightGrayColor]];
[footerLabel setFont:[UIFont boldSystemFontOfSize:14]];
[footerLabel setFrame:CGRectMake(customView.center.x/0.3, 0.0, 600, 40.0)];
[footerLabel setText:footerText];
[customView addSubview:footerLabel];
[footerLabel release];
NSLog(#"customView width = %f", customView.frame.size.width);
NSLog(#"tableview width = %f", self.tableView.frame.size.width);
NSLog(#"tableview center = %f", self.tableView.center.x);
return customView;
} else {
return nil;
}
The table's center in portrait should be 384 (it's in the detail view/right side) and 351.5 in landscape. But when I use setCenter or try to adjust the left edge based on that center it does not center up.
Final question: How does one center a custom view in a footer with support for orientation when the footer seems to have no concept of the table bounds? I must be missing something in the docs because this has to be a problem solved by someone else but I can't find it.
Thanks for your time.
To center something within the tableview, you need to wrap it in a container, and set the appropriate autoresize mask for both the embedded view and the container.
The container should be flexible width, and the embedded view should have both flexible side margins.
eg:
- (UIView *) tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section
{
static UIView *footerView;
if (footerView != nil)
return footerView;
NSString *footerText = NSLocalizedString(#"Some Display Text Key", nil);
// set the container width to a known value so that we can center a label in it
// it will get resized by the tableview since we set autoresizeflags
float footerWidth = 150.0f;
float padding = 10.0f; // an arbitrary amount to center the label in the container
footerView = [[UIView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, footerWidth, 44.0f)];
footerView.autoresizingMask = UIViewAutoresizingFlexibleWidth;
// create the label centered in the container, then set the appropriate autoresize mask
UILabel *footerLabel = [[[UILabel alloc] initWithFrame:CGRectMake(padding, 0, footerWidth - 2.0f * padding, 44.0f)] autorelease];
footerLabel.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin;
footerLabel.textAlignment = UITextAlignmentCenter;
footerLabel.text = footerText;
[footerView addSubview:footerLabel];
return footerView;
}

Resources