Get indexPath of UITextField in UITableViewCell with Swift - ios

So, I'm building a Detail View Controller App that presents a Table with a two-part cell: the label and the Text Field.
I'm trying to retrieve the Text Field value and add it to an array.
I tried to use the "textField.superview.superview" technique but it didn't worked.
func textFieldDidEndEditing(textField: UITextField!){
var cell: UITableViewCell = textField.superview.superview
var table: UITableView = cell.superview.superview
let textFieldIndexPath = table.indexPathForCell(cell)
}
Xcode fails to build and presents that "UIView is not convertible to UITableViewCell" and "to UITableView".
The referring table has two sections, of four and two rows, respectively.
Thanks in advance.
EDIT:
added ".superview" at the second line of the function.

While the currently accepted answer might work, it assumes a specific view hierarchy, which is not a reliable approach since it is prone to change.
To get the indexPath from a UITextField that is inside a cell, it's much better to go with the following:
func textFieldDidEndEditing(textField: UITextField!){
let pointInTable = textField.convert(textField.bounds.origin, to: self.tableView)
let textFieldIndexPath = self.tableView.indexPathForRow(at: pointInTable)
...
}
This will continue to work independent of eventual changes to the view hierarchy.

You'll want to cast the first and second lines in your function, like this:
func textFieldDidEndEditing(textField: UITextField!){
var cell: UITableViewCell = textField.superview.superview as UITableViewCell
var table: UITableView = cell.superview as UITableView
let textFieldIndexPath = table.indexPathForCell(cell)
}
superview returns a UIView, so you need to cast it to the type of view you expect.

Using superview and typecasting isn't a preferred aaproach. The best practice is to use delegate pattern. If you have a textField in DemoTableViewCell which you are using in DemoTableViewController make a protocol DemoTableViewCellDelegate and assign delegate of DemoTableViewCell to DemoTableViewController so that viewcontroller is notified when eiditing ends in textfield.
protocol DemoTableViewCellDelegate: class {
func didEndEditing(onCell cell: DemoTableViewCell)
}
class DemoTableViewCell: UITableViewCell {
#IBOutlet var textField: UITextField!
weak var delegate: DemoTableViewCellDelegate?
override func awakeFromNib() {
super.awakeFromNib()
textField.delegate = self
}
}
extension DemoTableViewCell: UITextFieldDelegate {
func textFieldDidEndEditing(_ textField: UITextField) {
delegate.didEndEditing(onCell: self)
}
}
class DemoTableViewController: UITableViewController {
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: DemoTableViewCell.self, for: indexPath)
cell.delegate = self
return cell
}
}
extension DemoTableViewController: DemoTableViewCellDelegate {
func didEndEditing(onCell cell: DemoTableViewCell) {
//Indexpath for the cell in which editing have ended.
//Now do whatever you want to do with the text and indexpath.
let indexPath = tableView.indexPath(for: cell)
let text = cell.textField.text
}
}

You can use tag property of UITableViewCell
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "UpdateTableViewCell", for: indexPath) as! UpdateTableViewCell
cell.tag = indexPath.row
cell.setCellData()
return cell
}
now in UITableViewCell
func textFieldDidEndEditing(textField: UITextField!){
let textFieldIndexPath = self.tag
}

Related

How to get section count and row count of textfield in tableview?

I have a text field in a tableView. I need to get the position of textfield but the problem is there are multiple section in it. I am able to get only one thing section or row using textfield.tag but I need both.
You can find the parent UIResponder of any class by walking up the UIResponder chain; both UITextField and UITableViewCell inherit from UIView, which inherits from UIResponder, so to get the parent tableViewCell of your textfield you can call this function on your textfield:
extension UIResponder {
func findParentTableViewCell () -> UITableViewCell? {
var parent: UIResponder = self
while let next = parent.next {
if let tableViewCell = parent as? UITableViewCell {
return tableViewCell
}
parent = next
}
return nil
}
}
Then once you have the tableViewCell, you just ask the tableView for its index path with tableView.indexPAth(for:)
You never need to use the tag field:
guard let cell = textField.findParentTableViewCell (),
let indexPath = tableView.indexPath(for: cell) else {
print("This textfield is not in the tableview!")
}
print("The indexPath is \(indexPath)")
You can use a variation of a previous answer that I wrote.
Use a delegate protocol between the cell and the tableview. This allows you to keep the text field delegate in the cell subclass, which enables you to assign the touch text field delegate to the prototype cell in Interface Builder, while still keeping the business logic in the view controller.
It also avoids the potentially fragile approach of navigating the view hierarchy or the use of the tag property, which has issues when cells indexes change (as a result of insertion, deletion or reordering), and which doesn't work where you need to know a section number as well as a row number, as is the case here.
CellSubclass.swift
protocol CellSubclassDelegate: class {
func textFieldUpdatedInCell(_ cell: CellSubclass)
}
class CellSubclass: UITableViewCell {
#IBOutlet var someTextField: UITextField!
var delegate: CellSubclassDelegate?
override func prepareForReuse() {
super.prepareForReuse()
self.delegate = nil
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool
self.delegate?.textFieldUpdatedInCell(self)
return yes
}
ViewController.swift
class MyViewController: UIViewController, CellSubclassDelegate {
#IBOutlet var tableview: UITableView!
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! CellSubclass
cell.delegate = self
// Other cell setup
}
// MARK: CellSubclassDelegate
func textFieldUpdatedInCell(_ cell: CellSubclass) {
guard let indexPath = self.tableView.indexPathForCell(cell) else {
// Note, this shouldn't happen - how did the user tap on a button that wasn't on screen?
return
}
// Do whatever you need to do with the indexPath
print("Text field updated on row \(indexPath.row) of section \(indexPath.section")
}
}
You can also see Jacob King's answer using a closure rather than a delegate pattern in the same question.

UITableView Duplicate cells (custom cells with textfields)

I have spent days on resolving this issue and after trying much I am asking a question here. I am using a custom UITableViewCell and that cell contains UITextFields. On adding new cells to the table view, the table view behaves abnormal like it duplicates the cell and when I try to edit the textfield of new cell, the textfield of previous cel gets edited too.
The behavior of duplication is as follows: 1st cell is duplicated for 3rd cell. I don't know this is due to reusability of cells but could anyone tell me about the efficient solution?
I am attaching the screenshot of UITableViewCell.
The code for cellForRow is as follows:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell : Product_PriceTableViewCell = tableView.dequeueReusableCell(withIdentifier: "product_priceCell") as! Product_PriceTableViewCell
cell.dropDownViewProducts.index = indexPath.row
cell.txtDescription.index = indexPath.row
cell.tfPrice.index = indexPath.row
cell.dropDownQty.index = indexPath.row
cell.tfTotalPrice_Euro.index = indexPath.row
cell.tfTotalPrice_IDR.index = indexPath.row
cell.dropDownViewTotalDiscount.index = indexPath.row
cell.dropDownViewDeposit.index = indexPath.row
cell.tfTotalDeposit_Euro.index = indexPath.row
cell.tfRemaingAfterDeposit_IDR.index = indexPath.row
return cell
}
The issue is the cell is being reused by the UITableView, which is what you want to happen for good scrolling performance.
You should update the data source that supports each row in the table to hold the text the user inputs in the field.
Then have the text field's text property assigned from your data source in cellForRowAt.
In other words, the UITableViewCell is the same instance each time you see it on the screen, and so is the UITextField and therefore so is it's text property. Which means it needs to be assigned it's correct text value each time cellForRowAt is called.
I'm unsure of your code so I have provided an example of how I would do something like what you want:
class MyCell: UITableViewCell {
#IBOutlet weak var inputField: UITextField!
}
class ViewController: UIViewController {
#IBOutlet weak var table: UITableView!
var items = [String]()
fileprivate func setupItems() {
items = ["Duck",
"Cow",
"Deer",
"Potato"
]
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
setupItems()
}
}
extension ViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// the # of rows will equal the # of items
return items.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// we use the cell's indexPath.row to
// to get the item in the array's text
// and use it as the cell's input field text
guard let cell = tableView.dequeueReusableCell(withIdentifier: "myCell") as? MyCell else {
return UITableViewCell()
}
// now even if the cell is the same instance
// it's field's text is assigned each time
cell.inputField.text = items[indexPath.row]
// Use the tag on UITextField
// to track the indexPath.row that
// it's current being presented for
cell.inputField.tag = indexPath.row
// become the field's delegate
cell.inputField.delegate = self
return cell
}
}
extension ViewController: UITextFieldDelegate {
// or whatever method(s) matches the app's
// input style for this view
func textFieldDidEndEditing(_ textField: UITextField) {
guard let text = textField.text else {
return // nothing to update
}
// use the field's tag
// to update the correct element
items[textField.tag] = text
}
}
I suggest to do the following
class Product_PriceTableViewCell: UITableViewCell {
var indexRow: Int = -1
func configureCell(index: Int) {
cell.dropDownViewProducts.clean()
...
cell.tfRemaingAfterDeposit_IDR.clean()
}
}
where clean is the function to empty de view (depend on the type)
Then in the delegate:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell : Product_PriceTableViewCell = tableView.dequeueReusableCell(withIdentifier: "product_priceCell") as! Product_PriceTableViewCell
cell.configureCell(row: indexPath.row)
return cell
}
As #thefredelement pointed out when the cell is not in the view frame, it is not created. Only when the view is going to appear, it tries to reuse an instance of the cell and as the first is available, the table view uses it but does not reinitialize it. So you have to make sure to clean the data
The rest of the answer is for better coding.

Tableview button.tag throw lldb

i don't know what happen, i set the button.tag with the table row and when it reach row > 1, it will throw lldb. it works if the button.tag <= 1
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cells")! as UITableViewCell
let alertBtn = cell.viewWithTag(1) as! UIButton;
alertBtn.tag = indexPath.row
alertBtn.addTarget(self, action: Selector(("showAlert:")), for: UIControlEvents.touchUpInside)
return cell
}
Application crash on this line, because it fails to find a view with tag 1, the tag is updating in every cell with row value.
let alertBtn = cell.viewWithTag(1) as! UIButton
remove this line and Take #IBOutlet for alertBtn From UITableViewCell instead of refreshing with tag.
Swift 3X...
You are replacing your tag so first tag items are getting nil so replace this code ...
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cells")! as UITableViewCell
let alertBtn = cell.viewWithTag(1) as! UIButton
alertBtn.addTarget(self, action: #selcetor(showAlert(sender:))), for: .touchUpInside)
return cell
}
func showAlert(sender:UIButton) {
let point = sender.convert(CGPoint.zero, to: self.tableview)
let indexpath = self.tableview.indexPathForRow(at: point)
}
Try to do custom UITableViewCell.
Declare protocol and delegate for Your new class class. Wire up a action and call delegate
protocol MyCellDelegate: class {
func buttonPressed(for cell: MyCell)
}
class MyCell:UITableViewCell {
weak var delegate: MyCellDelegate?
#IBAction func buttonPressed(sender: Any){
self.delegate?.buttonPressed(for: self)
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
.......
cell.delegate = self
........
}
Remember to add new protocol implementation to Your VC. You can add prepareForReuse method and reset delegate to nil when cell is reused.
If you want to get indexPath of cell containing tapped button you can use function similar to this matching your requirement.
func showAlert(sender: AnyObject) {
if let cell = sender.superview?.superview as? UITableViewCell{ // do check your viewchierarchy in your case
let indexPath = itemTable.indexPath(for: cell)
}
print(indexPath)// you can use this indexpath to get index of tapped button
}
Remove this line from cellForRowAtIndexPath alertBtn.tag = indexPath.row
If you can use Custom Cell for this purpose you can get indexpath of selected button as you were getting previously.
Create CustomCell and create IBOutlet for your button and labels etc. You can access subviews of your cell in cellForRowAtIndexPath and assign tag to your button. If you have any queries regarding CustomCell do let me know.

UIView extension to get UITableViewCell?

I'm making a UIView extension that returns a possible UITableViewCell if that particular instance of UIView is indeed a subview of a UITableViewCell.
The idea is later I can pass that UITableViewCell reference to UITableView's indexPath(for:) method to get the cell's index path.
So if my table view cells contain UITextField, I'm able to identify which cell that text field comes from when UITextFieldDelegate's textFieldDidEndEditing(_ textField: UITextField) method is called.
So far this is what I came up with:
extension UIView {
var tableViewCell: UITableViewCell? {
get {
var view = self
while let superview = view.superview {
if let tableViewCell = superview as? UITableViewCell {
return tableViewCell
}
view = superview
}
return nil
}
}
}
I have 2 questions:
Since I'm new to programming with Swift, may I know if there is a better (Swiftier?) way to write this?
Is this a good way of identifying the index path of a UITableViewCell which contains a UITextField that is being edited? Is there a better way?
I'm actually new both to Swift and Stack Overflow, so sorry if I do something wrong (please be more forgiving) and I wish for your guidance. Thank you.
The cleanest method to do this is simply tag your cell textField.
For example, you can also tag with indexPath.row.
Then in the UITextFieldDelegate method textFieldDidBeginEditing(_:) simply check the tag of the textField that begins editing and you can simply create an NSIndexPath from it.
However, if you have multiple sections and UITextFields in different secitons, you need both section and rowfor the NSIndexPath to be correct.
Depending on how many textFields you have in the tableView, the solution could be to create an NSDictionary to keep a reference to the section and row.
This is an interesting way of figuring out the indexPath, but a safer way might be to use textView's delegate method and figure out the indexPath of the tableViewCell in relation to the tableView.
class TableViewCell: UITableViewCell {
#IBOutlet weak var textField: UITextField!
}
class ViewController: UIViewController {
var indexPath: IndexPath?
#IBOutlet weak var tableView: UITableView!
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as? TableViewCell else { return UITableViewCell() }
cell.textField.delegate = self
return cell
}
}
extension ViewController: UITextFieldDelegate {
func textFieldDidBeginEditing(_ textField: UITextField) {
let point = textField.convert(.zero, to: tableView)
indexPath = tableView.indexPathForRow(at: point)
}
}
These are the alternatives :
1. Find the cell based on point.
func textFieldDidEndEditing(_ sender: UITextField) {
let tableViewTouchPoint:CGPoint = sender.convert(CGPointZero, to:self.tableView)
let indexPath = self.tableView.indexPathForRow(at: tableViewTouchPoint)
}
Create an extension of Tableview for the same by
extension UITableView {
func indexPathForView(_ view: UIView) -> IndexPath? {
let tableViewTouchPoint:CGPoint = view.convert(CGPointZero, to:self)
let indexPath = self.indexPathForRow(at: tableViewTouchPoint)
return indexPath
}
}
Use it by
let indexPath = self.tableView.indexPathForView(textField)
Based on subview tag. In CellForRowATIndex, set the tags to view equal to indexPath.row. Then, to get back the IndexPath use :
let index = sender.tag
let indePath = IndexPath(row: index, section: 0)

tableView.cellForRowAtIndexPath returns nil with too many cells (swift)

So I have the weirdest thing;
I am looping a tableView in order to iterate over all cells. It works fine with less than 5 cells, but crashes with "unexpectedly found nil" for more cells. Here's the code:
for section in 0..<tableView.numberOfSections {
for row in 0..<tableView.numberofRowsInSection(section) {
let indexPath = NSIndexPath(forRow: row, inSection: section)
let cell = tableView?.cellForRowAtIndexPath(indexPath) as? MenuItemTableViewCell
// extract cell properties
The last line is the one that gives the error.
Any thoughts?
Because cells are reused, cellForRowAtIndexPath will give you cell only if cell for given indexPath is currently visible. It is indicated by the optional value. If you want to prevent from crash, you should use if let
if let cell = tableView?.cellForRowAtIndexPath(indexPath) as? MenuItemTableViewCell {
// Do something with cell
}
If you want to update values from cell, your cells should update the dataSource items. For example you can create delegate for that
protocol UITableViewCellUpdateDelegate {
func cellDidChangeValue(cell: UITableViewCell)
}
Add delegate to your cell and suppose we have a textField in this cell. We add target for the didCHangeTextFieldValue: for EditingDidChange event so it is called every time the user types somethink in it. And when he do, we call the delegate function.
class MyCell: UITableViewCell {
#IBOutlet var textField: UITextField!
var delegate: UITableViewCellUpdateDelegate?
override func awakeFromNib() {
textField.addTarget(self, action: Selector("didCHangeTextFieldValue:"), forControlEvents: UIControlEvents.EditingChanged)
}
#IBAction func didCHangeTextFieldValue(sender: AnyObject?) {
self.delegate?.cellDidChangeValue(cell)
}
}
Then in cellForRowAtIndexPath you add the delegate
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("MyCellIdentifier", forIndexPath: indexPath)
cell.delegate = self
return cell
}
And finally we implement the delegate method:
func cellDidChangeValue(cell: UITableViewCell) {
guard let indexPath = self.tableView.indexPathForCell(cell) else {
return
}
/// Update data source - we have cell and its indexPath
}
Hope it helps

Resources