I have a dynamic tableView with custom cells that each contain a textField and label. I want the following behavior:
A user taps the "Add" button, and the table inserts a cell at index 0, with the cell's textField becoming the first responder.
When the user finishes inputting data, the one-lined textField resigns as first responder and hides itself.
The cell's label then becomes visible, filled with the user input, and auto-resizes it's height to accommodate the input. The cell is resized at this time too.
I have code that does exactly this the first time a cell is added, but when more cells are added then it gets weird. The second added cell
s textField does not become first responder and is filled with the text from the previous textField. Subsequent added cells appear blank, except every 6 blank cells, the first cell's text shows up.
What's going on, and how can I fix this?
Edit: Thanks to #Rayfleck, I have resolved the issue of the second added cell being filled with the text from the previous textField, but the second cell that gets added (and subsequent added cells) still isn't becoming first responder.
Here's the demo showing this behavior:
Here's my code:
#define FONT_SIZE 16.0f
#define CELL_CONTENT_WIDTH 300.0f // This will change for different size!
#define CELL_CONTENT_MARGIN_WIDTH 10.0f
#define CELL_CONTENT_MARGIN_HEIGHT 20.0f
#interface ViewController ()
#property (strong) IBOutlet UITableView *tableView;
#property (strong) NSMutableArray *notesArray;
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.notesArray = [NSMutableArray arrayWithObjects:#"Testo 1", #"This is a long string of text to show how the cell resizes to fit a variable height label.", #"Testo 3", #"Testo 4", #"Testo 5", nil];
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
// Return the number of sections.
return 1;
}
- (IBAction)addButtonTapped {
NSMutableArray *indexPaths = [NSMutableArray array];
[indexPaths addObject:[NSIndexPath indexPathForRow:0 inSection:0]]; // insert at first index
[self.tableView setEditing:YES animated:NO]; // go into edit mode so cell has label hidden and textField visible
// do the insertion
[self.notesArray insertObject:#"" atIndex:0]; // placeholder for when user starts typing
[self.tableView beginUpdates];
[self.tableView insertRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationTop];
[self.tableView endUpdates];
[self.tableView setEditing:NO animated:NO];
}
-(BOOL)textFieldShouldReturn:(UITextField *)textField {
NSIndexPath *path = [NSIndexPath indexPathForRow:0 inSection:0]; // first table cell
HC_NoteCell *cell = (HC_NoteCell *)[self.tableView cellForRowAtIndexPath:path];
cell.noteTextField.hidden = YES;
cell.noteLabel.text = cell.noteTextField.text;
cell.noteLabel.hidden = NO;
[self.notesArray replaceObjectAtIndex:0 withObject:cell.noteTextField.text];
[textField resignFirstResponder];
// Need to reload rows so cell height can be recalculated
[self.tableView beginUpdates];
[self.tableView reloadRowsAtIndexPaths:#[path] withRowAnimation:UITableViewRowAnimationAutomatic];
[self.tableView endUpdates];
return YES;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return self.notesArray.count;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
NSString *text;
text = [self.notesArray objectAtIndex:indexPath.row];
CGSize constraint = CGSizeMake(CELL_CONTENT_WIDTH - (CELL_CONTENT_MARGIN_WIDTH * 2), 20000.0f);
UIFont *font = [UIFont fontWithName:#"Helvetica Neue" size:FONT_SIZE];
NSDictionary *attrsDictionary = [NSDictionary dictionaryWithObject:font
forKey:NSFontAttributeName];
NSAttributedString *attributedString = [[NSAttributedString alloc] initWithString:text attributes:attrsDictionary];
CGRect paragraphRect = [attributedString boundingRectWithSize:constraint options:(NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading) context:nil];
CGFloat height = MAX(paragraphRect.size.height, 22.0f);
return height + (CELL_CONTENT_MARGIN_HEIGHT * 2);
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"NoteCell";
HC_NoteCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[HC_NoteCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
if (self.tableView.isEditing) {
cell.noteLabel.hidden = YES;
cell.noteTextField.hidden = NO;
cell.noteTextField.delegate = self;
[cell.noteTextField becomeFirstResponder];
} else {
cell.noteLabel.hidden = NO;
cell.noteTextField.hidden = YES;
cell.noteTextField.delegate = self;
cell.noteLabel.text = [self.notesArray objectAtIndex:indexPath.row];
}
return cell;
}
#end
If you are reusing/dequeing cells, then in cellForRowAtIndexPath, you must set the new value for every subview of the cell, because it will probably contain the contents of the previous cell.
So cell.noteLabel.text = nil; in all cases, and then in your else case, set it's value from the array.
Related
I am developing IOS App. Multiple textfield add on tableviewcell.when enter data on texfield than scroll tableview textfield data hide. How to manage textfield on tableview.Please Help me and thanks in advance
code
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableViews{
// Return the number of sections.
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
// Return the number of rows in the section.
return 100;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
static NSString *MyIdentifier = #"cell";
UITableViewCell *cell;
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:MyIdentifier];
}
UITextField *textFieldName = [[UITextField alloc]initWithFrame:CGRectMake(8.0f, 3.0f, 80.0f, 36.0f)];
[cell addSubview:textFieldName];
[textFieldName setDelegate:self];
textFieldName.tag = indexPath.row+1;
UILabel *line = [[UILabel alloc]initWithFrame:CGRectMake(96.0f, 0.0f, 1.0f, 50.0f)];
line.backgroundColor = [UIColor colorWithRed:214.0/255.0 green:214.0/255.0 blue:214.0/255.0 alpha:1.0];
[cell addSubview:line];
[cell setSeparatorInset:UIEdgeInsetsZero];
tableView.allowsSelection=NO;
return cell;
}
-(void)textFieldDidEndEditing:(UITextField *)textField{
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:([textField tag]-1) inSection:0];
UITableViewCell *Cell = [_dairyTable cellForRowAtIndexPath:indexPath];
}
How to deal with UITextField Value in UITableView :
First of all you have to create mutable Array with count equal to the number of UITextField.
In cellForRow do cell.textField.tag = indexPath.rowsothat you can identify each UItextField.
Save every UITextFieldvalue in the array in the same sequence with each character change. So, do code in textdidChange delegate method of UITextField by identifing the UITextfield by its tag as textfield.tag.
Whenever user enters or edit the text, you get that text in array.
Now in cellForRow add below line:
cell.textfiled.text = arrDataText[index.row]
I have a custom UITableViewCell, and when it's selected, it expands and adds a UILabel to the selected cells UIView that I added in the storyBoard.
When I run the app and select a cell, the label gets added to myView as expected. The problem is, when I scroll down, the label is also shown at another cell.
Apparently the reason its behaving like so, is because I'm reusing the cell and I don't clean them as Emilie stated. I'm trying to call the method of prepareForReuse and 'cleaning' the cell, but I'm having trouble doing that. Here is my code:
- (void)prepareForReuse {
NSArray *viewsToRemove = [self.view subviews];
for (UILablel *v in viewsToRemove) {
[v removeFromSuperview];
}
Doing that, cleans even the selected cells label.
- (void)viewDidLoad {
self.sortedDictionary = [[NSArray alloc] initWithObjects:#"Californa", #"Alabama", #"Chicago", #"Texas", #"Colorado", #"New York", #"Philly", #"Utah", #"Nevadah", #"Oregon", #"Pensilvainia", #"South Dekoda", #"North Dekoda", #"Iowa", #"Misouri", #"New Mexico", #"Arizona", #"etc", nil];
self.rowSelection = -1;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
CategorieCell *customCell = [tableView dequeueReusableCellWithIdentifier:#"cellID" forIndexPath:indexPath];
customCell.title.text = [self.sortedDictionary objectAtIndex:indexPath.row];
return customCell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[tableView deselectRowAtIndexPath:indexPath animated:YES];
CategorieCell *customCell = (CategorieCell *)[tableView cellForRowAtIndexPath:indexPath];
if (self.info) {
[self.info removeFromSuperview];
}
self.info = [[UILabel alloc] init];
[self.info setText:#"Hello"];
[self.info setBackgroundColor:[UIColor brownColor]];
CGRect labelFrame = CGRectMake(0, 0, 50, 100);
[self.info setFrame:labelFrame];
[customCell.infoView addSubview:self.info];
NSLog(#"%ld", (long)indexPath.row);
self.rowSelection = [indexPath row];
[tableView beginUpdates];
[tableView endUpdates];
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
if ([indexPath row] == self.rowSelection) {
return 159;
}
return 59;
}
The answer is quite simple : you reuse your cell like you should, but never clean them
Reusing your UITableViewCell means that the cell you clicked on previously will be reused when it will go off-screen.
When clicked, you add a view to your UITableViewCell. When reused, the view is still there because you never remove it.
You have two choices : One, you could set a tag of the self.info view (or check with the indexpath you're keeping in memory), then check when you dequeue the cell if the info view is there, and remove it. The cleaner solution would be to implement the view removal by overriding the prepareForReuse method of your custom UITableViewCell
Precision
The first thing you need to do is set a tag for your self.info view after initializing it:
[self.info setTag:2222];
If you want to keep it as simple as possible, you could check and remove the self.info view directly in your cellForRowAtIndexPath method :
CategorieCell *customCell = [tableView dequeueReusableCellWithIdentifier:#"cellID" forIndexPath:indexPath];
customCell.title.text = [self.sortedDictionary objectAtIndex:indexPath.row];
if [customCell.infoView viewWithTag: 2222] != nil {
[self.info removeFromSuperview]
}
return customCell;
I am not a percent sure this code compiles, I cannot test it on my side for now. Hope it works !
I have a tableview with a customized cell in it, in my cell, I have one label and one image, text in label is long, so I just show first 2 lines and then show 3dots "..." using linebreaks, and then when user tap cell, it will expand to show full text of label
I change height of cell when taped, I do this like this:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
if ([tableView indexPathsForSelectedRows].count) {
if ([[tableView indexPathsForSelectedRows] indexOfObject:indexPath] != NSNotFound) {
MsgInfo *info = [_msgInfos objectAtIndex:indexPath.row];
NSString *displayString = info.msg;
CGRect boundingRect =
[displayString boundingRectWithSize:CGSizeMake(self.tableView.frame.size.width - 2 * 10, 1000) options:NSStringDrawingUsesLineFragmentOrigin attributes:
#{ NSFontAttributeName : [UIFont fontWithName:#"B Yekan" size:18.0f]} context:nil];
CGFloat rowHeight = ceilf(boundingRect.size.height);
NSLog(#"Height = %f for '%#'", rowHeight, displayString);
return 54 + rowHeight + 2 * 10; // Expanded height
}
return 92; // Normal height
}
return 92; // Normal height
}
So when I tap a cell, it expands based on my label text font and size,
But now I want to show full text of label, I know I have to set label.numberofline =0 in didSelectRowAtIndexPath, but it doesnot work.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"MsgCell";
UITableViewCell *cell =
[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle
reuseIdentifier:CellIdentifier];
}
UILabel *nameLabel = (UILabel *)[cell viewWithTag:100];
nameLabel.numberOfLines =0;
[nameLabel sizeToFit];
[nameLabel setLineBreakMode:NSLineBreakByWordWrapping];
[self.tableView beginUpdates];
[self.tableView endUpdates];
}
any help?
In didSelectRowAtIndexPath you're creating a new cell and changing its settings (not editing the existing cell and not any cell that will be used for display soon). What you should be doing is triggering a reload of the selected cell so that the new height is recalculated and the cell reloaded. In tableView:cellForRowAtIndexPath: you should have similar logic to check if the row is selected and then set the number of lines on the label.
Doing it in tableView:cellForRowAtIndexPath: will also prevent you from having issues on deselection as you must always set the number of lines for each cell whenever it is reused.
Am I able to create a UILabel that is layouted upon many UITableViewCells?
I'm trying to make something like (that is just one section of my UITableView, each section can have one or more rows):
---------------------------------------------
| Multi-lined label | row1 values |
| with some useless | row2 values |
| text | row3 values |
---------------------------------------------
I managed to create a UILabel (in the first row of a section) that is multi-lined and is not clipping to bounds. That works really well (it was a bit tricky to count each sections row heights, but doable) besides one case: when I'm scrolling UITableView from bottom to top - UITableView renders last row (without UILabel) so it has "no evidence" of having UILabel (because it is maintained in the first row of section). Can I force some kind of relayouting first cell in section? I tried reloadRowsAtIndexPaths:withRowAnimation: with first row in each section whenever I layouted not first cell in section but it gave me layouting errors that I really do not understand. Or maybe there is another idea to do so?
-- EDITED
To be clear: I have a custom UITableViewCell with an IB view, it has a few labels that each row consist of and a label named labelName that I want to be "multi-lined" along rows in that section. LabelName.text is empty for each row besides first one in each section.
I am adding somescreenshots:
Good screenshot - when I am scrolling to bottom I'm getting proper effect:
Bad screenshot - when I am scrolling up, UITableView renders last row of section firstly, and afterwards renders upper rows - that gives effect of cut label (because multi-line label is in the first row)
I am not sure if code here will add anything to question - it is rather simple and almost whole logic is in tableView:heightForRowAtIndexPath. I can only present how do I create custom UITableViewCell:
CustomTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:[CustomTableViewCell reuseIdentifier]];
if (cell == nil) {
cell = [[CustomTableViewCell alloc] initWithOwner:self];
cell.clipsToBounds = NO;
cell.labelName.clipsToBounds = NO;
cell.contentView.superview.clipsToBounds = NO;
}
-- EDIT 2
Here is most of the code:
- (void) reloadData
{
NSUInteger index = 0;
for (NSDictionary *object in self.list) {
CGFloat height = [[object objectForKey:#"name"] sizeWithFont:self.labelFont constrainedToSize:self.labelSize].height;
[self.labelHeights addObject:NSNumberFloat(ceilf(height))];
index++;
}
[self.tableView reloadData];
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSDictionary *object = [self.list objectAtIndex:indexPath.section];
CGFloat height = [[self.labelHeights objectAtIndex:indexPath.section] floatValue];
NSUInteger count = [[object objectForKey:#"list"] count];
CGFloat cellHeight = 30.f;
if((indexPath.row + 1) == count){
cellHeight = MAX(8.f + height - 30.f * indexPath.row, 30.f);
}
return cellHeight;
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return [self.list count];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [[[self.list objectAtIndex:section] objectForKey:#"list"] count];
}
- (UITableViewCell *) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSDictionary *person = [self.list objectAtIndex:indexPath.section];
NSDictionary *object = [[person objectForKey:#"list"] objectAtIndex:indexPath.row];
CustomTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:[CustomTableViewCell reuseIdentifier]];
if (cell == nil) {
cell = [[CustomTableViewCell alloc] initWithOwner:self];
cell.backgroundColor = [UIColor clearColor];
cell.userInteractionEnabled = NO;
cell.clipsToBounds = NO;
cell.labelName.clipsToBounds = NO;
[cell.contentView.superview setClipsToBounds:NO];
}
if(indexPath.row == 0){
cell.labelName.text = [person objectForKey:#"name"];
CGFloat height = [[self.labelHeights objectAtIndex:indexPath.section] floatValue];
cell.labelName.numberOfLines = (int)(height / self.fontSizeHeight);
cell.labelName.frame = CGRectChangeHeight(cell.labelName.frame, height);
}
else{
cell.labelName.text = #"";
}
CGFloat cellHeight = [self tableView:self.tableView heightForRowAtIndexPath:indexPath];
cell.borderTop.hidden = YES;
cell.borderBottom.hidden = YES;
cell.borderBottomSmall.hidden = NO;
if(indexPath.row == 0){
cell.borderTop.hidden = NO;
}
if(indexPath.row + 1 == [[person objectForKey:#"list"] count]){
cell.borderBottom.hidden = NO;
cell.borderBottom.frame = CGRectChangeY(cell.borderBottom.frame, cellHeight - 1.f);
cell.borderBottomSmall.hidden = YES;
}
cell.labelDate.text = [object objectForKey:#"date"];
cell.labelPremium.text = [[object objectForKey:#"premium"];
return cell;
}
-- PARTIAL ANSWER
I managed to create a hack, that makes multi-line UILabel visibile when scrolling bottom to up at some point:
- (void) scrollViewDidScroll:(UIScrollView *)scrollView
{
NSArray *cells = [self.tableView visibleCells];
UITableViewCell *cell = [cells objectAtIndex:0];
[cell.superview bringSubviewToFront:cell];
}
I noticed that the part of the UILabel is covered by a row thats below of the UILabels row and that hack makes it would be properly displayed. But it has a drawback, when scrolling slowly from bottom to top it generates a flicker when label is created (part of it should be visible before real creation of UILabel).
Up mentioned answers are not solutions, but "hacks".
In the cell == nil block should be only the initialization.
You should not add any subviews in cell in cellForRowAtIndexPath.
The reason is simple: I will reuse a cell with some labels already added and add a new label.
Either use the default cell.textLabel, either create a subclass for UITableViewCell, with a
-(void)setData:(dictionary or string)object;
and in implementation just set the proper data to proper UI controls.
Add/create controls either in init method in the subclass, or in IB/Storyboard.
Call the dictionary or string should be picked in correspondence to indexPath, so you will always get proper data for proper cell at proper indexPath.
Try This
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *cellId = #"cellId";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellId];
if (cell==nil)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellId];
}
for (UIView *subview in [cell.contentView subviews]) {
[subview removeFromSuperview];
}
/// your UI on cell goes here
return cell;
}
I need to show last cell record in tableview cell. Each time i will add new array in table view. After i will reload the tableview cell. Now its showing the record from first cell. Any one please advice to me how will show last cell .
// Customize the appearance of table view cells.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"TrackDataTimeCell";
TrackDataTimeCell *cell = (TrackDataTimeCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil)
{
#autoreleasepool {
[[NSBundle mainBundle] loadNibNamed:#"TrackDataTimeCell" owner:self options:nil];
cell = (TrackDataTimeCell *) cellChallengeFriends;
cellChallengeFriends = nil;
cell.selectionStyle = UITableViewCellSelectionStyleNone;
}
}
if (!cell) {
cell =(TrackDataTimeCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
UIActivityIndicatorView *activityIndicatorView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
cell.accessoryView = activityIndicatorView;
}
NSArray *tableData = [NSArray arrayWithObjects:#"Egg Benedict", #"Mushroom Risotto", nil];
NSArray *tableData1 = [NSArray arrayWithObjects:#"test1", #"test2", nil];
[cell.deleteButton addTarget:self action:#selector(deleteButtonAction:) forControlEvents:UIControlEventTouchUpInside];
cell.deleteButton.tag = indexPath.row;
cell.nameList.text= [tableData objectAtIndex:indexPath.row];
cell.marklist.text= [tableData1 objectAtIndex:indexPath.row];
return cell;
}
- (IBAction)addBillingItems:(id)sender
{
rowVal=rowVal+1;
[nameList addObject: self.codeAdd.text];
[marklist addObject: selectDate];
[tbl reloadData];
}
Please check the example image at http://postimg.org/image/g0dzs0r2p/
I have 5 cells. Now it is showing the first four. After I scroll it shows 5 but I want the last cell to show in first time. Please help me
you can use this to automatically scroll to the last inserted cell.
CGPoint bottomOffset = CGPointMake(0, self.tableView.contentSize.height - self.tableView.bounds.size.height + 150);
if ( bottomOffset.y > 0 ) {
[self.tableView setContentOffset:bottomOffset animated:YES];
}
The 150 value is to add extra space after the cell in tableView. Its optional i.e. you can use it if you want extra space after the cell
You have to set the tableview frame less.If you have iPhone tableview frame then set the frame as cgrectmake(0,0,320,430).If that will not solve your problem then add uitableview:viewforheaderinsection method.Just add uiview and set frame as cgrectmake(0,0,320,1).This will set the frame for the table as well as show all the cells.
There's an easy solution to your problem. Just use the tableView's method
[tbl scrollToRowAtIndexPath:[NSIndexPath indexPathForRow: rowVal inSection:0]
atScrollPosition:UITableViewScrollPositionBottom
animated:YES];
Just call it after [tbl reloadData]