I have a table view that needs a custom UITableViewCellAccessoryCheckmark. The checkmark displays when a row is selected and disappears when anther row is selected and then appears on the last most selected view. That works fine.
the problem arises when I use this line:
cell.accessoryView = [[ UIImageView alloc ]
initWithImage:[UIImage imageNamed:#"icon-tick.png" ]];
to add a custom UITableViewCellAccessoryCheckmark. After that code the UITableViewCellAccessoryCheckmark remain on all rows and don't disappear when another row is touched.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
int index = indexPath.row; id obj = [listOfItems objectAtIndex:index];
UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
NSLog(#"%d",indexPath.row);
if (rowNO!=indexPath.row) {
rowNO=indexPath.row;
[self.tableView cellForRowAtIndexPath:indexPath].accessoryType=UITableViewCellAccessoryCheckmark;
cell.accessoryView = [[ UIImageView alloc ]
initWithImage:[UIImage imageNamed:#"icon-tick.png" ]];
[self.tableView cellForRowAtIndexPath:lastIndexPth].accessoryType=UITableViewCellAccessoryNone;
lastIndexPth=indexPath;
}
A much cleaner and cooler way would be to overwrite UITableViewCell like this:
- (void)setAccessoryType:(UITableViewCellAccessoryType)accessoryType
{
// Check for the checkmark
if (accessoryType == UITableViewCellAccessoryCheckmark)
{
// Add the image
self.accessoryView = [[[UIImageView alloc] initWithImage:[UIImage imageNamed:#"YourImage.png"]] autorelease];
}
// We don't have to modify the accessory
else
{
[super setAccessoryType:accessoryType];
}
}
If you have done that, you can continue using UITableViewCellAccessoryCheckmark because your class will automatically replace it with an image.
You should only set the style in your cellForRowAtIndexPath method. Like this:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// [init subclassed cell here, dont forget to use the table view cache...]
cell.accessoryType = (rowNO != indexPath.row ? nil : UITableViewCellAccessoryCheckmark);
return cell;
}
And then, you just have to update rowNO in didSelectRowAtIndexPath to update your data and redraw the cell, like this:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if (rowNO != indexPath.row)
{
rowNO = indexPath.row;
}
[self.tableView reloadData];
}
Also, instead of reloading the whole table with [self.tableView reloadData], you could only reload the two cells that change their style (e.g. checkmark) using reloadRowsAtIndexPaths.
Hm don't know why but i can't add comments so im writing this as answer. The problem with Blauesocke answer is that the AccessoryType wil be not set to UITableViewCellAccessoryCheckmark so you can't check the cell AccessoryType. Is there some way to do it right so the cell AccessoryType will be corect type just another image.
I'm using the method like this :
- (void)setAccessoryType:(UITableViewCellAccessoryType)newAccessoryType
{
[super setAccessoryType:newAccessoryType];
// Check for the checkmark
switch(newAccessoryType)
{
case UITableViewCellAccessoryCheckmark:
self.accessoryView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"yorCheckmark.png"]];
break;
case UITableViewCellAccessoryNone:
self.accessoryView = nil;
break;
default:
break;
}
}
Related
I have tried several approaches posted here, but I cannot get my table full of switches to return an index value for the cell of the changed switch. I am creating the view containing the table programmatically (no xib).
TableSandboxAppDelegate.m I instantiate the view controller in didFinishLaunchingWithOptions: with:
...
TableSandboxViewController *sandboxViewController = [[TableSandboxViewController alloc]
init];
[[self window] setRootViewController:sandboxViewController];
...
TableViewController.h file reads:
#interface TableSandboxViewController : UITableViewController
{
NSMutableArray *_questionOrder;
NSMutableArray *switchStates;
}
#end
TableViewController.m cellForRowAtIndexPath: reads:
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"MainCell"];
UISwitch *theSwitch = nil;
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:#"MainCell"];
theSwitch = [[UISwitch alloc] initWithFrame:CGRectZero];
theSwitch.tag = 100;
[theSwitch addTarget:self action:#selector(switchChanged:)
forControlEvents:UIControlEventValueChanged];
[cell.contentView addSubview:theSwitch];
} else {
theSwitch = [cell.contentView viewWithTag:100];
}
if ([[switchStates objectAtIndex:indexPath.row] isEqualToString:#"ON"]) {
theSwitch.on = YES;
} else {
theSwitch.on = NO;
}
return cell;
TableViewController.m -(IBAction)switchChanged:(UISwitch *)sender reads:
UITableViewCell *theParentCell = [[sender superview] superview];
NSIndexPath *indexPathOfSwitch = [self.tableView indexPathForCell:theParentCell];
NSLog(#"Switch changed at index: %d", indexPathOfSwitch.row);
My log result is always "Switch changed at index: 0". I feel like the problem is in that CGPoint line where I've tried combinations of replacements for "sender" ([sender superview], [[sender superview]superview], etc). I don't feel like that line is pointing to the view that displays the table.
What am I doing wrong?
Note added 10/9, 9:15 EDT: my goal is to be able to handle about 100 yes/no questions in the table, so reuse is a key. I want to scroll and have the table the state of each switch, as well as be able to retrieve them when leaving the view.
Tags is an okay solution, but a little clumsy because the cells - and therefore their subviews - are continually being reused, changing their rows - and therefore the tags they need.
Instead, I generally keep one of these around:
- (NSIndexPath *)indexPathWithSubview:(UIView *)subview {
while (![subview isKindOfClass:[UITableViewCell self]] && subview) {
subview = subview.superview;
}
return [self.tableView indexPathForCell:(UITableViewCell *)subview];
}
Then when I get an IBAction:
- (IBAction)someSubviewAction:(id)sender {
NSIndexPath *indexPath = [self indexPathWithSubview:(UIView *)sender];
// carry on from here
}
You may set switch view tag to row index. Instead of theSwitch.tag = 100;
do
-(UITableViewCell*)tableView:table cellForRowAtIndexPath:indexPth
{
UISwitch *theSwitch = nil;
if (cell == nil) {
...
// as per your example
[cell.contentView addSubview:theSwitch];
} else {
theSwitch = subviewWithClass(cell.contentView, [UISwitch class]);
}
theSwitch.tag = indexPath.row;
...
}
Add this helper function to replace viewWithTag: call
UIView *subviewWithClass(UIView *contentview, Class klass)
{
for (UIView *view in contentview.subviews)
if ([view isKindOfClass:klass])
return view;
return nil;
}
Then retrieve tag, that is a row index now, in your switchChanged function
-(IBAction)switchChanged:(UISwitch *)sender {
NSLog(#"Selected Switch - %d", sender.tag);
...
}
If you use something block-based (like https://github.com/brightsoftdev/iOS-Block-Based-Bindings/blob/master/UISwitch%2BBindings.m), you don't need to worry about getting the row, because you can reference the indexPath that is passed into tableView:cellForRowAtIndexPath: in your block.
Similar to #danh, I've come up with this solution using an extention which I've used multiple times.
#interface UIView (Find)
- (id)findSuperviewOfClass:(Class)class;
- (NSIndexPath *)findIndexPath;
#end
#implementation UIView (Find)
- (id)findSuperviewOfClass:(Class)class
{
return [self isKindOfClass:class] ? self : [self.superview findSuperviewOfClass:class];
}
- (NSIndexPath *)findIndexPath
{
UITableView *tableView = [self findSuperviewOfClass:[UITableView class]];
return [tableView indexPathForCell:[self findSuperviewOfClass:[UITableViewCell class]]];
}
#end
for iOS6+ you could maintain a NSMutableArray queuedSwitches
in -tableView:cellForrowAtIndexPath: you would take a switch, if not empty and places it on the custom cell and assign it to a property. If empty you create a new one.
in -tableView:didEndDisplayingCell:forRowAtIndexPath: you would add it to quededSwitches and remove it from it cell.
This will just allocate enough switches for visible cells and reuse them.
the switches are all wired up to one action.
-(void)switchAction:(UISwitch *)switch
{
NSIndexPath *indexPath = [self.tableView indexPathForCell:[switch superView]];
//…
}
You could create a subclass of UISwitch and add an indexPath property, then just set the indexPath in cellForRowAtIndexPath:
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
SwitchCell *returnCell = [tableView dequeueReusableCellWithIdentifier:#"SwitchCell" forIndexPath:indexPath];
returnCell.switch.indexPath = indexPath;
return returnCell;
}
I am becoming increasingly frustrated as I can not figure out how to get this to work.
I have searched and searched and nothing seems to be helping me out to get this to do what I want.
What I am trying accomplish: I am trying to get the Task that the user will type in using the UITextField on top to add a new cell with their input as the text and with a check box to the left of it. When the cell is tapped, I would like to have the check box change to one with a check mark within it and for the entire cell to change opacity to 50%. Also, I would like to make the cell pop up with a delete button when it is swiped from left to right.
How would I make this all happen? I currently just have the UITextField added to the .xib using the Attribute Inspector to make it look the way it does. I also have a tool bar up top that I have used the Attributes Inspector to modify as well.
Nothing is coded to connect these to actions, they are just sitting in the .xib.
Here is an image of the rendering I created. (What I am trying to get)
http://i.imgur.com/rTKnvud.png
Here is what I have so far in Xcode in the .xib.
http://i.imgur.com/wfcVOrY.png
Thank you in advanced,
Jacob
You need to either add button to cell programatically or create a custom cell. When user taps on cell or checkbox you can change button's image to selected checkbox. for proper display of checkbox take a NSMutableArray and use the below code.
Note
1. specialNeedsList is a array which I'm using to display data on cell.
2. selectedSpecialNeed is a Mutable Array which I'm using to store selected/checked values
3. I'm using checkbox button for UITableViewCell accessory view. IN your case code will be little bit different.
Follow this tutorial Get row for button tapped as well along with my code. You will get idea for accomplish this. Whatever I'm doing on - (void)tableView:(UITableView *)tableView accessoryButtonTappedForRowWithIndexPath:(NSIndexPath *)indexPath
you need to do this on UIButton's action.
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
NSInteger returnValue = 1;
return returnValue;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
NSInteger returnValue = 0;
returnValue = ([self.specialNeedsList count] > 0) ? [self.specialNeedsList count]:0;
return returnValue;
}
- (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] autorelease];
UIButton * btnShare = [[UIButton alloc] initWithFrame:CGRectMake(0,0,30,30)];
[btnShare setImage:[UIImage imageNamed:#"checkbox.png"] forState:UIControlStateNormal];
[btnShare setImage:[UIImage imageNamed:#"checkboxChecked.png"] forState:UIControlStateHighlighted];
[btnShare setImage:[UIImage imageNamed:#"checkboxChecked.png"] forState:UIControlStateSelected];
[btnShare addTarget: self action: #selector(accessoryButtonTapped:withEvent:)
forControlEvents: UIControlEventTouchUpInside];
cell.accessoryView = btnShare;
[btnShare release];
}
NSString* subAreaOfStudy = [self.specialNeedsList objectAtIndex:indexPath.row];
if (self.specialNeedsList && [self.specialNeedsList count] > 0) {
[cell.textLabel setFont:[UIFont systemFontOfSize:15.0f]];
cell.textLabel.text = [self.specialNeedsList objectAtIndex:indexPath.row];
}
BOOL isShared = ([self.selectedSpecialNeeds count] > 0 && ([self.selectedSpecialNeeds indexOfObject:subAreaOfStudy] != NSNotFound));
UIButton* btnShare = (UIButton*)cell.accessoryView;
btnShare.selected = isShared;
[btnShare setNeedsDisplay];
return cell;
}
- (void) accessoryButtonTapped: (UIControl *) button withEvent: (UIEvent *) event{
NSIndexPath * indexPath;
indexPath = [tblVSpecialNeeds indexPathForRowAtPoint: [[[event touchesForView: button] anyObject] locationInView:tblVSpecialNeeds]];
if ( indexPath == nil )
return;
[self tableView:tblVSpecialNeeds accessoryButtonTappedForRowWithIndexPath:indexPath];
}
- (void)tableView:(UITableView *)tableView accessoryButtonTappedForRowWithIndexPath:(NSIndexPath *)indexPath{
NSString *draft = [self.specialNeedsList objectAtIndex:indexPath.row];
BOOL recordAlreadySelected = ([self.selectedSpecialNeeds indexOfObjectIdenticalTo:draft] != NSNotFound);
if(recordAlreadySelected) {
[self.selectedSpecialNeeds removeObject:draft];
}
else {
[self.selectedSpecialNeeds addObject:draft];
}
[tblVSpecialNeeds reloadData];
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if (indexPath.row > [self.specialNeedsList count]-1) {
[tableView deselectRowAtIndexPath:indexPath animated:YES];
return;
}
[self tableView:tblVSpecialNeeds accessoryButtonTappedForRowWithIndexPath:indexPath];
}
To delete on swipe, use Tableview's Delegate method:
(void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
[arrAddData removeObjectAtIndex:indexPath.row];
[tbldata reloadData];
I tried to put a custom UIButton with an image on a custom tableview cell to use it as checkmark, 1st tap, hides the checkmark and next tap brings it back... i'm trying the below code "example code" to perform this function but its not hiding the checkmark i.e."custom UIButton".
not sure what am i missing here?
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSLog(#"%#",indexPath);
TableViewCell *customCell = [[TableViewCell alloc]init];
if (customCell.checkMarkButton.hidden == NO) {
customCell.checkMarkButton.hidden = YES;
} else {
customCell.checkMarkButton.hidden = NO;
}
}
Replace this line: TableViewCell *customCell = [[TableViewCell alloc]init]; with this:
TableViewCell *customCell = [tableView cellForRowAtIndexPah:indexPath];. You don't have to crate a new cell, just get the one that was tapped.
The problem is that in didSelectRowAtIndexPath you alloc a new cell.
Try this :
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
TableViewCell *customCell = (TableViewCell*)[tableView cellForRowAtIndexPath:indexPath];
customCell.checkMarkButton.hidden = !customCell.checkMarkButton.hidden;
}
I've got a UITableView displayed on screen for a while. In each cell is a song and artist name. In the background, each song and artist name is searched for online (using the Spotify API). It finds the URL to play one song, and then moves on to the next one! :) Sounds simple... but what I want is when each song is found, for the Checkmark accessory to appear in that row.
Currently i've got the following code to do this...
[[table cellForRowAtIndexPath:[NSIndexPath indexPathForRow:currentConnectionNumber inSection:0]] setAccessoryType:UITableViewCellAccessoryCheckmark];
[table setNeedsDisplay];
But all that happens is when all of the songs has been found, THEN the checkmarks appear... Why is this and how can I make the checkmarks appear one at a time?
thanks
You need to set the checkmark in tableView:cellForRowAtIndexPath:
- (void)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *reuseIdentifier = #"cell";
UITableViewCell *cell = nil;
cell = [tableView dequeueReusableCellForIdentifier:reuseIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:reuseIdentifier] autorelease];
}
NSString *cellTitle = [self cellTitleForRowAtIndexPath:indexPath]; // You need to implement this method
BOOL hasURL = [self hasURLForRowAtIndexPath:indexPath]; // You need to implement this method
cell.textLabel.text = cellTitle;
if (hasURL)
cell.accessoryType = UITableViewCellAccessoryCheckmark;
else
cell.accessoryType = UITableViewCellAccessoryNone;
return cell;
}
Then reload the cells when your request finishes
- (void)myRequestFinished:(SomeKindOfWebRequest *)webRequest {
NSIndexPath *indexPathToReload = [self indexPathForWebRequest:webRequest]; // You need to implement this method
NSArray *indexPaths = [NSArray arrayWithObject:indexPathToReload];
[self.tableView reloadRowsAtIndexPaths:indexPaths withRowAnimtation:UITableViewRowAnimationNone];
}
I think rather than doing this you can set a bool flag for as per your requirement and add your checkmark logic in
cellForRowAtIndexPath:
and if that flag is true add accessary checkmark otherwise don't.
if (isValid) {
cell.accessoryType = UITableViewCellAccessoryCheckmark;
}
else{
cell.accessoryType = UITableViewCellAccessoryNone;
}
Are you reloading your tableView using
[yourtableView reloadData]
when your data modifies.
I have two tableviews, one has several tableview cells each cell opens the same subview but initalized with new data..
There are around about 100 - 200 entries into the table and I have a accessory view that is a tick that when a cell is selected it ticks the cell then loads the main view again.
If I select that same cell to get the same dataset back it loads the previously selected cell in the middle of the screen (so it knows its index path) however the tick "depending on how deep in the list" will or will not be visible..
It tends to work in about the top 30/40% of the table but anything lower the tick will not be visible... that is unless I go back and forth getting deeper and deeper each time then sometimes I can get the tick to appear in the deeper part of the tableview.. Would anyone know why this is happening?
Has anyone had something of this nature happen to them before?
At further investigation I think something is going wrong inside this method..
First of all, in the subview once the user selects a cell this method is called
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
// Navigation logic may go here. Create and push another view controller.
[self.navigationController popViewControllerAnimated:YES]; //pops current view from the navigatoin stack
//accesses selected cells content
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
// now you can use cell.textLabel.text
//This if statment is based off which cell was selected in the parent view so that it knows which cell to pass the data back to
if (parentViewSelectedIndexPath.section == 0) {
if (parentViewSelectedIndexPath.row == 0) {
manufactureCellTextLabel = cell.textLabel.text; //passing label text over to NSString for use with delegate (check "viewwilldissapear")
[[self delegate] setManufactureSearchFields:manufactureCellTextLabel withIndexPath:indexPath]; //This is where I pass the value back to the mainview
}
// a few more If statements for the other methods I can pass data too.
//--- this if block allows only one cell selection at a time
if (oldCheckedData == nil) { // No selection made yet
oldCheckedData = indexPath;
[cell setAccessoryType:UITableViewCellAccessoryCheckmark];
}
else {
UITableViewCell *formerSelectedcell = [tableView cellForRowAtIndexPath:oldCheckedData]; // finding the already selected cell
[formerSelectedcell setAccessoryType:UITableViewCellAccessoryNone];
[cell setAccessoryType:UITableViewCellAccessoryCheckmark]; // 'select' the new cell
oldCheckedData = indexPath;
}
}
This passes Index path over to the main view method...
- (void) setManufactureSearchFields:(NSString *)cellLabeltext withIndexPath:(NSIndexPath *)myIndexPath
{
manufactureSearchObjectString = cellLabeltext;
manufactureResultIndexPath = myIndexPath;
[self.tableView reloadData]; //reloads the tabels so you can see the value.
}
//Which then sets the manufactureResultIndexPath that is used in the next method to pass it back to the subview
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
// Navigation logic may go here. Create and push another view controller.
//--- Idendify selected indexPath (section/row)
if (indexPath.section == 0) {
//--- Get the subview ready for use
VehicleResultViewController *vehicleResultViewController = [[VehicleResultViewController alloc] initWithNibName:#"VehicleResultViewController" bundle:nil];
// ...
//--- Sets the back button for the new view that loads
self.navigationItem.backBarButtonItem = [[[UIBarButtonItem alloc] initWithTitle:#"Back" style: UIBarButtonItemStyleBordered target:nil action:nil] autorelease];
// Pass the selected object to the new view controller.
[self.navigationController pushViewController:vehicleResultViewController animated:YES];
[vehicleResultViewController setDelegate:self];
if (indexPath.row == 0)
{
vehicleResultViewController.title = #"Manufacture";
[vehicleResultViewController setRequestString:#"ID.xml"]; //sets the request string in searchResultsViewController
vehicleResultViewController.dataSetToParse = #"ID"; // This is used to controll what data is shown on subview... logic
[vehicleResultViewController setAccessoryIndexPath:manufactureResultIndexPath]; //sends indexpath back to subview for accessory tick
vehicleResultViewController.parentViewSelectedIndexPath = indexPath;
}
//etc etc
}
And finaly I pass it to the method in my subview that passes the indexpath to oldCheckedData
- (void)setAccessoryIndexPath:(NSIndexPath *)myLastIndexPath
{
oldCheckedData = myLastIndexPath;
[self.tableView reloadData]; //<<---- this is where I reload the table to show the tick...
}
Try moving the cell.accessoryType = lines to the willDisplayCell: delegate function like so:
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {
// You can move this one here too:
cell.selectionStyle = UITableViewCellSelectionStyleNone; // no blue selection
if (indexPath == oldCheckedData) {
cell.accessoryType = UITableViewCellAccessoryCheckmark;
} else {
cell.accessoryType = UITableViewCellAccessoryNone;
}
}
I read that the willDisplayCell: method should be used for any basic visual related modifications to a cell like selectionStyle/accessoryType, and the cellForRowAtIndexPath: method for cell data related operations like setting text, images, etc...
I have recently come across this issue, if turned out the in my case the cell has a accessoryview set. This snippet withh ensure the view is removed.
public func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
..logic here to to determine if cell should be selected...
if ( cell.accessoryView != nil) {
cell.accessoryView?.removeFromSuperview()
cell.accessoryView = nil
}
cell.accessoryType = .checkmark