I am making an coredata application in which I was instructed to make a tableview (filling form) with textfield in each row which I did successfully. Then I made a toolbar with previous and next button and add to textField's inputAccsoryView for navigating between the textfields of each cell. One textfield per row . I am also able to do the magic with previous and next button methods . Now the problem :
Suppose I have 3 sections . In first section 4 rows . In second section 6 rows and in the last section 1 row . Now when I start from the 1st row and presses next till the 11th row it is working properly but then I press next nothing happens as the first row is dequeued I get "nil" for it . The code I am using :
configuring the cell
if (cell == nil) {
NSLog(#"tag inside:%i",tag);
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1
reuseIdentifier:identifier] autorelease];
UITextField *theTextField = [[UITextField alloc] initWithFrame:CGRectMake(120, 10, 170, 25)];
theTextField.textAlignment = UITextAlignmentRight;
theTextField.delegate = self;
theTextField.tag = tag;
theTextField.inputAccessoryView = [self configureTheToolBar];
[theTextField setKeyboardAppearance:UIKeyboardAppearanceAlert];
[cell.contentView addSubview:theTextField];
[theTextField release];
}
cell.textLabel.text = rowLabel;
UITextField *textField = (UITextField *)[cell.contentView viewWithTag:tag];
textField.text = rowValue
I am providing tags for textfield like 601 ,602 etc .
Then getting the currentTextField tag
- (void)textFieldDidBeginEditing:(UITextField *)textField{
currentTextField = textField.tag;
}
then the next method
if(currentTextField == 611){
currentTextField = 601;
} else{
currentTextField = currentTextField + 1;
}
NSLog(#"current text fiedld = %i",currentTextField);
NSLog(#"text ; %#",[self cellForTag:currentTextField].textLabel.text);
UITextField *firstResponderField = (UITextField *) [[self cellForTag:currentTextField].contentView viewWithTag:currentTextField];
[firstResponderField becomeFirstResponder];
and the cellForTag method :
-(UITableViewCell *)cellForTag:(NSInteger)tag{
UITableViewCell *cell;
switch (tag) {
case 601:{
NSUInteger onlyRow[] = {0, 0};
NSIndexPath *onlyRowPath = [NSIndexPath indexPathWithIndexes:onlyRow length:2];
//[self.tableView scrollToRowAtIndexPath:onlyRowPath atScrollPosition:UITableViewScrollPositionTop animated:YES];
cell = [self.tableView cellForRowAtIndexPath:onlyRowPath];
break;
}
return cell
this code is working partially as once off screen cells get out of memory I can't reach textfield in that cell and next button stops working till the currentTextfield value reaches to any visiblerow . What could be the solution . my stack is over flowed. :)
Ok this thread does the trick but I don't know how . can anyone explain me how ?
Once a cell goes off screen it will no longer be accessible. Because of this you might want to grab the values of your text fields as soon as the user has finished editing them by wiring up the UIControlEventEditingDidEnd event:
[myTextField
addTarget:self
action:#selector(myTextChanged:)
forControlEvents:UIControlEventEditingDidEnd];
In your myTextChanged function you can store the values away, perhaps in a dictionary if you want to keep them keyed against your tags.
Related
I've got a UITableView with a list of 10 sections, each containing a textLabel.
I've limited the amount of text in each cell to 10 lines and put a button to allow to user to expand the text in the cell.
The problem is, I want to change the text on the button when it's clicked between "show more" (if cell is not expanded) and "show less" (if cell is expanded). How can I do that?
Right now I have this code in cellForRowAtIndexPath. It doesnt seem to work correct, sometimes the button displays "show less" when there are only 10 lines displayed.
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"ButtonCell"];
if (!cell) {
UIButton *readmoreButton = [UIButton buttonWithType:UIButtonTypeCustom];
[readmoreButton setTitle:#"Read More" forState:UIControlStateNormal];
[cell addSubview:readmoreButton];
}
else {
NSEnumerator *e = [cell.subviews objectEnumerator];
id object;
while (object = [e nextObject]) {
if ([object class]==UIButton.class && [((UIButton*)object).titleLabel.text isEqualToString:#"Read Less"])
((UIButton*)object).titleLabel.text = #"Read More";
}
}
The following method is called when the user clicks the button. The NSMutableArray ProposalInfo keeps track of which cell is/is not expanded.
- (void)toggleFullProposal:(id) sender {
// get cell
NSIndexPath *index = [self.tableView indexPathForCell:(UITableViewCell *)[sender superview]];
ProposalInfo *proposalInfo = [self.proposalInfoArray objectAtIndex:index.section];
if ([proposalInfo expanded]==YES) {
[proposalInfo setExpanded:NO];
[(UIButton *)sender setTitle:#"Read More" forState:UIControlStateNormal];
} else {
[proposalInfo setExpanded:YES];
[(UIButton *)sender setTitle:#"Read Less" forState:UIControlStateNormal];
}
// create index paths to reload
NSMutableArray *indexPathsToReload = [[NSMutableArray alloc] init];
[indexPathsToReload addObject:[NSIndexPath indexPathForRow:0 inSection:index.section]];
// refresh
[self.tableView reloadRowsAtIndexPaths:indexPathsToReload withRowAnimation:UITableViewRowAnimationNone];
}
When the button is clicked make sure that now you set the full text to the cell. And then only reload the cell and mind it you need to fix the height of label in layoutSubviews: and then return appropriate height for heightForRowAtIndexPath: as well.
((MyCell*)[tableview cellForRowAtIndexPath:buttonClickedindexPath]).fullText = myModel.fullText;
and in the layoutSubviews: method of the custom class cell expand the frame of the the label so as to fit the text inside, then return the appropriate height in heightForRowAtIndexPath: and that should do the trick.
i want to add textfield on tableview each and access the textfield's text by using their tag.
how can i do that see below image.
// To get the text from UITextField , add the bellow code to after you initialize the Textfield
[yourTextField addTarget:self action:#selector(getTextFieldValue:) forControlEvents:UIControlEventEditingDidEnd];
and this method
- (void) getTextFieldValue:(UITextField *)textField{
NSLog(#"GetTextvalue");
if (textField.tag == 0) {
NSLog(#"1--->%#",textField.text);
}else if(textField.tag == 1){
NSLog(#"2--->%#",textField.text);
}else if(textField.tag == 2){
NSLog(#"3--->%#",textField.text);
}
}
You have to customize your tableViewCell you can do that in your cellForRowAtIndexPath method.
While you create your cell in the method you can do following:
UITextField *textField = [[UITextField alloc] init];
// Set a unique tag on each text field
textField.tag = 101;
// Add general UITextAttributes if necessary
textField.enablesReturnKeyAutomatically = YES;
textField.autocorrectionType = UITextAutocorrectionTypeNo;
textField.autocapitalizationType = UITextAutocapitalizationTypeNone;
[cell.contentView addSubview:textField];
[textField release];
After that where every you want to access your text field you can do following:
UITextField *myTextField = (UITextField*)[cell viewWithTag:yourTag];
Hope this is what you are looking for..
Update
As you can see I assigned tag 101 as static. This can be any number but make sure that you don't have any other view with the same tag in the same cell. If you have in another cell than its not an issue.
Now at anyplace if lets say in didSelectRowAtIndexPath you can do following:
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
UITextField *myTextField = (UITextFiled*)[cell viewWithTag:101];
This way you will get your cell first. Now your cell has only one view with 101 tag so you can get your text field regardless you have same tag for other textField because the cell is not same for all.
Cheers
UITextField *myTextField = (UITextField*)[cell viewWithTag:yourTag];
myTextField.tag=indexPath.row;
This way assign 10 textfields tags.
After in textfield delegate method you can retrieve that text from the different 10 tags.
when i press the new button i need to add a textfield on last row of tableview.
can any one explain the flow.
Thanks in advance.
Just i tried this scenario.
Create a one Boolean variable like this
BOOL newButtonPress = NO;
then fallow the below code
-(IBAction)newButtonClicked:(id)sender{
if (self.newButtonPress == NO) {
//if new button press then newbuttpress is yes and reload the tableview
self.newButtonPress = YES;
[self .reloadTableview reloadData];
}
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
if(self.newButtonPress == YES)//creating noOfRows when newbutton pressed
{
return [self.itemNames count]+1;
}
else {
return [self.itemNames count];
}
}
Then add the below code into the cellForRowAtIndexPath method
//when new button is pressed adding new cell
if (self.newButtonPress == YES) {
if([self.itemNames count] == [indexPath row]){
cell.selectionStyle = UITableViewCellSelectionStyleNone;
//creating the textfield if new button is pressed
UITextField *extraField = [[UITextField alloc] initWithFrame:CGRectMake(80, 6, 100, 30)];
extraField.borderStyle = UITextBorderStyleNone;
extraField.textAlignment = UITextAlignmentCenter;
[extraField becomeFirstResponder];
[extraField setDelegate:self];
[cell addSubview:extraField];
[extraField release];
}
}
If you just want the text field to appear at the end of the table, why not consider using UITableview's tableFooterView property?
In that case you don't have to ensure that you return the UITextField in your delegate methods.
You should nearly be able to drag drop a new UITextField in your Tableview if you are using InterfaceBuilder or XCode 4.
I am very new to iphone coding,i want to share my thoughts. If that is wrong please ignore.
In cellForRowAtIndexPath method u need to set a textfield in
if(indexPath.row==numberOfRows-1) condition.
In button pressed method u can set textField.hidden=NO and in viewdidload textField.hidden=YES.
I have a UITableView with a dozen rows, each containing a UITextField.
By default the UITextField contains a placeholder value "Add Value" if the user hasn't previously edited the text field:
UITextField *textField = [[UITextField alloc] initWithFrame:CGRectMake(158, 6, 148, 24)];
NSString *strReplacement = [valueArray objectAtIndex:indexPath.row];
if (([strReplacement length] != 0) {
textField.text = strReplacement;
} else {
textField.placeholder = #"Add Value";
}
textField.delegate = self;
[cell addSubview:textField];
[textField release];
So far so good.
I've also added a UIButton to the footer of the UITableView.
What I want is to clear all the edited values and refresh all the UITextFields in the UITableView when the user clicks the UIButton.
I can easily enough remove all objects from the valueArray but I can't figure out how to refresh all the UITableView cells to reflect the changes.
Any help is appreciated.
lq
I believe what you're looking for is
[tableView reloadData];
Your solution feels weird. Filipe's right that the correct way to do it is with [wordsTableView reloadData], which will cause tableView:cellForRowAtIndexPath: to be called for each visible cell. That method is also called as you scroll through the table, so if reloadData isn't working, you're probably also going to end up with bugs with data not updating correctly as you change it and scroll. In your clearValues method, you're doing the same thing by calling tableView:cellForRowAtIndexPath:.
I think the real problem is in your tableView:cellForRowAtIndexPath: implementation. That method generally has 2 sections. First, you create or recycle a cell to get a reference with something like:
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier] autorelease];
}
Inside that if statement is generally the only place you should be adding subviews to your cell. If dequeueReusableCellWithIdentifier: returns a cell, it should already have the subview.
Then, after that if statement, you populate or update the contents of the subviews. The problem with your original code is that it's populating the text field and adding it as a subview, assuming there isn't already a text field in the cell. So your tableView:cellForRowAtIndexPath: should look something more like this:
int textFieldTag = 100;
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier] autorelease];
UITextField *textField = [[UITextField alloc] initWithFrame:CGRectMake(158, 6, 148, 24)];
[textField setTag:textFieldTag];
[textField setDelegate:self];
[cell addSubview:textField];
[textField release];
}
UITextField *textField = [cell viewWithTag:textFieldTag];
NSString *strReplacement = [valueArray objectAtIndex:indexPath.row];
if (([strReplacement length] != 0) {
textField.text = strReplacement;
} else {
textField.placeholder = #"Add Value";
}
It looks like you may be setting the textField's tag value to the row number, presumably so you can use it in the UITextFieldDelegate. That could also lead to bugs, as if the cell from row 1 is recycled by dequeueReusableCellWithIdentifier: and becomes row 12, it's going to have an unexpected tag value. Even if it doesn't happen now, it's a bug waiting to happen, and will be tricky to troubleshoot.
Filipe's solution should also work, however, calling reloadData should be avoided wherever possible as calling this method has a high performance overhead.
You need some class that has a reference to both the UIButton instance as well as all the instances of the UITextField that you have on the screen/in the table. Sounds like the perfect job for your UITableView controller subclass!
In your code above, why don't you also add each UITextField that you create to an NSArray of text fields that lives in your UITableView controller? Then when the user presses the UIButton, the action can call some method in your controller class, which loops through all the UITextField elements in the NSArray setting the text property of each instance to #"".
Warning: If you're reusing cells then you may have to ensure that the controller's NSArray of UITextFields is being updated properly.
After a few hours of trial and error, I came up with this solution. The UIButton "Clear All" invokes the following method:
- (IBAction)clearValues:(id)sender {
// count the number of values in the array (this is the same as the number of rows in the table)
int count = [valueArray count];
// remove all values from the array (deletes any user added values):
[self.valueArray removeAllObjects];
UITableViewCell *cell;
UITextField *textField;
// loop through each row in the table and put nil in each UITextField:
for (NSUInteger i = 0; i < count; ++i) {
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:i inSection:0];
cell = [self.wordsTableView cellForRowAtIndexPath:indexPath];
// NOTE: Make sure none of your tags are set to 0 since all non-tagged objects are zero.
// In table construction, your textFieldTags should be: textField.tag=indexPath.row+1;
textField = (UITextField*)[cell viewWithTag:i+1];
textField.text = nil;
}
}
UPDATE 4.0
Seems like iOS 4.0 changed something here. Same code producing incorrect backgrounds for section header in the described scenario is working with 4.0 according to my first quick check!
Original
I have a UITableView grouped style with custom header and footer view. Inside the footer I put a UILabel and a UIButton.
Clicking on the button hides or show some rows, updates the UILabel in the footer view and finally resizes footer view.
Basically everything is working fine. BUT the text ion the label is not updated on the screen. It is updated in the UILabel text property, but only if I scroll the section footer out of the visible area and scroll it back, it is updated. So it's a typical redraw problem here of the UITableView.
I tried every method to force update like needsLayout etc. Nothing helped.
I have seen some related questions but with some different behaviour and no solution. Any help/ideas?
Thanks, Gerd
UPDATE:
My problems occurs with section footer, so here is my viewForFooterInSection.
Basically I want to collapse/expand a section, but not completely (that was an easy thing) instead only the empty cell (ItemSize empty). The footerView is large if it is collapsed and will shrink if it is expanded. Furthermore the label text will change.
- (UIView *)tableView: (UITableView *)tableView viewForFooterInSection: (NSInteger)section{
NSLog(#"viewForFooterInSection section:%i", section);
UIButton *myView;
UILabel *label;
if ([[[self.sectionStatus objectAtIndex:section] valueForKey:#"collapseStatus"] isEqual:#"collapse"]){
myView = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, 320, 52)];
[myView setBackgroundImage:[UIImage imageNamed:#"ItemViewFooter.png"] forState:UIControlStateNormal];
label = [[UILabel alloc] initWithFrame:CGRectMake(20, 32, 300, 20)];
label.text = NSLocalizedString(#"list_expand",#"");
} else { //is expanded
myView = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, 320, 21)];
[myView setBackgroundImage:[UIImage imageNamed:#"ListCollapseExpand.png"] forState:UIControlStateNormal];
label = [[UILabel alloc] initWithFrame:CGRectMake(20, 1, 300, 20)];
label.text = NSLocalizedString(#"list_collapse",#"");
}
myView.tag=section;
[myView addTarget:self action:#selector(collapseExpandAction:) forControlEvents:UIControlEventTouchUpInside];
myView.backgroundColor = [UIColor clearColor];
myView.adjustsImageWhenHighlighted = NO;
myView.showsTouchWhenHighlighted = YES;
label.textColor = FONTCOLOR;
label.font = [UIFont systemFontOfSize:14];
label.numberOfLines = 1;
label.textAlignment = UITextAlignmentCenter;
label.backgroundColor = [UIColor clearColor];
[myView addSubview:label];
return myView;
};
In the button action method I store status of section collapse/expand and the number of displayed rows. Than I delete or insert rows. (It has to be with insert/delete because I need the animation).
- (void) collapseExpandSection: (NSInteger) section{
NSMutableArray *paths = [NSMutableArray arrayWithCapacity:10];
NSInteger row;
NSInteger numberOfDisplayedItems=[[[self.sectionStatus objectAtIndex:section] valueForKey:#"numberOfDisplayedRows"] intValue];
id <NSFetchedResultsSectionInfo> sectionInfo = [[fetchedResultsController sections] objectAtIndex:section];
NSInteger numberOfAllItems=[sectionInfo numberOfObjects];
Item *tmpItem=nil;
NSSet *itemsWithSizes=nil;
//filter not used cells
for ( row = 0; row < numberOfAllItems; row++ ) {
tmpItem=[fetchedResultsController objectAtIndexPath:[NSIndexPath indexPathForRow:row inSection:section]];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"itemSize != nil"];
NSSet *itemsWithSizes = [tmpItem.itemSizes filteredSetUsingPredicate:predicate];
if ([itemsWithSizes count]==0){
[paths addObject:[NSIndexPath indexPathForRow:row inSection:section]]; //all unused cells
};
}
if (numberOfDisplayedItems == numberOfAllItems){ //currently all shown => Collapse
[self.tableView beginUpdates];
[[self.sectionStatus objectAtIndex:section] setValue:[NSNumber numberWithInt:(numberOfDisplayedItems-[paths count])] forKey:#"numberOfDisplayedRows"];
[[self.sectionStatus objectAtIndex:section] setValue:#"collapse" forKey:#"collapseStatus"];
[self.tableView deleteRowsAtIndexPaths:paths withRowAnimation:UITableViewRowAnimationFade];
[self.tableView endUpdates];
} else { //Not all shown so expand with the unused cells
[[self.sectionStatus objectAtIndex:section] setValue:[NSNumber numberWithInt:(numberOfDisplayedItems+[paths count])] forKey:#"numberOfDisplayedRows"];
[[self.sectionStatus objectAtIndex:section] setValue:#"expand" forKey:#"collapseStatus"];
[self.tableView beginUpdates];
[self.tableView insertRowsAtIndexPaths:paths withRowAnimation:UITableViewRowAnimationFade];
[self.tableView endUpdates];
}
return;
};
Doing all this works fine in general. After the blocks begin/endupdate the viewForFooter is called for every section and the label text is set correct in the property. However the display doesn't update correctly. As soon as a redisplay is forced (srolling out- scrolling in) the display is OK.
There 2 problems.
First problem is that section footer not updated.
Try call [tableView reloadData] or [tableView reloadSections:sections withRowAnimation:UITableViewRowAnimationFade] after your update (may be with dalay).
Second problem is memory leaks in myView and label.
Also why do you use label when you can use button's internal label?
P.S. Don't allocate UIButton object directly because it is factory. Call [UIButton buttonWithType:UIButtonTypeCustom] instead.
Upd: Another way to update is to update footer directly by accessing footer views.
- (void) collapseExpandSection: (NSInteger) section{
Check that section is actualy your button
- (void) collapseExpandSection: (UIButton*) sender{
// Do update of sender here
// Do other stuff
}
Also you can try next trick: create UIView object in delegate, add your button and label on it and return instaed of buttom view itself.
My problem was that I was expanding the Section header to show a search bar, but it wouldn't redraw the view until I scrolled the UITableView.
I had my own SectionHeader class that subclassed UIView and controlled the searching stuff.
After my animation, I just used this to force an update. It's not pretty but it works.
CGPoint point = CGPointMake(((UIScrollView *)self.superview).contentOffset.x,
((UIScrollView *)self.superview).contentOffset.y+1);
[((UIScrollView *)self.superview) setContentOffset:point animated:NO];
point = CGPointMake(((UIScrollView *)self.superview).contentOffset.x,
((UIScrollView *)self.superview).contentOffset.y-1);
[((UIScrollView *)self.superview) setContentOffset:point animated:NO];
Basically force the UITableView to scroll down 1 pixel and up 1 pixel.
I had an issue just like this where I wanted to update the section header after inserting a new row. I found that calling tableView reloadSections() method with an animation setting of .None after I call the insertRows method worked for me (both calls in the same tableView update block). I got the insert animation I wanted and also the section header was updated.