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.
Related
I'm trying to implement UISegmentedControl in each dequeueReusableCell UITableViewCell like so:
The Issue: Each TableViewCell is referencing to the same Segmented Control and I'm unable to fetch the state of the control for any cell in particular. As per my understanding, there's only one instance of SegmentedControl that is being initialised and that instance is being shared by all the TableViewCells, and because of that I can't access the unique value of the state for any particular TableViewCell, eg: I'm unable to access what the SegmentControl state is set to for the 3rd cell.
View Controller Code:
import UIKit
import UserNotifications
class MarkAttendanceViewController: UIViewController {
var pickedDate: Date = Date.init()
override func viewDidLoad() {
super.viewDidLoad()
if #available(iOS 13.0, *) {
overrideUserInterfaceStyle = .light
}
}
#IBAction func datePicker(_ sender: UIDatePicker) {
pickedDate = sender.date.addingTimeInterval(19800)
let weekDay = Calendar(identifier:.gregorian).component(.weekday, from: pickedDate)
print(weekDay)
updateSubjects(pickedDate)
}
func updateSubjects(_ pickedDate: Date) {
}
}
extension MarkAttendanceViewController: UITableViewDataSource, UITableViewDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "subjectCell", for: indexPath) as! SubjectTableViewCell
cell.SessionType.text = "Lecture"
cell.SessionName.text = "Network Security"
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
}
}
class SubjectTableViewCell: UITableViewCell {
#IBOutlet var SessionType: UILabel!
#IBOutlet var SessionName: UILabel!
#IBOutlet var segmentControlOutlet: UISegmentedControl!
#IBAction func segmentedControlIndex(_ sender: UISegmentedControl) {
print(sender.selectedSegmentIndex)
}
}
Github Link here
Please let me know if there's any more information that I need to provide or if the question isn't clear. TIA
You should set the tag of your segmentControlOutlet to indexPath.row in cellForRowAt:IndexPath method.
Also you must add an action on valueChange event on each of your UISegmentedControl in the same method.
below code might give you some idea:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "subjectCell", for: indexPath) as! SubjectTableViewCell
cell.SessionType.text = "Lecture"
cell.SessionName.text = "Network Security"
// add an action on value change to detect a change in the value of segmented control
cell.segmentControlOutlet.addTarget(self, action: #selector(segmentValueChanged(_:)), for: .valueChanged)
// set the tag property of your segmented control to uniquely identify each segmented control in the value change event
cell.segmentControlOutlet.tag = indexPath.row
return cell
}
and you can distinguish among various instances of UISegmentedControl using the tag property that you set inside the cellForRow method.
#objc func segmentValueChanged(_ sender: UISegmentedControl) {
switch sender.tag {
case 0:
// do something on value changed in segmented control in first cell and so on...
print(sender.tag)
default:
break
}
print(sender.selectedSegmentIndex)
}
Hope this helps
Use this toterial. add UITableViewCell to your project and set UISegment action in custom UITableViewCell
it seems that the root cause of the issue that would like to pass the data between the cell and the VC containing the table and this is done simple by delegate and protocol design pattern as below
you will have a protocol defining the data to be passed between two members as below
protocol SubjectTableViewCellDelegate {
func didSelectSegmentControlCell(cell: SegmentCell)
}
then you will have cell containing the segment control and a delegate var of type SegmentControlDelegate as below
import UIKit
class SubjectTableViewCell: UITableViewCell {
// MARK: Properties
var delegate: SubjectTableViewCellDelegate?
// MARK: IBOutlets
#IBOutlet weak var segmentControl: UISegmentedControl!
// MARK: Life Cycle Methods
override func awakeFromNib() {
super.awakeFromNib()
}
// MARK: IB Actions
#IBAction func segmentControlAction(_ sender: UISegmentedControl) {
delegate?.didSelectSegmentControlCell(cell: self)
}
}
then you will have your VC acting as a delegate of the Segment cell after having each cell delegate to be the VC containing the Table
import UIKit
class MarkAttendanceViewController: UIViewController, SegmentCellDelegate, UITableViewDelegate, UITableViewDataSource {
// MARK: SegmentCellDelegate Methods
func didSelectSegmentControlCell(cell: SegmentCell){
// you will have the cell that contains all the data
/* all your business here */
}
extension MarkAttendanceViewController: UITableViewDataSource, UITableViewDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "subjectCell", for: indexPath) as! SubjectTableViewCell
/* remember to have thee delegate of the cell to self as the below line */
cell.delegate = self
cell.SessionType.text = "Lecture"
cell.SessionName.text = "Network Security"
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
}
}
}
the idea is a general idea imagine there is a button or date picker or any other outlet you should use this pattern to move data between two sides
I would suggest that the answers you have been given, including the accepted answers, are quick fixes that don't actually address the real problem with how you have architected this piece of software. You may not care at this point, but for future readers of the question this may be helpful.
You may have heard of the Model-View-Controller (MVC) architecture that is commonly used when developing for the iOS platform. In the case of your software you have a View -- for simplicity's sake, let's just consider the table view cells as the view in this case. You have a controller -- your MarkAttendanceViewController which implements the UITableViewDataSource and UITableViewDelegate interfaces. The issue, however, is that you don't really have a model for the data you are displaying in the view. In fact, the root of your problem stems from the fact that you are using the view as the model as well, which is problematic because table view cells are reused and the data contained in them can be lost during the cell reuse process if it is not stored somewhere else. If the data is stored in a data model class, you can keep it separate from the table view cells and it will persist through cell reuse.
You have 3 pieces of data associated with each table view cell: The SessionType, the SessionName and the attendance status for the session (ie: Attended, Missed, Mass Bunk or No Lecture). A data model for this could look like this (with an enumerated type to represent the attendance status):
enum AttendanceStatus: Int {
case attended
case missed
case massBunk
case noLecture
}
struct Session {
let name: String
let type: String
var attendanceStatus: AttendanceStatus
}
You may also want to represent type with an enum, but let's keep this simple.
You can instantiate an instance of this data model as follows:
var session = Session(name: "Network Security", type: "Lecture", attendanceStatus: .attended)
Note the var keyword to make it mutable, as you will want to change the attendanceStatus when the UISegmentedControl value changes. Changing this property is done like so:
session.attendanceStatus = .noLecture
To map from your segmented control to AttendanceStatus, you can use the Int raw value for the enum, as follows:
AttendanceStatus(rawValue: segmentedControl.selectedSegmentIndex)
And to map from your data model's attendanceStatus property to a selectedSegmentIndex for your segmented control:
segmentedControl.selectedSegmentIndex = session.attendanceStatus.rawValue
Now in your view controller, you can instantiate an array of Session objects and use that to populate your table view. When a segmented control changes, you can use the indexPath.row of the table view cell for the segmented control in order to find the Session instance in your array of sessions!
(For a more advanced implementation of this, you can also looking into the Model-View-ViewModel (MVVM) architecture which provides an even cleaner way of bidirectional mapping between the data model and the view)
There are multiple way to solve this Problem.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "subjectCell", for: indexPath) as! SubjectTableViewCell
cell.SessionType.text = "Lecture"
cell.SessionName.text = "Network Security"
// add an action on value change to detect a change in the value of segmented control
cell.segmentControlOutlet.addTarget(self, action: #selector(segmentChanged(_:)), for: .valueChanged)
// set the tag property of your segmented control to uniquely identify each segmented control in the value change event
cell.segmentControlOutlet.tag = indexPath.section
return cell
}
Then Find cell based on Segment Control.
#objc func segmentChanged(_ sender: UISegmentedControl) {
if let cell = sender.superview as! UITableViewCell {
let indexPath = tableView.indexPathForCell(cell)
print(indexPath.row)
if indexPath.row == 0 {
print("segment event of cell 0")
}
else if indexPath.row == 1 {
print("segment event of cell 1")
}
}
}
you can also use delegate and Clouser
I am creating a UITableView that enables the user to add a variable amount of data. Table looks like this initially:
When the user clicks on the "+" button, i would like to add a new cell with a UITextField for entering data. This new cell is a Custom UITableViewCell called "RecordValueCell". Here's what is looks like:
//Custom UITableViewCell
class RecordValueCell : UITableViewCell {
#IBOutlet weak var textField: UITextField!
#IBOutlet weak var deleteButton: UIButton!
var onButtonTapped : ((_ sender : UIButton)->Void)?
#IBAction func deleteButtonTouched(_ sender: Any) {
guard let senderButton = sender as? UIButton else {
return
}
onButtonTapped?(senderButton)
}
}
However when i try to add another cell, using the tableView.dequeueReusableCell(withIdentifier: ) function, it seems to return the same cell. And here is what my UI looks like:
Empty space at the top of the section where my new cell should be. Here is the code to add the cell:
func addNewValueCell() {
guard let reusableValueCell = self.tableView.dequeueReusableCell(withIdentifier: "valueCell") as? RecordValueCell else {
fatalError("failed to get reusable cell valueCell")
}
var cell = Cell() //some custom cell Object
//add the gray horizontal line you see in the pictures
reusableValueCell.textField.addBorder(toSide: .Bottom, withColor: UIColor.gray.cgColor, andThickness: 0.5)
reusableValueCell.onButtonTapped = { (sender) in
self.removeValue(sender: sender)
}
cell.cell = reusableValueCell
self.sections[self.sections.count - 1].cells.insert(cell, at: 0)
//When i put a break point at this spot, i find that reusableValueCell is the same object as the cell that is already being used.
tableView.reloadData()
reusableValueCell.prepareForReuse()
}
When i debug it, i find that dequeueReusableCell(withIdentifier: ) returns the exact same RecordValueCell multiple times.
Here is my cellForRowAt:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = self.sections[indexPath.section].cells[indexPath.row].cell else {
fatalError("error getting cell")
}
return cell
}
numberOfRowsInSection
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.sections[section].cells.count
}
First of all, you will need to set the View Controller Class that this table is contained in as the table's UITableViewDataSource
tableView.dataSource = self // view controller that contains the tableView
Create an array of strings as member of your View Controller class which contains the data for each cell:
var strings = [String]()
Then you will need to implement the following method for the UITableViewDataSource protocol:
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return strings.count
}
You should also be dequeueing the cells in your cellForRowAt method like so:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: yourIdentifier) as! YourCellClass
cell.textLabel = strings[indexPath.row]
return cell
}
Then whenever the user enters into the textField, their input will be appended to this array:
let input = textField.text
strings.append(input)
tableView.reloadData()
Once the data is reloaded, the cell will be added to the table automatically since the number of rows are defined by the String array's length and the label is set in the cellForRowAt method.
This feature is very easy to implement if you will do in a good way.
First, you have to create two TableCell. First to give the option to add a record with plus button and second for entering a value with textfield. Now always return first cell (AddRecordTableCell) in the last row in tableView, and return the number of rows according to entered values like
return totalValues.count + 1
(Before you mark as duplicate you have to read the whole question and I am posting this cause I din't found the relevant and proper solution also need the solution in swift)
I have created one demo project and load and displayed name and area from array on custom cell.
I have noticed that after every 5th cell means 6th row is repeating with contents of 0th cell
for e.g.
the demo code is given below
class demoTableCell: UITableViewCell {
#IBOutlet var name : UILabel!
#IBOutlet var area : UILabel!
}
extension ViewController:UITableViewDelegate, UITableViewDataSource{
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 80
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.arrDemo.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cellIdentifier = "Cell"
var cell : demoTableCell = demoTable.dequeueReusableCell(withIdentifier: cellIdentifier)! as! demoTableCell
cell.name.text = (arrDemo.object(at: indexPath.row) as! NSDictionary).value(forKey: "Name") as? String
cell.area.text = (arrDemo.object(at: indexPath.row) as! NSDictionary).value(forKey: "Area") as? String
if indexPath.row == 0{
cell.name.isHidden = true
}
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
}
}
As I hide the first label on 0th cell so I found that 6th row is also effected with implemented functionality of 0th cell. It means that also hide label1 of every 6th cell as I have attached the screenshot below so you can get the exact issue (This issue happened only if table view is scrollable)
As I have try to solve this issue from this link also but facing the same issue and cannot find the proper solution, and I am stuck here.
Cells are reused, you have to make sure that every UI element is set to a defined state.
You are using an if clause but there is no else case or a default value.
Simple solution:
Just replace
if indexPath.row == 0 {
cell.name.isHidden = true
}
with
cell.name.isHidden = indexPath.row == 0
this sets the hidden property always to a defined state.
And the usual dont's
Do not use NSDictionary in Swift.
Do not valueForKey unless you really need KVC (actually here you don't).
Remember - the cells are being reused.
You hide the cell, but you never explicitly unhide the cell
When you come to row 6, you are re-using the cell that was at row 0, and isHidden = true
All you need to do is extend your check, and hide the rows that you need to be hidden, and explicitly show the cells that you need to see. If you also have a moving banner that you add - you will also need to check to see if it's been loaded, and remove it if not required. Remember - it may not be row 6 - that's just how it works out with the current screensize
If you do have significant differences between the cells you want to use, you might be better using two different classes - and then you don't have to think about hiding labels
class demoTableCell: DemoTableCellNormalRow {
#IBOutlet var name : UILabel!
#IBOutlet var area : UILabel!
}
class demoTableCell: DemoTableCellFirstRow {
#IBOutlet var area : UILabel!
#IBOutlet var movingBannerView : LCBannerView!
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cellIdentifier = "Cell"
if row == 0 {
var cell : demoTableCell = demoTable.dequeueReusableCell(withIdentifier: cellIdentifier)! as! DemoTableCellFirstRow
cell.area.text = (arrDemo.object(at: indexPath.row) as! NSDictionary).value(forKey: "Area") as? String
// populate the bannerview which already exists, or create a new one
return cell
} else {
var cell : demoTableCell = demoTable.dequeueReusableCell(withIdentifier: cellIdentifier)! as! DemoTableCellNormalRow
cell.name.text = (arrDemo.object(at: indexPath.row) as! NSDictionary).value(forKey: "Name") as? String
cell.area.text = (arrDemo.object(at: indexPath.row) as! NSDictionary).value(forKey: "Area") as? String
return cell
}
}
Implement prepareForReuse in your cell class
override func prepareForReuse() {
name.isHidden = false
}
I'm not very experienced in Swift. I have a tableView and custom cells in it, where are several labels, UISlider and UISwitch.
When i change slider values and hit submit(bar button item), I want to collect UISlider and UISwitch values from all cells.
What i tried:
1.Tags: I reached some cells, but stopped and could not reach currently invisible cells, and finally read some opinions, that tags are unlikely to use.
Question: Are there any clear pro et contra?
2.CellForRowAtIndexPath:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("CustomTableViewCell") as! CustomTableViewCell
cell.Label1.text = "Long Tongue, size \(indexPath.row) cm"
cell.Label2.text = "Big Banana, size \(indexPath.row) inches"
return cell
}
#IBAction func submitTapped(sender: AnyObject) {
let cell = tableView(self.tableView , cellForRowAtIndexPath: NSIndexPath(forRow: 1, inSection: 0)) as! CustomTableViewCell
print(cell.Label1.text) // gives me
print(cell.Label2.text) // values
print(cell.customSlider.value) // gives me the value stated as
print(cell.customSwitch.on) // default
}
Do i understand correctly, that i call cellForRowAtIndexPath here and no wonder i get new instance of Custom Cell (processed by function)?
3."Wag the dog"
Unfortunately i've lost a SO link to discussion of this solution :(
I tried to reach UIViewController using .superview.superview..., but Xcode refused to eat 4 superviews (and i was not sure that i found correct number of .superviews).
Main idea is to give access to UIViewController property in Custom Cell:
add a property in CustomTableViewCell:
class CustomTableViewCell: UITableViewCell {
var viewController : MyViewController?
var cellNo = 0
//and so on
}
class MyViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
var sliderValues: [Float] = []
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("CustomTableViewCell") as! CustomTableViewCell
cell.Label1.text = "Long Tongue, size \(indexPath.row) cm"
cell.Label2.text = "Big Banana, size \(indexPath.row) inches"
self.sliderValues.append (0.0) // just to be sure that each slider can put it's value to Array
cell.cellNo = indexPath.row // so the Custom Cell knows it's No
//---------------------//
cell.viewController = self
//---------------------//
return cell
}
}
//----------------------
//and back to Custom Cell
#IBAction func sliderValueChanged(sender: AnyObject) {
self.viewController?.sliderValues[self.cellNo] = self.customSlider.value
}
// and the same way with UISwitch
Good News, This works!
Question: are there any methods not to "wag the dog" and reach Custom Cell from UIViewController?
Looking at your solution I think you will have an issue because the cell is holding onto a reference to your viewController, so it will lead to memory leaks. You should use "weak var viewController : MyViewController?" if you were going to go down this route
However, as matt has said, you should not do this. It is better to update your model with this data. You might be able to pass the data directly to the cell to modify the data, but I do not know the format of your data, so another idea is you could create a delegate to pass the values back from the cell. An example is:
protocol CustomTableViewCellDelegate: class {
func didChangeSlider(value: Float, cellNo: Int)
//func didSwitchOn(value: Bool, cellNo: Int)
}
You would then add this to your cell, like this:
class CustomTableViewCell: UITableViewCell {
weak var delegate: CustomTableViewCellDelegate?
var cellNo = 0
//and so on
}
Then use the delegate here:
#IBAction func sliderValueChanged(sender: AnyObject) {
self.delegate?.didChangeSlider(self.customSlider.value, cellNo)
}
Finally in your ViewController when you create the cell, you need to do this:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("CustomTableViewCell") as! CustomTableViewCell
cell.Label1.text = "Long Tongue, size \(indexPath.row) cm"
cell.Label2.text = "Big Banana, size \(indexPath.row) inches"
cell.delegate = self
cell.cellNo = indexPath.row
return cell
}
Add at the end of your ViewController, add the delegate handler:
extension MyViewController: CustomTableViewCellDelegate {
func didChangeSlider(value: Float, cellNo: Int) {
//Save your value here
}
}
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
}