I have two UITextFields inside a custom cell of a UITableView.
I need to edit and store values of the textFields.
When I click inside a UITextField I have to know the row it belongs to in order to save the value to the correct record of a local array.
How can I get the row index of the textField?
I tried :
-(void)textFieldDidBeginEditing:(UITextField *)textField
{
currentRow = [self.tableView indexPathForSelectedRow].row;
}
But the currentRow does not change when I click inside the UITextFieldRow.It changes only when I click (select) the entire row...
The text field did not send touch event to the table view so indexPathForSelectedRow is not working. You can use:
CGPoint textFieldOrigin = [self.tableView convertPoint:textField.bounds.origin fromView:textField];
NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint:textFieldOrigin];
In iOS 8 I found that the simulator and device had different number of superviews, so this is a little more generic and should work across all versions of iOS:
UIView *superview = textField.superview;
while (![superview isMemberOfClass:[UITableViewCell class]]) { // If you have a custom class change it here
superview = superview.superview;
}
UITableViewCell *cell =(UITableViewCell *) superview;
NSIndexPath *indexPath = [self.table indexPathForCell:cell];
Try this
//For ios 7
UITableViewCell *cell =(UITableViewCell *) textField.superview.superview.superview;
NSIndexPath *indexPath = [tblView indexPathForCell:cell];
//For ios 6
UITableViewCell *cell =(UITableViewCell *) textField.superview.superview;
NSIndexPath *indexPath = [tblView indexPathForCell:cell];
1>You can achieve it by programmatically creating textfields in your CellForRowAtIndexPath and setting text field's tag as indexpath.row.
then textFieldDidBeginEditing you can just fetch textField.tag and achieve what you want.
2>another way is to have 2 custom cells in one table view. in that way you cam place text feild individually and set their tags from your utility panel.
What I do is create a custom cell, and put whatever custom UI elements I need inside and create a property indexPath which gets set when the cell is dequeued. Then I pass the indexPath along to the custom elements in didSet.
class EditableTableViewCell: UITableViewCell {
#IBOutlet weak var textField: TableViewTextField!
var indexPath: IndexPath? {
didSet {
//pass it along to the custom textField
textField.indexPath = indexPath
}
}
}
class TableViewTextField: UITextField {
var indexPath: IndexPath?
}
In TableView:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "EditableCell") as! EditableTableViewCell
cell.indexPath = indexPath
return cell
}
Then I implement the UITextFieldDelegate protocol, and since the textField has its indexPath you will always know where it came from.
Not sure where the best place to set the delegate is. The easiest is to set it when the cell is dequeued.
override func textFieldDidEndEditing(_ textField: UITextField) {
guard let myTextField = textField as? TableViewTextField else { fatalError() }
guard let indexPath = myTextField.indexPath else { fatalError() }
}
Related
I have a method in Objective-C that I've used to uncheck all cells in a UITableView:
- (void)resetCheckedCells {
for (NSUInteger section = 0, sectionCount = self.tableView.numberOfSections; section < sectionCount; ++section) {
for (NSUInteger row = 0, rowCount = [self.tableView numberOfRowsInSection:section]; row < rowCount; ++row) {
UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:row inSection:section]];
cell.accessoryType = UITableViewCellAccessoryNone;
cell.accessoryView = nil;
}
}
}
In Swift, I think I need to use enumeration to accomplish this. I'm stumped as to how to get the values I need. Here's a "physics for poets" sketch of what I'm trying to do:
func resetCheckedCells() {
// TODO: figure this out?
for (section, tableView) in tableView.enumerate() {
for (row, tableView) in tableView {
let cell = UITableView
cell.accessoryType = .None
}
}
}
This doesn't work, but it's illustrative of what I'm trying to accomplish. What am I missing?
UPDATE
There was a very simple, but non-apparent (to me), way to do this involving cellForRowAtIndexPath and a global array...
var myStuffToSave = [NSManagedObject]()
... that's instantiated with the UITableViewController loads. I'm posting this update in hopes that someone else might find it helpful.
My UITableViewController is initially populated with NSManagedObjects. My didSelectRowAtIndexPath does two things:
1) adds/removes NSManagedObjects from a global myStuffToSave array
2) toggles cell.accessoryType for the cell between .Checkmark and .None
That when cellForRowAtIndexPath is called, I compare items from myStuffToSave with what's in the tableView.
Here's a snippet of my cellForRowAtIndexPath:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath)
// I set the cells' accessory types to .None when they're drawn
// ** SO RELOADING THE tableView NUKES THE CHECKMARKS WITH THE FOLLOWING LINE... **
cell.accessoryType = .None
// boilerplate cell configuration
// Set checkmarks
// ** ...IF THE ARRAY IS EMPTY
if self.myStuffToSave.count > 0 {
// enumerate myStuffToSave...
for (indexOfMyStuffToSave, thingToSave) in stuffToSave.enumerate() {
// if the object in the array of stuff to save matches the object in the index of the tableview
if stuffInMyTableView[indexPath.row].hashValue == stuffToSave[indexOfMyStuffToSave].hashValue {
// then set its accessoryView to checkmark
cell.accessoryType = .Checkmark
}
}
}
return cell
}
So removing everything from myStuffToSave and reloading the tableView will reset all the checked cells. This is what my resetCheckedCells method looks like at the end:
func resetCheckedCells() {
// remove everything from myStuffToSave
self.myStuffToSave.removeAll()
// and reload tableView where the accessoryType is set to .None by default
self.tableView.reloadData()
}
Thanks to #TannerNelson for pointing me towards a solution.
This seems like a strange way to use UITableView.
You should look at the UITableViewDataSource protocol and implement your code using that.
The main function you will need to implement is tableView:cellForRowAtIndexPath. In this function, you dequeue and return a cell.
Then when you need to update cells to be checked or unchecked, you can just call reloadAtIndexPaths: and pass the visible index paths.
This gist has a nice UITableView extension for reloading only visible cells using self.tableView.reloadVisibleCells()
https://gist.github.com/tannernelson/6d140c5ce2a701e4b710
I want to implement a custom UITableViewCell class that is made up of subviews such as, labels, buttons, and images. These cells will display content fetched from the web using an API.
I do not want to implement the UITableView delegate method didSelectRowAtIndexPath as this will make the entire cell selectable. Only the button in the cell should be able to trigger any action.
The button is connected from the storyboard to the custom UITableViewCell via IBOutlet. The addTarget(_:action:forControlEvents:) method is called on the UIButton in cellForRowAtIndexPath of the UITableViewController class.
The roadblock occurs when we want to detect the indexPath of the selected cell in the Selector function of the button.
This is how the indexPath for the selected cell can be detected in the Selector function
#IBAction func doSomething(sender: AnyObject) {
var location: CGPoint = sender.convertPoint(CGPointZero, toView: self.tableView)
var indexPath: NSIndexPath = self.tableView.indexPathForRowAtPoint(location)!
println("The indexPath for Selected Cell is - \(indexPath.row)")
}
Although, this successfully gets me around the issue, my question is;
1) Have you found an alternate way of being able to use UIButtons to pass selected cell data in a custom UITableViewCell?
2) What would be the best practice, so far, in Swift to implement a similar scenario?
One way would be to iterate over the superviews of the sender and see if a UITableViewCell can be found...
Let's say you created a generic UIView extension with a method that would check to see if any of its superview's was a UITableViewCell...
extension UIView {
func parentTableViewCell() -> UITableViewCell? {
var view = self
while let superview = view.superview {
if let cell = superview as? UITableViewCell {
return cell
} else {
view = superview
}
}
return nil
}
}
Then you could do the following...
if let cell = (sender as UIView).parentTableViewCell() {
let indexPath = tableView.indexPathForCell(cell)
println("The row for this cell is - \(indexPath.row)")
}
Another way would be to use the tag property of a view by setting it to an Int and then check to see what the tag of the sender was in the method.
i.e. In your tableView:cellForRowAtIndexPath: method
cell.myButton.tag = indexPath.row
Then in your doSomething method
let rowIndex = (sender as UIButton).tag
println("The row for this cell is - \(rowIndex)"
If your tableView only has one section, you can use the tag property of the button to store the indexPath.row.
In cellForRowAtIndexPath, when you set the target-action for the button, set button.tag = indexPath.row.
Then in your doSomething routine:
#IBAction doSomething(sender: UIButton) {
println("This button is from row - \(sender.tag)")
}
I have a tableView and inside of it is custom cells. The whole thing syncs fine with NSUserDefaults, however there is a problem. These custom cells have textfields inside of them among other interactive subviews. When the user modifies the textfield of a cell, how can I determine the index of the cell so that I can properly match the data model to the tableView? I've tried assigning an index variable to each cell in the cellForIndexPath(...) method but this causes alot of potential bugs.
To get a sense for what my tableView looks like, just look at the Apple reminders app, which has custom cells which also contain interactive subviews.
Anyway, any alternative methods of determining this sort of data? There are tons of reminders apps that contain interactive subviews so there must be a better way!
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as GoalTableViewCell
cell.goalTextField.text = goalsArray[indexPath.row].title
cell.checkmarkImageView.visible = goalsArray[indexPath.row].checkmarked
cell.blackLineView.visible = goalsArray[indexPath.row].checkmarked
cell.enabled = goalsArray[indexPath.row].enabled
cell.isLastCell = goalsArray[indexPath.row].isLastCell
cell.priority = goalsArray[indexPath.row].priority as Priority
cell.indexInTable = indexPath.row //this is how I assign the index of a cell. Problem is that cellForRowAtIndexPath is inconsistent.
return cell
}
Then to retrieve that data
func finishCreatingGoal(notification : NSNotification) { //this executes after a textfield resigns first responder status
if (notification.name == "FinishCreatingGoal") {
var userInfo = notification.userInfo!
var text = userInfo["text"]! as String
var index = userInfo["index"]! as Int //this index is pulled from the cell.indexInTable variable which is assigned in CellForIndexPath(...)
goalsArray[index].title = text //this line crashes often after various manipulation of the tableView such as deleting rows and reordering them.
saveGoals(goalsArray)
let indexPath = NSIndexPath(forRow:index,inSection:0)
self.tableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation: UITableViewRowAnimation.None)
}
}
UITableView has a method indexPathForRowAtPoint: which you can use.
// textField is a subview of a cell
let point = tableView.convertPoint(CGPointZero, fromView: textField)
if let indexPath = tableView.indexPathForRowAtPoint(point) {
let yourObject = dataSource[indexPath.row]
// do something with it
}
Should be pretty self-explanatory.
A better option would be to have a Goal class (which is what I'm guessing you already have in goalsArray) that would contain all the information that the user stores. Then instead of setting all of the UI from the table view's delegate, just pass it a Goal object. I.e.
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as GoalTableViewCell
cell.goal = goalsArray[indexPath.row]
return cell
}
Then in your GoalTableViewCell class you keep a reference to this goal as well as update the UI. Then when you want to check the index of the cell's Goal you can just search for it.
You could get the index of the cell via TouchEvents i.e. getting the point coordinates in the UITableview. Then extracting the index path from point location and then via which you could get the cell index. Here is the method that i used in my code to do the same.
- (void)checkButtonTapped:(id)sender event:(id)event
{
NSSet *touches = [event allTouches];
UITouch *touch = [touches anyObject];
CGPoint currentTouchPosition = [touch locationInView:self.tableView];
NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint: currentTouchPosition];
if (indexPath != nil)
{
NSLog(#"the cell index : %d", (int)indexPath.row);
}
}
Update ---
This is how its done in Objective-C. I haven't still read about swift so i cannot translate it in swift.
I have a static UITableView, content of each cell I've made in storyboard, but I need to change texLabels of some cells programmatically while runtime. How can I do it?
Create a property for each cell you want to change in your table view controller, like so:
#property (weak) IBOutlet UITableViewCell *cell1;
#property (weak) IBOutlet UITableViewCell *cell2;
Connect each one to a cell in Interface Builder.
When you only need to change the label's text, you can use
self.cell1.textLabel.text = #"New Text";
If you need to replace the whole label, use
UILabel *newLabel = [[UILabel alloc] init];
self.cell2.textLabel = newLabel;
#RossPenman has posted a good answer.
#ggrana noted a potential issue to do with memory and cell reuse, however don't worry about that ...
For a UITableView with Static cells all the cells are instantiated up front, before viewDidLoad is called on your UITableViewController and aren't reused in the way that dynamic cells are. As such you can even just take IBOutlets directly to the UITextFields, UISwitches, UILabels and things you are really interested in that you have dropped into you static cells in the Storyboard.
I needed this.
dispatch_async(dispatch_get_main_queue(), ^{
(...textLabel updates)
[self.tableView reloadData];
});
You can get the cell from your table view using the method cellForRowAtIndexPath, you will need to define a outlet that retrieves your tableview.
__weak IBOutlet UITableView *tableView;
after that you can get the cell like that:
NSIndexPath* indexPath = [NSIndexPath indexPathForRow:yourRow inSection:yourSection];
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
[[cell textLabel] setText:#"Your new text"];
Maybe after setting the text you will need to adjust your label, or your cell height, if you want a deeper help provide more information, and I will be glad to help you.
And you are done, hope it helps.
Swift solution:
First make a IBOutlet of static TableViewCell
#IBOutlet weak var cellFirst: UITableViewCell!
then in viewDidLoad change label Name.
cellFirst.textLabel?.text = "Language"
Note: If you have attached a label on TableViewCell then just hide it using storyboard.
Hope it works:
#IBOutlet weak var yourTextField: UITextField!
private var yourText: String?
I just custom in this tableview's delegate:
override func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {
guard let yourText = yourText else { return }
yourTextField.text = yourText
}
When you change the text:
yourText = "New text here"
tableView.reloadData()
You can do something like this
for (int section = 0; section < [table numberOfSections]; section++) {
for (int row = 0; row < [table numberOfRowsInSection:section]; row++) {
NSIndexPath* cellPath = [NSIndexPath indexPathForRow:row inSection:section];
UITableViewCell* cell = [self cellForRowAtIndexPath:cellPath];
cell.backgroundColor = [UIColor blackColor];
cell.textLabel.textColor = [UIColor whiteColor;
cell.textLabel.text = #"Your text";
}
}
Also, make sure to keep your changes out of viewDidAppear and to place them in viewWillAppear or you will run into problems
Your can try it
override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath)
{
cell.textLabel?.text = titles[indexPath.row]
}
I have a question regarding uitable view.
I am implementing an app which is similar to the address book app.I am able to present the table view in editing mode. I want to let the user to edit the text in the cells in editing mode. I know that in order to edit the text in the cells, I need a textfield. I have created a textfield.
My question is:
what should I do in order to present that textfield in the cells.
what are the methods I need to implement in order to present that text field in the table view in editing mode.
Once I am done with editing ,How can I update the data which is in my contacts view controller(contains all the contacts).The saving should persist in the address book. For this question I know that I need to implement some delegate method,But I am not sure how to do that.
Please have a look at the following code,so that you will have an idea about my problem.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath: (NSIndexPath *)indexPath
{
[tableView setSeparatorColor:[UIColor clearColor]];
//[self.tableView setEditing: YES animated: YES];
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"Cell"];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
}
// Configure the cell...
if(isEditingOn) {
if(cell == nil)
cell = [self getCellContentView:CellIdentifier];
UILabel *lblTemp1 = (UILabel *)[cell viewWithTag:1];
UITextField *textfield1=(UITextField*)[cell viewWithTag:2];
if(indexPath.row == 0) {
lblTemp1.text = #"Name";
textfield1.text = myContact.name;
}
else if(indexPath.row == 1) {
lblTemp1.text = #"Phone";
textfield1.text = myContact.phone;
}
else if(indexPath.row == 2) {
lblTemp1.text = #"Email";
textfield1.text = myContact.email;
}
}
else {
if(indexPath.row == 0) {
cell.textLabel.text = myContact.name;
}
else if(indexPath.row == 1) {
cell.textLabel.text = myContact.phone;
}
else if(indexPath.row == 2) {
cell.textLabel.text = myContact.email;
}
}
return cell;
}
- (UITableViewCell *) getCellContentView:(NSString *)cellIdentifier {
CGRect CellFrame = CGRectMake(0, 0, 60, 20);
CGRect Label1Frame = CGRectMake(10, 10, 180, 25);
UILabel *lblTemp;
UITableViewCell *cell = [[[UITableViewCell alloc] initWithFrame:CellFrame reuseIdentifier:cellIdentifier] autorelease];
lblTemp = [[UILabel alloc] initWithFrame:Label1Frame];
lblTemp.tag = 1;
[cell.contentView addSubview:lblTemp];
[lblTemp release];
CGRect TextFieldFrame=CGRectMake(240, 10, 60, 25);
UITextField *textfield;
textfield=[[UITextField alloc]initWithFrame:TextFieldFrame];
textfield.tag=2;
textfield.placeholder = #"";
[cell.contentView addSubview:textfield];
}
This is a really complex question to answer this fully and in-depth with code examples, but I'll try to point you in the right direction.
1) Add a UITextField as a subview of your table cell when you create the cell in the tableView:cellForRowAtIndexPath: method (I assume that's what your getCellContentView: method is for). Set a tag on your UITextField that matches the row index of the cell and make your tableviewcontroller the delegate for the cell. Set the textfield to hidden. (remember to set the tag each time the cell is requested, not just the first time you create it).
2) In the tableView:didSelectRowAtIndexPath: method, grab the cell using tableViewCellForRowAtIndexPath and then show the textfield inside it (you may have to do some view traversal to get it) and call becomeFirstResponder on the textfield.
3) When the user has typed something, your textfielddelegate methods will be fired. You can look at the tag on the textfield to work out which row the field belongs to and then update the dat source with the new text. Then just reload the table to hide the textfield and update the cell content.
If you know how to use custom table cell subclasses then you can make your life a bit easier by creating a custom cell that already contains a textfield and has an property for accessing it, but otherwise the technique will be mostly the same.
Also, tableView:didSelectRowAtIndexPath: won't normally fire when a tableview is in edit mode unless you set tableView.allowsSelectionDuringEditing = YES;
It's better to use 2 UITableViewCells, The first one for view and the last for edit mode.
Also we will depend on the variable rowToEdit which refers to the current editing row. (in my case one cell is allowed to be edited at the same time)
let us begin:
First I depend on accessoryButtonTap action to edit the row:
var rowToEdit: IndexPath? = nil
override func tableView(_ tableView: UITableView, accessoryButtonTappedForRowWith indexPath: IndexPath) {
// End edit mode if one cell being in edit mode other than this one
if let row = self.rowToEdit {
// return current edit cell to view mode
self.rowToEdit = nil
self.tableView.reloadRows(at: [row], with: .automatic)
}
self.rowToEdit = indexPath
self.tableView.reloadRows(at: [self.rowToEdit!], with: .automatic)
}
Differentiate between the 2 modes when you will load the cell:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath == self.rowToEdit {
let cellId = "ContactEditTableViewCell"
let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath as IndexPath) as! ContactEditTableViewCell
cell.accessoryType = .none
self.configEditCell(cell: cell, indexPath: indexPath)
return cell
} else {
let cellId = "ContactTableViewCell"
let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath as IndexPath) as! ContactTableViewCell
self.configCell(cell: cell, indexPath: indexPath)
return cell
}
}
Additional option if you want to change the height based on mode:
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if indexPath == self.rowToEdit {
return 120
} else {
return 70
}
}
Last option to add Save and Cancel buttons:
I added them to each cell, So I pass a reference to the ContactTable to each cell.
#IBAction func btnSave_click(_ sender: UIButton) {
// save the record
btnCancel_click(sender)
}
#IBAction func btnCancel_click(_ sender: UIButton) {
let tmp = self.tbl.rowToEdit
self.tbl.rowToEdit = nil
self.tbl.tableView.reloadRows(at: [tmp!], with: .automatic)
}