iOS - Problems dynamically resizing tableviewcell - ios

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

Related

Table View Cell AutoLayout in iOS8

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.

How can I make a UITextView layout text the same as a UILabel?

I have a UILabel that I need to convert to a UITextView because reasons. When I do this, the text is not positioned the same, despite using the same (custom) font.
I found that if I set:
textView.textContainer.lineFragmentPadding = 0;
textView.textContainerInset = UIEdgeInsetsZero;
This gets the text very close, but if I superimpose the UITextView over top of the UILabel, I see the text positioning get farther apart with each new line.
The UILabel is green, the UITextView is black. This is using NSParagraphStyle to set min and max line height to 15.
I've played with setting the paragraph style and min/max line height, but I haven't been able to match it exactly. I'm not a printer, so I don't necessarily understand all of the font related terms in the documentation for NSLayoutManager and NSTextContainer and all that.
I only need to support iOS 7 and up.
I'm not going to switch to some crazy CoreText-based custom widget or use some random third party library. I'm okay with close enough if I have to. But it seems like there should be some combination of random properties to make them layout the same.
I took the solution for line spacing found at this link and applied it to your issue. I managed to get it incredibly close by adjusting the lineSpacing property. I tested with HelveticaNeue size 13 and managed to get it to line up as shown in the screen shot below.
textView.textContainer.lineFragmentPadding = 0;
textView.textContainerInset = UIEdgeInsetsZero;
NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc] init];
paragraphStyle.lineSpacing = -0.38;
NSDictionary *attrsDictionary =
#{ NSFontAttributeName: [UIFont fontWithName:#"HelveticaNeue" size:13.0f],
NSParagraphStyleAttributeName: paragraphStyle};
textView.attributedText = [[NSAttributedString alloc] initWithString:textView.text attributes:attrsDictionary];
I've been able to successfully 'impersonate' a non-editable multiline UILabel (as it happens, in a UITableViewCell subclass) with an equivalent editable multiline UITextView using the following :
_textView = UITextView.new;
_textView.font = _label.font;
_textView.textColor = _label.textColor;
_textView.textAlignment = _label.textAlignment;
_textView.backgroundColor = UIColor.clearColor;
_textView.textContainer.lineFragmentPadding = 0;
_textView.textContainerInset = UIEdgeInsetsZero;
and to make it behave well when doing actual edits, add the following to your UITextViewDelegate:
- (void)textViewDidChange:(UITextView *)textView
{
...
[textView scrollRangeToVisible:NSMakeRange(textView.text.length, 0)];
[textView scrollRectToVisible:[textView caretRectForPosition:textView.endOfDocument] animated:NO];
}

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

UILabel not drawing multiline

I want the label below (in yellow) to be at least two lines rather one.
I've made sure to uncheck Use Autolayout in Interface Builder. When I set the numberOfLines from 0 to 2, I get two words stacked on top of each other, with the yellow background tightly fitting the words. The result is the same regardless of whether the lineBreakMode is NSLineBreakByWordWrapping or NSLineBreakByTruncatingTail. It's also the same if I set the frame of the terms Label using the result of sizeWithAttributes or not, and it's the same if I use sizeToFit or not. I've also tried making the label a UILabel rather than a subclass of UILabel, which is TTTAttributedLabel, but the result is the same.
_termsLabel.font = [UIFont systemFontOfSize:12];
_termsLabel.textColor = [UIColor grayColor];
_termsLabel.textAlignment = NSTextAlignmentCenter;
_termsLabel.lineBreakMode = NSLineBreakByWordWrapping;
_termsLabel.numberOfLines = 0;
_termsLabel.delegate = self;
_termsLabel.backgroundColor = [UIColor yellowColor];
// Terms label
NSString *termsText = [NSString stringWithFormat:#"%# %# %# %#", NSLocalizedString(#"TermsIAgree", nil),
NSLocalizedString(#"SettingsTOS", nil),
NSLocalizedString(#"LocalizedAnd", nil),
NSLocalizedString(#"SettingsPrivacyPolicy", nil)];
_termsLabel.text = termsText;
_termsLabel.linkAttributes = #{ (__bridge NSString *)kCTUnderlineStyleAttributeName : [NSNumber numberWithBool:YES]};
CGSize termsSize = [_termsLabel.text sizeWithAttributes: #{ NSFontAttributeName : _termsLabel.font}];
_termsLabel.frame = CGRectMake(65,
395,
termsSize.width, termsSize.height);
[_termsLabel addLinkToURL:[NSURL URLWithString:TOS_URL] withRange:[termsText rangeOfString:NSLocalizedString(#"SettingsTOS", nil)]];
[_termsLabel addLinkToURL:[NSURL URLWithString:PRIVACY_POLICY_URL] withRange:[termsText rangeOfString:NSLocalizedString(#"SettingsPrivacyPolicy", nil)]];
EDIT: By finding the terms text size using CGSize termsSize = [_termsLabel.text sizeWithFont:_termsLabel.font forWidth:200 lineBreakMode:NSLineBreakByWordWrapping];
Yet the height of the termsSize is then 14, resulting in just one line:
How can I get the second line? SOLUTION At this point, just add [_termsLabel sizeToFit].
If you've got static text, just set the break mode to wrap, set lines to the number you want, and adjust the label's frame in interface builder until it wraps the way you like. Of you've got dynamic text, you can use sizeToFit after setting the label's text to have it automatically adjust it's height to fit the specified width:
Set frame to max desired width
Set lines to 0
Set break mode to wrap
Call sizeToFit
Determine the maximum width of your label and try sizeWithFont:forWidth:lineBreakMode: method with this value and desired NSLineBreakMode to get the size of resulting string's bounding box.

my chat system looks a little wierd, cant get dynamic height for cell

Ive got a chat system in my app, and im attempting to make dynamic cells to have dynamic height according to how much text is in the cell, pretty common thing people try to do, however i cant get to get mine working properly.
Also the messages align to the right, the sender is supposed to be on the left and the reciever should be on the right... heres what i have done with the storyboard.
created a TableView with 2 dynamic prototypes, inside a UIViewControllerhere is the viewController for that... each cell has a label, one left one right, the whole right and left thing work... heres my issue. Its only pulling to the right for all, so basically my if isnt happening and my else is overruling. Heres a SS.
So i have two issues... Text wont have multiple lines... along with wont do dynamic height, also... if someone can point me i the right dirrection for getting sender and reciever to show on different sides.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSArray *myWords = [[getMessage objectAtIndex:indexPath.row] componentsSeparatedByString:#":oyr4:"];
if (myWords[1] == [MyClass str]){
static NSString *sender = #"sender";
UITableViewCell* cellSender = [_tableView dequeueReusableCellWithIdentifier:sender];
messageContentTo = (UILabel *)[cellSender viewWithTag:83];
self->messageContentTo.backgroundColor = [UIColor colorWithWhite:1.0 alpha:0.8];
self->messageContentTo.lineBreakMode = NSLineBreakByWordWrapping;
[self->messageContentTo sizeToFit];
messageContentTo.text = myWords[4];
return cellSender;
} else {
static NSString *reciever = #"reciever";
UITableViewCell* cellReciever = [_tableView dequeueReusableCellWithIdentifier:reciever];
messageContentFrom = (UILabel *)[cellReciever viewWithTag:84];
messageContentFrom.backgroundColor = [UIColor colorWithWhite:1.0 alpha:0.8];
messageContentFrom.lineBreakMode = NSLineBreakByWordWrapping;
messageContentFrom.font = [UIFont systemFontOfSize:22];
messageContentFrom.numberOfLines = 0;
messageContentFrom.text = myWords[4];
return cellReciever;
}
}
#pragma mark - UITableViewDelegate methods
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
CGSize size = [[getMessage objectAtIndex:indexPath.row]
sizeWithFont:[UIFont systemFontOfSize:22]
constrainedToSize:CGSizeMake(1000, CGFLOAT_MAX)];
return size.height + 15;
}
The left-right problem might be due to this:
if (myWords[1] == [MyClass str])
If myWords[1] is a string, you need to use isEqualToString: not "==" to compare it.
if ([myWords[1] isEqualToString:[MyClass str]])
As far as the label height not adjusting properly, it's hard to tell what's going on without knowing how your labels are set up. I usually do it by making constraints between the label and the top and bottom of the cell in IB. That way, when you change the height of the cell, the label will follow (and of course, set numberOfLines to 0). Also, in your sizeWithFont:constrainedToSize: method, the width you pass into CGSizeMake() should be the width of the label, not 1000.

Resources