UISwitch in TableViewCell - Change cell's text upon switching - iphone

I want a UISwitch within a TableViewCell to change the text from 'Active' to 'Disabled' and vice versa when switched but when the switch changes, all the data in my table view disappears. I'm using 'reload data' since I do not know how to change a specific cell's text.
FYI, 'current item' is a core data entity with a BOOL property 'itemEnabled'.
The switch is only visible during 'editing mode'.
I have a UISwitch in a table view cell within my 'detail view controller':
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = nil;
NSString *cellDetail = nil;
static NSString *EnabledCellIdentifier = #"Enabled";
cell = [tableView dequeueReusableCellWithIdentifier:EnabledCellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:EnabledCellIdentifier] autorelease];
UISwitch* actSwitch = [[UISwitch alloc] initWithFrame:CGRectZero];
[cell setEditingAccessoryView:actSwitch];
[actSwitch addTarget:self action:#selector(actSwitchChanged:) forControlEvents:UIControlEventValueChanged];
if ([[currentItem valueForKey:#"itemEnabled"] boolValue]) {
cellDetail = #"Active";
actSwitch.on = YES;
} else {
cellDetail = #"Disabled";
actSwitch.on = NO;
}
[actSwitch release];
cell.textLabel.text = cellDetail;
return cell;
}
I have a method to receive the action:
- (void)actSwitchChanged:(id)sender {
UISwitch* swEnabled = (UISwitch*)sender;
NSManagedObjectContext* itemContext = [currentItem managedObjectContext];
currentItem.itemEnabled = [NSNumber numberWithBool:swEnabled.on];
NSError *error = nil;
if (![itemContext save:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
}

Here is an approach to getting the cell from within the action method that does not require subclassing UISwitch:
- (void)switchAction:(id)sender {
// Cast the sender as a UISwitch
UISwitch *aSwitch = (UISwitch *)sender;
// Cast the superview of aSwitch to a UITableViewCell
UITableViewCell *cell = (UITableViewCell *)aSwitch.superview;
// You could get an indexPath as follows (though you don't need it in this case)
NSIndexPath *indexPath = [self.tableView indexPathForCell:cell];
// Set the text of the label in your cell
cell.textLabel.text = aSwitch.on ? #"Active"; : #"Disabled";
}

You can get a specific cell with -[UITableView cellForRowAtIndexPath:]. You just need to know the index path of the cell (also note that you are asking the tableview for the cell, not calling the delegate method).

So I subclassed UISwtich and included property for storing the cell's index path, which can then be used in the action method:
- (void)actSwitchChanged:(id)sender {
TLSwitchInCell* swEnabled = (TLSwitchInCell*)sender;
currentItem.itemEnabled = [NSNumber numberWithBool:swEnabled.on];
UITableViewCell* theCell = [self.tableView cellForRowAtIndexPath:swEnabled.cellIndexPath];
theCell.textLabel.text = swEnabled.on ? #"Active" : #"Disabled";
}

Another variation on a theme! If you have lots of settings fields with switch buttons you can use something like this to grab the label text associated with the switch button that was trigger! You can set the text using usual methods!
#IBAction func switch(sender: UISwitch) {
let labelIndex = sender.superview!.subviews.indexOf({$0 is UILabel})
NSLog("The text from the label associated with this switch is %#",
(sender.superview!.subviews[labelIndex!] as! UILabel).text!)
}

Related

How do I retrieve UITableView row number of a UISwitch?

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;
}

custom tableviewcell is nil

I have a table and I want to fill it with custom tableViewCells, in order to provide the user with information on orders. The problem is that the tableview shows the cells but does not fill them with the information I need. When I inserted breakpoints, I found out that the cells are nill, not matter what I do. this is my cellForRowAtIndexPath:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
OrderOverViewCell *cell = (OrderOverViewCell *)[tableView dequeueReusableCellWithIdentifier: #"orderOverviewCell"];
OrderClass *orderClass = nil;
if (filteredArray == nil) {
if (sortedArray.count >0) {
orderClass = sortedArray[indexPath.row];
}
}
else if (filteredArray != nil) {
orderClass = filteredArray[indexPath.row];
}
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:#"dd/MM/yyyy HH:mm"];
NSString *orderDate = [dateFormatter stringFromDate:orderClass.orderDate];
cell.orderTitle.text = [NSString stringWithFormat:#"%#%# %#",orderClass.companyName,#",", orderDate];
cell.orderDetail.text = [NSString stringWithFormat:#"%# %# %# %#.", #"order nummer:",orderClass.orderNumber, #"betaling:", orderClass.orderPaymentType];
if ([orderClass.orderDelivery isEqualToString:#"Bezorgen"]) {
cell.orderDelivery.text = [NSString stringWithFormat:#"Levering: Bezorgen op %#, %#, %#", orderClass.orderAdress, orderClass.orderAdressZip, orderClass.orderCity];
}
if ([orderClass.orderDelivery isEqualToString:#"Afhalen"]) {
cell.orderDelivery.text = [NSString stringWithFormat:#"Levering: Afhalen op ingestelde datum en tijd."];
}
cell.orderIndication.image = nil;
if ([orderClass.preparing isEqualToString:#"1"]) {
if ([orderClass.ready isEqualToString:#"1"] ){
if ([orderClass.delivered isEqualToString:#"1"]){
cell.orderIndication.image = [UIImage imageNamed:#"order_3.png"];
}
else {
cell.orderIndication.image = [UIImage imageNamed:#"order_2.png"];
}
}
else {
cell.orderIndication.image = [UIImage imageNamed:#"order_1.png"];
}
}
cell.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:#"orderCellBG.png"]];
return cell;
}
Does anybody have an idea what i'm doing wrong? any help would be very appreciated.
Add this in viewDidLoad to indicate the cell to use:
UINib *const cell = [UINib nibWithNibName:#"nibName" bundle:[NSBundle mainBundle]];
[_tableView registerNib:cell forCellReuseIdentifier:#"orderOverviewCell"];
If you're defining your custom cell as a Prototype Cell inside a Storyboard, make sure you have assigned the proper identifier (orderOverviewCell)
According to Apple developer docs
Because the prototype cell is defined in a storyboard, the dequeueReusableCellWithIdentifier: method always returns a valid cell. You don’t need to check the return value against nil and create a cell manually.
Couple of things to check! If you are using Storyboards and your cell is a prototype cell which is contained inside a UITableView instead of a separate Nib file then make sure the unique identifier is correct.
The case as well as spelling matters so make sure you have named the "orderOverviewCell" correctly in the code as well as in the storyboard when you click on the cell.
OrderOverViewCell *cell = (OrderOverViewCell *)[tableView dequeueReusableCellWithIdentifier: #"orderOverviewCell"];
The above code will instantiate the cell and return a valid cell so you don't need to check for nil or anything. I am also assuming that you are using UITableViewController and not a ViewController with a UITableView as an outlet.
changing it to this solved it:
OrderOverViewCell *cell = (OrderOverViewCell *)[self.tableView dequeueReusableCellWithIdentifier: #"orderOverviewCell"];

Adding Accessory to UITableView

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.

UITableView not refreshed

I have an app consisting of a TabBar with a few TabBarControllers. One Controller contains a very simple table, which is supposed to display the contents of a NSMutableDictionary. When you hit the appropriate button, the Dictionary is updated in a separate Controller and the view switches to the UITableViewController, displaying the newly updated table.
I can see the Dictionary being updated. But the TableView never reflects the changes. In fact, it seems to display the changes only the 1st time I enter that screen.
I have tried [self table.reloadData] and while it gets called, the changes aren't reflected to the UITableView.
Does anyone have any suggestions? I am happy to post code, but am unsure what to post.
Update: the table is updated and refreshed properly only the 1st time it is displayed. Subsequent displays simply show the original contents.
Background:
The tableview gets filled from a dictionary: appDelegate.currentFave. The tableview should get refreshed each time the ViewController is invoked by the TabBarController.
- (void)viewWillAppear:(BOOL)animated
{
NSLog(#"in viewWillAppear");
[super viewWillAppear:animated];
[self loadFavesFile];
[self.tableView reloadData];
}
// load the Favorites file from disk
- (void) loadFavesFile
{
// get location of file
NSString *path = [self getFavesFilePath];
// The Favorites .plist data is different from the Affirmations in that it will never be stored in the bundle. Instead,
// if it exists, then use it. If not, no problem.
if ([[NSFileManager defaultManager] fileExistsAtPath:path]) {
// read Faves file and store it for later use...
NSMutableDictionary *tempDict = [NSMutableDictionary dictionaryWithContentsOfFile:path];
appDelegate.sharedData.dictFaves = tempDict;
// grab the latest quote. Append it to the list of existing favorites
NSString *key = [NSString stringWithFormat:#"%d", appDelegate.sharedData.dictFaves.count + 1];
NSString *newFave = [NSString stringWithFormat:#"%#", appDelegate.currentFave];
[appDelegate.sharedData.dictFaves setObject:newFave forKey:key];
} else {
NSLog(#"Favorites file doesn't exist");
appDelegate.sharedData.dictFaves = nil;
}
}
// this gets invoked the very first call. Only once per running of the App.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// reuse or create the cell
static NSString *cellID = #"cellId";
UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:cellID];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellID];
}
// allow longer lines to wrap
cell.textLabel.numberOfLines = 0; // Multiline
cell.textLabel.lineBreakMode = UILineBreakModeWordWrap;
cell.textLabel.font = [UIFont fontWithName:#"Chalkduster" size:(16)];
cell.textLabel.textColor = [UIColor yellowColor];
// NOTE: for reasons unknown, I cannot set either the cell- or table- background color. So it must be done using the Label.
// set the text for the cell
NSString *row = [NSString stringWithFormat:#"%d", indexPath.row + 1];
cell.textLabel.text = [appDelegate.sharedData.dictFaves objectForKey:row];
return cell;
}
I found the problem. I was not properly initializing and assignng the TableView in my view controller. See below
- (void)viewDidLoad
{
[super viewDidLoad];
tableView = [[UITableView alloc] initWithFrame:[[UIScreen mainScreen] applicationFrame] style:UITableViewStylePlain];
tableView.dataSource = self;
tableView.delegate = self;
tableView.backgroundColor=[UIColor blackColor];
self.view = tableView;
}
Assuming the code you have put up is correct, you want to use [self.table reloadData]. You have the . in the wrong place.
I had this same problem yesterday, for me it turned out I had set the wrong file owner in interface builder and hadn't set up the data source and delegates for the table view properly.
Try going into interface builder and right-clicking on the file owner, this should show you if anything isn't connected up properly.
You should make sure that your Interface Builder connections are set up properly, but what this problem really sounds like is that you have your UITableViewCell setup code in cellForRowAtIndexPath: inside your if(cell == nil) statement. Which it shouldn't be. Let me explain. If you have a list of cells, and you want to set the titles to each cell to a string in an array called myArray, right now your (incorrect) code looks like this:
- (UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"cellIdentifier"];
if (cell == nil) {
// No cell to reuse => create a new one
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:#"cellIdentifier"] autorelease];
[[cell textLabel] setText:[myArray objectAtIndex:[indexPath row]]];
}
return cell;
}
Can you see the problem with that logic? The cell will only get an updated title if no reusable cell can be found, which, in your case, sounds like the situation. Apple says that you should create a 'new' cell each time cellForRowAtIndexPath: is called, which means that you put all of your setup code outside of the if(cell == nil) check.
Continuing with this example, the proper code would look like this:
- (UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"cellIdentifier"];
if (cell == nil) {
// No cell to reuse => create a new one
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:#"cellIdentifier"] autorelease];
}
[[cell textLabel] setText:[myArray objectAtIndex:[indexPath row]]];
return cell;
}
This way, the cell gets assigned the proper string whether or not a reusable cell is found and so calling reloadData will have the desired effect.

Fetching Values from textField in a custom cell iPhone

I have a custom cell in a table View, see below.
I have two textFields txt1 and txt2 in my custom cell, as seen below
How can i access the values i enter in each of the text fields so that i can add the values in to seperate Arrays...
the "Addset" button will increment the no of sections of the grouped table View there by incrementing the one more set.
my table view code is as follows.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *cellID= #"catogoryCell";
CustomCell *cell = (CustomCell *)[tableView dequeueReusableCellWithIdentifier:cellID];
if(cell==nil)
{
NSArray *nibObjects = [[NSBundle mainBundle] loadNibNamed:#"CustomCell" owner:nil options:nil];
for(id currentObject in nibObjects)
{
if([currentObject isKindOfClass: [CustomCell class]])
{
cell = (CustomCell *)currentObject;
}
}
}
//cell.txt1.text = #"0";
return cell;
}
Thanks all..
Cells are reusable, so they need a more persistent way to keep their info.
Method 1:
You could hold some UITextField objects into an array in case you don't want to reuse the textfields as well, and at cellForRowAtIndexPath you'd only need to set the textfields to their cells such as:
cell.txt1 = [textFieldsArray objectAtindex:indexPath.section*2];
cell.txt2 = [textFieldsArray objectAtindex:indexPath.section*2+1]; //txt1 and txt2 properties should have assign
Method 2:
If you want to reuse the textfields as well I suggest using an array with mutable dictionaries, each dictionary holding the 'settings' for a cell. The textfields will be fully managed by the custom cell (e.g: at the UIControlEventValueChanged event update #"txt1" or #"txt2" values from the dictionary attached to the cell).
///somewhere in the initialization (in the class holding the tableview)
contentArray = [[NSMutableArray alloc] init];
///when adding a new cell (e.g: inside the 'Add set' action)
[contentArray addObject:[NSMutableDictionary dictionaryWithObjectsAndKeys:#"", #"txt1", #"", #"txt2", nil]];
//add a new cell to the table (the same way you do now when tapping 'Add set')
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
...
[cell attachDictionary:[contentArray objectAtIndex:indexPath.section]];
return cell;
}
///anywhere where you'd like to access the values inserted inside a cell
NSMutableDictionary *cell3Content = [contentArray objectAtIndex:3];
NSString *text1 = [cell3Content valueForKey:#"txt1"];
NSString *text2 = [cell3Content valueForKey:#"txt2"];
///CustomCell.m
-(id)initWithCoder:(NSCoder *)decoder{
self = [super initWithCoder:decoder];
if(!self) return nil;
[txt1 addTarget:self action:#selector(txt1Changed:) forControlEvents:UIControlEventValueChanged];
[txt2 addTarget:self action:#selector(txt2Changed:) forControlEvents:UIControlEventValueChanged];
return self;
}
-(void)attachDictionary:(NSMutableDictionary *)dic{
contentDictionary = dic;
txt1.text = [contentDictionary valueForKey:#"txt1"];
txt2.text = [contentDictionary valueForKey:#"txt2"];
}
-(void)txt1Changed:(UITextField *)sender{
[contentDictionary setValue:txt1.text forKey:#"txt1"];
}
When you make the IBOutlet connections in your UITableViewCell subclass, connect them to properties in the File Owner (the viewController), instead of the view itself. That way you'll be able to access them from your viewController (the UItableViewDataSource)