uitableviewcell textlabel too long and push detailtextlabel out of view - iphone

When i'm using UITableViewCellStyleValue1, i got a long string of textLabel, and somehow the detailTextLabel got push out from the view.
When i shorted my textLabel text, then i can see the detailTextLabel's text.
Is there anyway to limit the width of textLabel in the above style so that it will truncate the textLabel with it's too long?
My code is:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:CellIdentifier] autorelease];
}
cell.textLabel.lineBreakMode = UILineBreakModeTailTruncation;
//---get the letter in each section; e.g., A, B, C, etc.---
NSString *alphabet = [self.currencyNameIndex objectAtIndex:[indexPath section]];
//---get all states beginning with the letter---
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"SELF beginswith[c] %#", alphabet];
self.currencyList = [self.keyCurrencyName filteredArrayUsingPredicate:predicate];
if ([self.currencyList count] > 0)
{
NSString *currencyName = [self.keyCurrencyName objectAtIndex:indexPath.row];
cell.textLabel.text = currencyName;
NSString *currencyCode = [self.valueCurrencyCode objectAtIndex:indexPath.row];
cell.detailTextLabel.text = currencyCode;
}
return cell;
}
so my currency name will be a long one on some entry.

Simplest for me was to subclass UITableViewCell and override the layoutSubviews.
Couldn't find a reliable way to calculate the positions from just the label frames so just hardcoded the accessory width for in this case a UITableViewCellStyleValue1 cell with a UITableViewCellAccessoryDisclosureIndicator accessory type.
- (void)layoutSubviews
{
[super layoutSubviews];
CGFloat detailTextLabelWidth = [self.detailTextLabel.text sizeWithFont:self.detailTextLabel.font].width;
CGRect detailTextLabelFrame = self.detailTextLabel.frame;
if (detailTextLabelFrame.size.width <= detailTextLabelWidth && detailTextLabelWidth > 0) {
detailTextLabelFrame.size.width = detailTextLabelWidth;
CGFloat accessoryWidth = (floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_6_1) ? 28.0f : 35.0f;
detailTextLabelFrame.origin.x = self.frame.size.width - accessoryWidth - detailTextLabelWidth;
self.detailTextLabel.frame = detailTextLabelFrame;
CGRect textLabelFrame = self.textLabel.frame;
textLabelFrame.size.width = detailTextLabelFrame.origin.x - textLabelFrame.origin.x;
self.textLabel.frame = textLabelFrame;
}
}

#Jhaliya #lucas
cell.textLabel.numberOfLines = 3; // set the numberOfLines
cell.textLabel.lineBreakMode = UILineBreakModeTailTruncation;
see here: Custom UITableViewCell. Failed to apply UILineBreakModeTailTruncation

I ran into a similar problem when trying to use "Right Detail" in UITableView; the right detail's built in title label was clobbering my subtitle label.
Eventually I gave up on the "Right Detail" in favor of my own custom one (using swift and autolayout):
I created my own simple class that inherited from the UITableViewCell:
class TransactionCell: UITableViewCell
{
}
I set my prototype cell use that custom class by setting the "Style" field to "Custom" on the "Table View Cell" Menu and by adding "TransactionCell" to the "Class" field of the "Custom Class" menu. These menus are available when you select the prototype cell in the storyboard.
I added two labels to my prototype cell and connected them to my custom class by right click dragging from my labels to my class (Oddly, I had to clean my build before it would let me do this):
class TransactionCell: UITableViewCell{
#IBOutlet weak var detailsLabel: UILabel!
#IBOutlet weak var amountLabel: UILabel!
}
I added new constraints to my labels, taking advantage of swift's autolayout features (You will need to set these to match your own requirements; see a tutorial on autolayout if you are stuck)
...and set the "Lines" and "Line Breaks" fields in the respective "Label" menus so that the spacing between labels would be even and so that my details label could flex to multiple lines.
It worked for me, allowing me to have the flexibility of different amounts of multiple lines in a UITableView in swift per cell, while formatting the word wrapping so that it looked nice and even, like I would have expected the "Right Detail" to do automatically.

I had the same problem and had to create a UITableViewCell subclass. It's easier to do that than I thought:
Basically, just make a new file, that is a subclass of UITableViewCell
Add the labels and synthesize them:
// in the .h file
#property (nonatomic, weak) IBOutlet UILabel *textLabel;
#property (nonatomic, weak) IBOutlet UILabel *detailTextLabel;
// in the .m file
#synthesize textLabel, detailTextLabel;
In the StoryBoard, set your class as the cell's class, make the Style "Custom" and add two labels in the cell to look exactly as you want (I made them look the same as the default: http://cl.ly/J7z3)
The most important part is to make sure you connect the labels to the cell
You need to control-click from the Cell to the label in the Document outline. Here's a picture of what it looks like: http://cl.ly/J7BP
What helped me to understand how to create custom cell, dynamic cells, and static cells is this youtube video: http://www.youtube.com/watch?v=fnzkcV_XUw8
Once you do that, you should be all set. Good luck!

Swift version supporting the latest iOS (12):
override func layoutSubviews() {
super.layoutSubviews()
guard let detailWidth = detailTextLabel?.intrinsicContentSize.width, var detailFrame = detailTextLabel?.frame else {
return
}
let padding = layoutMargins.right
if (detailFrame.size.width <= detailWidth) && (detailWidth > 0) {
detailFrame.size.width = detailWidth
detailFrame.origin.x = frame.size.width - detailWidth - padding
detailTextLabel?.frame = detailFrame
var textLabelFrame = textLabel!.frame
textLabelFrame.size.width = detailFrame.origin.x - textLabelFrame.origin.x - padding
textLabel?.frame = textLabelFrame
}
}

Adjust the frame of the view: textLabel
CGRect aFrame = cell.textLabel.frame;
aFrame.size.width = 100; // for example
cell.textLabel.frame = aFrame;

Updated Gosoftworks Development`s answer.
Swift 3
class BaseTableViewCell: UITableViewCell {
override func layoutSubviews() {
super.layoutSubviews()
guard let tl = textLabel, let dl = detailTextLabel else { return }
if (tl.frame.maxX > dl.frame.minX) {
tl.frame.size.width = dl.frame.minX - tl.frame.minX - 5
}
}
}

Create a custom UITableViewCell with a UILabel in it that you can control however you want, or truncate the text that you assign to the base-class textLabel to fit the space that you have.
It's not perfect, but I have used the text-truncation technique in places where a custom cell is overkill (ex. when the only issue was fitting the text in) using an NSString category with a method similar to the following:
- (NSString *)stringByTruncatingToWidth:(CGFloat)width withFont:(UIFont *)font
{
NSString *result = [NSString stringWithString:self];
while ([result sizeWithFont:font].width > width)
{
result = [result stringByReplacingOccurrencesOfString:#"..." withString:[NSString string]];
result = [[result substringToIndex:([result length] - 1)] stringByAppendingString:#"..."];
}
return result;
}
Probably not 'optimized' but it works for simple scenarios.

1st: set line break mode
textLabel.lineBreakMode = NSLineBreakByTruncatingTail;
2nd: set texLabel frame width you want (e.g. 200)
CGRect textFrame = self.textLabel.frame;
CGRect newTextFrame = CGRectMake(textFrame.origin.x, textFrame.origin.y, 200, textFrame.size.height);
self.textLabel.frame = newTextFrame;

It works!! But i just changed Christopher King`s code:
- (NSString *)stringByTruncatingToWidth:(CGFloat)width withFont:(UIFont *)font :(NSString*) result
{
while ([result sizeWithFont:font].width > width)
{
result = [result stringByReplacingOccurrencesOfString:#"..." withString:[NSString string]];
result = [[result substringToIndex:([result length] - 1)] stringByAppendingString:#"..."];
}
return result;
}
and usage:
NSString* text = #"bla bla bla some long text bla bla";
text = [self stringByTruncatingToWidth:cell.frame.size.width-70.0 withFont:[UIFont systemFontOfSize:17] :text];
cell.textLabel.text = text;

I've struggled with this a bunch and found a pretty simple answer. My textLabel was on the left and would push out the detailText on the right to the point you couldn't see it at all sometimes.
My solution, change the Table View Cell style to Subtitle from Left Detail or Right Detail. This solution works if you don't mind your detailText being below instead of on the right or left.
If you are having issues with the height of the row, you can adjust that using the code below in viewDidLoad.
self.tableView.estimatedRowHeight = 500 // set this as high as you might need, although I haven't tested alternatives
self.tableView.rowHeight = UITableViewAutomaticDimension
self.tableView.reloadData()

Use the UILabel lineBreakMode property to restrict your text within the width of your UILabel
#property(nonatomic) UILineBreakMode lineBreakMode
Use it as below.
myLabel.lineBreakMode = UILineBreakModeTailTruncation;
Here is the list of values which could be used with lineBreakMode.
http://developer.apple.com/library/ios/#documentation/uikit/reference/NSString_UIKit_Additions/Reference/Reference.html#//apple_ref/doc/c_ref/UILineBreakMode
EDITED:
Set the width of your UILabel as per your requirment
eg.
myLabel.frame.size.width = 320;

Related

How to bring to front a programmatically generated UILabel in a UITableView, in Swift

I have a programmatically generated UITableView with many UILabel's.
Each added UILabel should be seen in front.
All works ok until I add the final UILabel, which appears behind all the others.
How can I bring it to the front?
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
...
if (cell == nil)
{
if( dbg ) NSLog( #" - cell nil");
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier: CellIdentifier];
/* Though it's UITableViewCellStyleDefault, the three defaults (image, label, detail label) are nil
if not set. */
// UI controls must be preset for re-used, to prevent memory leak:
// Allocate max. possible UI controls for this row, once per boot:
int instance;
for( instance=0; instance < MAX_CELL_UILABEL; ++instance )
{
UILabel* cell_UILabel = [[UILabel alloc] initWithFrame: CGRectZero]; // allocate next UI control
[cell.contentView addSubview: cell_UILabel ]; // add it permanently to the cell
cell_UILabel.tag = BASE_UILABEL_TAG + instance; // assign unique ID for later lookup
}
...
OTHER UILABELS ARE ADDED HERE.
AND, HERE IS THE FINAL UILABEL, WHICH APPEARS BEHIND THE REST, WHEN IT SHOULD APPEAR IN FRONT:
UILabel* battery_percent = (UILabel*)[cell.contentView viewWithTag: BASE_UILABEL_TAG + ul++];
battery_percent.frame = CGRectMake (x,y, w,h);
battery_percent.backgroundColor = [UIColor orangeColor];
battery_percent.textAlignment = NSTextAlignmentCenter; // NSTextAlignmentRight, NSTextAlignmentLeft
battery_percent.font = [UIFont systemFontOfSize: font_size];
battery_percent.textColor=[UIColor whiteColor];
battery_percent.numberOfLines=0;
// Show battery %:
battery_percent.text = [NSString stringWithFormat: #"%d%%", battery_charge_percent ];
[cell bringSubviewToFront:label];
I found the answer elsewhere on Stackoverflow:
[cell.contentView bringSubviewToFront: battery_percent];
Sweet!
From
int instance;
for( instance=0; instance < MAX_CELL_UILABEL; ++instance )
{
UILabel* cell_UILabel = [[UILabel alloc] initWithFrame: CGRectZero]; // allocate next UI control
[cell.contentView addSubview: cell_UILabel ]; // add it permanently to the cell
cell_UILabel.tag = BASE_UILABEL_TAG + instance; // assign unique ID for later lookup
}
The battery_percent label should have a tag value of (BASE_UILABEL_TAG + MAX_CELL_UILABEL - 1)
When you grab the battery_percent label later on with
UILabel* battery_percent = (UILabel*)[cell.contentView viewWithTag: BASE_UILABEL_TAG + ul++];
What is the value of ul at this point?
If it isn't equivalent to (MAX_CELL_UILABEL - 1) then you're grabbing the wrong label.
Here I am not sure where you are adding the battery_percent label...either cell or cell.contentView
So I would suggest you to Use this.
[[battery_percent superView] bringSubviewToFront:battery_percent];
Hope this will help you.

Expanded UITableViewCell according to UILabel size

I have a UITableView with 4 UILabel's: Title, Body, Author and Date, he looks like this:
What I want to accomplish is, when user click on the cell itself, another label should be added to the cell, the "Body" label and the cell should expand according to this label size.
Something like this:
How can I do that? I've searched stackoverflow, tried some code pieces, but still didn't found the right solution.
Thanks!
Edit 1: 14.11.12 at 14:52
I managed to change the size of the UILabel with the current text:
- (CGRect )resizeLabelByFontSize:(UILabel *)customCellLabel withMaxHeightSize:(CGFloat )maxHeight
{
CGSize maximumLabelSize = CGSizeMake(239, maxHeight);
CGSize expectedLabelSize = [customCellLabel.text sizeWithFont:customCellLabel.font constrainedToSize:maximumLabelSize lineBreakMode:customCellLabel.lineBreakMode];
//adjust the label the the new height.
CGRect newFrame = customCellLabel.frame;
newFrame.size.height = expectedLabelSize.height;
return newFrame;
}
But how can I change the size of the cell according to the size of the new UILabel?
By seeing Images in Question
Here is the method which just create the Dynamic FRAME for UILabel have a look at this
By getting the Height and Width for UIlabel you can calculate the Whole height and could set the Row Height of UITableView.
- (void)setLabeltextWithVerticalAlignTop:(NSString *)theText
{
CGSize labelSize;
// here labelSize is hard-wired but could use constants to populate the size
labelSize = CGSizeMake(210, 129);//this is just for example
//now create the Size from textString SO that We could assign this size to the Label.
CGSize theStringSize = [theText sizeWithFont:lblTitle.font constrainedToSize:labelSize lineBreakMode:lblTitle.lineBreakMode];
lblTitle.frame = CGRectMake(lblTitle.frame.origin.x, lblTitle.frame.origin.y, theStringSize.width, theStringSize.height);
lblTitle.text = theText;
}
Call Above Method For setting the height and Width of description Label you need to pass the text to be shown on that description label.
As you gets the height for that Label, Now On the Basis of this You can Adjust the heigh of Row of TableView.
EDIT:Above Code Just Create the Dynamic Frame For The UILabel
You should take a view of this this is what you looking for....!!!.here you would find a sample code too.
EDIT:As you edited your Question see ,it just the logic which you need to convert it into runnable code here it is.
Use Below Method in Your Code called for each row, and make some calculation inside it.
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
CGFloat rowHeight=0.0;
//here it seems cell have 4 subview added on it.
//so if you could calculate the totla hieht of them.
//so what you really need to do.you just use hieght calculative Method for getting hieght of each of three UILabel
//you need to modify `setLabeltextWithVerticalAlignTop` method .
rowHeight= [self setLabeltextWithVerticalAlignTop:#"pass the correspondingText"];// suppose it returns some hieght for FisrtLabel.
//suppoose here you get the 20.0 height here
rowHeight= rowHeight+[self setLabeltextWithVerticalAlignTop:#"pass the correspondingText"];
// suppose it returns some hieght for secondUIlabel.
//suppoose here you get the 40.0 height here
rowHeight= rowHeight+ [self setLabeltextWithVerticalAlignTop:#"pass the correspondingText"];
// suppose it returns some hieght for ThirdUIlabel.
// suppoose here you get the 15.0 height here
//here you have totla height you just need to add some gapping floating value for all of three UIlabel.so that the could not overlap like as.
rowHeight= rowHeight+20.0;
//now you can return that total height
return rowHeight;
}
Note:This is just logic you need to convert it into runnable code.i am sure this can help.
I hope it may help you.
Implement the following methods
– (void) tableView:(UITableView*)tableView didSelectRowAtIndexPath:(NSIndexPath*)indexPath
{
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
// cast cell, add label, expand labels etc
[tableView reloadRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
return [indexPath isEqualTo:[tableView indexPathForSelectedRow]] ? /* expanded height */ : 80 /* normal height */;
}
If you want the row to stay selected even after another row is selected then add a custom BOOL property to your custom cell, e.g. expanded, and use that to determine the height.
You can use tableView:didSelectRowAtIndexPath
In that method you can then create your code to unhide the body label, adjust the relative positions of everything else. Calculate the new height of the row and then call the Table View's reloadRowsAtIndexPath: withRowAnimation: method.
Sorry if there's not a lot of detail in that, but hopefully that should get you on the right track.
Ok, firstly... To expand you need something like this:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath;
Now here is the catch:
You should calculate the size of the UITableViewCell (expanded and non-expanded)
Doing so when you are actually scrolling might be expensive and will give you a bad experience
My advice:
Calculate both sides, before you have actually conclude to build the UITableView, since you want to have dynamic sizes. If you don't and all cells will have the same size expanded, you can use what lammmert said.
NSIndexPath *selectedRow;
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
selectedRow = indexPath;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
if(indexPath == selectedRow){
//return your custom value
}
return 100;
}
I think it will look something like that
So, in order to do this, using expended UITableViewCell, i've created 2 different custom cells, at start the table is showing the first cell, when I click on the cell, the table is showing the second one. Its that easy - yeah!
So I have the UIViewController with the UITableView that implements the table delegate methods:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
if([self.selectedCellIndexPath isEqual:indexPath])
{
return [self expandedCellHeight:indexPath];
}
else
{
return kRegularCellHeight;
}
}
-(CGFloat)expandedCellHeight:(NSIndexPath *)indexPath
{
CGSize maxSize = CGSizeMake(303, 200);
NSString* bodyText = [[self.data objectAtIndex:indexPath.row] objectForKey:kForumMessagesBody];
CGSize fitSize = [bodyText sizeWithFont:[UIFont systemFontOfSize:13] constrainedToSize:maxSize lineBreakMode:UILineBreakModeWordWrap];
CGFloat height = 384 - 69 + fitSize.height;
NSLog(#"expandedHeight: %f",height);
return height;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// Answer cell
if ([self.selectedCellIndexPath isEqual:indexPath])
{
cell = [tableView dequeueReusableCellWithIdentifier:[ForumCell expandedAnswerReuseIdentifier]];
if (cell == nil)
{
cell = [ForumCell expandedAnswerCell];
}
self.expandedCell = cell;
}
else
{
cell = [tableView dequeueReusableCellWithIdentifier:[ForumCell reqularAnswerReuseIdentifier]];
if (cell == nil)
{
cell = [ForumCell regularAnswerCell];
}
}
cell.labelMedia.text = [self.data objectAtIndex:indexPath.row];
return cell;
}
I also have custom cell, the class called ForumCell.h and ForumCell.m and it has 2 different XIB files: ForumRegularAnswerCell.xib and ForumExpandedAnswerCell.xib, I have the following code inside ForumCell.h:
+ (NSString*)reqularAnswerReuseIdentifier
{
return #"RegularAnswerCellReuseIdentifier";
}
+ (NSString*)expandedAnswerReuseIdentifier
{
return #"ExpandedAnswerCellReuseIdentifier";
}
+ (ForumCell*)regularAnswerCell
{
NSArray* objs = [[NSBundle mainBundle] loadNibNamed:#"ForumRegularAnswerCell" owner:self options:nil];
ForumCell* result = [objs objectAtIndex:0];
return result;
}
+ (ForumCell*)expandedAnswerCell
{
NSArray* objs = [[NSBundle mainBundle] loadNibNamed:#"ForumExpandedAnswerCell" owner:self options:nil];
ForumCell* result = [objs objectAtIndex:0];
return result;
}
- (id)initWithCoder:(NSCoder *)decoder
{
self = [super initWithCoder:decoder];
if (self)
{
_originalCellHeight = self.frame.size.height;
_originalLblBodyHeight = self.lblBody.frame.size.height;
}
return self;
}
You can also use more than 2 xibs if you'd like its up to you. but this is the basics.
Enjoy!

How do you dynamically resize two text label widths inside of a UITableViewCell?

I'm using a table view that uses a custom UITableViewCell subclass, NoteCell. The subclass has two text labels, a name label and a date label which are side-by-side in the cell. My goal is to have the name label resize itself so that the full date is shown no matter what.
in cellForRowAtIndexPath, I try to calculate and set the widths of the two text views so that the date is fully displayed and the name label is truncated.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"NoteCell";
NoteCell *cell = (NoteCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
// Configure the cell...
cell.dateLabel.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleLeftMargin;
cell.nameLabel.text = #"A name that should be truncated";
cell.dateLabel.text = #"A long date to use as an example";
// Set the widths
// First calculate how wide the date label needs to be.
cell.dateLabel.text = #"A really long date";
CGSize dateLabelSize = [cell.dateLabel.text sizeWithFont:cell.dateLabel.font];
CGFloat dateExpansionAmount = fabsf(dateLabelSize.width - cell.dateLabel.frame.size.width) + 10.0f;
cell.dateLabel.frame = CGRectMake(cell.dateLabel.frame.origin.x - dateExpansionAmount,
cell.dateLabel.frame.origin.y,
dateLabelSize.width,
cell.dateLabel.frame.size.height);
CGFloat nameLabelWidth = cell.dateLabel.frame.origin.x - cell.nameLabel.frame.origin.x;
cell.nameLabel.frame = CGRectMake(cell.nameLabel.frame.origin.x,
cell.nameLabel.frame.origin.y,
nameLabelWidth,
cell.nameLabel.frame.size.height);
}
Unfortunately, the results are not correct because I don't think I'm setting/calculating the frame bounds correctly. See the image below.
Disregarding the problems with the frame calculations is there a better way to approach this problem or is manually positioning the text label frame the only way?
try use this to set your dateLabel.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"NoteCell";
NoteCell *cell = (NoteCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
// Configure the cell...
cell.dateLabel.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleLeftMargin;
cell.nameLabel.text = #"A name that should be truncated";
cell.dateLabel.text = #"A long date to use as an example";
//set frame cell.dateLabel
CGSize maximumSize = CGSizeMake(INT_MAX, 44); // CGSizeMake(width,height).
CGSize dateStringSize = [[cell.dateLabel.text sizeWithFont:[UIFont systemFontOfSize:cell.dateLabel.font]
constrainedToSize:maximumSize
lineBreakMode:UILineBreakModeWordWrap];
CGRect dateFrame = cell.dateLabel.frame;
dateFrame.size.width = dateStringSize. width;
cell.dateLabel.frame = dateFrame;
return cell;
}
after you set the text try to call
the method "sizeToFit"
and then you can just get the yourLabel.frame.size.width correctly, so if the 2 labels exceed the cell frame just resize the name label with a lower size and set correct position for the second, starting where the first one ends...
NEW EDIT (see comment)
cell.nameLabel.text = #"A name that should be truncated";
cell.dateLabel.text = #"A long date to use as an example";
[cell.nameLabel sizeToFit];
[cell.dateLabel sizeToFit];
// now check if the 2 width exceed cell size:
if (cell.nameLabel.frame.size.width + cell.dateLabel.frame.size.width > cell.frame.size.width){
//resize nameText:
cell.nameLabel.frame.size.width = cell.frame.size.width - cell.dateLabel.frame.size.width;
// ...and maybe consider to give it a minim size requirement
}
// now set the positions
cell.nameLabel.frame = CGRectMake(0,0,cell.nameLabel.frame.size.width,cell.nameLabel.frame.size.height );
cell.dateLabel.frame = CGRectMake(cell.nameLabel.frame.size.width,0,cell.dateLabel.frame.size.width,cell.dateLabel.frame.size.height );
ps
be sure your autoresizingMask settings are corrected to top and left correctly for both labels

indentationLevelForRowAtIndexPath not indenting custom cell

I have overridden the tableView:indentationLevelForRowAtIndexPath method in my UITableViewController derived class as follows:
- (NSInteger)tableView:(UITableView *)tableView indentationLevelForRowAtIndexPath:(NSIndexPath *)indexPath {
NSDictionary* item = [self.projects objectAtIndex:indexPath.row];
int indentationLevel = [[item objectForKey:#"indent"] intValue];
DLog (#"Indentation Level for Row %d : %d", indexPath.row, indentationLevel);
return indentationLevel;
}
I initially thought that this was not being called but that was operator error (err, mine) and I hadn't defined the symbol DEBUG=1.
However, it is being called (duh me!) and this is the log output:
-[RootViewController tableView:indentationLevelForRowAtIndexPath:] [Line 129] Indentation Level for Row 0 : 1
-[RootViewController tableView:indentationLevelForRowAtIndexPath:] [Line 129] Indentation Level for Row 1 : 1
-[RootViewController tableView:indentationLevelForRowAtIndexPath:] [Line 129] Indentation Level for Row 2 : 2
-[RootViewController tableView:indentationLevelForRowAtIndexPath:] [Line 129] Indentation Level for Row 3 : 2
-[RootViewController tableView:indentationLevelForRowAtIndexPath:] [Line 129] Indentation Level for Row 4 : 2
-[RootViewController tableView:indentationLevelForRowAtIndexPath:] [Line 129] Indentation Level for Row 5 : 1
-[RootViewController tableView:indentationLevelForRowAtIndexPath:] [Line 129] Indentation Level for Row 6 : 2
-[RootViewController tableView:indentationLevelForRowAtIndexPath:] [Line 129] Indentation Level for Row 7 : 2
-[RootViewController tableView:indentationLevelForRowAtIndexPath:] [Line 129] Indentation Level for Row 8 : 1
But, this is not affecting the layout of the cells. No indentation.
This is my itemCellForRowAtIndexPath implementation, if that makes any difference:
-(UITableViewCell*)tableView:(UITableView *)tableView itemCellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString* cellIdentifier = #"projectItemCell";
ProjectItemTableViewCell* cell = (ProjectItemTableViewCell*)[tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (cell == nil) {
NSArray* nib = [[NSBundle mainBundle] loadNibNamed:#"ProjectItemTableViewCell" owner:self options:nil];
for (id oneObject in nib) {
if ([oneObject isKindOfClass:[ProjectItemTableViewCell class]]) {
cell = (ProjectItemTableViewCell*)oneObject;
}
}
}
NSDictionary* item = [self.projects objectAtIndex:indexPath.row];
cell.projectDescLabel.text = [item objectForKey:#"name"];
cell.itemCountlabel.text = [NSString stringWithFormat:#"%d", [[item objectForKey:#"cache_count"] intValue]];
cell.itemCountlabel.backgroundColor = [UIColor colorForHex:[item objectForKey:#"color"]];
cell.indentationWidth = 20;
return cell;
}
How do I indent a custom UITableViewCell which I have defined in Interface Builder?
If I change the itemCellForRowAtIndexPath to use a default UITableViewCell with the code below, then it indents fine.
static NSString* cellIdentifier = #"projectItemCell";
UITableViewCell* cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier] autorelease];
}
NSDictionary* item = [self.projects objectAtIndex:indexPath.row];
cell.textLabel.text = [item objectForKey:#"name"];
cell.indentationWidth = 40;
return cell;
Yeah, it seems like custom table cells don't do this automatically? You need to override the layoutSubviews method in the table cell class. See this question for how to do this.
This code worked perfectly for me (although be careful if you are setting a custom height w/ the delegate as well, they seem to interfere with each other):
- (void)layoutSubviews
{
[super layoutSubviews];
float indentPoints = self.indentationLevel * self.indentationWidth;
self.contentView.frame = CGRectMake(
indentPoints,
self.contentView.frame.origin.y,
self.contentView.frame.size.width - indentPoints,
self.contentView.frame.size.height
);
}
Edit for iOS8 and later
The above does work for me on iOS, but it causes subtle bugs when trying to autosize the height of the cell as well. There is n easier solution: If you have autolayout turned for the cell just set the left margin of the contentView:
override func layoutSubviews() {
super.layoutSubviews()
self.contentView.layoutMargins.left = CGFloat(self.indentationLevel) * self.indentationWidth
self.contentView.layoutIfNeeded()
}
If you use a custom cell with constraints, the most convenient way is to set up a constraint that would shift all content from the left edge, and then update it according to the indentation level in your cell subclass.
Assuming indentationWidth is already set for the cell:
override var indentationLevel: Int {
didSet {
self.leftConstraint.constant = CGFloat(self.indentationLevel) * self.indentationWidth
}
}
The indentation level is a property of the UITableViewCell itself. Try setting it on the cell when you create it, and return this value in tableView:indentationLevelForRowAtIndexPath:
Have you added the subviews of your ProjectItemTableViewCell to the cell's contentView? Also, you need to set the subviews' autoresizing masks so that they are repositioned when the contentView size changes.
Similar to the accepted answer, this is how it can be done in iOS 8 while still using layoutSubviews with AutoLayout instead.
With _viewConstraints as an NSMutableArray ivar and _imageView as the closest view to the left side of the cell's content view.
- (void)layoutSubviews
{
float indentPoints = indentationLevel * [self indentationWidth];
[self removeConstraints:_viewConstraints];
[_viewConstraints removeAllObjects];
NSDictionary *views = NSDictionaryOfVariableBindings(imageView);
[_viewConstraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:[NSString stringWithFormat:#"H:|-(%f#1000)-[_imageView]", indentPoints] options:0 metrics:nil views:views]];
[self addConstraints:_viewConstraints];
}
If you have AutoLayout constraints defining everything else in the view then this should push the whole view over the desired indentation amount.
NOTE if using Nib:
You should define the constraint (in this case between _imageView and its super view) as >= some number (in my case it was 20). Then the original constraint, and the one being added/removed in layoutSubviews don't conflict with each other.
You should also consider calling the following in awakeFromNib
[_imageView setTranslatesAutoresizingMaskIntoConstraints:NO]
This is so the old "springs and struts" don't get in the way of the constraints you are adding.
The accepted answer could lead to an infinite loop on iOS8 in some cases. So it's better just to override setFrame of your custom UITableViewCell and adjust frame there.
-(void)setFrame:(CGRect)frame {
float inset = self.indentationLevel * self.indentationWidth;
frame.origin.x += inset;
frame.size.width -= inset;
[ super setFrame:frame ];
}
Have you accidentally overridden shouldIndentWhileEditing: to NO in your custom table cell class?
Constrain to the leading edge of the content view of the custom table cell you have in interface builder. For a simple margin, this seems to suffice. If you need to change the indentation programmatically, see Dean's answer. Or https://stackoverflow.com/a/7321461/5000071.
I used this code to indent my tableView cell. Initially it worked well, but later on this caused some problem (for example, interfering with my UITextView dynamic height update when indented, which is a subview of my tableView cell. Somehow my UITextView think it's width still is the original contentView's width).
float indentPoints = self.indentationLevel * self.indentationWidth;
self.contentView.frame = CGRectMake(
indentPoints,
self.contentView.frame.origin.y,
self.contentView.frame.size.width - indentPoints,
self.contentView.frame.size.height
)
So I don't recommend the code above, instead I use the auto layout method as Dean's answer with a little difference (Swift version):
override func awakeFromNib() {
super.awakeFromNib()
self.indentationWidth = 20.0
self.indentationLevel = 0
}
override func layoutSubviews() {
super.layoutSubviews()
let margin: CGFloat = 20.0
let indentPoints = CGFloat(self.indentationLevel) * self.indentationWidth
indentConstraint.constant = margin + indentPoints
}
The "indentConstraint" is a IBOutlet (leading constraint with the contentView), and the code is written in a custom tableViewCell subclass, it worked perfectly, and you don't need to remove or add any constraints, which is more expensive. I think the auto layout method is the better way to indent tableView cell.

Updating imageView on UIButton within UITableViewCell

I have custom UITableViewCells (created in interface builder) which are used for a static UITableView which for the sake of argument I will call the AddUserTableViewController (i.e. I do not use autoreleased cells as I only have 5 rows).
In the top cell (used for the user name) I have embedded a UIButton which I use to open an UIActionSheet which in turn allows an UIImagePicker to set an image on the UIButton (i.e. [button.imageView.image = imageFromImagePicker]). The button has a default image (added in IB) which says "add image".
The image on the button is set without issue and remains in place even when I navigate to the detail page of any of the table cells with the exception of the user name cell which contains the button. When I select this cell the image dissapears and does not reappear once I navigate back to the AddUserTableViewController from the "add name" detail view. The "add image" default image, referred to above, is displayed.
I have tried many strategies amongst which have been:
1) using [self.tableView reloadData], [button.imageView setNeedsDisplay], [self.view setNeedsDisplay] in the viewWillAppear method;
2) using the above methods in the cellForRowAtIndexPath method;
3) using the above methods in the overriden willDisplayCell:forRowAtIndexPath: method;
When I place debugging NSLog statements in the program I can see that the button.imageView.image property is still set to the image selected using the UIImagePicker but it is not displayed (the default image from the nib is displayed).
As I mentioned above, this only happens if I navigate away from the AddUserTableViewController by selecting the UITableViewCell within which the UIButton is embedded.
I am currently at a loss as to what to do and would be extremely grateful for any assistance that anyone could offer. I just need to find a way to update the image on the embedded UIButton in the above-mentioned circumstances.
I've added the a section of the code from the cellForRowAtIndexPath: method just for illustrative purposes -
//populates the personal info section
if (section == kPersonalInfoAddSection) {
//Covers the add image button and the name adding field
if (row == kNameRow) {
UILabel *nameLabel = (UILabel *)[nameCell viewWithTag:kMainTextTag];
UILabel *reqLabel = (UILabel *) [nameCell viewWithTag:kSubTextTag];
//UIButton *imageButton = (UIButton *) [nameCell viewWithTag:kImageTag];
if (mainUser.imagePath != nil) {
UIImage *img = [UIImage imageWithContentsOfFile:mainUserImagePath];
imageButton.imageView.image = img;
[imageButton.imageView setNeedsDisplay];
//[imageButton setNeedsDisplay];
//[nameCell setNeedsDisplay];
NSLog(#"************************** Added the BESPOKE image (%#)to the image button", img);
}
else {
NSLog(#"************************** Added the DEFAULT image (%#)to the image button", addUserImage);
imageButton.imageView.image = addUserImage;
}
UIColor *blackText = [[UIColor alloc] initWithWhite:0 alpha:1];
NSString *firstName;
if (mainUser.firstName){
nameLabel.textColor = blackText;
firstName = mainUser.firstName;
}
else {
nameLabel.textColor = reqLabel.textColor;
firstName = NAME_STRING;
}
NSString *lastName = (mainUser.lastName)? mainUser.lastName : EMPTY_STRING;
NSString *name = [[NSString alloc] initWithFormat:#"%# %#", firstName, lastName];
nameLabel.text = name;
reqLabel.text = REQUIRED_STRING;
[blackText release];
return nameCell;
}
In the cellForRowAtIndexPath are you doing something that would 'unset' the image?