Okay I know I'm running up against my limits of understanding as regards objective-c, cocoa, xcode, and blah blah and so on. But here's what I'm trying to do:
I have a tableview in a viewcontroller. The tableview's delegate is the viewcontroller. Viewcontroller has an outlet to the tableview. The table is put together using custom cells (with IB xib) and data from an xml file. In the custom cell there are two buttons - and when the cell is created the button action is added as an addTarget to self (the viewcontroller) which then goes to an action. The viewcontroller button action method gets the row of the button pressed in the table and then changes the cell's text and the button's title.
But of course when I scroll that particular cell out of view and back into view it's been reset to the beginning state. I assume this has to do with cell dequeueing etc. Any ideas?
here's the relevant bits and code:
custom cell: has outlets to buttons and textlabel.
cell create code in the datasource cellForRowAtIndexPath...
{
static NSString *dialogueCellIdentifier = #"dialogueCellIdentifier";
dialogue_cell *cell = (dialogue_cell *)[tableView dequeueReusableCellWithIdentifier:dialogueCellIdentifier];
if (cell == nil) {
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"dialogue_cell" owner:self options:nil];
cell = [nib objectAtIndex:0];
[[cell lButton] addTarget:self action:#selector(lButtonPressed:) forControlEvents:UIControlEventTouchUpInside];
}
[[cell lButton] setTag:[indexPath row]];
NSString *row = [NSString stringWithFormat:#"%i",[indexPath row]];
NSString *en = [[self.dataArray objectForKey:row] valueForKey:#"en"];
cell.mainText.text = en;
return cell;
}
and the lButton method...
NSIndexPath *thisCellPath = [NSIndexPath indexPathForRow:[sender tag] inSection:0];
dialogue_cell *thisCell = (dialogue_cell *)[self.dialogueTable cellForRowAtIndexPath:thisCellPath];
NSString *row = [NSString stringWithFormat:#"%i",[sender tag]];
if ([thisCell.languageButton.currentTitle isEqualToString:#"en"]) {
[thisCell.languageButton setTitle:#"zc" forState:UIControlStateNormal];
thisCell.mainText.text = [[self.lineArray objectForKey:row] valueForKey:#"lineText_zc"];
} else {
[thisCell.languageButton setTitle:#"en" forState:UIControlStateNormal];
thisCell.mainText.text = [[self.lineArray objectForKey:row] valueForKey:#"lineText_en"];
}
so two questions:
1. is there a way to make the cell retain what is in the label and button name even though it's scrolled offscreen?
2. is there a way to set the label and button name within the cell custom class instead of sending the button action to the viewcontroller that the table is in?
Thanks!
It does not really have anything to do with the dequeueing of cells but the data you are displaying in the cellForRowAtIndexPath. Every time a cell is going to be displayed, this method is called.
I assume you are changing the language of the cell's text on button click. But every time the cell is redrawn you are redisplaying the english text. One way to "retain" the cell's state would be to create an array for the languages being used on each cell and based on the array's value for a particular row, populate the cell's text. You will just have to maintain the state of the cell in the array.
OK i had this problem too, took me awihle but I figure it out, the cell queue is they key here, when you are trying to get your cell back your are using this
NSIndexPath *thisCellPath = [NSIndexPath indexPathForRow:[sender tag] inSection:0];
dialogue_cell *thisCell = (dialogue_cell *)[self.dialogueTable cellForRowAtIndexPath:thisCellPath];
When the cell is off the view, the table view unloads this cell and instead you get a cell thats in the original state (or prolly nil) when you call this because its n ot longer part of the table view until the cells come back into the view screen.
What you have to do is since you a re enqueuing your cells, I am going to assume you are doing this correctly and you are giving a different name to each cell, instead of doing what you did above you must get your cell like so
cell=[tableView dequeueReusableCellWithIdentifier:dialogueCellIdentifier];
This will give you your cell with the state it was in when the user modified it...Hope this helps!
As for your second question, you can just set the target to be the cell instead of the view controller, this will call that action inside your custom cells class..
Related
I have a problem, setting a button to a UITableviewCell.
After viewDidLoad, the button is on the right place. But when I am scrolling down, the button is anyplace else.
Here is my code, I hope you can help me.
Thanks In Advance.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
}
if (indexPath.section == 0 && indexPath.row == 0 && _isAddImageViewLoad == NO) {
// Add Image Button
UIButton *addImage = [UIButton buttonWithType:UIButtonTypeCustom];
UIImage* image = [UIImage imageNamed:#"AddImage#2x"];
addImage.frame = CGRectMake(110.0f, 10.0f, 110.0f, 110.0f);
[addImage setImage:image forState:UIControlStateNormal];
[cell.contentView addSubview:addImage];
_isAddImageViewLoad = YES;
} else {
NSDictionary *dictionary = [_items objectAtIndex:indexPath.section];
NSArray *array = [dictionary objectForKey:#"data"];
NSString *cellValue = [array objectAtIndex:indexPath.row];
cell.textLabel.text = cellValue;
}
return cell;
}
It is because you are reusing the cells, and the button is getting placed when it shouldn't an easy solution in your else section. Write addImage.hidden = YES; and in your if statement put addImage.hidden = NO;
Just a couple things. If you use "AddImage" it will use the "AddImage#2x" automatically if it's a retina display. I don't think that will solve your issue but it could be causing weirdness.
When a table view cell is scrolled off the view it is "recycled" in a sense. It appears like you are using a bool to exclude the original cell from being loaded again with a button. You may want to use a header to hold your button if you always want it at the "top". You may also want to verify that the button is being removed when the cell is reused. if its not it will show up in the next row that reuses that cell.
On a side note... Buttons don't usually work very well in table view cells because they handle touches in very different ways. It's quite a bit of modification to get them to feel natural but that's another matter.
Hope that helps!
The problem is because of cell reuse. You need to put some code in the else clause to delete the button if it exits. One way to do this, would be to give your button a tag, like:
addImage.tag = 10;
Then in your else clause:
}else{
if (cell viewWithTag:10) [[cell viewWithTag: 10] removeFromSuperview];
...
The problem is because of the dequeue for the cells. The first time the tableview creates the cells, all the cells run through the
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
code. But when the section 0 row 0 is moved off the screen, that cell is pushed into the cell reusable queue.
Now when your tableview needs to display section 0 row 0, it will get a cell from the reuse queue. you will not get the same cell as the first time. So now you might have 2 cells with the button.
What you should do is have different CellIdentifier for section 0 row 0 , and all other sections and rows. Also create the button when creating the cell. So after the first time the tableView creates the cell, you will not be creating the the button everything.
Look at this line of code:
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
This line of code means the UITableViewCells are not created every time. They are re-used as you scroll up and down. Using the code you have above, the UIButton will be created in the correct spot, but then as the cells are re-used, it will create the button in random spots.
One quick way to solve the problem, change the above line of code to simply
UITableViewCell *cell;
e.g as you are watching the tableView having cell with plus Button, when I press that button it hides the current button but when I scroll the tableview Some other button on other customCells are also get hide but I didn't hide them. please help me out. How to fit this problem.
The UITableView is caching the cells, which means it doesn't store all cells at all time, which is brilliant in terms of memory. The problem with it, that it has no reference to the value stored in that cell when the cell is reused. What is often done, is that all values stored in a dynamic cell is stored in separate NSMutableArrays.
For your problem, you could add an array with the boolean values indicating whether they are hidden or not, and read from that in the tableView:cellForRowAtIndexPath: delegate method with
cell.hidden = [[self.yourArray objectAtIndex:indexPath.row] boolValue];
And in the button callback method you should change the hiddenproperty as well as updating the value in the array.
i'll do it as follow:
first you have to track the button's state:
Shown
or
Hidden
this is done by holding the state in an NSMutableArray
in the viewDidLoad method add the following
NSMutableArray *shownButtons = [[NSMutableArray alloc] init];
then in your tableView:cellForRowAtIndexPath do the following
NSString *tmpIndexPathString = [NSString stringWithFormat:#"%d",indexPath.row];
if ([shownButtons containsObject:tmpIndexPathString])
{
[cell.myButton setHidden:YES];
}
else
{
[cell.myButton setHidden:NO];
}
In the tableView:cellForRowAtIndexPath: delegate method you need to loop through all the visible cells:
for (UITableViewCell *cell in [self.tableView visibleCells]) {
// now you have a cell that you can update
}
You also have to remember to update your data source so scrolling the table will update the cells accordingly and not show the plus button. If you're updating your data source what you can do is reload the cell in the above for loop for example.
I believe I am incorrectly implementing cellForRowAtIndexPath. I have a UISlider that is hidden by default and appears when a button is pressed in the table cell. When I press the button in the first cell,the slider appears not only in the first cell, but in every third cell when I scroll down. I am currently avoiding this by reseting the slider's hidden property to YES in cellForRowAtIndexPath. I also do this for other views in the cell I need hidden by default. This creates a new issue when I scroll back up to the first cell, the slider is hidden because the property is reset in cellForRowAtIndexPath. This leads me to believe I'm doing something wrong.
Here is my code:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
SongsCustomCell *songCell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (songCell == nil) {
songCell = [[SongsCustomCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
NSLog(#"new cell created");
}
NSDictionary *dictionary = [parseTrackArray objectAtIndex: indexPath.row];
NSString *trackTitle = [dictionary objectForKey:#"trackTitle"];
NSString *trackLink = [dictionary objectForKey:#"trackStreamLink"];
songCell.trackLinkString = trackLink;
songCell.trackTitleString = trackTitle;
[songCell.trackTitleLabel setFont:[UIFont fontWithName:#"Calibri" size:22]];
songCell.trackTitleLabel.text = [NSString stringWithFormat:#"%#", trackTitle];
songCell.playButton.hidden = NO;
songCell.playbackSlider.hidden = YES;
songCell.playerHasLoaded = NO;
songCell.moviePlayer.view.hidden = YES;
return songCell;
}
You need to store the hidden / unhidden status of your slider in your data model somewhere, and then set the slider's visibility appropriately from that in cellForRowAtIndexPath. If only one slider is visible at once, you can store the index path as an ivar in your view controller, if not then you will need to have another key in your dictionary holding an NSNumber bool or something.
Your problem is that your cells are being reused when you are scrolling down. So when your slider is visible for a cell thats is scrolling out of view, that cell will be reused and thus the slider is still visible.
To avoid this implement prepareForReuse in your custom cell to reset your slider as soon as your cell is being reused.
Next to that you still need to store the state of the slider in your model, so you are able to restore that state once that object is coming back in view.
Nevertjeless its a good practice to set the default state of your custom cell in prepareForReuse
I have table view with UIButton , UILable and UITextfield
I have wrote in cellForRowAtIndexPath:
UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:CellIdentifier];
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
[cell addSubview:button];
[cell addSubview:lable];
[cell addSubview:textfield];
I have set the text on lable and textfield are working properly.
When i change the text on textfield are also working properly.
And i have other button for save data - it's a out side of tableview but in the same view.
My Question is when i click save button i want to get text of tableview's textfield.
If you have one or two textfields create it in the IB but outside the currentview, hook to outlet, then use the outlet to save the text .this is not a good idea if your table view containing many rows, in that case consider creating custom cell
Check this answer
Do you want the text in every text field or a single row's?
Also, the code you use to create your table view cells isn't doing what you want it to do. First you are trying to dequeue a reusable cell, which is great. But you then immediately overwrite that reused cell by alloc/initing a brand new cell every time. Only if cell == nil should you alloc/init a new one.
All of the textFields? I would recommend adding the textFields to a mutableArray as they are created, creating another mutableArray to store the textField text, then on button push, calling:
for(UITextField *textField in myArray){ [myTextArray addObject:textField.text];}
Then you could save the array, or whatever. If there's only one textField, how you access it will depend where it's declared, etc.
I have a tableView, and in that i have a UISegmentController. So each cell will have a UISegmentController each.
When the user clicks on a Segment in the UISegmentController (of a given cell), how do i know which cell was clicked? and i need to NSLog the title of that cell, how can i do this (note: the user will only be clicking on the UISegmentController of the cell, and not the cell it self)
The following code is the method that will be called when the UISegmentController is clicked;
-(void)segmentOfCellPressed:(id)sender{
}
Try this:
-(void)segmentOfCellPressed:(id)sender{
UISegmentController *segmentController = (UISegmentController *)sender;
YourCellClass *cell=(YourCellClass *)segmentController.superview.superview; // your clicked cell
// or a little bit more verbose but imho easier to understand:
// UIView *contentView = [segmentController superview];
// YourCellClass *cell = (YourCellClass *)[contentView superview];
NSIndexPath *path = [yourTableView indexPathForCell:cell]; //indexPath of clicked cell
}
Should be pretty easy. UISegmentedController derives from UIControl which derives UIView. UIView objects have a tag property. When you create each UISegmentedControler, give each one a unique tag value. You will need to manage which tag values separately in a dictionary perhaps.
-(void)segmentOfCellPressed:(id)sender
{
UISegmentedController *segmentedController = (UISegmentedController *)sender;
UITableViewCell *cell = [self cellFromTag:segmentedController.tag];
}
You will need to create a method called cellFromTag that will return the cell from the tag value of the UISegmentedController. If you don't want the cell but something else, then you can return that instead.
You could set the tag property of the segment controller to be the index of the cell in the cellForRowAtIndexPath then in the selector you could query the segment controller and then get the cell by the tag:
UISegmentController *segmentController = (UISegmentController*)sender;
int row = segmentController.tag;
Hopefully that helps.
In the cellForRowAtIndexPath method where you add the UISegmentController to the cell do the following:
segment.tag = indexPath.row;