I'm using a UISwitch in the accessoryView of a UITableViewCell.
The switch uses target-action to communicate when it has been selected or deselected:
switchV.addTarget(self, action: "onChangeSwitch:", forControlEvents: UIControlEvents.ValueChanged)
The problem is figuring out which switch was selected. I know of 3 methods. Each is unsatisfying somewhow.
1. I can use tags:
switchV.tag = indexPath.row
But this sucks if you have sections (and I do), because I need to parse it into and out of a two-number section/row format.
2. I could use the data model and store the switch view on the data item that the cell is drawing:
dataItem.switch.addTarget(self, action: "onChangeSwitch:", forControlEvents: UIControlEvents.ValueChanged)
cell.accessoryView = dataItem.switch
I can then identify which switch was selected by looping over my data set and doing an identity match with sender. This is a lot of looping, and I don't want to put views in my data model.
3. Then there's this method that uses the coordinate of the switch to find the row. Stateless-ish, involves no string parsing or data model muddying, but coordinates, rly? Can I trust it?
tableView.indexPathForRowAtPoint(
sender.convertPoint(CGPointZero, toView: tableView)
)
Is there a better way to get the indexPath of the UISwitch that is selected?
Method #3 is pretty good in my opinion. However if you want it to be really clean here's what I would do .
Declare a custom TableviewCell say CustomTableViewCell and have a delegate protocol called CustomCellDelegate. Here the delegate gets informed like so :
-(void)switchChanged:(UISwitch*)switch inCell:(CustomTableViewCell*)cell
In cellForRowAtIndexPath set your view controller as the delegate of the cell.
Add the switch to to your custom cell and make the cell as the target and implement the switch's action method. Inside the action method call the delegate :
-(void)switchChanged:(id)sender {
if(self.delegate && [self.delegate respondsToSelector:#selector(switchChanged:inCell:])) {
[self.delegate switchChanged:sender inCell:self];
}
Now in your viewController use the passed in cell in the delegate method to calculate the index path :
-(void)switchChanged:(id)sender inCell:(CustomTableViewCell*)cell {
NSIndexPath *path = [self.tableview indexPathForCell:cell];
}
Its a bit of work but if you want to do it the proper way , you can .
Another option is to use an hash which maintains the switch -> data link
var switchToData=[UISwitch:yourData]()
in cellForRowAtIndexPath:
switchToData[newSwitch]=myData
in onChangeSwitch
dataChanged=switchToData[switchChanged]
The hash will be quite small, the same size of the visible switch....
Related
I am looking for is a clean way of changing the model after tapping a button in a custom cell. It's tapping the button that matters here and not selecting the row, so didSelectRowAtIndexPath isn't what I am interested in.
For example
let array = [true, false, true]
When the user taps the button in the second cell, the model should become
array[1] = true
In cellForRowAtIndexPath a target action is added to the button :
cell.button.addTarget(self, action: "myAction:", forControlEvents: UIControlEvents.TouchUpInside)
the action looks like :
func myAction(sender:UIButton!)
{
//how to change the model and reloadRowsAtIndexPaths since the sender is just a UIButton!?
}
If you only have one section in the tableViewController, you could always set button.tag = [indexPath row] inside cellForRowAtIndexPath. Then your action would always know which row was touched from the sender.tag value. Quick and dirty, but it would work.
Or, if you will reuse this custom cell in tableviews with multiple sections, you could subclass the UIButton and add a property for the indexPath. Then you would always have access to both section and row values. Personally, I would take this approach because it gives you the most flexibility in the long run.
inside of cellForRowAtIndexPath()
cell.button.tag = indexPath.row
Then inside of your function myAction()
func myAction(sender:UIButton)
{
var index = sender.view.tag
array[index] = true // just like you want
}
I am a little unclear about your question - are you asking how you can change a uitableviewcell's content once a button is pressed in the cell then one way to go about it would be to tag the button with the cell's indexPath when the cell is created in the cellForRowAtIndexPath method. Then you can read the tag from the selector once the button is pushed.
I just tested this and it works. When you create the cell add the line:
cell.tag=indexPath.row;
and then in the selector method for the button you can use:
NSInteger p=sender.tag;
int x=(int)p;
NSIndexPath *path = [NSIndexPath indexPathForRow:x inSection:0];
UITableViewCell *cell = [_tableView cellForRowAtIndexPath:path];
cell.backgroundColor=[UIColor blackColor];
In my example I changed the background color of the cell that the pressed button was in.
I am currently developing an app that features a UITableView with custom cells that contain a UITextField. The problem I am having is that, after the user inputs a number to the textfield, upon scrolling, the tableView reuses that cell and the user's previous input is initialized in the new cell. For example, if I put the number 7 in the top cell's textfield, when I scroll, the newest cell already has a 7 in it. I am wondering how I can fix this problem? Thank you for any help.
Edit: Since my problem is unclear, I basically need a way for my UITableViewCell to "talk" to the model in my UITableViewController so that when the user is done editing the UITextField in the cell, I can save the input to an array in my view controller. Thanks again
Override -prepareForReuse in your cell subclass.
In that method, set your text to nil and then call super.prepareForReuse
The quick fix is to create an array of integers to represent numbers in the table.
Then, in cellForRowAtIndexPath method, just do something like
cell.textField.text = numberArray[indexPath.row]
You need to actually save the text fields data into this array now though, in the same method add
cell.textField.addTarget(self, action: "onTextChanged:", forControlEvents: UIControlEvents.EditingChanged)
Create the ,,onTextChanged'' method like
func onTextChanged(sender: UITextField) {
let cell = sender.superview! as! UITableViewCell
let indexPath = self.tableView.indexPathForCell(cell)!
numberArray[indexPath.row] = sender.text.toInt()!
}
I'm trying to implement the block method outlined in the answer to the question in this link, but I don't understand how to set the block. Assuming that...
cell.onButtonTapped = ^{
[self buttonSelectedAtIndexPath:indexPath];
}
is pseudo code, how would I actually implement this assuming I have a UITableViewCell (not a custom cell) and a UIButton within that cell. Is it possible to do this without creating my own UITableViewCell subclass?
If you're gonna do it this way, you'd really have to make a UITableViewCell subclass which is a target of its button, has a property for the block and calls the block when the button is tapped.
There's actually a simple way to know from which indexPath a subview (eg. the button here) of a cell is from. First you'll need a point on the tableView's coordinates, which is contained by the subview. Let's say you'll choose the center of the subview. To convert it to the tableView's coordinates:
CGPoint subViewCenter = [self.tableView convertPoint:subview.center fromView:subView.superView]; // because a view's center is on its superview's coordinates
to get the indexPath:
NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint:subViewCenter];
No, as per the answer you are referring to, you need a custom UITableViewCell. Essentially you are calling a method on the cell and passing the current indexPath for that cell.
Whether you use the block method or the delegate method you will need a custom UITableViewCell as you need somewhere to store the indexPath and a method to set it.
I have the tag for a custom UITableViewCell but I am not sure how I should reference it?
What is the standard approach here.
I thought something close to this but of course this will not work right?:
CustomCell *cell = (CustomCell*)[self.mainTableView cellForRowAtIndexPath:[NSIndexPath indexPathForItem:tag inSection:0]];
I make this reference when a button is pressed in the custom implementation of that cell using a delegate method to pass it to my view controller.
You should not refer to cell itself, because is just interface to show your data. Cells can be (and should be) reused and filled with different data.
Better is store data id, or at lest index of element that cell.
Read more in docs A Closer Look at Table View Cells
To get from a button in a cell to the cell itself, walk up the view hierarchy:
UIView* v = sender; // sender is the button that was tapped
do {
v = v.superview;
} while (![v isKindOfClass: [UITableViewCell class]]);
DiscoverCell* cell = (DiscoverCell*)v;
I have a UITableView and each UITableViewCell contains an editable UITextView.
My data source is a NSMutableArray containing NSMutableDictionary that holds the text value and some styling keys for the text.
How can I (efficiently) make it so that any changes a user makes to the UITextView are updated in the corresponding datasource NSMutableDictionary?
A rather simple way is to utilize the index path of the table, it is NOT the cleanest so it depends on the complexity of your datasource, and if you have multiple tables etc.
What you can do is when the user ends editing the textView or selects another row in tableView, you read the indexPath of the selected row (That requires that the row keeps actually being in the selected state while editing the textView which it should by default). From there you call your update method.
To catch the end of editing you implement
-(void)textViewDidEndEditing:(UITextView *)textView
{
NSIndexPath *selectedpath = [myTable indexPathForSelectedRow];
[self myUpdateMethodForIndexPath:selectedpath];
}
To catch deselect of the table row and the above doesnt get called, you implement
-(void)tableView:(UITableView *)tableView didDeselectRowAtIndexPath:(NSIndexPath *)indexPath
{
[self myUpdateMethodForIndexPath:indexPath];
}
Your update method must then read the value of the textView at the corresponding cell at the indexPath and handle this in the datasource. To care for sections of course you need to correctly handle the indexPath, in the example just the row is used (1 section).
-(void)myUpdateMethodForIndexPath:(NSIndexPath *)editPath
{
UITableViewCell *editCell = [myTable cellForRowAtIndexPath:editPath];
NSString *newText = editCell.theTextView.text;
....
NSMutableDictionary *dict = [myDictArray objectAtIndex:editPath.row];
....
}
First of all, you must assign a tag to each UITextView, to know exactly which UITextView are you refering.
Then you must implement UITextViewDelegate in your view controller which holds the tableview. Then, make this view controller the delegate of each UITextView. Read here how to implement it: UITextViewDelegate reference.
Look for the protocol method that better fits your needs (probably – textView:shouldChangeTextInRange:replacementText:, wich is called each time the text changes in any range.
In the delegate method, you can read the text with UITextView.text property, and assign this value to your data model (the dictionary).
Another possible approach is to use KVO pattern, but it requires more coding and a better understanding both, the pattern and the implementation. Hope it helps!
Make your view controller the delegate of each text view. Listen for appropriate events to get the updated text. Then have the view controller update the data model with the updated text.
If you have custom cells then have the cell be the text view delegate. Then the cell should notify its delegate (the view controller) about the updated text. Of course this requires that your custom cell class define its own delegate protocol and the view controller should make itself the delegate of each cell.
That's as specific as an answer can be for such a vague question.