I have my own TableViewCell class which inherit UITableViewCell. On my cell nib file, I have put a image in my TableViewCell. (The image does not fully occupy the whole cell area, there are spaces around image)
Then, I would like to implement a touch feedback feature that when user touch the image in the cell, the image will be replaced by another image.
I tried to do it in tableView:didSelectRowAtIndexPath: method :
- (void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
//my TableViewCell
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
//New image to replace the current one
UIImage* bg = [CCTheme imageNamed:#"green_btn_bg"];
UIImageView* bgView = [[UIImageView alloc] initWithImage:bg];
cell.selectedBackgroundView = bgView;
...
But it does not work at all. So, how can I implement this touch feedback feature?? That's when user finger touched the image in cell, the image get changed to another one.
Create a UIImageView property in your custom UITableViewCell class as an IBOutlet then set the image on that property from the cellForRowAtIndexPath: rather than the background property.
cell.yourCustomImageView.image = bgView;
Or add the UIImageView to the current generic UITableViewCell like below.
with your Current cell
[cell addSubview:bgView];
In didSelectRowAtIndexPath: you have to first change your data model (indicate that the status of the cell has changed).
You set the appropriate image cellForRowAtIndexPath:. (I.e. you check what the status is and provide the correct image.)
To update the cell, call reloadRowsAtIndexPaths:.
Explanation
You should not store the state of your cell in some view of the cell. E.g. if the different image represents some kind of selection, your array or other data structure that feeds your table view should keep track of which row is in this state.
Each time the cell has to be regenerated, cellForRowAtIndexPath is called. (This could be because the cell becomes visible, or because you explicitly update it.) This is the best place to check for the state information and display the cell accordingly.
I would recommend hooking a gesture recognizer to the UIImageView within your custom cell's init method:
// Add recognizer for tappable image
UITapGestureRecognizer *tapRecognizer = [[UITapGestureRecognizer alloc]
initWithTarget:self action:#selector(imageViewTapped:)];
[tapRecognizer setNumberOfTouchesRequired:1];
[tapRecognizer setDelegate:self];
self.tappableImageView.userInteractionEnabled = YES;
[self.tappableImageView addGestureRecognizer:tapRecognizer];
Then the handler:
- (void)imageViewTapped:(id)sender {
NSLog(#"image tapped");
self.tappableImageView.image = [UIImage imageNamed:#"some_different_image.png"];
}
Also, don't forget to have you custom cell declaration decorated like so:
#interface CustomTableViewCell : UITableViewCell <UIGestureRecognizerDelegate>
In the cellForRowAtIndexPath: method you need to ensure that the code in your init method is being fired so that the tapRecognizer is added.
Good luck!
[EDIT]
Depending on how you create your cell with the custom XIB you may not need this, but in my case I needed to explicitly call a method to initialize the state of the UI in the table cell. The way I do this is offer an initState method on the custom table view cell:
- (void)initState {
// Do other things for state of the UI in table cell.
// Add recognizer for tappable image
UITapGestureRecognizer *tapRecognizer = [[UITapGestureRecognizer alloc]
initWithTarget:self action:#selector(imageViewTapped:)];
[tapRecognizer setNumberOfTouchesRequired:1];
[tapRecognizer setDelegate:self];
self.tappableImageView.userInteractionEnabled = YES;
[self.tappableImageView addGestureRecognizer:tapRecognizer];
}
Then in cellForRowAtIndexPath I make sure to call initState on my table cell after it has been created:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
CustomTableViewCell *cell = (CustomTableViewCell*)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
UIViewController *temporaryController = [[UIViewController alloc] initWithNibName:#"CustomTableViewCell" bundle:nil];
// Grab a pointer to the custom cell.
cell = (CustomTableViewCell *)temporaryController.view;
[cell initState];
}
return cell;
}
Related
in my table view, i placed a custom "Add to Favs" button in every cell to enable the user to copy the exact cell content to a second tableview controller. when you hit the "Add to Favs" button an alert view shows up to ask if you want to copy the cell and paste it to the second view controller or not. now there are two things.
1- is there a way to delete the "Add to Favs" button permanently from that cell if "OK" is selected from the alert view to indicate that the cell is copied and pasted to the second tableview? - so the user won't be able to add the cell content over and over again.
2- this is the bigger question: how would i copy and paste the cell content to the secondtableview controller with "Add to Favs" click?
here is the way my cells re configured:
- (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];
NSString* letter = [letters objectAtIndex:indexPath.section];
NSArray* arrayForLetter = (NSArray*)[filteredTableData objectForKey:letter];
Songs* songs = (Songs*)[arrayForLetter objectAtIndex:indexPath.row];
cell.textLabel.text = songs.name;
cell.detailTextLabel.text = songs.description;
CGSize itemSize = CGSizeMake(50, 50);
UIGraphicsBeginImageContextWithOptions(itemSize, NO, UIScreen.mainScreen.scale);
CGRect imageRect = CGRectMake(0.0, 0.0, itemSize.width, itemSize.height);
[cell.imageView.image drawInRect:imageRect];
cell.imageView.image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
UIButton *addtoFavsButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
addtoFavsButton.frame = CGRectMake(200.0f, 5.0f, 105.0f, 70.0f);
[addtoFavsButton setImage:[UIImage imageNamed:#"fav.png"] forState:UIControlStateNormal];
[addtoFavsButton setTintColor:[UIColor whiteColor]];
[cell addSubview:addtoFavsButton];
[addtoFavsButton addTarget:self
action:#selector(addtoFavs:)
forControlEvents:UIControlEventTouchUpInside];
return cell;
}
- (IBAction)addtoFavs:(id)sender
{
UIAlertView *alert = [[UIAlertView alloc]initWithTitle:#"Dikkaaaat!"
message:#"Şarkıyı Favori Akorlarınıza Alıyorsunuz..."
delegate:nil
cancelButtonTitle:#"Cancel"
otherButtonTitles:#"OK", nil];
[alert show];
}
Considering the data is in the app only, and considering you are using a regular table cell, not a custom one, what I would recommend is:
Make a new simple array in your app called "myFavorites" or whatever, which will hold just a list of numerical values.
When the user confirms to add to favorites, take the current section and row indexes from the songs array, and store in this new array.
In your first view controller, add to your "cellForRowAtIndexPath" a simple check to see if the song for that row exist in the new array with.
Something like:
if([[myFavorites objectAtIndex:indexPath.section] containsObject:[NSString stringWithFormat:#"%ld", (long)indexPath.row]]){
// Don't make button because is already a favorite.
}else{
// Make button because not yet a favorite.
}
Your second table view will be almost identical, except near the top of your "cellForRowAtIndexPath", do a similar check:
if([[myFavorites objectAtIndex:indexPath.section] containsObject:[NSString stringWithFormat:#"%ld", (long)indexPath.row]]){
// Is a favorite, so put regular cell stuff here to have the cell show
....
}else{
// Not a favorite, don't generate cell.
cell.visible = NO;
return cell;
}
There are other things you can do but it would require changing your setup from a regular cell to a custom cell with a new class and properties blah blah, so is a little more complicated to implement, but would still amount to practically the same output/processing.
Firstly, you aren't copying and pasting - you're referencing. Specifically you're saying that some of your songs are special.
Secondly, the user should be able to tell if they're special, and be able to toggle it. Dispense with the alert, just show the state on the button and toggle the special setting on and off as its tapped.
Now, the second table view works in th same way as the first, it just filters the songs to decide what to display.
You need to decide how to mark each song as special, probably by adding a Boolean property to the class and saving it with the rest of the data. An alternative would be to have a separate list of song IDs (or unique names).
If you want to change you cell, you need make the custom view as a #property.
#property (nonatomic, strong) UIView *cellContent;
then setup in cellForRowAtIndexPath
- (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];
}
[cell.contentView addSubview:[self setupCellContentView:indexPath]];
return cell;
}
- (UIView *)setupCellContentView:(NSIndexPath *)indexPath
{
if (!_cellContent) {
_cellContent = [[UIView alloc] initWithFrame:CGRectZero];
}
// do sth like you did in cellForRowAtIndexPath//
return _cellContent;
}
then you can manipulate the cell.contentView in alertView:clickedButtonAtIndex:
and pass the view #property (nonatomic, strong) UIView *cellContent to next viewController
ps:After you remove the "Add to Favs" button from the _cellContent,don't forget
[_cellContent removeFromSuperview];
[tableView reloadData];
To answer the first question, YES you can remove the "Add to Favs" button from cell object but it will create issue with you rest of the songs display because of the fact that UITableView reuses the cell objects. So if you mark a cell favourite and remove it's button, any upcoming row which will reuse this object will not be able to show that favourite button to user. So you better discard this approach.
To maintain or copy the content of a cell, which is nothing but a reference to a Songs object in you master array, you can create another array of fav songs and add those Songs objects to this array. Now you can remove this song object from your master array and reload the table data. This approach is suitable if you are using 2 different table views for data display.
If you are displaying both type of songs in one table view by indicating through "Fav Icon", then you should add a BOOL property to Songs model object and set it when you confirm with alert view.
I want to know that how to get selected cell's row index on selection of cell. But how to do it without usingUITableViewDelegate methods.
Please tell me.
I have search lot but not getting solution so please if anyone know tell me.
Please shared the viewpoints regarding it.
Thanks in Advance...!!!
In that case, your interviewer wanted to know how can you implement the delegation yourself...
To achieve that, Create a custom class "YourTableViewCell" extended fromUITableViewCell and use this class object to be returned in -tableView:cellForRowAtIndexPath:
Write a protocol "CellSelectionProtocol" with a method -
-(void) cellSelected: (YourTableViewCell *) cell;
Now delegate this protocol to your ViewController that has your TableView
and define the implementation of the method as below -
-(void) cellSelected: (YourTableViewCell *) cell
{
NSIndexPath *selectedIndexPath = [_yourTableView indexPathForCell: cell];
}
My answer would be this if it was an interview, and I am pretty sure it would be accepted.
But for a good architecture... the protocol & delegate implementation should be in two levels, like ->
YourTableViewCell -> delegates -cellSelected: -> YourTableView -> delegates -tableView:didSelectRowAtIndexPath: -> YourViewController
Please see: Your interviewer just wanted to know how you create delegations manually, instead of using default UITableViewDelegates.
EDIT # Unheilig
I mean in 2 levels because, the selection of a UITableViewCell has to be delegated to the UITableView and not directly to the controller for the following reasons
UITableViewCell is subview of UITableView
There can be multiple UITableView in a controller. So if you delegate cell selection directly, how will you tell the controller that cell got selected for which UITableView object?
Also UITableView might have to do other processing with other UITableViewCell, Like if selected and changes backgroundColor, the previous selected should get deselected and get default backgroundColor. Or add to the array of selected cells, if multiple selection is enabled.
There are many such similar architectural necessities that make me say - "But for a good architecture... the protocol & delegate implementation should be in two levels, like ->"
I hope that is pretty explanative now...
There is no way to get selected cell row index with out using tableview delegate methods.
when you click on tableview, didSelectRowAtIndexPath called and get the tableview cell index.
There is one way to do this, but it is not correct procedure to get tableview cell index. Create a button on tableviewcell and pass the indexvalue as sender tag to button action. But need to click only on that button.
Answer edited:
Create a transparent button on tableview cell in cellForRowAtIndexPath method and pass the cell index to button tag.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] ;
}
UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
button.frame = CGRectMake(0, 0, cell.frame.size.width, cell.frame.size.height);
button.backgroundColor = [UIColor colorWithWhite:1.0 alpha:0.0];
[button addTarget:self action:#selector(buttonClicked:) forControlEvents:UIControlEventTouchUpInside];
button.tag = indexPath.row;
[cell addSubview:button];
cell.textLabel.text = [NSString stringWithFormat:#"%#",[numberArray objectAtIndex:indexPath.row]];
return cell;
}
-(void)buttonClicked:(id)sender
{
NSLog(#"%ld",(long)[sender tag]);
}
I am creating an app in where I need to add a bunch of sliders in a Dynamic UITableView. I added the slider like this:
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:#"Cell" forIndexPath:indexPath];
UISlider *slider = [[UISlider alloc]init];
//ALL OTHER CODE
[cell addSubview:slider];
return cell;
}
Now the slider is added to the UITableView but the if I changed the value of the first slider another slider changes with it.I know this is something to do with dequeing the cell but how do I fix it?
EDIT:
I tried #daveMack answer like this:
CustomCell.m:
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
// Initialization code
self.checkBox = [[M13Checkbox alloc]initWithTitle:#"Checkbox!"];
self.checkBox.checkAlignment = M13CheckboxAlignmentLeft;
[self addSubview:self.checkBox];
}
return self;
}
Cell For Row At Index Path:
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
CheckboxCell *cell;
cell = [self.tableView dequeueReusableCellWithIdentifier:#""];
if (!cell) {
cell = [[CheckboxCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"Cell"];
return cell;
}
return cell;
}
No, no, no! I would not suggest you add views like that to your UITableViewCell. Funky stuff can happen as you have experienced yourself because of the dequeueing process.
I suggest you do the following:
Create a custom table view cell with its appropriate .h, .m file and .xib file
In your custom cell you can add WHATEVER views you like and however many views that you like.
Make sure you create a property of type UIScrollView in your .h file and link it to the interface builder to your custom cell's slider, call the property slider.
Now in your main view controller where you are creating your table, make sure you have an NSMutableArray and name it something like sliderValuesArray that can store all your slider values for each cell. You want to make sure that the number of cells is equal to the number of elements in your sliderValuesArray.
Then you can do something like this in your cellForRowAtIndexPath delegate method:
In your cellForRowAtIndexPath method do something like this:
myCustomCell.slider.maximumValue = 100;
myCustomCell.slider.minimumValue = 1;
myCustomCell.slider.continuous = TRUE;
//set a method which will get called when a slider in a cell changes value
[myCustomCell.slider addTarget:self action:#selector(sliderChanged:) forControlEvents:UIControlEventValueChanged];
//Keep a reference to each slider by assigning a tag so that we can determine
//which slider is being changed
myCustomCell.slider.tag = indexPath.row;
//Grab the value from the sliderValuesArray and set the slider knob to that position
myCustomCell.slider.value = [[sliderValuesArray objectAtIndex:indexPath.row] intValue];
Now in your sliderChanged method you can do this:
-(void)sliderChanged:(UISlider)sender{
//Grab the slider value, it needs to be converted to an NSNumber so that we can
//store it successfully as an object in our sliderValuesArray
NSNumber sliderValue = [NSNumber numberWithInt:sender.value];
//This is how we determine which position in our slidersArray we want to update,
//Based on the tag we set our slider view on initialisation in our cellForRowAtIndexPath
int cellPosition = sender.tag;
//Use the cellPosition to update the correct number in our sliderValuesArray
//with the sliderValue retrieved from the slider that the user is sliding
[sliderValuesArray replaceObjectAtIndex:cellPosition withObject:sliderValue];
}
I ran into this same issue awhile back. The solution I came up with was to subclass UITableViewCell. Then I added the slider in the init method of the subclass and exposed it via a property.
Now, when you want to change the value of JUST ONE slider, you would do something like:
[cell slider]setValue:(someValue)];
This is working fine for my plain style table views, but not for my grouped style. I'm trying to customize how the cell looks when it is selected.
Here is my code:
+ (void)customizeBackgroundForSelectedCell:(UITableViewCell *)cell {
UIImage *image = [UIImage imageNamed:#"ipad-list-item-selected.png"];
UIImageView *imageView = [[UIImageView alloc] initWithImage:image];
cell.selectedBackgroundView = imageView;
}
I have verified that the correct cell is indeed being passed into this function. What do I need to do differently to make this work?
It's not clear from your question whether or not you're aware that the tableViewCell automatically manages showing/hiding it's selectedBackgroundView based on its selection state. There are much better places to put that method other than in viewWillAppear. One would be at the time you initially create the tableViewCells, i.e.:
- (UITableViewCell *)tableView:(UITV*)tv cellForRowAtIP:(NSIndexPath *)indexPath {
UITableViewCell *cell = nil;
cell = [tv dequeueCellWithIdentifier:#"SomeIdentifier"];
if (cell == nil) {
cell = /* alloc init the cell with the right reuse identifier*/;
[SomeClass customizeBackgroundForSelectedCell:cell];
}
return cell;
}
You only need to set the selectedBackgroundView property once in the lifetime of that cell. The cell will manage showing/hiding it when appropriate.
Another, cleaner, technique is to subclass UITableViewCell, and in the .m file for your subclass, override:
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
self = [super initWithBla....];
if (self) {
UIImageView *selectedBGImageView = /* create your selected image view */;
self.selectedBackgroundView = selectedBGImageView;
}
return self;
}
From then on out your cell should show it's custom selected background without any further modifications. It just works.
Furthermore, this method works better with the current recommended practice of registering table view cell classes with the table view in viewDidLoad: using the following UITableView method:
- (void)registerClass:(Class)cellClass forCellReuseIdentifier:(NSString *)identifier
You would use this method in your table view controller's viewDidLoad method, so that your table view cell dequeuing implementation is much shorter and easier to read:
- (void)viewDidLoad {
[super viewDidLoad];
[self.tableView registerClass:[SomeClass class]
forCellReuseIdentifier:#"Blah"];
}
- (UITableViewCell *)tableView:(UITV*)tv cellForRowAtIP:(NSIndexPath *)indexPath {
UITableViewCell *cell = nil;
cell = [tableView dequeueReusableCellWithIdentifier:#"Blah"
forIndexPath:indexPath];
/* set your cell properties */
return cell;
}
This method is guaranteed to return a cell as long as you have registered a class with the #"Blah" identifier.
I want to make a link using label in every cell of table.
When the link is clicked, the table will get the [indexpath row] of the cell and we will use the index to match with the array index containing string data. The string will be sent to the next push page.
I'm using UITapGestureRecognizer to tap the label and put parameter to selector method.
How to get the current indexpath row the label on the selected cell?
This is my sample code :
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
...
UITapGestureRecognizer *gestureRec = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(openUrl:) ];
gestureRec.numberOfTouchesRequired = 1;
gestureRec.numberOfTapsRequired = 1;
[cell.listPrice addGestureRecognizer:gestureRec];
[gestureRec release];
...
}
- (void)openUrl:(id)sender
{
NSLog(#"DOwnload URL send >> %#",urlDownloadSend);
DownloadNowController *download =[[DownloadNowController alloc]initWithNibName:#"DownloadNowController" bundle:nil];
[self.navigationController pushViewController:download animated:YES];
[download release];
}
To determine the current selected cell you can use next method of UITableView:
- (NSIndexPath *)indexPathForSelectedRow
But I'm not sure that your cell will be selected after UITapGestureRecognizer fired.
I advice you to store row of the cell directly in gestureRec.view in tag property:
gestureRec.view.tag = indexPath.row;
Then in openUrl you can determine the selected cell by getting value of sender.view.tag
It is not very clear what you want to do.. do you have a link layed out on the UITableViewCell which triggers some other actions?
The UITableViewDelegate gives you some really cool methods called:
– tableView:willSelectRowAtIndexPath:
– tableView:didSelectRowAtIndexPath:
– tableView:willDeselectRowAtIndexPath:
– tableView:didDeselectRowAtIndexPath:
When you tap a cell, the willSelectRowAtIndexPath and didSelectRowAtIndexPath are called - supplying you the currently selected NSIndexPath which you can then use to get the row as follows:
indexPath.row;
You can use a global variable to keep the value of indexpath.row
store the row in didSelectRow:atIndexPath: method
var = indexPath.row;
[tableView reloadData];
var = indexPath.row;
[tableView reloadData];
then use it in cellForRowAtIndexPath
if(indexPath.row==var){
}
You may implement method – tableView:willSelectRowAtIndexPath: in your UITableViewDelegate. There you can easily obtain information on indexPath.
you can do like this
in cellForRowAtIndexPath data source method
UIImageView* selectedBg = [[UIImageView alloc]initWithImage:[UIImage imageNamed:#"bg_selected.png"]];
cell.backgroundView = [[UIView alloc] initWithFrame: CGRectZero];
cell.selectedBackgroundView = selectedBg;
[cell.backgroundView setNeedsDisplay];
[selectedBg release];
The following code can be used to obtain the current selected indexpath row:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath: (NSIndexPath *)indexPath {
NSLog(#"selected tableview row is %ld",(long)indexPath.row);
}