OK, I don't know what's up with iOS 8 but when I run my app on it, I see these extra UITableViewSeparatorViews:
Update
I used the Xcode 6 View Debugger and I see that in this problematic UITableView, I see these extra views being added as a subview of my UITableViewCellContentView
_UITableViewCellSeparatorView
Why does iOS 8 add these random subviews to my cell's contentView ?
As you can see from the screenshot, those extra lines are the ones that doesn't reach the edge of the screen.
In my view controller, I am using the following code to make the lines go all the way to the edge of the screen:
-(void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
cell.contentView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
if ([tableView respondsToSelector:#selector(setSeparatorInset:)]) {
[tableView setSeparatorInset:UIEdgeInsetsZero];
}
if ([tableView respondsToSelector:#selector(setLayoutMargins:)]) {
[tableView setLayoutMargins:UIEdgeInsetsZero];
}
if ([cell respondsToSelector:#selector(setLayoutMargins:)]) {
[cell setLayoutMargins:UIEdgeInsetsZero];
}
}
In my custom cell class, I am setting:
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
// Initialization code
_reuseID = reuseIdentifier;
self.selectionStyle = UITableViewCellSelectionStyleNone;
self.contentView.backgroundColor = [[BSGThemeManager sharedTheme] clearColor];
self.contentView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
self.containerView = [[UIView alloc] init];
_photo = [[BSGUserPhotoView alloc] init];
[_photo clipPhotoToRadius:37.5];
//_photo.layer.cornerRadius = 37.5 / 2.0;
//_photo.layer.borderColor = [[BSGThemeManager sharedTheme] photoBorderColor];
//_photo.layer.borderWidth = [[BSGThemeManager sharedTheme] photoBorderWidth];
//_photo.clipsToBounds = YES;
self.photo.alpha = 0;
_message = [[UILabel alloc] init];
_message.backgroundColor = [[BSGThemeManager sharedTheme] clearColor];
_message.numberOfLines = 0;
_message.lineBreakMode = NSLineBreakByWordWrapping;
_message.font = [[BSGThemeManager sharedTheme] varelaRoundFontWithSize:15];
self.message.alpha = 0;
_time = [[UILabel alloc] init];
_time.textAlignment = NSTextAlignmentRight;
_time.font = [[BSGThemeManager sharedTheme] helveticaNeueFontWithSize:16];
_time.textColor = [[BSGThemeManager sharedTheme] postTimeColor];
[_time setContentCompressionResistancePriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisHorizontal];
[_time setContentHuggingPriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisHorizontal];
self.time.alpha = 0;
[self.containerView addSubview:_photo];
[self.containerView addSubview:_message];
[self.containerView addSubview:_time];
[self.contentView addSubview:self.containerView];
[self addAllConstraints];
}
return self;
}
I'm not sure if this line was the cause of it:
self.contentView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
My Autolayout code for my cell is:
-(void)addAllConstraints
{
self.containerView.translatesAutoresizingMaskIntoConstraints = NO;
self.photo.translatesAutoresizingMaskIntoConstraints = NO;
self.message.translatesAutoresizingMaskIntoConstraints = NO;
self.time.translatesAutoresizingMaskIntoConstraints = NO;
id views = #{
#"containerView": self.containerView,
#"photo": self.photo,
#"message": self.message,
#"time": self.time
};
[self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[containerView]|" options:0 metrics:nil views:views]];
[self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[containerView]|" options:0 metrics:nil views:views]];
// --------------------------------------------------
// Horizontal Constraints
// --------------------------------------------------
[self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-10-[photo(37.5)]-15-[message(195)]" options:0 metrics:nil views:views]];
[self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:[time]-10-|" options:0 metrics:nil views:views]];
// --------------------------------------------------
// Vertical Constraints
// --------------------------------------------------
[self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:[photo(37.5)]" options:0 metrics:nil views:views]];
[self.contentView addConstraint:[NSLayoutConstraint constraintWithItem:self.photo attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self.contentView attribute:NSLayoutAttributeCenterY multiplier:1.0 constant:0.0]];
[self.contentView addConstraint:[NSLayoutConstraint constraintWithItem:self.message attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self.photo attribute:NSLayoutAttributeCenterY multiplier:1.0 constant:0.0]];
[self.contentView addConstraint:[NSLayoutConstraint constraintWithItem:self.time attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self.photo attribute:NSLayoutAttributeCenterY multiplier:1.0 constant:0.0]];
}
Anyone else came across this weird iOS 8 bug?
It doesn't show up on iOS 7.
I found an easy solution, when i overwrote the layoutSubviews method, i also called the super method as well, like so:
-(void)layoutSubviews {
[super layoutSubviews];
// rest of custom implementation code here
. . .
}
Hope it helps
Change the highlighted to '0' then the it resolves. It was through XIB.
Lol....OK...I think I fixed this problem.
One of my subview is called "message" which is just a UILabel.
On my custom tableview cell's class, this code was causing the extra line to appear:
-(void)layoutSubviews
{
self.message.preferredMaxLayoutWidth = self.bounds.size.width * 0.75;
}
After getting rid of it, the cells behaved how I liked it to like in iOS 7.
For me, the dynamic height of the custom tableViewCells was causing an issue, to be specific, the heightForRowAtIndexPath and estimatedHeightForRowAtIndexPath delegate methods. One worked perfect for iOS 7 and the other for iOS 8. Solved it by conditionally checking for the iOS version.
Related
I want to set the width constraints programmatically using Objective-C.
The below code is working fine when I build my app using Xcode 7.2.
[secHeaderView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[_sectionHeaderView]|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(_sectionHeaderView)]];
[secHeaderView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[_sectionHeaderView]|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(_sectionHeaderView)]];
Now when I run my app using Xcode 9.3, then my secHeaderView's are missing. It's due to the constraints which are mentioned above.
If I comment out the above constraints, then I can see my secHeader View, but the width is not properly set. It shows 70% width of UIScreen.
This is how my secHeaderView looks, but the width is not properly set...
Here is my actual code:
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
_sectionHeaderView = [[NSBundle mainBundle] loadNibNamed:#"BasicProdInfoSectionHeaderView" owner:self options:nil].firstObject;
SectionHeader *currentHeader = [self.titleStringArray objectAtIndex:section];
[_sectionHeaderView setTitleWithText:currentHeader.titleLabel];
_sectionHeaderView.expandableButton.tag = section;
[_sectionHeaderView.expandableButton addTarget:self action:#selector(expandButtonClicked:) forControlEvents:UIControlEventTouchUpInside];
UIView *secHeaderView = [[UIView alloc] initWithFrame:CGRectMake(0, 0,[[UIScreen mainScreen] bounds].size.width , 50)];
_sectionHeaderView.translatesAutoresizingMaskIntoConstraints = NO;
[secHeaderView addSubview:_sectionHeaderView];
[secHeaderView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[_sectionHeaderView]|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(_sectionHeaderView)]];
[secHeaderView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[_sectionHeaderView]|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(_sectionHeaderView)]];
if (section != 3) {
secHeaderView.layer.shadowOffset = CGSizeMake(0, 1);
secHeaderView.layer.shadowRadius = 1;
[secHeaderView.layer setShadowColor:[UIColor blackColor].CGColor];
secHeaderView.layer.shadowOpacity = 0.25;
}
return secHeaderView;
}
Replace this two lines
[secHeaderView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[_sectionHeaderView]|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(_sectionHeaderView)]];
[secHeaderView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[_sectionHeaderView]|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(_sectionHeaderView)]];
With
[secHeaderView
addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[_sectionHeaderView
]|" options:0 metrics:nil views:#{#"_sectionHeaderView
":_sectionHeaderView
}]];
[secHeaderView
addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[_sectionHeaderView
]|" options:0 metrics:nil views:#{#"_sectionHeaderView
":_sectionHeaderView
}]];
Use below constraints I have change below constraints
// center sectionHeaderView horizontally in secHeaderView
[secHeaderView addConstraint:[NSLayoutConstraint constraintWithItem:sectionHeaderView attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:secHeaderView attribute:NSLayoutAttributeCenterX multiplier:1.0 constant:0.0]];
// center sectionHeaderView vertically in secHeaderView
[secHeaderView addConstraint:[NSLayoutConstraint constraintWithItem:sectionHeaderView attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:secHeaderView attribute:NSLayoutAttributeCenterY multiplier:1.0 constant:0.0]];
// width constraint
[secHeaderView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:[sectionHeaderView(==0)]" options:0 metrics:nil views:NSDictionaryOfVariableBindings(sectionHeaderView)]];
// height constraint
[secHeaderView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:[sectionHeaderView(==0)]" options:0 metrics:nil views:NSDictionaryOfVariableBindings(sectionHeaderView)]];
I would suggest this. You dont have to set the frame for the section superview. As tableview sets its autolayout automatically.
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
_sectionHeaderView = [[NSBundle mainBundle] loadNibNamed:#"BasicProdInfoSectionHeaderView" owner:self options:nil].firstObject;
SectionHeader *currentHeader = [self.titleStringArray objectAtIndex:section];
[_sectionHeaderView setTitleWithText:currentHeader.titleLabel];
_sectionHeaderView.expandableButton.tag = section;
[_sectionHeaderView.expandableButton addTarget:self action:#selector(expandButtonClicked:) forControlEvents:UIControlEventTouchUpInside];
UIView *secHeaderView = [[UIView alloc] init];
_sectionHeaderView.translatesAutoresizingMaskIntoConstraints = NO;
[secHeaderView addSubview:_sectionHeaderView];
[_sectionHeaderView.bottomAnchor constraintEqualToAnchor:secHeaderView.bottomAnchor constant:0]
[_sectionHeaderView.leadingAnchor constraintEqualToAnchor:secHeaderView.leadingAnchor constant:0]
[_sectionHeaderView.trailingAnchor constraintEqualToAnchor:secHeaderView.trailingAnchor constant:0]
if (section != 3) {
secHeaderView.layer.shadowOffset = CGSizeMake(0, 1);
secHeaderView.layer.shadowRadius = 1;
[secHeaderView.layer setShadowColor:[UIColor blackColor].CGColor];
secHeaderView.layer.shadowOpacity = 0.25;
}
[secHeaderView layoutIfNeeded]
return secHeaderView;
}
According to Apple document for tableView:viewForHeaderInSection: method
This method only works correctly when tableView:heightForHeaderInSection: is also implemented.
So I guess that problem is because you didn't implement tableView:heightForHeaderInSection: method in your class.
Another problem is you don't need to give secHeaderView a size inside tableView:viewForHeaderInSection: because
A section's header will always have same width with tableView by default.
The right way to change tableView section's height is using UITableViewDelegate's tableView:heightForHeaderInSection: method.
Actually I created a new project and tried your code. It works as expected.
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
_sectionHeaderView = [[NSBundle mainBundle] loadNibNamed:#"BasicProdInfoSectionHeaderView" owner:self options:nil].firstObject;
SectionHeader *currentHeader = [self.titleStringArray objectAtIndex:section];
[_sectionHeaderView setTitleWithText:currentHeader.titleLabel];
_sectionHeaderView.expandableButton.tag = section;
[_sectionHeaderView.expandableButton addTarget:self action:#selector(expandButtonClicked:) forControlEvents:UIControlEventTouchUpInside];
UIView *secHeaderView = [[UIView alloc] initWithFrame:_sectionHeaderView.frame];
_sectionHeaderView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
[secHeaderView addSubview:_sectionHeaderView];
if (section != 3) {
secHeaderView.layer.shadowOffset = CGSizeMake(0, 1);
secHeaderView.layer.shadowRadius = 1;
[secHeaderView.layer setShadowColor:[UIColor blackColor].CGColor];
secHeaderView.layer.shadowOpacity = 0.25;
}
return secHeaderView;
}
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section {
return 50;
}
First of all I've been reading all questions similar to this one, but with no success, so finally I'll try asking my specific case.
I have a UIScrollView that I fill with components programmatically this way:
- (void) fillScrollView(int numItems)
{
float posY = 10.0;
for(int i = 0; i < numItems; i++)
{
UIImageView *leftImg = [[UIImageView alloc] init];
[leftImg setTranslatesAutoresizingMaskIntoConstraints:NO];
[leftImg setFrame:CGRectMake(10.0, posY, 20.0, 20.0)];
[leftImg setImage:[UIImage imageNamed:#"img1"]];
UILabel *txtLb = [[UILabel alloc] init];
[txtLb setTranslatesAutoresizingMaskIntoConstraints:NO];
[txtLb setFont:[UIFont systemFontOfSize:15.0]];
[txtLb setNumberOfLines:0];
[txtLb setLineBreakMode:NSLineBreakByWordWrapping];
[txtLb setFrame:CGRectMake(40.0, posY, 240.0, 20.0)];
NSString *data = #"This is my example text, it could be longer.";
[txtLb setText:data];
CGRect paragraphRect = [dato boundingRectWithSize:CGSizeMake(txtLb.frame.size.width, 9999.0)
options:(NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading)
attributes:#{NSFontAttributeName: txtLb.font}
context:nil];
float height = paragraphRect.size.height;
CGRect frame = txtLb.frame;
frame.size.height = ceil(height);
[txtLb setFrame:frame];
UIImageView *rightImg = [[UIImageView alloc] init];
[rightImg setTranslatesAutoresizingMaskIntoConstraints:NO];
[rightImg setFrame:CGRectMake(290.0, posY, 20.0, 20.0)];
[rightImg setImage:[UIImage imageNamed:#"img2"]];
[_scrollV addSubview:leftImg];
[_scrollV addSubview:txtLb];
[_scrollV addSubview:rightImg];
height = height + 20.0;
if(height < 40.0) height = 40.0;
posY = posY + height;
}
[_scrollV setContentSize:CGSizeMake(_scrollV.frame.size.width, posY)];
}
I would like that theis constraints were:
H:|-10-[leftImg(20)]-10-[txtLb]-10-[rightImg(20)]-10-|
And vertically each row has a space of 10px of vertical separation from the row above.
I've tried using constraintWithItem but I'm confused about how to use it in this case.
Any help would be very appreciated. Kind regards!
EDIT
Following Zhang's suggestion I've put the 3 components inside a UIView. This way I can use autolayout between them without problems and everything is in the correct position inside each UIView.
However I'm still having problems using autolayout between UIViews inside the loop. I'm doing this:
All blocks have no left/right margin with the UIScrollView.
// 0px to the left of the UIScrollView
NSLayoutConstraint *constraintLeft = [NSLayoutConstraint constraintWithItem:block
attribute:NSLayoutAttributeLeftMargin
relatedBy:NSLayoutRelationEqual
toItem:_scrollV
attribute:NSLayoutAttributeRightMargin
multiplier:1.0
constant:0.0];
// 0px to the right of the UIScrollView
NSLayoutConstraint *constraintRight = [NSLayoutConstraint constraintWithItem:block
attribute:NSLayoutAttributeRightMargin
relatedBy:NSLayoutRelationEqual
toItem:_scrollV
attribute:NSLayoutAttributeLeftMargin
multiplier:1.0
constant:0.0];
[_scrollV addConstraint:constraintLeft];
[_scrollV addConstraint:constraintRight];
Regarding to the vertical separation between blocks, when the UIView is the first block of the UIScrollView:
// 10px below UIScrollView top
NSLayoutConstraint *constraintTop = [NSLayoutConstraint constraintWithItem:block
attribute:NSLayoutAttributeTopMargin
relatedBy:NSLayoutRelationEqual
toItem:_scrollV
attribute:NSLayoutAttributeBottomMargin
multiplier:1.0
constant:10.0];
[_scrollV addConstraint:constraintTop];
And when the UIView has any block above it:
// 10px below previous block
NSLayoutConstraint *constraintTop = [NSLayoutConstraint constraintWithItem:block
attribute:NSLayoutAttributeTopMargin
relatedBy:NSLayoutRelationEqual
toItem:previousBlock
attribute:NSLayoutAttributeBottomMargin
multiplier:1.0
constant:10.0];
[_scrollV addConstraint:constraintTop];
This shows all blocks without vertical separation, all in the same Y position, and also give errors applying constraints.
I'm sure I'm not using the right way constraintWithItem, but I can not find examples for this use.
It seems like you're trying to reinvent the wheel mate. You probably should be using UICollectionView or UITableView instead of UIScrollView and manually adding your cells.
Anyhow, for your scrollView method, one way you can implement it is like this:
ViewController Header File
#import <UIKit/UIKit.h>
#interface ViewController : UIViewController
#property (nonatomic, strong) UIScrollView *scrollView;
#property (nonatomic, strong) UIView *contentView;
#end
ViewController Implementation File
#import "ViewController.h"
#import "Cell.h"
#interface ViewController ()
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
[self initViews];
[self initConstraints];
[self fillScrollView:15];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
-(BOOL)prefersStatusBarHidden
{
return YES;
}
-(void)initViews
{
self.scrollView = [[UIScrollView alloc] init];
// ------------------------------------------------------------------
// This content view will be the only child view of scrollview
// ------------------------------------------------------------------
self.contentView = [[UIView alloc] init];
// add content view to scrollview now
[self.scrollView addSubview:self.contentView];
// add scrollview to main view
[self.view addSubview:self.scrollView];
}
-(void)initConstraints
{
self.scrollView.translatesAutoresizingMaskIntoConstraints = NO;
self.contentView.translatesAutoresizingMaskIntoConstraints = NO;
id views = #{
#"scrollView": self.scrollView,
#"contentView": self.contentView
};
// setup scrollview constraints
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[scrollView]|" options:0 metrics:nil views:views]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[scrollView]|" options:0 metrics:nil views:views]];
// ---------------------------------------
// setup content view constraint
//
// note: need to pin all four side of
// contentView to scrollView
// ---------------------------------------
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[contentView]|" options:0 metrics:nil views:views]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[contentView]|" options:0 metrics:nil views:views]];
}
-(void)fillScrollView:(int) numItems
{
// clear any previous cells before adding new ones
[self.contentView.subviews makeObjectsPerformSelector:#selector(removeFromSuperview)];
// Need to construct the layout visual format string
NSMutableString *strVerticalConstraint = [[NSMutableString alloc] init];
[strVerticalConstraint appendString:#"V:|"];
// this dictionary will hold all the key-value pair that identifies all the subviews
NSMutableDictionary *subviews = [[NSMutableDictionary alloc] init];
for(int i = 0; i < numItems; i++)
{
Cell *cell = [[Cell alloc] init];
// customize the cell's appearance here
cell.leftImage.image = [UIImage imageNamed:#"leftImage.png"];
cell.textLabel.text = #"This is my example text, it could be longer.";
cell.rightImage.image = [UIImage imageNamed:#"rightImage.png"];
[self.contentView addSubview:cell];
cell.translatesAutoresizingMaskIntoConstraints = NO;
id views = #{
#"cell": cell
};
[self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[cell]|" options:0 metrics:nil views:views]];
// prevent cell's width to extend beyond screen width
[self.contentView addConstraint:[NSLayoutConstraint constraintWithItem:cell attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeWidth multiplier:1.0 constant:self.view.bounds.size.width]];
// cell name
NSString *cellName = [[NSString alloc] initWithFormat:#"cell%d", i];
// construct each cell's vertical constraint to add it strVerticalConstraint
NSString *viewName = nil;
if(i < numItems - 1)
{
viewName = [[NSString alloc] initWithFormat:#"[%#(50)]-10-", cellName];
}
else
{
viewName = [[NSString alloc] initWithFormat:#"[%#(50)]", cellName];
}
[strVerticalConstraint appendString:viewName];
// add cell name to dictionary
[subviews setValue:cell forKey:cellName];
}
[strVerticalConstraint appendString:#"|"];
NSLog(#"strVerticalConstraint: \n%#", strVerticalConstraint);
// Finally, use the long vertical constraint string
[self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:strVerticalConstraint options:0 metrics:nil views:subviews]];
}
#end
Cell Header File
#import <UIKit/UIKit.h>
#interface Cell : UIView
#property (nonatomic, strong) UIImageView *leftImage;
#property (nonatomic, strong) UILabel *textLabel;
#property (nonatomic, strong) UIImageView *rightImage;
#end
Cell Implementation File
#import "Cell.h"
#implementation Cell
-(id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if(self)
{
[self initViews];
[self initConstraints];
}
return self;
}
-(void)initViews
{
self.backgroundColor = [UIColor grayColor];
self.leftImage = [[UIImageView alloc] init];
self.leftImage.contentMode = UIViewContentModeScaleAspectFill;
self.leftImage.clipsToBounds = YES;
self.leftImage.layer.cornerRadius = 10.0;
self.textLabel = [[UILabel alloc] init];
self.textLabel.numberOfLines = 0;
self.textLabel.lineBreakMode = NSLineBreakByWordWrapping;
self.rightImage = [[UIImageView alloc] init];
self.rightImage.contentMode = UIViewContentModeScaleAspectFill;
self.rightImage.layer.cornerRadius = 10.0;
self.rightImage.clipsToBounds = YES;
[self addSubview:self.leftImage];
[self addSubview:self.textLabel];
[self addSubview:self.rightImage];
}
-(void)initConstraints
{
self.leftImage.translatesAutoresizingMaskIntoConstraints = NO;
self.textLabel.translatesAutoresizingMaskIntoConstraints = NO;
self.rightImage.translatesAutoresizingMaskIntoConstraints = NO;
id views = #{
#"leftImage": self.leftImage,
#"textLabel": self.textLabel,
#"rightImage": self.rightImage
};
// horizontal constraints
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-10-[leftImage(20)]-10-[textLabel]-[rightImage(20)]-10-|" options:0 metrics:nil views:views]];
// vertical constraints
[self addConstraint:[NSLayoutConstraint constraintWithItem:self.leftImage attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeCenterY multiplier:1.0 constant:0.0]];
[self addConstraint:[NSLayoutConstraint constraintWithItem:self.textLabel attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeCenterY multiplier:1.0 constant:0.0]];
[self addConstraint:[NSLayoutConstraint constraintWithItem:self.rightImage attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeCenterY multiplier:1.0 constant:0.0]];
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:[leftImage(20)]" options:0 metrics:nil views:views]];
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:[rightImage(20)]" options:0 metrics:nil views:views]];
}
#end
You should see something like this:
Maybe you have your reason's for using a scrollView and manually adding each row, otherwise, if you're open to alternative, you can use UICollectionView or UITableView.
The method above will result in a lot of memory use as you can imagine if you had 1000 rows, the app needs to calculate, rendering and store 1000 rows in memory. Not scalable, and not feasible.
That's where UITableView or UICollectionView comes in, they reuse each cell when it goes offscreen that way, you'll only ever need to render and store the visible cells on screen.
UICollectionView Demo
So, if you want to see a UICollectionView approach, this is a demo of how you can do it:
ViewController Header File
#import <UIKit/UIKit.h>
#interface ViewController : UIViewController <UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout>
#property (nonatomic, strong) UICollectionView *collectionView;
#property (nonatomic, strong) NSArray *items;
#end
ViewController Implementation File
#import "ViewController.h"
#import "CustomCell.h"
#interface ViewController ()
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
[self initViews];
[self initConstraints];
// --------------------------------------------------------
// Hardcoding 15 sample items as the data source.
// Your data might be from a JSON webservice REST API.
// --------------------------------------------------------
self.items = #[
#"This is my example text, it could be longer.",
#"This is my example text, it could be longer.",
#"This is my example text, it could be longer.",
#"This is my example text, it could be longer.",
#"This is my example text, it could be longer.",
#"This is my example text, it could be longer.",
#"This is my example text, it could be longer.",
#"This is my example text, it could be longer.",
#"This is my example text, it could be longer.",
#"This is my example text, it could be longer.",
#"This is my example text, it could be longer.",
#"This is my example text, it could be longer.",
#"This is my example text, it could be longer.",
#"This is my example text, it could be longer.",
#"This is my example text, it could be longer.",
#"This is my example text, it could be longer."
];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
-(void)initViews
{
UICollectionViewFlowLayout *flowLayout = [[UICollectionViewFlowLayout alloc] init];
flowLayout.minimumInteritemSpacing = 0;
flowLayout.minimumLineSpacing = 10;
self.collectionView = [[UICollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:flowLayout];
self.collectionView.backgroundColor = [UIColor whiteColor];
// need to tell CollectionView beforehand the cell class you want to use
[self.collectionView registerClass:[CustomCell class] forCellWithReuseIdentifier:#"cellID"];
self.collectionView.delegate = self;
self.collectionView.dataSource = self;
[self.view addSubview:self.collectionView];
}
-(void)initConstraints
{
self.collectionView.translatesAutoresizingMaskIntoConstraints = NO;
id views = #{
#"collectionView": self.collectionView
};
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[collectionView]|" options:0 metrics:nil views:views]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[collectionView]|" options:0 metrics:nil views:views]];
}
#pragma mark - UICollectionView Methods -
-(NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
return self.items.count;
}
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
// note: reuse identifier must match what you specified in the register cell above
CustomCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"cellID" forIndexPath:indexPath];
// ---------------------------------------------------------------
// hardcoding images here, you might load your images from JSON
// data using an image caching library like SDWebImage
// ---------------------------------------------------------------
cell.leftImage.image = [UIImage imageNamed:#"leftImage.png"];
// getting text data from data source "self.items"
cell.textLabel.text = self.items[indexPath.row];
cell.rightImage.image = [UIImage imageNamed:#"rightImage.png"];
return cell;
}
// ----------------------------------------------------------------
// Tells the collection view the width and height of each cell
// ----------------------------------------------------------------
-(CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath
{
CGSize size = CGSizeMake(self.view.frame.size.width, 50.0);
return size;
}
#end
CustomCell Header File
#import <UIKit/UIKit.h>
#interface CustomCell : UICollectionViewCell
#property (nonatomic, strong) UIImageView *leftImage;
#property (nonatomic, strong) UILabel *textLabel;
#property (nonatomic, strong) UIImageView *rightImage;
#end
CustomCell Implementation File
#import "CustomCell.h"
#implementation CustomCell
-(id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if(self)
{
[self initViews];
[self initConstraints];
}
return self;
}
-(void)initViews
{
self.backgroundColor = [UIColor grayColor];
self.leftImage = [[UIImageView alloc] init];
self.leftImage.contentMode = UIViewContentModeScaleAspectFill;
self.leftImage.clipsToBounds = YES;
self.leftImage.layer.cornerRadius = 10.0;
self.textLabel = [[UILabel alloc] init];
self.textLabel.numberOfLines = 0;
self.textLabel.lineBreakMode = NSLineBreakByWordWrapping;
self.rightImage = [[UIImageView alloc] init];
self.rightImage.contentMode = UIViewContentModeScaleAspectFill;
self.rightImage.layer.cornerRadius = 10.0;
self.rightImage.clipsToBounds = YES;
[self.contentView addSubview:self.leftImage];
[self.contentView addSubview:self.textLabel];
[self.contentView addSubview:self.rightImage];
}
-(void)initConstraints
{
self.leftImage.translatesAutoresizingMaskIntoConstraints = NO;
self.textLabel.translatesAutoresizingMaskIntoConstraints = NO;
self.rightImage.translatesAutoresizingMaskIntoConstraints = NO;
id views = #{
#"leftImage": self.leftImage,
#"textLabel": self.textLabel,
#"rightImage": self.rightImage
};
// horizontal constraints
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-10-[leftImage(20)]-10-[textLabel]-[rightImage(20)]-10-|" options:0 metrics:nil views:views]];
// vertical constraints
[self.contentView addConstraint:[NSLayoutConstraint constraintWithItem:self.leftImage attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self.contentView attribute:NSLayoutAttributeCenterY multiplier:1.0 constant:0.0]];
[self.contentView addConstraint:[NSLayoutConstraint constraintWithItem:self.textLabel attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self.contentView attribute:NSLayoutAttributeCenterY multiplier:1.0 constant:0.0]];
[self.contentView addConstraint:[NSLayoutConstraint constraintWithItem:self.rightImage attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self.contentView attribute:NSLayoutAttributeCenterY multiplier:1.0 constant:0.0]];
[self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:[leftImage(20)]" options:0 metrics:nil views:views]];
[self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:[rightImage(20)]" options:0 metrics:nil views:views]];
}
#end
You end up with something like this:
Look the same but more efficient :D
No I didn't upload the same screenshot :P
Hope that helps?
I have a UIViewController which creates this layout:
The part that gives me trouble is the darker area on the bottom, containing the text.
The darker part is a UIScrollView subclass, which creates this layout using Auto Layout:
I have a couple of UILabels ("Omschrijving", "Informatie", and the small labels at the bottom of the view) and a UITextView (the view containing the text starting with "Robert Langdon").
I set the UITextViews height explicitly to 60 points, and when the "Meer" button is tapped, I calculate its full height using boundingRectWithSize:options:attributes:context. I then change its height constraint constant from its prior hardcoded value to the value I've calculated.
That's where it goes wrong. The layout is absolutely fine until the UITextView's height changes. All the content in the UIScrollView subclass seems to move inexplicably. I looked at the view hierarchy with Reveal: .
I've been messing with the constraints for hours now and I can't find a solution.
All the views have translatesAutoresizingMaskIntoConstraints set to NO.
This is iOS 8 on the simulator, with Xcode 6 b4.
My init methods looks like this:
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
self.translatesAutoresizingMaskIntoConstraints = NO;
[self layoutIfNeeded];
[self addExpandButton];
[self setupUI];
[self setupConstraints];
[self layoutIfNeeded];
self.backgroundColor = [UIColor clearColor];
self.textView.backgroundColor = [UIColor clearColor];
self.textView.userInteractionEnabled = NO;
}
return self;
}
setupUI creates all the views and adds them to the scrollView. I've removed some repetitive lines of code for the sake of brevity
- (void)setupUI
{
// Initialization
UILabel *descriptionLabel = [[UILabel alloc] initWithFrame:CGRectZero];
UITextView *textView = [[UITextView alloc] initWithFrame:CGRectMake(0.0, 0.0, 100.0, 60.0) textContainer:nil];
textView.translatesAutoresizingMaskIntoConstraints = NO;
textView.editable = NO;
textView.textContainerInset = UIEdgeInsetsMake(0.0, -5.0, 0.0, -5.0);
textView.textColor = [UIColor whiteColor];
// init all the UILabels with CGRectZero frames
// ...
// ...
UIView *separator = [[UIView alloc] initWithFrame:CGRectZero];
separator.backgroundColor = [UIColor colorWithWhite:1.0 alpha:0.5];
// Set the text
descriptionLabel.text = #"Omschrijving";
textView.text = #"Robert Langdon, hoogleraar kunstgeschiedenis en symboliek, wordt op een nacht wakker in een ziekenhuis in Florence zonder te weten hoe hij daar is beland. Geholpen door een stoïcijnse jonge vrouw, Sienna Brooks, vlucht Langdon en raakt hij verzeild in een duizelingwekkend avontuur. Langdon ontdekt dat hij in het bezit is van een reeks verontrustende codes, gecreëerd door een briljante wetenschapper; een genie dat geobsedeerd is door het einde van de wereld en het duistere meesterwerk Inferno van Dante Alighieri.";
// Set the text of the labels
// ...
// ...
descriptionLabel.font = [UIFont fontWithName:#"ProximaNova-Semibold" size:14.0];
textView.font = [UIFont fontWithName:#"ProximaNova-Regular" size:12.0];
informationLabel.font = [UIFont fontWithName:#"ProximaNova-Semibold" size:14.0];
UIFont *font = [UIFont fontWithName:#"ProximaNova-Regular" size:10.0];
// Set the font of the labels
// ...
// ...
// Add the views to the view hierarchy
[self addSubview:descriptionLabel];
[self addSubview:textView];
[self addSubview:separator];
[self addSubview:informationLabel];
// Add the other labels as subviews
// ...
// ...
[self.subviews enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
if ([obj isKindOfClass:[UILabel class]]) {
UILabel *label = (UILabel *)obj;
[label sizeToFit];
label.textColor = [UIColor whiteColor];
}
}];
// Assign the local properties to properties
// ...
// ...
for (UIView *view in self.subviews) {
view.translatesAutoresizingMaskIntoConstraints = NO;
}
}
Now on to the constraints. I have a big method that adds all the constraints called -addConstraints.
- (void)setupConstraints
{
// Omschrijving label top
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-3-[descriptionLabel]-3-[textView]"
options:NSLayoutFormatAlignAllLeading
metrics:nil
views:#{#"descriptionLabel": self.descriptionLabel,
#"textView": self.textView}]];
[self addConstraint:[NSLayoutConstraint constraintWithItem:self.descriptionLabel
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:self
attribute:NSLayoutAttributeTop
multiplier:1.0
constant:3.0]];
// Omschrijving label leading
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"|-10-[descriptionLabel]"
options:0
metrics:nil
views:#{#"descriptionLabel": self.descriptionLabel}]];
// Text view width
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"[textView(==width)]"
options:0
metrics:#{#"width": #220.0}
views:#{#"textView": self.textView}]];
// Text view height
NSArray *textViewHeightConstraints = [NSLayoutConstraint constraintsWithVisualFormat:#"V:[textView(==60.0)]"
options:0
metrics:nil
views:#{#"textView": self.textView}];
[self addConstraints:textViewHeightConstraints];
self.textViewHeightConstraints = textViewHeightConstraints;
NSLog(#"%#", self.textViewHeightConstraints);
// Text view expand button
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:[textView][expandButton(==12.0)]"
options:NSLayoutFormatAlignAllTrailing
metrics:nil
views:#{#"textView": self.textView, #"expandButton": self.expandButton}]];
// Separator
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:[expandButton]-6-[separator]"
options:NSLayoutFormatAlignAllTrailing
metrics:nil
views:#{#"expandButton": self.expandButton, #"separator": self.separator}]];
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"[separator(==textView)]"
options:0
metrics:nil
views:#{#"separator": self.separator, #"textView": self.textView}]];
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:[separator(==0.5)]"
options:0
metrics:nil
views:#{#"separator": self.separator}]];
NSString *leftVisualFormatString = #"V:[separator]-6-[informationLabel]-spacing-[languageDescriptionLabel]-spacing-[categoryDescriptionLabel]-spacing-[publisherDescriptionLabel]-spacing-[publishedDateDescriptionLabel]-spacing-[pageCountDescriptionLabel]-|";
NSDictionary *descriptionViews = #{#"separator": self.separator,
#"informationLabel": self.informationLabel,
#"languageDescriptionLabel": self.languageDescriptionLabel,
#"categoryDescriptionLabel": self.categoryDescriptionLabel,
#"publisherDescriptionLabel": self.publisherDescriptionLabel,
#"publishedDateDescriptionLabel": self.publishedDateDescriptionLabel,
#"pageCountDescriptionLabel": self.pageCountDescriptionLabel};
NSDictionary *metrics = #{#"spacing": #1.0};
// All at once: vertical spacing and leading alignment for the labels on the left
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:leftVisualFormatString
options:NSLayoutFormatAlignAllLeading
metrics:metrics
views:descriptionViews]];
// Same, for the righthand labels
NSDictionary *views = #{#"languageLabel": self.languageLabel,
#"categoryLabel": self.categoryLabel,
#"publisherLabel": self.publisherLabel,
#"publishedDateLabel": self.publishedDateLabel,
#"pageCountLabel": self.pageCountLabel};
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"[pageCountDescriptionLabel]-20-[pageCountLabel]"
options:0
metrics:nil
views:#{#"pageCountDescriptionLabel": self.pageCountDescriptionLabel,
#"pageCountLabel": self.pageCountLabel}]];
NSString *rightVisualFormatString = #"V:[languageLabel]-spacing-[categoryLabel]-spacing-[publisherLabel]-spacing-[publishedDateLabel]-spacing-[pageCountLabel]-|";
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:rightVisualFormatString
options:NSLayoutFormatAlignAllLeading
metrics:metrics
views:views]];
}
As I said, this works fine until I call this method:
- (void)tappedExpandButton:(id)sender
{
if (self.textViewHeightConstraints.count == 1) {
NSLayoutConstraint *constraint = self.textViewHeightConstraints.firstObject;
CGFloat newHeight = [self.textView.text boundingRectWithSize:CGSizeMake(self.textView.textContainer.size.width, CGFLOAT_MAX)
options:NSStringDrawingUsesLineFragmentOrigin
attributes:#{NSFontAttributeName: self.textView.font}
context:nil].size.height;
constraint.constant = ceilf(newHeight);
[self layoutIfNeeded];
}
}
Thanks already!
I think I've figured it out. For some reason I don't yet understand, the containing UIScrollView's frame changed when adding the constraint. That still seems really weird to me, since I think only its contentSize should change, but adding constraints from the dark area (which is a UIView) to the UIScrollView fixed it. This is the code:
[self.darkEffectView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"|[descriptionView]|"
options:0
metrics:nil
views:#{#"descriptionView": self.descriptionView}]];
[self.darkEffectView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[descriptionView]|"
options:0
metrics:nil
views:#{#"descriptionView": self.descriptionView}]];
I've been searching for a clear answer on how to add auto layout to a UITableView. So far, my code looks like:
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
UINib *nib = [UINib nibWithNibName:#"HomeHeaderView" bundle:nil];
UIView *headerView = (UIView *)[nib instantiateWithOwner:self options:nil][0];
[headerView.layer setCornerRadius:6.0];
[headerView setTranslatesAutoresizingMaskIntoConstraints:NO];
// NSDictionary *viewsDictionary = NSDictionaryOfVariableBindings(headerView);
// NSMutableArray *headerConstraints = [[NSMutableArray alloc] init];
// [headerConstraints addObject:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-[headerView]-|" options:0 metrics:nil views:viewsDictionary]];
// [headerConstraints addObject:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-[headerView]-|" options:0 metrics:nil views:viewsDictionary]];
// [self.actionsTableView addConstraints:headerConstraints];
// [self.view addSubview:headerView];
tableView.tableHeaderView = headerView;
[headerView layoutSubviews];
NSLayoutConstraint *centerX = [NSLayoutConstraint constraintWithItem:headerView attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeCenterX multiplier:1 constant:0];
NSLayoutConstraint *centerY = [NSLayoutConstraint constraintWithItem:headerView attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeCenterY multiplier:1 constant:0];
NSLayoutConstraint *width = [NSLayoutConstraint constraintWithItem:headerView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:0 multiplier:1 constant:300];
NSLayoutConstraint *height = [NSLayoutConstraint constraintWithItem:headerView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:0 multiplier:1 constant:90];
[self.view addConstraints:#[centerX, centerY, width, height]];
return headerView;
}
I basically have a nib file for my header view and I want to center that nib in my UITableViewHeader. I'd like it to grow and shrink accordingly in portrait and landscape orientations. I'm honestly unsure if I set up the constraint properly. I was not sure if my toItem was supposed to be the view controller's view, or the tableview itself.
I also did not know if I was supposed to add the headerview as a subview to either the view controller's view, or the tableview itself.
Or, I wasn't sure if setting tableView.tableHeaderView = headerView was enough.
I really have no clue what the best practices are for something like this. I wasn't sure if it all could be done in IB as well. Currently, with the code you see, I get this error:
'Auto Layout still required after executing -layoutSubviews. UITableView's implementation of -layoutSubviews needs to call super.'
It's because of that error, that I added [headerView layoutSubviews]
Thoughts on this? Thanks in advance!
The real problem is that you've confused viewForHeaderInSection: with the table's headerView. They are unrelated.
The former is the header for a section. You return the view from the delegate method.
That latter is the header for the table. You set the view, probably in your viewDidLoad.
Constraints operate in the normal way. But they should only be internal constraints to their subviews. At the time you form it, the view is not in your interface. And its size and place are not up to you at that time. If it's the section header, it will be resized automatically to fit correctly (in accordance with the table's width and the table's or delegate's statement of the header height). If it's the table header, you can give it an absolute height, but its width will be resized to fit correctly.
Here is a complete example of constructing a section header with internal constraints on its subviews.
- (UIView *)tableView:(UITableView *)tableView
viewForHeaderInSection:(NSInteger)section {
UITableViewHeaderFooterView* h =
[tableView dequeueReusableHeaderFooterViewWithIdentifier:#"Header"];
if (![h.tintColor isEqual: [UIColor redColor]]) {
h.tintColor = [UIColor redColor];
h.backgroundView = [UIView new];
h.backgroundView.backgroundColor = [UIColor blackColor];
UILabel* lab = [UILabel new];
lab.tag = 1;
lab.font = [UIFont fontWithName:#"Georgia-Bold" size:22];
lab.textColor = [UIColor greenColor];
lab.backgroundColor = [UIColor clearColor];
[h.contentView addSubview:lab];
UIImageView* v = [UIImageView new];
v.tag = 2;
v.backgroundColor = [UIColor blackColor];
v.image = [UIImage imageNamed:#"us_flag_small.gif"];
[h.contentView addSubview:v];
lab.translatesAutoresizingMaskIntoConstraints = NO;
v.translatesAutoresizingMaskIntoConstraints = NO;
[h.contentView addConstraints:
[NSLayoutConstraint
constraintsWithVisualFormat:#"H:|-5-[lab(25)]-10-[v(40)]"
options:0 metrics:nil views:#{#"v":v, #"lab":lab}]];
[h.contentView addConstraints:
[NSLayoutConstraint
constraintsWithVisualFormat:#"V:|[v]|"
options:0 metrics:nil views:#{#"v":v}]];
[h.contentView addConstraints:
[NSLayoutConstraint
constraintsWithVisualFormat:#"V:|[lab]|"
options:0 metrics:nil views:#{#"lab":lab}]];
}
UILabel* lab = (UILabel*)[h.contentView viewWithTag:1];
lab.text = self.sectionNames[section];
return h;
}
I found that solution provided by matt might not be the perfect, because he's adding custom views and constraints to UITableViewHeaderFooterView's contentView. That is always causing Auto Layout warnings in runtime: Unable to simultaneously satisfy constraints when we want to have dynamic header height.
I am not sure about the reason, but we can assume that iOS adds some extra constrains to contentView that sets fixed width and height of that view. Warnings generated in runtime tells that constraints we added manually can't be satisfied with those, and it's obvious because our constraints should stretch header view so the subviews can fit in it.
Solution is pretty easy - don't use UITableViewHeaderFooterView's contentView, just add your subviews directly to UITableViewHeaderFooterView. I can confirm that it's working without any issues on iOS 8.1. If you want to add several views and change the background color of you header, consider adding UIView that fills header view (thanks to AutoLayout constraints) and then all the subviews you would like to have to that view (I am calling it customContentView). That way we can avoid any AutoLayout issues and have auto-sizing headers in UITableView.
This is a neat solution:
Optional: initWithStyle:UITableViewStyleGrouped to prevent floating tableViewHeader
Make two properties, the label is just for demonstration:
#property (nonatomic, strong, readwrite) UIView *headerView;
#property (nonatomic, strong, readwrite) UILabel *headerLabel;
Setup everything in viewDidLoad:
self.headerView = [[UIView alloc] initWithFrame:CGRectZero];
self.headerLabel = [[UILabel alloc] init];
self.headerLabel.text = #"Test";
self.headerLabel.numberOfLines = 0; //unlimited
self.headerLabel.textAlignment = NSTextAlignmentCenter;
self.headerLabel.translatesAutoresizingMaskIntoConstraints = NO; //always set this to NO when using AutoLayout
[self.headerView addSubview:self.headerLabel];
NSString *horizontalFormat = #"H:|-[headerLabel]-|";
NSArray *horizontalConstraints = [NSLayoutConstraint constraintsWithVisualFormat:horizontalFormat options:0 metrics:nil views:#{#"headerLabel":self.headerLabel}];
[self.headerView addConstraints:horizontalConstraints];
NSString *verticalFormat = #"V:|-[headerLabel]-|";
NSArray *verticalConstraints = [NSLayoutConstraint constraintsWithVisualFormat:verticalFormat options:0 metrics:nil views:#{#"headerLabel":self.headerLabel}];
[self.headerView addConstraints:verticalConstraints];
In viewForHeaderInSection:
return self.headerView;
In heightForHeaderInSection:
self.headerLabel.preferredMaxLayoutWidth = tableView.bounds.size.width;
return [self.headerView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;
I create a subview with a UIview (acts as header), a UIImage, and 10 UILabels. I'm putting these into a UICollectionView as cells.
When designed completely, it does not scroll smoothly. If i remove all the UILabels, it scrolls smoothly.
I'm assuming it's sluggish cause the UICollectionView loads on demand, so when it needs each new cell, it has to draw it which locks up the main thread.
Is it just a matter that its too much for iOS to handle to create them? If so, is there another way I can put text into it?
what my cell looks like:
Here is DatasetFilterListPanelView, this creates the UIView that I put into the UICollectionViewCell. I did it this way cause I created this before I decided to use UICollectionView.
#implementation DatasetFilterListPanelView
-(id)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
self.translatesAutoresizingMaskIntoConstraints = FALSE;
UIView *contentView = [self createContentView];
[self addSubview:contentView];
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[contentView]|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(contentView)]];
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[contentView]|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(contentView)]];
}
return self;
}
-(UIView *) createContentView {
UIView *contentView = [[UIView alloc] initWithFrame:self.frame];
// contentView.translatesAutoresizingMaskIntoConstraints = FALSE;
contentView.backgroundColor = [UIColor myDarkGrayColor];
UIView *headerView = [self createHeaderView];
[contentView addSubview:headerView];
[contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[headerView]|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(headerView)]];
[contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[headerView]" options:0 metrics:nil views:NSDictionaryOfVariableBindings(headerView)]];
UIImageView *imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"gear12.png"]];
imageView.translatesAutoresizingMaskIntoConstraints = FALSE;
imageView.backgroundColor = [UIColor blueColor];
self.imageView = imageView;
[imageView addConstraint:[NSLayoutConstraint constraintWithItem:imageView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:imageView attribute:NSLayoutAttributeWidth multiplier:1 constant:0]];
[contentView addSubview:imageView];
[contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-[imageView]" options:0 metrics:nil views:NSDictionaryOfVariableBindings(imageView)]];
[contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:[headerView]-[imageView]-|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(headerView, imageView)]];
UILabel *acresLabel = [self createLabelWithTitle:#"Label01:" andFont:[UIFont fontWithName:HELVETICA_FONT_STYLE_BOLD size:12]];
[contentView addSubview:acresLabel];
UILabel *addedLabel = [self createLabelWithTitle:#"Label02:" andFont:[UIFont fontWithName:HELVETICA_FONT_STYLE_BOLD size:12]];
[contentView addSubview:addedLabel];
UILabel *typeLabel = [self createLabelWithTitle:#"Label03:" andFont:[UIFont fontWithName:HELVETICA_FONT_STYLE_BOLD size:12]];
[contentView addSubview:typeLabel];
UILabel *zonesLabel = [self createLabelWithTitle:#"Label04:" andFont:[UIFont fontWithName:HELVETICA_FONT_STYLE_BOLD size:12]];
[contentView addSubview:zonesLabel];
UILabel *sceneLabel = [self createLabelWithTitle:#"Label05:" andFont:[UIFont fontWithName:HELVETICA_FONT_STYLE_BOLD size:12]];
[contentView addSubview:sceneLabel];
UILabel *acresValueLabel = [self createLabelWithTitle:#"Data" andFont:[UIFont fontWithName:HELVETICA_FONT_STYLE size:12]];
acresValueLabel.textAlignment = NSTextAlignmentLeft;
[contentView addSubview:acresValueLabel];
UILabel *addedValueLabel = [self createLabelWithTitle:#"Data" andFont:[UIFont fontWithName:HELVETICA_FONT_STYLE size:12]];
addedValueLabel.textAlignment = NSTextAlignmentLeft;
[contentView addSubview:addedValueLabel];
UILabel *typeValueLabel = [self createLabelWithTitle:#"Name" andFont:[UIFont fontWithName:HELVETICA_FONT_STYLE size:12]];
typeValueLabel.textAlignment = NSTextAlignmentLeft;
[contentView addSubview:typeValueLabel];
UILabel *zonesValueLabel = [self createLabelWithTitle:#"Data" andFont:[UIFont fontWithName:HELVETICA_FONT_STYLE size:12]];
zonesValueLabel.textAlignment = NSTextAlignmentLeft;
[contentView addSubview:zonesValueLabel];
UILabel *sceneValueLabel = [self createLabelWithTitle:#"Name" andFont:[UIFont fontWithName:HELVETICA_FONT_STYLE size:12]];
sceneValueLabel.textAlignment = NSTextAlignmentLeft;
[contentView addSubview:sceneValueLabel];
NSDictionary *views = NSDictionaryOfVariableBindings(headerView, imageView, acresLabel, acresValueLabel, addedLabel, addedValueLabel, typeLabel, typeValueLabel, zonesLabel, zonesValueLabel, sceneLabel, sceneValueLabel);
[contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:[headerView]-[acresLabel]"
options:0
metrics:nil
views:views]] ;
[contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:[acresLabel]-[addedLabel(==acresLabel)]-[typeLabel(==acresLabel)]-[zonesLabel(==acresLabel)]-[sceneLabel(==acresLabel)]-|"
options:NSLayoutFormatAlignAllRight
metrics:0
views:views]];
[contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:[acresValueLabel]-[addedValueLabel(==acresLabel)]-[typeValueLabel(==acresLabel)]-[zonesValueLabel(==acresLabel)]-[sceneValueLabel(==acresLabel)]-|"
options:NSLayoutFormatAlignAllLeft
metrics:nil
views:views]];
[contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:[imageView]-20-[acresLabel]-[acresValueLabel]" options:0 metrics:nil views:views]];
return contentView;
}
-(UIView *)createHeaderView {
UIView *view = [UIView new];
view.translatesAutoresizingMaskIntoConstraints = FALSE;
view.backgroundColor = [UIColor blueColor];
view.clipsToBounds = YES;
[view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:[view(30)]" options:0 metrics:nil views:NSDictionaryOfVariableBindings(view)]];
UILabel *title = [UILabel new];
title.translatesAutoresizingMaskIntoConstraints = FALSE;
title.text = #"Default text";
title.font = [UIFont fontWithName:HELVETICA_FONT_STYLE_BOLD size:14];
title.textColor = [UIColor whiteColor];
title.backgroundColor = [UIColor clearColor];
self.headerLabel = title;
[view addSubview:title];
[view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-[title]" options:0 metrics:nil views:NSDictionaryOfVariableBindings(title)]];
[view addConstraint:[NSLayoutConstraint constraintWithItem:title attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:view attribute:NSLayoutAttributeCenterY multiplier:1 constant:0]];
self.headerGradient = [UIColor grayGradient];
self.headerGradient.frame = CGRectMake(0, 0, 360, 30);
[view.layer insertSublayer:self.headerGradient atIndex:0];
return view;
}
-(UILabel *)createLabelWithTitle:(NSString *)title andFont:(UIFont *)font; {
UILabel *label = [UILabel new];
label.translatesAutoresizingMaskIntoConstraints = FALSE;
label.text = title;
label.font = font;
label.textAlignment = NSTextAlignmentRight;
label.textColor = [UIColor whiteColor];
label.backgroundColor = [UIColor clearColor];
return label;
}
Here is my UICollectionViewCell file, i just addSubview a DatasetFilterListPanelView to it.
#implementation DatasetViewCell
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
[self addSubview:[[DatasetFilterListPanelView alloc] initWithFrame:CGRectMake(0, 0, 360, 160)]];
}
return self;
}
When I use the same panels in a UIScrollview, once they are all loaded and positioned, it will scroll smoothly. So it has to be the loading a cell on demand aspect of the UICollectionView.
I followed this UICollectionView Tutorial
EDIT: creating the cell:
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
DatasetViewCell *datasetCell = [collectionView dequeueReusableCellWithReuseIdentifier:DatasetCellIdentifier forIndexPath:indexPath];
return datasetCell;
}
EDIT 2: Instrument tracing:
Ok, after much playing around I figured out the culprit: constraints! CodaFI was right. I didn't have that many constraints in the panel so i didn't think it could be the issue.
I created a nib file and removed autolayout and it now scrolls smoothly.
Lesson of the day: Constraints are slow to compute!
Generally the problem is that you don't reuse the cells. Make sure you use dequeueReusableCellWithReuseIdentifier:forIndexPath: to reuse existing cells.