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)
Related
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.
I have two UITextFields on the UITableViewCell and their IBOutlets are connected in the custom UITableViewCell class called as "CustomCell.swift".
The Enter button is there on the UIView of ViewController and its IBAction is there in the UIViewController class called as "ViewController".
On click of the Enter button I want to see if the two textFields are empty. How do I do it? Please help
create a Bool variable in your class where you have the button action
var isTextFieldTextEmpty: Bool!
then in your table view dataSource method cellForRowAtIndexPath add
if myCell.myTextField.text?.isEmpty == true {
self.isTextFieldTextEmpty = true
} else {
self.isTextFieldTextEmpty = false
}
then in the IBAction of your (Enter) button add
self.myTableView.reloadData()
self.myTableView.layoutIfNeeded()
print(self.isTextFieldTextEmpty)
if all text fields in all cells of the table view have text, it will print false, else if only one text fields among all the text fields has no text, it will print true
Here is a simple solution. It will work for any number of cells.
What you need to do is iterate through the cells and figure out if the textField that particular cell is holding is empty or not. Now the question is how will you iterate through the cells, is there any delegate for that? The answer is No.
You have to manually construct the indexPaths to get the cells from the Table.
Here is a simple walk through. Your set up is quite right. You should have a tableview in your ViewController. So, the IBOutlet of the tableview should be there. I named my TableView "myTableView". And the textField's Outlet should be inside the TableViewCell which is also right. At the end the action method for the Enter button should be in the view controller.
Make sure, you properly connect all the outlets.
Here is the sample custom TableViewCell -
import UIKit
class CustomTableViewCell: UITableViewCell {
#IBOutlet weak var internalTextField : UITextField!
override func awakeFromNib() {
super.awakeFromNib()
}
}
And now just go to the ViewController.swift-
import UIKit
class ViewController: UIViewController, UITableViewDataSource {
#IBOutlet weak var myTableView : UITableView!
var numberOfCells = 2 //you can update it to be any number
override func viewDidLoad() {
super.viewDidLoad()
self.myTableView.dataSource! = self //assign the delegate
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return numberOfCells
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell : CustomTableViewCell = tableView.dequeueReusableCellWithIdentifier("customCell", forIndexPath: indexPath) as! CustomTableViewCell
return cell;
}
#IBAction func pressedEnter(){
var row = 0
while row < numberOfCells { //iterate through the tableview's cells
let indexPath : NSIndexPath = NSIndexPath(forRow: row, inSection: 0) //Create the indexpath to get the cell
let cell : CustomTableViewCell = self.myTableView.cellForRowAtIndexPath(indexPath) as! CustomTableViewCell
if cell.internalTextField.text!.isEmpty{
print("TextField is Cell \(row) is Empty")
}
else{
print("TextField is Cell \(row) is NOT Empty")
}
row += 1
}
}
}
There are comments which explains everything. Hope this helps.
What property can I access to find out?
I was learning some swift from this tutorial, and I decided to give myself some problems to do. One was that in the tutorial, to edit a name, there is a UIAlert. I want to get rid of that and instead, have UITextField in place of the UILabel, so the user could simply tap on a name, and edit it. I would use the textFieldDidEndEditing(textField: UITextField) function to update the model, which is a dictionary of names and picture filenames.
I set the view controller as the UITextFieldDelegate, I put in the function, but now I'm stuck, because although the text was updated just fine in one of the cells, I don't know how to tell which cell it happened in.
In this case, you probably can consider to subclass a UITextField to refer a Dictionary item.
class DictionaryTextField: UITextField {
var item: [String : AnyObject]?
}
Also create a subclass of UITableViewCell to hold above DictionaryTextField as an IBOutlet property.
class TextFieldTableViewCell: UITableViewCell {
#IBOutlet weak var textField: DictionaryTextField!
}
After finished above setting, an item Dictionary of datasource can be set in func tableView(:, cellForRowAtIndexPath: ).
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("CellIdentifier", forIndexPath: indexPath) as! TextFieldTableViewCell
cell.textField.delegate = self
let item = items[indexPath.row]
cell.textField.item = item
cell.textField.text = "TextField \(item["name"]!)"
return cell
}
Later, in the UITextFiedDelegate, cast the textField as DictionaryTextField. Then the item can be retrieved directly.
func textFieldDidBeginEditing(textField: UITextField) {
guard let textField = textField as? DictionaryTextField else {
return
}
print("Did begin editing: \(textField.item)")
}
The revised codes can be downloaded again with this link: https://db.tt/8j9ENf7b
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
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
}