Table View Cell AutoLayout in iOS8 - ios

I can't seem to get AutoLayout working on my Table View Cells.
On some cells it seems to work, and on others it seems to not work. Even cells of the exact same kind.
For example, on some cells the Description will be more than 1 lines worth of text and it will work correctly...
...Yet on other cells the Description will be more than 1 lines worth of text but only show 1 line of it with a bunch of empty space.
Can you help me figure out what I'm missing or doing wrong? Thanks!
I'm using this StackOverflow question to guide my process as a first-timer doing this: Using Auto Layout in UITableView for dynamic cell layouts & variable row heights
1. Set Up & Add Constraints
These are working well for the most part I believe.
2. Determine Unique Table View Cell Reuse Identifiers
I'm not totally sure if I need to worry about this part since I will always have a Headline, Time, and Description.
For iOS 8 - Self-Sizing Cells
3. Enable Row Height Estimation
I added this to viewDidLoad:
self.tableView.rowHeight = UITableViewAutomaticDimension;
self.tableView.estimatedRowHeight = 180.0;
UPDATE: Adding more info per Acey request
To be clear, I put constraints:
Headline: 15 left, 85 top, 15 right
Vertical Spacing between Headline and Time, of 10
Vertical Spacing between Time and Description, of 10
I Cmd clicked all three labels and added Leading Edges and Trailing
Edges
I pinned 20 between Description and the bottom of the Table View Cell
UPDATE 2: Solved
Answer below worked really well, but also any extra spacing was due to height set for cell being too large, so Xcode was automatically adding extra space to fill out height of cell since text labels didn't fill out the full height of the Table View Cell.
Let me know if you have any questions or need any help on this if you come across this and have the same problem.
Thanks everyone!

I haven't tried using the new iOS 8 mechanisms yet. But I have faced similar issues when I was doing this with iOS 6 / 7. After updating the app to iOS 8 it still works fine, so maybe the old way is still the best way?
I have some examples of my code here:
AutoLayout multiline UILabel cutting off some text
And here:
AutoLayout uitableviewcell in landscape and on iPad calculating height based on portrait iPhone
Long story short the pre iOS 8 way involved keeping a copy of a cell just for calculating the height inside tableView:heightForRowAtIndexPath:. But this wasn't enough for dealing with multi line UILabel's. I had to subclass UILabel to update the preferredMaxLayoutWidth every time layoutSubviews was called.
The preferredMaxLayoutWidth "fix" seemed to be the magic secret I was missing. Once I did this most of my cells worked perfectly.
The second issue I had only required me to set the content compression resistance and content hugging properties correctly, so for example telling the label to hug the text will mean it won't expand to fill the whitespace which will cause the cell to shrink.
Once I did these 2 things my cells now handle any font size, or any amount of text without any messy layout code. It was a lot to learn but I do think it paid off in the end, as I have a lot of dynamic content in my app.
Edit
After coming across a few issues of my own with iOS 8, i'm adding some more details to solve these very odd autoLayout bugs.
With the code I mentioned, it doesn't seem to work when the cell "Row Height" is not set to custom. This setting is found in IB by selecting the cell and clicking the autoLayout tab (where all the content compression resistance settings etc are). Press the checkbox and it will fill with a temporary height.
Second is, in my code I keep a local copy of a cell, and then reuse it many times inside the heightForRowAtIndexPath: method. This seems to increase the cell height by a lot every time it is called. I had to re-init the local copy by calling:
localCopy = [self.tableView dequeueReusableCellWithIdentifier:#"mycell"];
It appears the new Xcode 6 / iOS 8 changes are very much so not backwards compatible with iOS 7 and it seems to be managed quite differently.
Hope this helps.
Edit 2
after reading this question: iOS AutoLayout multi-line UILabel
I've come across another issue with iOS 7 / iOS 8 autolayout support!!! I was overriding layoutSubviews for iOS 8 I also needed to override setBounds to update the preferredMaxLayoutWidth after calling super. WTF have apple changed!
Seems to be an issue with the setting in IB for preferredMaxLayoutWidth, because iOS 7 can't use the automatic feature, if you use the same UILabel on multiple devices, its only going to use the 1 width. So UITableViewCell's on an iOS 8 tablet will be bigger because the same cell needs to have 2 lines on an iOS 8 iPhone.

Here is my attempt.
You could create a method/function that get's you the cellview that you need. Like so:
- (UIView *) getCellView {
UIView *cellView = [[UIView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, self.view.frame.size.width, 0.0f)];
cellView.tag = 1;
cellView.backgroundColor = [UIColor clearColor];
UIImageView *imgView = [[UIImageView alloc] initWithFrame:CGRectMake(15.0f, 10.0f, 15.0f, 15.0f)]; //I assumed that was the size of your imageView;
imgView.image = [UIImage imageNamed:#"whatever-your-image-is-called"];
[cellView addSubview:imgView];
CGFloat xPadding = 15.0f;
CGFloat yPadding = 15.0f;
UILabel *headlineLabel = [[UILabel alloc ]initWithFrame:CGRectMake(xPadding, 0.0f, self.view.frame.size.width - (xPadding*2), 0.0f)];
headlineLabel.numberOfLines = 0;
headlineLabel.text = #"Red Sox season fell apart after World Series title (The Associated Press)";
[headlineLabel sizeToFit];
CGRect hFrame = headlineLabel.frame;
if(hFrame.size.width > self.view.frame.size.width - 31.0f)
hFrame.size.width = self.view.frame.size.width - 30.0f;
hFrame.origin.y = imgView.frame.size.height + yPadding;
headlineLabel.frame = hFrame;
UILabel *timeLabel = [[UILabel alloc] initWithFrame:CGRectMake(xPadding, 0.0f, self.view.frame.size.height-(xPadding*2), 0.0f)];
timeLabel.text = #"4h";
//timeLabel.numberOfLines = 0; //uncomment if it will wrap on multiple lines;
[timeLabel sizeToFit];
hFrame = timeLabel.frame;
if(hFrame.size.width > self.view.frame.size.width - 31.0f)
hFrame.size.width = self.view.frame.size.width - 30.0f;
hFrame.origin.y = headlineLabel.frame.size.height + yPadding;
timeLabel.frame = hFrame;
UILabel *descriptLabel = [[UILabel alloc] initWithFrame:CGRectMake(xPadding, 0.0f, self.view.frame.size.height - (xPadding*2), 0.0f)];
descriptLabel.text = #"Boston (AP) -- To Boston Red Sox manager John Farrel, it hardly seems possible that just 11 months ago his team was celebrating the World Series championship on the field at Fenway Park.";
descriptLabel.numberOfLines = 0; //I would suggest something like 4 or 5 if the description string vary from 1 line to more than 5 lines.
[descriptLabel sizeToFit];
hFrame = descriptLabel.frame;
if(hFrame.size.width > self.view.frame.size.width - 31.0f)
hFrame.size.width = self.view.frame.size.width - 30.0f;
hFrame.origin.y = timeLabel.frame.size.height + yPadding;
descriptLabel.frame = hFrame;
cellView.frame = CGRectMake(0.0f, 0.0f, self.view.frame.size.width, descriptLabel.frame.origin.y + descriptLabel.frame.size.height + 15.0f /*some padding*/);
return cellView;
}
If you are using indexPath.row, you could just change the method name to be - (UIView *)getCellView:(NSIndex) *indexPath and it should work the same.
Then in your heightForRowAtIndexPath you could do
return [[self getCellView] frame].size.height;
or
return [[self getCellView:indexPath] frame].size.height
And in your cellForRowAtIndexPath you could just do the following
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if(cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
cell.accessoryType = UITableViewCellAccessoryNone;
cell.textLabel.text = #"";
cell.detailTextLabel.text = #"";
}
[[cell viewWithTag:1] removeFromSuperview];
[cell addSubview:[self getCellView]; //or [cell addSubview:[self getCellView:indexPath]];
return cell;
Hope this helps. Let me know if something was unclear or not quite working. There are some stuff you may need to tweak to fit your usage, especially in cellForRowAtIndexPath, but that should be more or less everything you need to get going. Happy coding.

Related

Can't tap UICollectionView item

I'm building an app whereas I've got a UICollectionView with a custom layout. I have a problem whereas I cannot tap some rows. Here's an overview over what happens
1) App starts and presents a UICollectionView
2) 3 test items are displayed, using the visual debugger in Xcode, you can see that there is something not right with the hierarchy
The red row can't be tapped now, but the yellow and green rows can.
3) User taps the yellow item, and segues to another page
4) User pops the shown UIViewController and returns to the UICollectionView whereas the viewWillAppear method reloads the UICollectionView like so:
[self.collectionView reloadData];
5) Re-entering the visual debugger shows that the hierarchy seems shifted, and the yellow row is now untappable, but the red and green rows can be tapped.
What could be the reason for this? I'll post any relevant code, ask for what parts you'd like to see.
Update
The UIViewController displaying the UICollectionView
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
ShareViewCell *view = [collectionView dequeueReusableCellWithReuseIdentifier:#"share" forIndexPath:indexPath];
view.share = [self.sharesController shareAtIndex:indexPath.item];
return view;
}
Custom cell:
- (instancetype)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame]) {
self.leftBorderView = [UIView new];
[self.contentView addSubview:self.leftBorderView];
self.label = [UILabel new];
self.label.font = [UIFont boldSystemFontOfSize:10];
self.label.numberOfLines = 0;
self.label.lineBreakMode = NSLineBreakByWordWrapping;
[self.contentView addSubview:self.label];
}
return self;
}
- (void)layoutSubviews
{
[super layoutSubviews];
self.leftBorderView.frame = CGRectMake(0, 0, 1.5, self.bounds.size.height);
CGSize labelSize = [self.label sizeThatFits:CGSizeMake(self.bounds.size.width - 10.0f, MAXFLOAT)];
NSTimeInterval durationOfShare = [self.share.endSpan timeIntervalSinceDate:self.share.startSpan] / 3600;
CGFloat middleOfCell = self.bounds.size.height / 2;
CGFloat yPositionToUse = middleOfCell - (labelSize.height / 2);
if (durationOfShare < 0.25)
{
[self.label setHidden:YES];// to small to be shown
} else if (durationOfShare > 0.5)
{
yPositionToUse = 8; // show it at the top
}
self.label.frame = CGRectMake(8, yPositionToUse, labelSize.width, labelSize.height);
}
Update 2
Is the UICollectionReusableView blocking tap of the UICollectionViewCell?
Ok,
First, you should be more precise in your question : you're using some code found on some question on StackOverflow.
The code seems to be this Calendar UI made with UICollectionView.
This is a sample code, quickly built for the sake of a StackOverflow answer (with a bounty). It's not finished, has no contributors, and you should try to read it and improve over it !
From your debugger's captures, it seems you have some view which overlays on top of your collectionView's cells.
From reading this code, I see it uses some supplementary view to present 'hour' blocks. This supplementary view class is HourReusableView. And it's the view coming on top on your debugger's captures
CalendarViewLayout is responsible to compute these supplementary views frame, as it does for colored event blocks (see method - (void)prepareLayout)
I might bet that these supplementary views's Z-order isn't predictable - all views have a default zIndex of 0. One way to fix it ? After line 67 of this file, set a negative zIndex on the UICollectionViewLayoutAttributes of the hour block. This way, you make sure hour supplementary views are always behind your cells, and don't intercept the cell's touch

UITableView rotation behaving strange after Xcode 6 and iOS 8 update

I am working on an app since a while, it is going well. However, this weekend I updated to Xcode 6 and now my app is behaving differently as opposed to before the update to Xcode 6.
I have a UITableView in my app which I rotate in viewDidLoad:
//Rotate playerCardsTable.
CGAffineTransform rotateTable = CGAffineTransformMakeRotation(-M_PI_2);
_playerCardsTable.transform = rotateTable;
_playerCardsTable.frame = CGRectMake(0, 0, _playerCardsTable.frame.size.width, _playerCardsTable.frame.size.height);
In Xcode before the update (Xcode 5) I had this view:
But now, after updating to Xcode 6, I have this view:
The tableview is rotated, ergo I have horizontal scrolling, but it seems like the frame is not changed correctly after rotation. It is 320 pixels high and 80 pixels wide and it should be the other way round. I don't know why, but it seems I cannot change the frame afterwards in code, in other words, I don't see any change after changing the width and height.
The tableview is added via the interface builder and holds custom cells:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
NSString *cardName = [[[[[Game game] localPlayer] playerCards] objectAtIndex:indexPath.row] fileNameCard];
cell.cardImage.image = [UIImage imageNamed:cardName];
Card *card;
card = [[[[Game game] localPlayer] playerCards] objectAtIndex:indexPath.row];
if(card.playable == IsPlayable){
cell.backgroundColor = isPlayableColor;}
else if (card.playable == IsNotPlayable) {
cell.backgroundColor = isNotPlayableColor;}
else if (card.playable == IsReallyPlayable) {
cell.backgroundColor = isReallyPlayableColor;}
//Rotate image to align with rotated tableview.
CGAffineTransform rotateImage = CGAffineTransformMakeRotation(M_PI/2);
cell.cardImage.transform = rotateImage;
cell.playableImage.transform = rotateImage;
cell.cardImage.layer.borderWidth = 2;
cell.cardImage.layer.borderColor = [UIColor clearColor].CGColor;
cell.cardImage.layer.shouldRasterize = YES;
cell.cardImage.layer.rasterizationScale = [[UIScreen mainScreen] scale];
cell.cardImage.layer.shadowColor = [UIColor blackColor].CGColor;
cell.cardImage.layer.shadowOffset = CGSizeMake(0, 1);
cell.cardImage.layer.shadowOpacity = 0.7;
cell.cardImage.layer.shadowRadius = 2.0;
cell.cardImage.clipsToBounds = NO;
return cell;}
To be clear; I did not change any code after the update, so the different behavior is caused by the update.
Hope you guys could help.
Thanks!
I ran into a very similar situation after upgrading iPad to ios8 just recently - Horizontal tables not appearing correctly.
I don't have a complete solution, but can tell you from research and testing that in my case it was due to the transform action and AutoLayout constraints on the UITableView not coordinating/cooperating. Evidently the way these two concepts work together changed somewhat in ios8.
A top constraint set in a storyboard becomes a left constraint after the transform (or right, never did figure it out properly).
Bottom line for me was that after trying a number of combinations of constraints, including removing them before transform, then adding constraints back in after the transform, I ended up converting from a UITableView to UICollectionView.

iOS - Problems dynamically resizing tableviewcell

I know that there are many potential answers to this question on here already but 99.995% of all the answers so far that I have seen use sizeWithFont, which is now deprecated.
I have one tableviewcell that I am dealing with here so it should be simple but things are not working out for me. here is the code that has been put together by reading some answers online. This is for a cell label.
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
NSString *myString = [self.locations[indexPath.row] locationDescription];
return [self heightForText:myString] + 44.0;
}
-(CGFloat)heightForText:(NSString *)text{
NSInteger MAX_HEIGHT = 10000;
UILabel *cellLabelView = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 320, MAX_HEIGHT)];
cellLabelView.text = text;
cellLabelView.font = [UIFont fontWithName:#"Helvetica Neue Light" size:18];
[cellLabelView sizeToFit];
return cellLabelView.frame.size.height;
}
The height defined in the storyboard is set to 44 and the lines is set to 0 for the label itself so that the label can decide how big it needs to be.
Problem #1: If the text that is set to the label doesn't word wrap I still (obviously) get the extra padding which is not what i want. I am not sure how to calculate if there was actually a word wrap or not. If there wasnt a word wrap (i.e the text wasnt longer than the label, then i just want to return 44).
Problem #2: For some reason if my text is too long, like 5 lines worth as an example, some of the text at the end of the string gets cut off for some reason, despite the MAXHEIGHT being 10,000, its almost as if it decides to stop word wrapping. If i increase the padding at this point, to lets say 88 then i can see everything. (Weird).
Looking for some elegant solutions, feedback, help, whatever. THanks!
NSString's sizeWithFont: has indeed been deprecated in iOS 7, but replaced by sizeWithAttributes:. For multiple-line text, boundingRectWithSize:options:attributes:context: is useful. See
iOS 7 sizeWithAttributes: replacement for sizeWithFont:constrainedToSize
for a relevant alternative to your UILabel method.
Problem #1: I'm not sure exactly what your desired effect is here. If you're looking to have a constant padding above and below your UILabel, but with a minimum cell height of 44 points, you might want to do something like:
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
NSString *myString = [self.locations[indexPath.row] locationDescription];
CGFloat myPadding = 20.0; // for example
return MAX([self heightForText:myString] + myPadding, 44.0);
}
However, if you'd like to base your cell height on the number of lines of text somehow, then dividing the calculated height by the UIFont's lineHeight property and rounding will work:
NSUInteger numberOfLines = roundf(calculatedTextHeight / font.lineHeight);
Problem #2: The label you're creating to calculate the height hasn't had its numberOfLines set to 0, so won't be sizing itself for a multiple-line label.
I think the NSString method is the best route to go down, but as an aside, if you'd like to use the UILabel approach, I'd recommend creating the label only once to help the UITableView scrolling performance e.g.
-(CGFloat)heightForText:(NSString *)text{
NSInteger MAX_HEIGHT = 10000;
static UILabel *cellLabelView = nil;
if (!cellLabelView)
{
cellLabelView = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 320, MAX_HEIGHT)];
cellLabelView.text = text;
cellLabelView.font = [UIFont fontWithName:#"Helvetica Neue Light" size:18];
cellLabelView.numberOfLines = 0;
}
return [cellLabelView sizeThatFits:CGSizeMake(320, MAX_HEIGHT)].height;
}

Set UILabel Align top is not working in the cell

I have a problem to implement the text vertical alignment inside a table cell.
What I want to do is based on the length of the text I want to display a message top aligned in side one UILabel inside a cell.
For example if the message is only one line
The text should align top:
And if there are two rows then it should look like this:
At the beginning what I can see is like this
So I have searched the web and what I found is to
use the
[label1 sizeToFit];
But the problem with that is within the table view cell it is not always necessarily called especially when I switched to and from another tab view.
Then I tried to generate the label on the fly by code, but the problem is that let alone the complicated process of setting up the font format I want. I have to manage whether the label has been inserted or not then reuse it every time cellForRowAtIndexpath is called.
And more weirdly, once I select the row. The alignment is switched from the one you see in the first picture to the third one. It also happens when I switched to a different tab view and switch back to the view.
I was wondering if anybody has encountered such issue and have a solution to the problem.
Thank you for your reply in advance.
Edit:
#βḧäṙℊặṿῗ, what you said I have tried. It successfully align the label text if there is only one line. My situation is that, since I have multiple tab views. Once I switch back and forth between tabs view. The alignment just restored to centre-vertical alignment again. It also happens when I selected the row. Any idea?
Try this
// label will use the number of lines as per content
[myLabel setNumberOfLines:0]; // VERY IMP
[myLabel sizeToFit];
EDIT:
As you have one extra condition that maximumly display two lines then you need to set setNumberOfLines: to 2
[myLabel setNumberOfLines:2];
Create UILabel+Extras and add following methods to this class.
- (void)alignTop{
CGSize fontSize = [self.text sizeWithAttributes:#{NSFontAttributeName:self.font}];
double finalHeight = fontSize.height * self.numberOfLines;
double finalWidth = self.frame.size.width; //expected width of label
CGRect rect = [self.text boundingRectWithSize:CGSizeMake(finalWidth, finalHeight) options:NSStringDrawingTruncatesLastVisibleLine attributes:#{NSFontAttributeName:self.font} context:nil];
CGSize theStringSize = rect.size;
int newLinesToPad = (finalHeight - theStringSize.height) / fontSize.height;
for(int i=0; i< newLinesToPad; i++)
self.text = [self.text stringByAppendingString:#" \n"];
}
Call this method like this..
[YOUR_LABEL alignTop];
Either You set no of Lines to be 0 like
[yourLabelObject setNumberOfLines:0];
[yourLabelObject sizeToFit];
Or
You can find the height of label at run time depending upon textString length.
Following method will return you the size(height & width) of label for length of text string.
here width is fixed and only height will change :
- (CGSize) calculateLabelHeightWith:(CGFloat)width text:(NSString*)textString andFont:(UIFont *)txtFont
{
CGSize maximumSize = CGSizeMake(width, 9999);
CGSize size = [textString sizeWithFont:txtFont
constrainedToSize:maximumSize
lineBreakMode:UILineBreakModeWordWrap];
return size;
}
Yo need to calculate frame of label each time when u r doing to set text and set frame of label
I hope this will helps you.
Might sound a bit silly but this is my approach: For any field that needs to be top aligned I fill the text up with multiple "\n"s. This causes the text to be automatically top aligned. Pretty much the same as Mehul's method above.
http://i.stack.imgur.com/8u5q4.png

ios create 'readmore'. Let superview be dependent of subview

gaaah designing in ios gives me headache!!
So please, help me out and maby explain to me how one should think trying to come up with the solution.
I have:
As you can see the UITextField has its frame being set in storyboard to be smaller than its actual content.
Everything above the prototypecell is within a UIView that is set to be tableHeader programatically.
I want to:
Press the read more btn so that the UITextField get its actual size. No problem, I can do that with getting the contentSize programmatically. It works but it ofc overflows the tableHeader
So I thought, good. Then all I have to do is set the tableHeader to be the size of the 'new' calculated height of UITextField + height of the 2 UIImageViews.
But nope. it only resizes to the existing height set in storyboard insted. In other word, it does one or the other.
And using autolayout it totally breaks but not giving me any errors about constraints.
This seems so easy wich makes me feel so stupid haha
this is what I have i code
- (IBAction)toggleReadMore:(id)sender{
_toggleReadMoreBtn.hidden = YES;
CGRect textFrame = _cityDescription.frame;
_cityDescription.frame = textFrame;
CGRect tableHeaderViewFrame = CGRectMake(0, 0, self.tableView.tableHeaderView.frame.size.width, _cityDescription.contentSize.height + 218.0f ); //textFrame.size.height
self.tableView.tableHeaderView.frame = tableHeaderViewFrame;
textFrame.size.height = _cityDescription.contentSize.height;
[self.tableView setTableHeaderView:self.viewForTableHeader];
please, guide me how to think
- (IBAction)readMoreBtnClicked:(UIButton *)sender
{
NSLog(#"Read more Btn Clicked");
NSString *stringToBeDisplayed = #"Any Text Here";
CGSize textSize=[stringToBeDisplayed sizeWithFont:[UIFont systemFontOfSize:30]
constrainedToSize:CGSizeMake(270, 500)
lineBreakMode:NSLineBreakByWordWrapping];
NSLog(#"textSize = %#",NSStringFromCGSize(textSize));
[self.textView setFrame:CGRectMake(self.textView.frame.origin.x,self.textView.frame.origin.y, textSize.width, textSize.height)];
NSLog(#"self.textView.frame = %#",NSStringFromCGRect(self.textView.frame));
[self.textView setText:stringToBeDisplayed];
[self.headerView setFrame:CGRectMake(self.headerView.frame.origin.x,self.headerView.frame.origin.y, 320, dynamicHeightCalculatedAfterTextSize)];
[self.tableView reloadData];
}

Resources