How to consistently draw NSAttributedString in UITextView in UITableViewCell - objective-c

I am having trouble getting consistent results from UITextViews in a UITableViewCell using NSAttributedStrings.
Inside - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath:
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
headerText = [[UITextView alloc]initWithFrame:CGRectZero];
[headerText setUserInteractionEnabled:NO];
headerText.tag = HEADER_TEXT;
[cell.contentView addSubview:headerText];
} else {
headerText = (UITextView*)[cell.contentView viewWithTag:HEADER_TEXT];
}
//...setting up attributed strings
[headerText setAttributedText:headerString];
CGSize headerSize = [headerText sizeThatFits:CGSizeMake(246, CGFLOAT_MAX)];
headerText.frame = CGRectMake(45, 8, headerSize.width, headerSize.height);
Results:
As you can see, the first two appear to draw the text in a way that I would expect/want. In the last two the UITextView sizeThatFits method returns a much larger size then is required to draw the text and the text becomes centered in the frame rather then tight to the top of the frame. This is an issue because I want to be able to layout other views based on the uitextview frame height.
After Scrolling out of frame and back in:
Now it gets even stranger, when the cells are reused, and the attributed string is set again. the uitextview draws the text in an inconsistent way.
Even Setting the contentInsets to
headerText.contentInset = UIEdgeInsetsMake(-8, -8, -8, -8);
Does not provide any sort of consistent results:
And After Scrolling with contentinset set:
Are there other attributes on UITextView that would allow me to get the behavior that I need?

When setting the attributed string of a UITextView that previously had a different attributed string, you must always set all of the UITextView's string-related properties to nil first, e.g.:
self.tv.text = nil;
self.tv.font = nil;
self.tv.textColor = nil;
self.tv.textAlignment = NSTextAlignmentLeft;
self.tv.attributedText = s2;
Otherwise, as you have discovered, old features of the previous attributed string still hang around and affect the new attributed string.
In general, though, I have to say I don't see why you're using UITextView at all. If you don't need the user to be able to edit these attributed strings, use UILabel or else just draw the attributed string directly for the most accurate possible rendering. NSAttributedString gives you all the power you need to measure the size and draw within that size.

Related

UITableViewCell ReuseIdentifier Behavior [obj-c]

I am curious as to the behavior of the reuse identifier system.
I am working with a uitableviewcell that has two different uilabels on them.
Label one is always there and is updating correctly,
Label two starts with .alpha=0 and appears on a certain condition within the specific cell.
Label one is acting as suspected,
Label two acts strangely, when I first load the scene(viewdidload) it appears as it should.
When I scroll down and find a uitableviewcell has the labeltwo with .alpha=1 because it satisfied the conditional.
Up to this point everything is working, but now if I scroll back up, all label two's have an .alpha=1.
After doing some research, I have come to the conclusion that this has to do with me redefining what the reuse identifier cell template actually looks like programatically.
My question is , is there a way to reset the "buffer" to the uitableview cell i have created in storyboard that DOESNT have the changes I have made programatically.
Also since these are my own deductions, I may be incorrect in my assumptions,
Thanks for your time.
Your cells begins .alpha = 1, because of reuseIdentifier, that already see .alpha = 1, and set another sell to this parameter. As a first decision, you can control .alpha parameter for the each cell individually.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"FeedbackCell";
VZDetailFeedbackTableViewCell *cell = (VZDetailFeedbackTableViewCell *)[self.p_tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil)
{
cell = [[VZDetailFeedbackTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
// There can be more cells with Identifier
CGFloat alpha = 1.0f;
//if (something)
{
alpha = 0.0f;
}
[cell setSellAlpha:alpha];
}
// In your custom cell class you make method setAlpha and there control visibility

UIImage as accessory view in UITableViewCell not showing

Within my app, I bring up a UITableView when prompting the user to select an item. Based on whether the item is in stock, out of stock, on order, etc., I want to display an image on the right-hand side of the UITableViewCell.
I have tried using the accessory view button, but it isn't showing up on the UITableViewCell. I have tried changing the UITableViewCellStyle to UITableViewCellStyleValue2 (the style with the detail closure button), and have also tried setting the accessory type of the cell directly.
static NSString *CellIdentifier = #"MyCellIdentifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
//cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue2 reuseIdentifier:CellIdentifier];
}
[cell.textLabel setText:#"Testing"];
[cell.detailTextLabel setText:#""];
//cell.accessoryType = UITableViewCellAccessoryNone;
//cell.accessoryType = UITableViewCellAccessoryDetailDisclosureButton;
UIImageView *imageView = [[UIImageView alloc] initWithImage:[myImageFilePath stretchableImageWithLeftCapWidth:0 topCapHeight:1.0]];
[imageView setContentMode: UIViewContentModeScaleAspectFit];
[imageView setFrame:CGRectMake(0, 5, cell.frame.size.height - 10, cell.frame.size.height - 5)];
cell.accessoryView = imageView;
return cell;
When the app runs, the UITableViewCells appear with the "Testing" text, but there is no image on the right-hand side of the cells.
Is there another method that needs to be overridden in order to implement a custom UIImage for the detail diclosure button?
The "button" is more of a status indicator, because I don't want the tap of the button to do anything different than tapping anywhere else on the UITableViewCell does (just select the row).
Some additional info:
If I remove
[imageView setFrame:CGRectMake(0, 5, cell.frame.size.height - 10, cell.frame.size.height - 5)];
then the image shows up in the UITableViewCell, but it is too big and does not fit on a single row, so it overlaps with other rows' accessory views.
I was using the setFrame so that it would resize the image to fit with the UIViewContentModeScaleAspectFit so that it would fit on the row, but it seems to prevent the image from appearing at all.
Note that in this example cell.frame.size.height = 44.
If I change the frame to:
[imageView setFrame:CGRectMake(0.0, 0.0, 120, 44)];
then the image appears on the row.
However, since it is 120 pixels wide, it is cutting off the text with the trailing ... earlier that needed. The image could fit about 3 times before the trailing ..., with the width being 120 and height being 44.
How can I get the trailing ... to get closer to the image?
If I reduce the 120 to a lower value, the image moves to the right and eventually runs off the cell.
The width of the UITableView was 804. Updating it to 768 allowed me to use the original code that was posted.
Your question is very similar to this one here:
Using a custom image for a UITableViewCell's accessoryView and having it respond to UITableViewDelegate
You can ignore the "have it respond" bit unless you need to. But, as you can see there, the rect is square for the accessoryView property, so an image that is 120x44 is probably not going to fit correctly, ever.
I would rethink your approach to this problem, and create a custom UITableViewCell subclass that has the necessary items you require (sounds like a UILabel and a UIImageView). This way, you can easily control how large the label is versus how large the image is, how the label will wrap lines (if it wraps at all or maybe truncates) and so on.
Another way is just subclass the UITableviewCell and set frame's in "layoutSubviews" and also make sure to pass correct row height for each cell.
By subclassing you will get full flexibility.
hope this helps
I guess there's an issue with your CGRect X and Y, in this case here 0 and 5. Maybe trying to set them dinamically can fix this. Let me know

UITableView: load all cells

Is it possible to load all cells of an UITableView when the view is loaded so that they are not loaded when I'm scrolling?
(I would show a loading screen while doing this)
Please, it's the only way at my project (sorry too complicate to explain why ^^)
EDIT:
Okay let me explain you, what I'm definite doing:
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *cellIdentifier = [NSString stringWithFormat:#"Identifier %i/%i", indexPath.row, indexPath.section];
CustomTableCell *cell = (CustomTableCell *)[tableView dequeueReusableCellWithIdentifier:cellIdentifier];
NSDictionary *currentReading;
if (cell == nil)
{
cell = [[[CustomTableCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier] autorelease];
UILabel *label;
UIView *separator;
if(indexPath.row == 0)
{
// Here I'm creating the title bar of my "table" for each section
}
else
{
int iPr = 1;
do
{
currentReading = [listData objectAtIndex:iPr-1];
iPr++;
} while (![[currentReading valueForKey:#"DeviceNo"] isEqualToString:[devicesArr objectAtIndex:indexPath.section]] ||
[readingresultsArr containsObject:[currentReading valueForKey:#"ReadingResultId"]]);
[readingresultsArr addObject:[currentReading valueForKey:#"ReadingResultId"]];
//
// ...
//
}
}
return cell;
}
My error happens in the do-while-loop:
"listData" is an array with multiple dictionaries in it.
My problem ist that when I’m scrolling my table slowly down, all is fine, but when I’m scrolling quickly to the end of the view and then I’m scrolling to the middle, I get the error that iPr is out of the array’s range. So the problem is, that the last row of the first section has already been added to the "readingresultsArr", but has not been loaded or wants to be loaded again.
That’s the reason why I want to load all cells at once.
You can cause all of the cells to be pre-allocated simply by calling:
[self tableView: self.tableView cellForRowAtIndexPath: indexPath];
for every row in your table. Put the above line in an appropriate for-loop and execute this code in viewDidAppear.
The problem however is that the tableView will not retain all of these cells. It will discard them when they are not needed.
You can get around that problem by adding an NSMutableArray to your UIViewController and then cache all the cells as they are created in cellForRowAtIndexPath. If there are dynamic updates (insertions/deletions) to your table over its lifetime, you will have to update the cache array as well.
put a uitableview on a uiscrollview
for example , you expect the height of the full list uitableview is 1000
then set the uiscrollview contentsize is 320X1000
and set the uitableview height is 1000
then all cell load their content even not visible in screen
In my case it was that I used automaticDimension for cells height and put estimatedRowHeight to small that is why tableview loaded all cells.
Some of the answers here and here suggest using automaticDimension for cells height and put mytable.estimatedRowHeight to a very low value (such as 1).
Starting with iOS 15 this approach seems not to work anymore. Hence, another way to achieve the table to "load" all cells could be by automatically scrolling to the last cell. Depending on the tables height and how many rows it can show some cells are discarded but each cell would be loaded and shown at least once.
mytable.scrollEnabled = YES;
NSIndexPath* indexPath = [NSIndexPath indexPathForRow:cellCount - 1 inSection:0];
[mytable scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionBottom animated:YES];
mytable.scrollEnabled = NO;
If you want to scroll up again just scroll to the top as outlined here.
Following the comment that was made by juancazalla, I found that if you have a tableView that is using automaticDimension, loading all the cells at once can be best achieved by setting estimatedRowHeight to a low value (such as 1).

Oddness of UILable on customized table cell

In my iphone app, I have a UITableView with customized cell. The text for one of the label on the cell could be quite long, and I would like it to wrap and clip to bound. I have tried couple of things, however the label still extend all way underneath the accessory icon.
If you have a custom UITableViewCell then you can implement the - (void) layoutSubviews method and resize the UILabel in the cell.
Something like:
CGRect titleFrame = _title.frame;
NSString *currentText = [_title text];
CGSize size = [currentText sizeWithFont:_title.font constrainedToSize:CGSizeMake(titleFrame.size.width, 50.0f) lineBreakMode:_title.lineBreakMode];
titleFrame.size.height = size.height;
_title.frame = titleFrame;
Where _title is the UILabel.
I would add to elpri's answer that you don't need to go through all the trouble of creating a custom subclass of UITableViewCell. Instead you can use tag's but the general approach is correct.
Calculate the size of the frame constraining it to a max size using sizeWithFont:constrainedToSize:lineBreakMode:
Set width/height of your UILabel frame using the calculatedSize.width and calculatedSize.height
Using tags this would look like:
#define NAMELABEL_TAG 1
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"ACellIdentifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
UILabel *nameLabel = (UILabel *)[cell viewWithTag:NAMELABEL_TAG];
// the rest of your code to resize the frame can go here...
}
Also make sure to set the struts and springs correctly in interface builder so that it autoexpands when the device is rotated (assuming that is what you want).

Aligning images of different sizes

I have a different size of image to be input as a thumbnail of a tableview, is there a way to make all the image align despite their different in size? since in my case, the location of the text will follow the image, so it won't be neat.
Here's the code that I use to input the image
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
cell.textLabel.text = title;
cell.textLabel.adjustsFontSizeToFitWidth = YES;
cell.imageView.image = thumb;
cell.selectionStyle = UITableViewCellSelectionStyleNone;
return cell;
You can always set the frame of the imageView and textLabel objects as you want them to be.
Cell.imageView is a UIImageView, which is a subclass of UIView. Look at documentation of UIView - exactly "Configuring the Resizing Behavior". Maybe something of that helps.
But beware - resizing on the fly can reduce scrolling behaviour of the tableview. Maybe better way is to resize images to fit the size of the uiimageview before actually using them in tableview.