I have a UITableView with each cell is having a UITableViewCellAccessoryCheckmark. The user can check and uncheck the cells depending on his preference. The user can check/select multiple cells in the tableview. A selected/checked cell can be unchecked and rechecked again on user preference.
I have a done button in the UITableViewController. On its click, I need to return to the previous view, before that I have to have a collection of the text in the checked cells(only checked cells).
How can I do this.
I was planning on developing a logic, by keeping an NSMutableArray and update it on - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath, when a cell gets checked/selected. But when every time a cell is unchecked I have to remove the item from the array and if the cell is checked again, then I have to add it again. I reckon thats not the right way to do this. What would be the right way to do this.
I couldn't find a question of similar kind here in Stackoverflow, which is very unusual. Would be helpful if someone could post a link, if the question was asked before.
Maintain two arrays, like this:
#property (nonatomic, retain) NSMutableArray *features, *selectedFeature;
synthesize them in .m file. initialize your both arrays something like this in viewDidLoad:
self.features = [NSArray arrayWithMyOwnResourceLikeDownloadedFromServerOrWhatever];
self.selectedFeature = [NSMutableArray array];
Then do something like this in didSelectRowAtIndex:
NSString * stirng = [features objectAtIndex:indexPath.row];
if ([self.selectedFeature containsObject:stirng]) {
[self.selectedFeature removeObject:stirng];
}
else{
[self.selectedFeature addObject:stirng];
}
and in your cellForRowAtIndexPath:
NSString * stirng = [features objectAtIndex:indexPath.row];
[cell.textLabel setText:stirng];
if ([self.selectedFeature containsObject:stirng]) {
//it is selected feature
[cell setAccessoryType:UITableViewCellAccessoryCheckmark];
}
else{
//it is un-selected feature
[cell setAccessoryType:UITableViewCellAccessoryNone];
}
Create a boolean array of the same length as the number of checkable cells and update the value on click events. If the table content is dynamic, you can use methods provided by NSMutabeArray to keep the boolean array in accordance with the table content. Upon return use the array to grab the text you need.
Related
In my app, I have a detailed view where the user can edit attributes of for example a person, their name, address etc.
In two cells, rather than being able to select them to edit their contents, they have a right accessory, a UISwitch, however sometimes, its inconsistent, but they replicate onto other cells in my last section.
I have been scanning my code dozens of times over with a fine comb and can't find the damn cause. What might cause this? Here is the code that I use to create the UISwitch on just a single cell:
if (indexPath.section == 0 && indexPath.row == 1)
{
cell.textLabel.text = #"Confirmed";
//Make the cell unselectable
[cell setSelectionStyle:UITableViewCellSelectionStyleNone];
//Create and add uiswitch
confirmedSwitch = [[UISwitch alloc] initWithFrame:CGRectZero];
[confirmedSwitch addTarget:self action:#selector(switchConfirmedStatus:) forControlEvents:UIControlEventValueChanged];
[confirmedSwitch setOn:[venue.isConfirmed boolValue]];
cell.accessoryView = confirmedSwitch;
}
So you expect it to only show up on that cell, see anything odd with that code? I have checked my if statements and all my brackets indexPath checks are correct.
Anyone see this before or have any clues?
The problem is because of reusability issues in the UITableView. You probably use the same identifier for all cells and this causes the cellForRowAtIndexPath to be implemented in other rows (when you scroll up and down).
This is a common problem when you are developing applications with tableView and there are plenty of questions like this on StackOverflow.
As a general solution, you will need to do either of these.
Use different identifiers when you assign dequeueReusableCellWithIdentifier for each cell. This is fairly simple, and you just need to assign different identifiers for them.
Subclass you UITableViewController, and create your own CustomTableViewController which will implement the necessary components in the cell. I believe you will need to override the set Layout Subviews method.
Take a array in view will appear and add object 0 for this row and 1 for all other rows and then in cellForRowAtIndexPath check if array has 0 at that index if it has then put switch otherwise nill..
for(int i =0;i<10;i++)
{
if(i==1)
{
[arrayForSwitch addObject:#"0"];
}
else
{
[arrayForSwitch addObject:#"1"];
}
}
then in cellForRowAtIndexPath write condition
if([arrayForSwitch objectAtIndex:indexPath.row isEqualToString:#"0"])
{
cell.accessoryView = confirmedSwitch;
}
else
{
cell.accessoryView =UITableViewCellAccessoryNone;
}
it will now remain same
This is related to another question of mine which wasn't answered in a helpful way (message when a UITableView is empty).
I'm trying to show an UIImage graphic that says You haven't saved any bookmarks over an UITableView when it's empty. I have NSNotification set-up so that when bookmarks are added or deleted, a message is sent so that the UITableView can be updated.
I've been trying to do it with this code. Why won't this work?
- (void)bookmarksChanged:(NSNotification*)notification
{
[self.tableView reloadData];
UIImageView* emptyBookmarks = [[UIImageView alloc] initWithFrame:CGRectMake(75, 100, 160, 57)];
emptyBookmarks.alpha = 1;
emptyBookmarks.image = [UIImage imageNamed:#"emptyBookmark.png"];
[self.view addSubview:emptyBookmarks];
[emptyBookmarks release];
if ([self.dataModel bookmarksCount] == 0)
{
emptyBookmarks.alpha = 1;
}
else
{
emptyBookmarks.alpha = 0;
}
}
I'm probably approaching this the wrong way... But if salvageable, what am I doing wrong?
When I initially have an empty bookmarks tableview, there's no image displayed. After I add a bookmark and then delete it, the image shows. Grrh.
Another way (and IMO the correct way) to do this is to manipulate the backgroundView property on the UITableView.
While making a single cell with a custom image cell would certainly works, I think it overly complicates the logic of your UITableViewController's data source. It feels like a kludge.
According to UITableView documentation:
A table view’s background view is automatically resized to match the
size of the table view. This view is placed as a subview of the table
view behind all cells , header views, and footer views.
Assigning an opaque view to this property obscures the background color
set on the table view itself.
While you probably don't want to just set it to your UIImageView, it is very easy to make a UIView that contains the UIImageView that you want.
Well first off if you were going to do it that way, you would need to reload the tableView after updating the image or model etc. and not before.
But you are probably making things more complicated than they need to be!
Why not just check to see if the data for section 0 and indexPath.row 0 are empty and if so in cellForRowAtIndexPath display a text message accordingly.
// First make sure there is always one row returned even if the dataModel is empty.
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
NSInteger numRows = 0;
if ([self.dataModel lastObject]) {
// Return the number of rows in the section.
numRows = [self.dataModel count]; // etc.
}
if (numRows < 1) numRows = 1;
return numRows;
}
// Then display the data if there is some, otherwise a message if empty.
-(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 ([self.dataModel lastObject]) {
// setup the cell the normal way here.
} else { // the datasource is empty - print a message
cell.textLabel.text = nil;
cell.detailTextLabel.text = NSLocalizedString(#"You haven't saved any bookmarks", #"");
cell.detailTextLabel.textColor = [UIColor colorWithRed:0/255.0 green:0/255.0 blue:0/255.0 alpha:0.7];
cell.accessoryType = UITableViewCellAccessoryNone;
}
return cell;
}
Are you sure [self.dataModel bookmarksCount] is equal to 0 ?
While I agree that you are probably going about this the wrong way,
your image is allocated and added in your bookmark changed, your notification does not trigger when there are no bookmarks initially. Hence you don't see the image. Call the bookmar changed when your table view inits or appears.
Probably the best way to achieve this is to perform a check in your numberOfRowsInSection method to return 1 if your data source is empty. Then in cellForRowAtIndexPath check if your data source is empty and if it is, create a custom cell that contains whatever you want. In heightForRowAtIndexPath you need to return your custom cell height if your datasource is empty, but only if you want the cell larger than the default. At least that is how I would approach it.
when bookmarks count is nil add one to your row method:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
int c;
c = bookmarks.count;
if(c == 0){
c = 1;
}
return c;
}
and then the same check again in your cellforrowatindexpath.
Another thing to be aware of in this situation is that if you're using core data and you're datasource is feeding off an entity, you will want to make sure your model matches. You can get some weird side-effect behavior in certain situations. This is especially true if you allow editing and core data has an empty model but you're tableview is still showing a cell.
I have this delegate method below that is setting a few fields in my tableview. What happens is when one of the tableview cells is pressed it loads a subview with a bunch more tableview cells when one is selected is pops the view from the viewcontrollerand loads the value that was selected into the parentviews cell that was initially selected.
This main cell effects what I am able to set in the second cell. so when it is clicked it shows data related to the first selection.
I am enabling my user to go back to any of the cells to change their selection.. or maybe they might go back thinking they want to change but don't.
In which case for the second tableviewcell of my parentview will either need to change if the first cell changes or stay the same if the value of the first cell dosnt change.
I have this delegate that is used with the first cell, and it is where I am trying to control the value of the secondcell, as shown below.
- (void) setManufactureSearchFields:(NSArray *)arrayValues withIndexPath:(NSIndexPath *)myIndexPath
{
manufactureSearchObjectString = [[arrayValues valueForKey:#"MANUFACTURER"] objectAtIndex:0];
manufactureIdString = [[arrayValues valueForKey:#"MANUFACTURERID"] objectAtIndex:0]; //Restricts Models dataset
manufactureResultIndexPath = myIndexPath;
[self.tableView reloadData]; //reloads the tabels so you can see the value in the tableViewCell.
//need some sort of if statment here so that if the back button is pressed modelSearchObjectString is not changed..
if (oldManufactureSearchObjectString != manufactureSearchObjectString) {
modelResultIndexPath = NULL;
modelSearchObjectString = #"empty";
oldManufactureSearchObjectString = manufactureSearchObjectString;
}
}
The thing being is that it enters the if statment every time even if the same cell is selected for the first cell.. (in which case is should not enter the if statement.
I thought I could do this by checking oldMan vrs man if != then go through and set the second cell stuff and then pass man to oldMan so next time you use the first cell it has a value to comapre against. but obviously this dosn't seem to be working. Is my logic bad or is it something in my code.
this is how I set these values in .h they are all #synthesised
//...
NSString *manufactureSearchObjectString;
NSString *oldManufactureSearchObjectString;
NSString *manufactureIdString;
NSIndexPath *manufactureResultIndexPath;
//...
#property (copy) NSString *manufactureSearchObjectString;
#property (copy) NSString *oldManufactureSearchObjectString;
#property (copy) NSString *manufactureIdString;
#property (copy) NSIndexPath *manufactureResultIndexPath;
//...
You have to compare the value of the NSStrings as below:
BOOL isEqual = [aString isEqualToString:bString]; // provide aString and bString
if (!isEqual)
{
// code
}
What you've done is to compare the pointers, which can be different for strings of the same value.
Caveat: I have looked for the answer to my question and several come close, but I'm still missing something. Here is the scenario:
I want a way to create UITableViewCells that contain UISwitches dynamically at run time, based on the data in a table (which I can do). The problem becomes connecting the switches such that I can get their value when that view is changed (navigated away, closed, etc). I have tried to use the events UIControlEventValueChanged to be notified, but have failed to specify it correctly, because it dumps when that switch is tapped. Also, there doesn't seem to be any way to uniquely identify the switch so that if all the events are handled by a single routine (ideal), I can't tell them apart.
So...
If I have a UITableView:
#interface RootViewController : UITableViewController
{
UISwitch * autoLockSwitch;
}
#property (nonatomic, retain) UISwitch * autoLockSwitch;
-(void) switchFlipState: (id) sender;
#end
// the .m file:
#implementation RootViewController
// ...
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString * CellIdentifier = #"Cell";
int row = 0;
NSString * label = nil;
TableCellDef_t * cell_def = nil;
row = indexPath.row;
cell_def = &mainMenuTableCellsDef[ row ];
UITableViewCell * cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil)
{
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
}
label = (NSString *) mainMenuTableCellsDef[indexPath.row].text;
[cell.textLabel setText:(NSString *) mainMenuItemStrings[ indexPath.row ]];
if (cell_def->isSpecial) // call special func/method to add switch et al to cell.
{
(*cell_def->isSpecial)(cell ); // add switch, button, etc.
}
else
{
[cell setAccessoryType:UITableViewCellAccessoryDisclosureIndicator];
}
}
and this is the 'special' function:
-(void) autoLockSpecialItem :(UITableViewCell *) cell
{
autoLockSwitch = [[[UISwitch alloc] initWithFrame:CGRectZero] autorelease];
[autoLockSwitch addTarget:self action:#selector(switchFlipState:) forControlEvents:UIControlEventValueChanged ];
[cell addSubview:autoLockSwitch];
cell.accessoryView = autoLockSwitch;
}
and finally:
-(void) switchFlipState: (id) sender
{
NSLog(#"FLIPPED");
}
==============================================================
Questions:
Why would it crash (bad selector) when the switch was tapped? I believe that my code follows all the example code that I have seen, but obviously something is wrong.
I cannot put a instance method into a table as a function pointer; and it doesn't seem to like a class method either. If I make it a 'C/C++' function, how do I get access to the class/instance member variables? That is, if I want to put a call to autoLockSpecialItem into a static table (or reasonable facsimile) such that I can get autoLockSwitch member variable? If I make it a class method and the autoLockSwitch var a static, will that be valid?
More simply: how do I connect the UIControlEventValueChanged to my view (I have tried and failed) and can I differentiate at runtime within the event handler which switch has changed?
Is there a better way? I cannot believe that I am the first person to have to solve this type of problem.
Apologies for the length, appreciation for attention and grateful for any and all help.
:bp:
Don't know about why your method isn't connected, but a simple way to "differentiate at runtime within the event handler which switch has changed" is to take the (id)sender given to your event handler, walk your tableview, and compare the sender to any switches, if present, in each table item. If that's too slow, a hash table connecting senders to table cells, or something like that, is a possible optimization.
If you want to use C function pointers, you need to pass the object to the function to use it to call the object's property accessor methods within the function. (Or you could assign the object to a global variable if it's clearly a singleton, but that's a very politically incorrect answer.)
First, and easy way to define your different switches would be defining their tag based on the row number. When one of the switches is tapped you can access sender.tag to get the row number this way.
Also, you should probably be adding the switch the the cells content view, not the actual cell, [cell.contentView addSubview:autoLockSwitch]. Also the frame does need to be set (note CGRectZero, cocoa will ignore the width and height but uses the x,y coords to define where you want the switch in the cell.
I have a UIButton that is created inside of each table cell. I want to hook up a touch event like so:
[imageButton addTarget:self
action:#selector(startVote:)
forControlEvents:UIControlEventTouchUpInside];
I want to pass data about the current row (the id of the object for that row) to the startVote method. Is there a method that I am missing to do this or am I breaking some best practice. This seems like a very normal thing to do?
I assume you have some sort of NSArray with the data that gets passed on to the buttons in cellForRowAtIndexPath.
Try this in startVote:
- (void)startVote:(id)sender {
NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow];
NSDictionary *myData = [myArray objectAtIndex:indexPath.row];
}
EDIT:
If for some reason the row is not selected, you can assign a unique tag to every button upon creation and then:
- (void)startVote:(id)sender {
int myTag = [(UIButton *)sender tag];
NSDictionary *myData = [myArray objectAtIndex:myTag];
}
Maybe you would do some sort of operation with the tag so it can be used as an index (I add a certain amount to every tag so it will not conflict with "automatic" tagging used by the OS.
The UITableViewCell doesn't know, out of the box, what row it's displaying in the table. Remember, the intent is that the same cell instances are re-used all over the table to display its data. That said, your UITableViewController is responsible for setting up the cells and passing them to the system (and has the index path, of course). You could, at that point, do something like:
Assuming it's a custom cell class, set a property on the cell instance to identify what row it's displaying, and which your button can later use.
If you're putting these buttons in the cells as their accessory views, take a look at the table delegate's tableView:accessoryButtonTappedForRowWithIndexPath: method.
If it's a one-section table, you could do something really cheesy like store the row index in the button's tag property. Your startVote: method is passed the button, and could then extract its tag.