What is the best way to implement a variable-sized multi-line UITableCell? - cocoa-touch

I'm trying to display a table full of twitter statuses (yes, this is the Stanford Presence 2 assignment), which are variably sized. I can relatively easily determine the appropriate height for my rows with code that approximates (from accompanying lecture 9):
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *) indexPath
{
NSString *text = ...;
UIFont *font = [UIFont systemFontOfSize:...];
CGSize withinSize = CGSizeMake(tableView.width, 1000];
CGSize size = [text sizeWithFont:font constrainedToSize:withinSize lineBreakMode:UILineBreakModeWordWrap];
return size.height + somePadding;
}
I have tried two approaches (and some tweaks to both) to get a multi-line word-wrapping field of text into my table row.
Add a UILabel as a subview to my custom UITableCell subclass, and set the numberOfLines property to either a calculated number based on the height above (say, 6), or to 0 (theoretically unlimited). The numberOfLines is ignored; I see either 1 or 2 lines, and no more.
Add a read-only UITextView as a subview. This has the problem that the UITextView eats my scrolling; I end up scrolling inside a UITextView row instead of moving smoothly from row to row. If I disable scrolling on the UITextView, I end up being unable to scroll at all.
This is a pretty common thing to do; what's the best way to accomplish it?

You might want to look at the userInteractionEnabled property of the UITextView. That should allow input to be passed through to the UITableView so you get scrolling.

Here's a link to a blog I posted on this subject. I used a UILabel with numberOfLInes = 0. I hope this will be of some help.
Sample Project with Variable Sized UITableViewCell

While playing with userInteractionEnabled=NO, scrollEnabled=NO, and getting the right autosizing parameters set in IB worked, I think that going with a UILabel with numberOfLines=0 and the same autosizing parameters is ultimately a better idea, for the next person.

Related

UITableViewCell UILabel with attributed text seems to have fixed font (iOS 7 / 8)

I'm having enough trouble with UILabel attributedText that I am now considering creating a UIView subclass that renders its attributed string manually in drawRect.
The gist of the problem I'm having is that UILabels defined in storyboard files always seem to render size 17.0 font at runtime. I've tried a number of hacks to fix this issue, but nothing has worked. No matter what I do, the label's font size remains at 17.0.
The first thing I tried, which I considered to be the most natural solution, was to modify the font size directly in the storyboard. The in-storyboard preview of the label content updated appropriately, but at runtime the font had reset to size 17.0.
The next thing I tried was to adjust the font size at runtime. Some interesting results were observed here: (a) when querying the font size, the label's attributedText property was reporting the correct font size, but it was still rendered as the default size 17.0. Setting the attributed text to something else had no effect. Furthermore, changing the attributed text font was unsuccessful (I tried a number of different things because I wasn't sure if it was just the size that was failing to set, but apparently everything under NSFontAttributeName was immutable.
I also tried replacing the UILabel with a UITextView with user interaction disabled etc, because I thought this might be a bug specific to labels. Once again the text view's attributed text proved to be immutable at runtime, at least from a rendering perspective.
I've had a number of suspicions about what might be causing this issue. I'm curious if it is related to the fact that the label view is within a UITableViewCell subclass, but I don't have time to run a number of experiments. I typically haven't made much use of UILabels with attributed text, but I haven't found on here or elsewhere on the internet evidence of known issues.
I've looked through all the APIs to the best of my abilities, tried disabling certain word wrap settings, and tried setting the Plain label's font size to 14.0 before marking it as attributed because I thought it might be possible the higher-level attributes could be overriding the lower-level attributed text settings. Once again, this approach was not successful.
Has anyone else experienced anything like this?
In the meantime, I will proceed towards implementing my own attributed label as a UIView subclass. If this ends up being the only viable solution for the time being, I'll post the code.
I don't know how you're setting the text in your label, but if you set it with an attributed string that has its font attribute set to what you want, it will work.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
RDCell *cell = [tableView dequeueReusableCellWithIdentifier:#"Cell" forIndexPath:indexPath];
NSString *plain = self.theData[indexPath.row];
cell.label.attributedText = [[NSAttributedString alloc] initWithString:plain attributes:#{NSFontAttributeName:[UIFont fontWithName:#"Snell Roundhand" size:24]}];
return cell;
}
I saw the same problems you did when I tried to setup the label in the storyboard to use attributed text.
As discussed, here is the solution I ended up using. It is not as versatile as UILabel in all respects (doesn't support scaling down of fonts etc.) but it turned out to be suitable for my needs.
#interface DHAttributedLabel : UIView
#property (nonatomic, strong) NSAttributedString * attributedText;
#end
#implementation DHAttributedLabel
-(id) initWithCoder:(NSCoder *)aDecoder {
self = [super initWithCoder:aDecoder];
if (self) {
[self setBackgroundColor:[UIColor clearColor]];
}
return self;
}
-(void) drawRect:(CGRect)rect {
CGRect boundingRect = [_attributedText
boundingRectWithSize : CGSizeMake(rect.size.width, CGFLOAT_MAX)
options : NSStringDrawingUsesLineFragmentOrigin
context : nil
];
CGRect drawInRect = rect;
CGFloat dy = .5 * (rect.size.height - boundingRect.size.height);
if (dy > 0) { // center vertically if appropriate
drawInRect.origin.y += dy; // (truncate bottom if the text is too big)
}
[_attributedText drawInRect:drawInRect];
}
#end
I have resolved the same issue by setting attributedText in the UITableViewDataSource cellForRowAtIndexPath: method. Seems that if attributedText property is set via IBOutlet before cell is returned by delegate, some of the attributedText properties are lost (like font size for attributed substring) while others are preserved (like text color for attributed substring).
Seems to me some kind of UIKit issue.
In my case it worked when setting attributedText in tableView(_:willDisplay:forRowAt:).
Attributed Text having HTML content, Means they having their own font specified in their content.
For increasing size in Label attributed Text you need to override the html Font via CSS.
Here is the Content which you get I.e HTMl Content.
<html><body><font face='Helvetica' size='3'>There is no description.</html></body>
As you can see, they have given size in HTML content You need to add extra property to override that font like this.
<style>
html * {font-size: 25.0pt !important;}
</style>
<html><body><font face='Helvetica' size='13'>There is no description.</html></body>
By adding this CSS, try to get attributed String, Set in the Label.
Hope this will increase the font size to 25.0.

How to automatically set row height in NSOutlineView to fit text?

TLDR: I want to implement the method of this question (or any other relevant method) on a NSOutlineView.
A NSOutlineView is more complicated than a NSTableView because some cells may be indented with respect to others, due to parent/child relationships.
I am open to using any method, including AutoLayout if that is possible. My views are simple NSTableCellView instances with only a NSTextField.
I need to be able to set the height of a row in a view-based NSOutlineView depending on the text in that row's NSTextField.
I am currently using the following (simplified) code, which makes use of NSString+Geometics to calculate the approximate height of the view given its width:
- (CGFloat)outlineView:(NSOutlineView *)outlineView
heightOfRowByItem:(id)item
{
static NSTableCellView *cell;
if (!cell) {
cell = [self.outlineView makeViewWithIdentifier:#"Cell"
owner:self];
}
NSString *text = [item text];
float width = cell.textField.bounds.size.width;
#warning this is completely wrong due to different levels of indentation
float height = [text heightForWidth:width];
return height;
}
The idea behind this code is to keep a scratch cell around in order to make use of its default bounds property. This works correctly for the top level row.
However, any 'child' (nested) rows in the NSOutlineView are indented towards the right. To calculate the required height of the NSTextField correctly, I need to know the width of the NSTextField after this indentation.
How can I obtain this information?
I should note that it is not possible to call
[outlineView viewAtColumn:0
row:[outlineView rowForItem:item]
makeIfNecessary:YES]
from within outlineView:heightOfRowByItem:, which means that you can't get a reference to the current cell.
Use -[NSOutlineView indentationPerLevel] to calculate reduced child width.

UILabel render text incorrectly in IOS7

I use following code to calculate the bound of a UILabel
CGRect bound = [lblName.text boundingRectWithSize:(CGSize){206, 99999}
options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading
attributes:stringAttributes
context:nil];
The UILabel is a embedded in a UIScrollView, which is a subview of UITableViewCell.
here what i got
I made a test which use a UILabel in a table cell, and a UILabel in UIScrollView separately, and results are as I expected
Note that all setting (font, line break mode etc) of UILabel are the same in all those case. The boundingRectWithSize returns same result in all those case, only difference is the way UILabel render the text.
What is the problem here? did i miss sometthing?
UPDATE: this happen only when i load UILabel from nib, if it is created programmatically, there is no problem. (my project is migrated from xcode 4 to xcode 5)
Try this:
bound.size.height += 1;
UPDATE:
According to Apple's document
- (CGRect)boundingRectWithSize:(CGSize)size options:(NSStringDrawingOptions)options attributes:(NSDictionary *)attributes context:(NSStringDrawingContext *)context
This method returns fractional sizes (in the size component of the returned CGRect); to use a returned size to size views, you must use raise its value to the nearest higher integer using the ceil function.
So you might want to use this approach:
bound.size.height = ceil(bound.size.height);
I was seeing the same behavior with some of my labels, which looked fine in iOS 6, but in iOS 7 they had extra padding at the top and bottom as in your pictures.
Here's what I had to do to finally get it to layout correctly in viewDidLoad - works on both iOS 6 and 7.
self.someLabel.autoresizingMask = UIViewAutoresizingNone;
self.someLabel.frame = CGRectMake(
self.someLabel.frame.origin.x,
self.someLabel.frame.origin.y,
labelWidth, // define elsewhere if you're targeting different screen widths
self.someLabel.bounds.size.height);
[self.someLabel sizeToFit];

Objective-C: Evenly displaying NSStrings

If I am looping through a bunch of strings and want to say use them as the stringValue of a NSTextField or title of a NSButton programmatically is there a way to determine the length I will need for the frame of the textfield or buttons and the spacing between...I know this is kind of relevant to the font selected for each but it would be great if I could dynamically figure out NSString.length = x pixels. Any thoughts?
Look into the sizeWithFont method on NSString.
CGSize size = [mystring sizeWithFont:myfont];
CGSize has a height and width that you can then examine.

How to make height of OHAttributedLabel scale with content height?

I use an OHAttributedLabel called demoLbl for displaying text with formatted areas. This label is laid out with Interface Builder and is connected to a property in my ViewController. After setting the attributedText to the label I want all the text to be displayed in the label.
If I don't resize the label then the text is cropped at the end of the label so the rest of the text is missing.
If I use [demoLbl sizeToFit]; then the height of the label is larger or smaller in height than the text (about 10 point, varying with the text's length) thus giving me blank areas at the bottom of my view (after scrolling) plus the width of the label is increased by about 2 points.
If I calculate the height of the original text (NSString) before putting it in a NSAttributedString and adding it to the label's attributedText property then the calculated height is way too small for setting it as the label's height.
Is there a hack or trick I can apply so that the label's height is adjusted according to the NSAttributedString's height?
PS: To be more specific I wanted to add OHAttributedLabel as a tag but it's not allowed to me yet.
I'm the author of OHattributedLabel.
I made some fixes recently about my computation of the size. Please check it out it will probably solve your issue.
I also added a method named sizeConstrainedToSize:fitRange: in NSAttributedString+Attributes.h that returns the CGSize of a given NSAttributedString (quite the same way UIKit's sizeWithFont:constrainedToSize: works, but for Attributed strings and CoreText and not plain stings an UIKit)
Actually OHAttributedLabel's sizeThatFits: calls this method itself now.
You can see if this category gives you a more reliable height.
https://gist.github.com/1071565
Usage
attrLabel.frame.size.height = [attrLabel.attributedString boundingHeightForWidth:attrLabel.frame.size.width];
I added this code to the implementation of the OHAttributedLabel class:
// Toni Soler - 02/09/2011
// Overridden of the UILabel::sizeToFit method
- (void)sizeToFit
{
// Do not call the standard method of the UILabel class, this resizes the frame incorrectly
//[super sizeToFit];
CGSize constraint = CGSizeMake(self.frame.size.width, 20000.0f);
CGRect frame = self.frame;
frame.size = [self sizeThatFits:constraint];
[self setFrame:frame];
}
// End Toni Soler - 02/09/2011
Thank you Olivier for sharing your code!