Resizing table cells dynamically in iOS application - ios

I am trying to make a Twitter client with XCode 4.2. (iOS version 5.) I want my application's main timeline to look similar to the Twitter iOS app's timeline:
I am using a UITableView with a prototype cell containing a label and three buttons. Below is the code I'm using to set the height:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString* cellIdentifier = #"TweetContainerCell";
UITableViewCell* cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
#try {
UILabel* tweetLabel;
if(cell != nil) {
tweetLabel = (UILabel*)[cell.contentView viewWithTag:1];
NSString* tweetText = tweetLabel.text;
CGSize expectedLabelSize = [tweetText sizeWithFont:[UIFont fontWithName:tweetLabel.font.fontName size:20] constrainedToSize:CGSizeMake(CGFLOAT_MIN, CGFLOAT_MAX) lineBreakMode:UILineBreakModeWordWrap];
//[tweetLabel setFrame:CGRectMake(tweetLabel.frame.origin.x, tweetLabel.frame.origin.y, cell.frame.size.width, expectedLabelSize.height)];
//[cell.textLabel setFrame:tweetLabel.bounds];
//[tweetLabel sizeToFit];
//[cell setFrame:CGRectMake(cell.frame.origin.x, cell.frame.origin.y, cell.frame.size.width, expectedLabelSize.height)];
//[cell sizeToFit];
NSLog(#"Font size: %f", expectedLabelSize.height);
return (expectedLabelSize.height * 2);
}
}
/* Imgur URL: http://i.imgur.com/lHnsAsP.png */
/* http://i.imgur.com/hA9EKfI.png */
#catch (NSException* exception) {
NSLog(#"Exception: %#", exception);
}
return 0;
}
However, this is what my app ends up looking like:
The problems are:
1) Each cell seems to have the same height as the tallest cell in the whole table, instead of having different heights.
2) Because of this, the space between the cell's top border and the text is different for each cell (because iOS centers the text vertically).
I am learning iOS development and being unable to do such a simple thing, even after doing a lot of research and spending a lot of hours, seems really discouraging. Any help is greatly appreciated.
(In case the information I have given is not enough, here's the ZIP file containing the whole project: https://db.tt/m5suxWCj)

Your problem is that your label has not been created, since tableView:heightForRowAtIndexPath: is initially called before tableView:cellForRowAtIndexPath:, which is where your cell is created. In tableView:heightForRowAtIndexPath: you should determine the height of the cell as efficiently as possible, without involving UIViews.
To achieve this, you should store the NSString elsewhere in your table view data source, and calculate expectedLabelSize based on that.
Note also that sizeWithFont: is deprecated in IOS 7, so for IOS 7 and beyond you should use sizeWithAttributes: instead.

Related

AutoLayout row height miscalculating for NSAttributedString

My app pulls HTML from an API, converts it into a NSAttributedString (in order to allow for tappable links) and writes it to a row in an AutoLayout table. Trouble is, any time I invoke this type of cell, the height is miscalculated and the content is cut off. I have tried different implementations of row height calculations, none of which work correctly.
How can I accurately, and dynamically, calculate the height of one of these rows, while still maintaining the ability to tap HTML links?
Example of undesired behavior
My code is below.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
switch(indexPath.section) {
...
case kContent:
{
FlexibleTextViewTableViewCell* cell = (FlexibleTextViewTableViewCell*)[TableFactory getCellForIdentifier:#"content" cellClass:FlexibleTextViewTableViewCell.class forTable:tableView withStyle:UITableViewCellStyleDefault];
[self configureContentCellForIndexPath:cell atIndexPath:indexPath];
[cell.contentView setNeedsLayout];
[cell.contentView layoutIfNeeded];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
cell.desc.font = [UIFont fontWithName:[StringFactory defaultFontType] size:14.0f];
return cell;
}
...
default:
return nil;
}
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
UIFont *contentFont = [UIFont fontWithName:[StringFactory defaultFontType] size:14.0f];
switch(indexPath.section) {
...
case kContent:
return [self textViewHeightForAttributedText:[self convertHTMLtoAttributedString:myHTMLString] andFont:contentFont andWidth:self.tappableCell.width];
break;
...
default:
return 0.0f;
}
}
-(NSAttributedString*) convertHTMLtoAttributedString: (NSString *) html {
return [[NSAttributedString alloc] initWithData:[html dataUsingEncoding:NSUTF8StringEncoding]
options:#{NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType,
NSCharacterEncodingDocumentAttribute: #(NSUTF8StringEncoding)}
documentAttributes:nil
error:nil];
}
- (CGFloat)textViewHeightForAttributedText:(NSAttributedString*)text andFont:(UIFont *)font andWidth:(CGFloat)width {
NSMutableAttributedString *mutableText = [[NSMutableAttributedString alloc] initWithAttributedString:text];
[mutableText addAttribute:NSFontAttributeName value:font range:NSMakeRange(0, text.length)];
UITextView *calculationView = [[UITextView alloc] init];
[calculationView setAttributedText:mutableText];
CGSize size = [self text:mutableText.string sizeWithFont:font constrainedToSize:CGSizeMake(width,FLT_MAX)];
CGSize sizeThatFits = [calculationView sizeThatFits:CGSizeMake(width, FLT_MAX)];
return sizeThatFits.height;
}
In the app I'm working on, the app pulls terrible HTML strings from a lousy API written by other people and converts HTML strings to NSAttributedString objects. I have no choice but to use this lousy API. Very sad. Anyone who has to parse terrible HTML string knows my pain. I use Text Kit. Here is how:
parse html string to get DOM object. I use libxml with a light wrapper, hpple. This combination is super fast and easy to use. Strongly recommended.
traverse the DOM object recursively to construct NSAttributedString object, use custom attribute to mark links, use NSTextAttachment to mark images. I call it rich text.
create or reuse primary Text Kit objects. i.e. NSLayoutManager, NSTextStorage, NSTextContainer. Hook them up after allocation.
layout process
Pass the rich text constructed in step 2 to the NSTextStorage object in step 3. with [NSTextStorage setAttributedString:]
use method [NSLayoutManager ensureLayoutForTextContainer:] to force layout to happen
calculate the frame needed to draw the rich text with method [NSLayoutManager usedRectForTextContainer:]. Add padding or margin if needed.
rendering process
return the height calculated in step 5 in [tableView: heightForRowAtIndexPath:]
draw the rich text in step 2 with [NSLayoutManager drawGlyphsForGlyphRange:atPoint:]. I use off-screen drawing technique here so the result is an UIImage object.
use an UIImageView to render the final result image. Or pass the result image object to the contents property of layer property of contentView property of UITableViewCell object in [tableView:cellForRowAtIndexPath:].
event handling
capture touch event. I use a tap gesture recognizer attached with the table view.
get the location of touch event. Use this location to check if user tapped a link or an image with [NSLayoutManager glyphIndexForPoint:inTextContainer:fractionOfDistanceThroughGlyph] and [NSAttributedString attribute:atIndex:effectiveRange:].
Event handling code snippet:
CGPoint location = [tap locationInView:self.tableView];
// tap is a tap gesture recognizer
NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint:location];
if (!indexPath) {
return;
}
CustomDataModel *post = [self getPostWithIndexPath:indexPath];
// CustomDataModel is a subclass of NSObject class.
UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
location = [tap locationInView:cell.contentView];
// the rich text is drawn into a bitmap context and rendered with
// cell.contentView.layer.contents
// The `Text Kit` objects can be accessed with the model object.
NSUInteger index = [post.layoutManager
glyphIndexForPoint:location
inTextContainer:post.textContainer
fractionOfDistanceThroughGlyph:NULL];
CustomLinkAttribute *link = [post.content.richText
attribute:CustomLinkAttributeName
atIndex:index
effectiveRange:NULL];
// CustomLinkAttributeName is a string constant defined in other file
// CustomLinkAttribute is a subclass of NSObject class. The instance of
// this class contains information of a link
if (link) {
// handle tap on link
}
// same technique can be used to handle tap on image
This approach is much faster and more customizable than [NSAttributedString initWithData:options:documentAttributes:error:] when rendering same html string. Even without profiling I can tell the Text Kit approach is faster. It's very fast and satisfying even though I have to parse html and construct attributed string myself. The NSDocumentTypeDocumentAttribute approach is too slow thus is not acceptable. With Text Kit, I can also create complex layout like text block with variable indentation, border, any-depth nested text block, etc. But it does need to write more code to construct NSAttributedString and to control layout process. I don't know how to calculate the bounding rect of an attributed string created with NSDocumentTypeDocumentAttribute. I believe attributed strings created with NSDocumentTypeDocumentAttribute are handled by Web Kit instead of Text Kit. Thus is not meant for variable height table view cells.
EDIT:
If you must use NSDocumentTypeDocumentAttribute, I think you have to figure out how the layout process happens. Maybe you can set some breakpoints to see what object is responsible for layout process. Then maybe you can query that object or use another approach to simulate the layout process to get the layout information. Some people use an ad-hoc cell or a UITextView object to calculate height which I think is not a good solution. Because in this way, the app has to layout the same chunk of text at least twice. Whether you know or not, somewhere in your app, some object has to layout the text just so you can get information of layout like bounding rect. Since you mentioned NSAttributedString class, the best solution is Text Kit after iOS 7. Or Core Text if your app is targeted on earlier iOS version.
I strongly recommend Text Kit because in this way, for every html string pulled from API, the layout process only happens once and layout information like bounding rect and positions of every glyph are cached by NSLayoutManager object. As long as the Text Kit objects are kept, you can always reuse them. This is extremely efficient when using table view to render arbitrary length text because text are laid out only once and drawn every time a cell is needed to display. I also recommend use Text Kit without UITextView as the official apple docs suggested. Because one must cache every UITextView if he wants to reuse the Text Kit objects attached with that UITextView. Attach Text Kit objects to model objects like I do and only update NSTextStorage and force NSLayoutManager to layout when a new html string is pulled from API. If the number of rows of table view is fixed, one can also use a fixed list of placeholder model objects to avoid repeat allocation and configuration. And because drawRect: causes Core Animation to create useless backing bitmap which must be avoided, do not use UIView and drawRect:. Either use CALayer drawing technique or draw text into a bitmap context. I use the latter approach because that can be done in a background thread with GCD, thus the main thread is free to respond to user's operation. The result in my app is really satisfying, it's fast, the typesetting is nice, the scrolling of table view is very smooth (60 fps) since all the drawing process are done in background threads with GCD. Every app needs to draw some text with table view should use Text Kit.
You need to update intrinsic content size.
I assume that you set attributed text to label in this code [self configureContentCellForIndexPath:cell atIndexPath:indexPath];
So, it should look like this
cell.youLabel.attributedText = NSAttributedString(...)
cell.youLabel.invalidateIntrinsicContentSize()
cell.youLabel.layoutIfNeeded()
You height calculation code (CGFloat)textViewHeightForAttributedText:(NSAttributedString*)text andFont:(UIFont *)font andWidth:(CGFloat)width should be replaced with cell height calculation using prototyping cell.
I'm assuming you are using a UILabel to display the string?
If you are, I have had countless issues with multiline labels with autoLayout. I provided an answer here
Table View Cell AutoLayout in iOS8
which also references another answer of mine that has a breakdown of how i've solved all my issues. Similar issues have cropped up again in iOS 8 that require a similar fix in a different area.
All comes down to the idea of setting the UILabel's preferredMaxLayoutWidth every time is bounds change. What also helped is setting the cells width to be the width of the tableview before running:
CGSize size = [cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];
I ran into a very similar issue on another project where fields using NSAttributedString weren't rendering with the correct height. Unfortunately, there are two bugs with it that made us completely drop using it in our project.
The first is a bug that you've noticed here, where some HTML will cause an incorrect size calculation. This is usually from the space between the p tags. Injecting CSS sort of solved the issue, but we had no control over the incoming format. This behaves differently between iOS7 and iOS8 where it's wrong on one and right on the other.
The second (and more serious) bug is that NSAttributedString is absurdly slow in iOS 8. I outlined it here: NSAttributedString performance is worse under iOS 8
Rather than making a bunch of hacks to have everything perform as we wanted, the suggestion of using https://github.com/Cocoanetics/DTCoreText worked out really well for the project.
If you can target iOS 8 using dynamic cell sizing is the ideal solution to your problem.
To use dynamic cell sizing, delete heightForRowAtIndexPath: and set self.tableView.rowHeight to UITableViewAutomaticDimension.
Here is a video with more details:
https://developer.apple.com/videos/wwdc/2014/?include=226#226
You can replace this method to calculate the height of attributed string:
- (CGFloat)textViewHeightForAttributedText:(NSAttributedString*)text andFont:(UIFont *)font andWidth:(CGFloat)width {
CGFloat result = font.pointSize + 4;
if (text)
result = (ceilf(CGRectGetHeight([text boundingRectWithSize:CGSizeMake(width, CGFLOAT_MAX) options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading context:nil])) + 1);
return result;
}
Maybe the font you changed doesnt matches with the font of content on html pages. So, use this method to create attributed string with appropriate font:
// HTML -> NSAttributedString
-(NSAttributedString*) convertHTMLtoAttributedString: (NSString *) html {
NSError *error;
NSDictionary *options = #{NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType};
NSAttributedString *attrString = [[NSAttributedString alloc] initWithData:[html dataUsingEncoding:NSUTF8StringEncoding] options:options documentAttributes:nil error:&error];
if(!attrString) {
NSLog(#"creating attributed string from HTML failed: %#", error.debugDescription);
}
return attrString;
}
// force font thrugh & css
- (NSAttributedString *)attributedStringFromHTML:(NSString *)html withFont:(UIFont *)font {
return [self convertHTMLtoAttributedString:[NSString stringWithFormat:#"<span style=\"font-family: %#; font-size: %f\";>%#</span>", font.fontName, font.pointSize, html]];
}
and in your tableView:heightForRowAtIndexPath: replace it with this:
case kContent:
return [self textViewHeightForAttributedText:[self attributedStringFromHTML:myHTMLString withFont:contentFont] andFont:contentFont andWidth:self.tappableCell.width];
break;
You should be able to convert to an NSString to calculate the height like this.
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
UIFont * font = [UIFont systemFontOfSize:15.0f];
NSString *text = [getYourAttributedTextArray objectAtIndex:indexPath.row] string];
CGFloat height = [text boundingRectWithSize:CGSizeMake(self.tableView.frame.size.width, maxHeight) options:(NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading) attributes:#{NSFontAttributeName: font} context:nil].size.height;
return height + additionalHeightBuffer;
}
[cell.descriptionLabel setPreferredMaxLayoutWidth:375.0];

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.

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.

UITable does not scroll smoothly and cells show wrong info in IOS

I have an UITable with just 6 custom cells. Each CustomCell have a horizontal scroll view which have custom Views into it. Each CustomView has a ImageView and Text on it.
So to put it all together it may look like this
UITable --> CustomCell ---> Horizontal ScrollView --> CustomView --> ImageView and Text
Here is the code for Cell in UITable
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *MySecondIdentifier = #"MySecondIdentifier";
UITableViewCell *cell2 = [tableView dequeueReusableCellWithIdentifier:MySecondIdentifier];
if(cell2 == nil){
cell2 = [(CustomCell* )[CustomCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:MySecondIdentifier target:self row:indexPath.row parent:self];
}
[cell2 setSelectionStyle:UITableViewCellSelectionStyleNone];
[cell2 setValueToCellViewItem:tempTitleString setOfImage:dictCatData];
return cell2;
}
where DictCatData = NSMutableArray of data nodes
and tempTitleString = Title string for the cell (using it for some other purpose)
Here is how I set a CustomCell values
- (void) setValueToCellViewItem:(NSString *)pTitle setOfImage:(NSMutableArray *)catData{
[the_pScrolView setContentSize:CGSizeMake([catData count] * 107 , 107)];
int counter = 0;
for(NSDictionary *tempDict in catData){
NSString *url = [[NSString alloc]init];
url = [tempDict objectForKey:#"main_img_url"];
url = [url stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
UIImageView *mSdWebImage = [[UIImageView alloc] initWithFrame:CGRectMake(counter * 107, 0, 107, 107)];
[mSdWebImage setImageWithURL:[NSURL URLWithString:url] placeholderImage:nil];
[mSdWebImage setBackgroundColor:[UIColor grayColor]];
[the_pScrolView addSubview:mSdWebImage];
///Setting the title properties
UILabel *the_pLable = [[UILabel alloc] initWithFrame:CGRectMake((counter * 107) + 15, 85, 97, 22)];
the_pLable.textColor = [UIColor whiteColor];
the_pLable.font = [UIFont fontWithName:#"Helvetica" size:10.0];
the_pLable.numberOfLines = 1;
the_pLable.backgroundColor = [UIColor clearColor];
the_pLable.text = [tempDict objectForKey:#"title"];
[the_pScrolView addSubview:the_pLable];
counter++;
}
I am using SDWebImage for async downloading and caching as I think thats the best we have on net.
The ScrollView can contain images ranging from 0 to 30+ images
When I open this page on my iPhone, the images are getting downloaded an cached properly I guess, as I am able to see them with no difficulties
My Problem are
When I try to scroll up and down the table, the scrolling is not smooth. So how can I make it more smoother without effecting the background image downloading and caching
When I scroll the table up and down several times, the custom cells are redrawn I guess so the CustomCells with no images (i.e. no customViews in scrollView) show the images from other custom cells below/top.
Sometimes the app crashes, I guess this is issue of memory management.
How big are the images being downloaded? I had similar issues (without the extra horizontal scrolling) and I was able to fix it by using actual thumbnails of the images instead of the actual images (in the TableView).
You may want to try downloading, caching, and creating thumbnails of the images in a separate object and then letting the TableViewCell load those thumbnails of the images instead of the actual images.
This sped up scrolling perfectly for me.
To fix the reused cells showing wrong images, simply remove the images in cellForRowAtIndexPath before calling your code to display new images. That way if the images are delayed, at least the old ones have been removed or hidden.

Creating an Instagram-like photo feed

I'm trying to make a photo feed for an app I'm making, similar to Instagram's:
Instagram photo feed
I've created a preliminary version using a UITableView for the feed, but it's becoming a hassle dealing with the dynamic nature of each cell. The number of likes and comments along with the comment text itself will determine how tall the cell is. I used UITextViews to draw the comment and like text, and approximate the cell height and positioning using the UITextViews' text with sizeWithFont. This solution seems very imprecise and has a lot of downsides.
I was thinking about using a UIWebView for the entire feed as an alternate solution. It would make positioning the like and comment text extremely simple, along with the ability to have variable font in the text as seen in Instagram. I haven't really used UIWebViews extensively, so I'm not sure how easy or hard it would be to create the whole feed this way.
Should I continue using my UITableView solution or look into redoing it all using a UIWebView?
In my app, I've got a UITableViewCell that dynamically adjusts its height based on the amount of text in the cell. I think it might help answer your question.
Here's the trick: There is no need to "approximate text sizes" --- check out the documentation for the method [NSString sizeWithFont: constrainedToSize: lineBreakMode:]
maximumSize = CGSizeMake(self.contentView.bounds.size.width - TEXT_LEFT_MARGIN * 2.0,
MAXIMUM_TEXT_HEIGHT);
textStringSize = [textLabel.text sizeWithFont:textLabel.font
constrainedToSize:maximumSize
lineBreakMode:textLabel.lineBreakMode];
So let's take a simple example where you want 10 pixels of whitespace above the label, and 15 pixels of whitespace below the label. You'd then generate a CGRect with the measurements above, with a height of 25 pixels added to the textStringSize.height value generated above.
Using this method means that your UITableViewCells will scale up and down nicely, regardless of the size of the comments in the different labels.
try this..
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *simpleIdendifer=#"item";
UITableViewCell *cell=[tableView dequeueReusableCellWithIdentifier:simpleIdendifer];
if(cell==nil)
{
cell=[[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:simpleIdendifer];
}
[[cell textLabel] setText:self.str];
[[cell textLabel] setNumberOfLines:0];
[[cell textLabel] setLineBreakMode:NSLineBreakByWordWrapping];
[[cell textLabel] setFont:[UIFont preferredFontForTextStyle:UIFontTextStyleBody]];
return cell;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
//add your table view cell content here
NSString *string = self.str;
NSDictionary *attributes = #{NSFontAttributeName: [UIFont preferredFontForTextStyle:UIFontTextStyleBody]};
CGRect frame = [string boundingRectWithSize:CGSizeMake(CGRectGetWidth(tableView.bounds), CGFLOAT_MAX) options:(NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading) attributes:attributes context:nil];
return ceilf(CGRectGetHeight(frame)+TableViewCellPadding);
}

Resources