As the title said; I want to read the data from multiple UITextFields for each cell and store them in an array. How would I do so?
I have created a subclass CustomCell that has 2 UITextFields in it. P/S: the identifier for the cell is also CustomCell
Many thanks
class TableViewController : UITableViewController, UITextFieldDelegate {
var data = [input]()
#IBOutlet var table: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let customCell = tableView.dequeueReusableCell(withIdentifier: "CustomCell") as! CustomCell
customCell.input1.tag = indexPath.row
customCell.input2.tag = indexPath.row
customCell.input1.delegate = self
customCell.input2.delegate = self
return customCell
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return data.count
}
#IBAction func addButton(_ sender: Any) {
table.beginUpdates()
table.insertRows(at: [IndexPath(row: data.count-1, section: 0)], with: .automatic)
table.endUpdates()
}
func textFieldDidEndEditing(_ textField: UITextField) {
let indexPath = IndexPath(row: textField.tag, section: 0)
if let customCell = self.table.cellForRow(at: indexPath) as? CustomCell{
let a = customCell.Credit.text!.isEmpty ? no:String(customCell.input1.text!)
let b = customCell.letterGrade.text!.isEmpty ? no:String(customCell.input2.text!))
inputRead.append(input(string1: a, string2: b))
}
#IBAction func foo(_ sender: Any) {
if inputRead.count == 0{
return
}
//the rest of implementation
}
CustomCell class:
Import UIKit
public class CustomCell: UITableViewCell {
#IBOutlet weak var input1: UITextField!
#IBOutlet weak var input2: UITextField!
}
Update your code as follow in your view controller.
Assign textfield's delegate self to ViewController in cellForRowAt
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let customCell = tableView.dequeueReusableCell(withIdentifier: "CustomCell", for: indexPath) as! CustomCell
customCell.input1.delegate = self
customCell.input2.delegate = self
return customCell
}
Now implemented text field delegate in your view controller.
func textFieldDidEndEditing(_ textField: UITextField) {
guard let jobTaskCell = textField.superview?.superview as? CustomCell else {
return
}
if textField == jobTaskCell.input1 {
// Get text from textfield and store in array
} else if textField == jobTaskCell.input2 {
// Get text from textfield and store in array
}
}
Note: Following code depends on how to place textfield in your cell. So make sure you need to check this recursively by adding and removing superView
guard let jobTaskCell = textField.superview?.superview as? CustomCell else {
return
}
This simply mean that, just for when textfield inside tableview cell without any extra view:
textField.superview = contentView of TableViewCell
textField.superview?.superview = TableViewCell
I hope this will fix your issue.
This can help you to store text in an array:-
1: Add indexPath.row as a tag to your textField in cellForRowAt
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let customCell = tableView.dequeueReusableCell(withIdentifier: "CustomCell", for: indexPath) as! CustomCell
customCell.input1.tag = indexPath.row
customCell.input2.tag = indexPath.row
customCell.input1.delegate = self
customCell.input2.delegate = self
return customCell
}
2: In textField delegate method you can get your textField text
func textFieldDidEndEditing(_ textField: UITextField) {
let indexPath = IndexPath(row: textField.tag, section: 0)
if let customCell = self.table.cellForRow(at: indexPath) as? CustomCell {
//From here you can get your particular input1 or input2 textfield text
print(customCell.input1.text)
print(customCell.input2.text)
}
}
Related
Here is where I generate my custom cells, which store an attribute called "indexPath". Furthermore, each cell contains a UITextField. The textfield delegate function is below. Both of these functions exist within the same ViewController.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ReusableCell", for: indexPath) as! BuyerCell
cell.nameField.delegate = self
cell.indexPath = indexPath
cell.nameField.text = potentialBuyers[indexPath.row]
return cell
}
Here is the TextField delegate function. I need to print the cell's indexPath from within the delegate function.
func textFieldDidEndEditing(_ textField: UITextField) {
print("called textFieldDidEndEditing")
//I need to print the cell's indexPath here
}
Welcome to Stackoverflow. I can think of multiple ways to do this, without even testing these ideas.
Subclassing
Have your own UITextField subclass, and put a variable there to hold the IndexPath object, and in cellForRow, store the current IndexPath to that textField.
So in your textField delegate textFieldDidEndEditing, you can either change the type UITextField to your own class like MyTextField.
OR you can just cast it like:
if let customTextField = textField as? MyTextField
Storing row as TAG
This one is much simpler, but perhaps only limited to a single section tableView. If you are using a single section, then you can just store the row of the IndexPath in your cellForRow to the TAG property of the textField.
Try this code below.
func textFieldDidEndEditing(_ textField: UITextField) {
let gpoint = textField.convert(CGPoint.zero, to: tableView)
let indexPath = tableView.indexPathForRow(at: gpoint)
}
I would decouple the text field delegate from the view controller and either use an your own delegation protocol between the cell and your view controller or, as I show below, use a closure to handle the event from the cell in the view controller
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ReusableCell", for: indexPath) as! BuyerCell
let buyer = potentialBuyers[indexPath.row]
cell.nameEditedHandler = { textField in
// Do something - you still have access to indexPath, cell and buyer
}
cell.nameField.text = buyer
return cell
}
In your BuyerCell class -
class BuyerCell: UITableViewCell {
var nameField: UITextField!
var nameEditedHandler: ((UITextField)->Void)?
func awakeFromNib() {
super.awakeFromNib()
self.nameField.delegate = self
}
}
extension BuyerCell: UITextFieldDelegate {
func textFieldDidEndEditing(_ textField: UITextField) {
self.nameEditedHandler?(textField)
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ReusableCell", for: indexPath) as! BuyerCell
cell.nameField.delegate = self
cell.indexPath = indexPath
cell.nameField.text = potentialBuyers[indexPath.row]
// add tag for the textField
cell.nameField.tag = indexPath.row
return cell
}
Now in textFieldDelegate Method get your cell and cell.indexPath like this
func textFieldDidEndEditing(_ textField: UITextField) {
print("called textFieldDidEndEditing")
let cell = tableView.cellForRow(at: IndexPath(row: textField.tag, section: 0)) as! BuyerCell
print(cell.indexPath)
}
you can use the .tag property of UITextfeild
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ReusableCell", for: indexPath) as! BuyerCell
cell.nameField.delegate = self
cell.indexPath = indexPath
cell.nameField.text = potentialBuyers[indexPath.row]
cell.nameField.tag = indexPath.row // here assign the indexpath
return cell
}
Now use the delegate method of UITextField
func textFieldDidEndEditing(_ textField: UITextField) {
print(textField.tag) // here you print the index
print("called textFieldDidEndEditing")
let indexPath = IndexPath.init(row: textField.tag, section: 0) //create indexpath from tag
}
I am trying to read the input from 2 textfield from multiple TableViewCell, Looks like it only access input2 and append input2 twice to the inputRead array. How could I fix this? Many thanks
P/S: This is how I layout 2 TextFields in a cell: [input1] [input2]
override func tableView(_ tableView: UITableView, cellForRowAt
indexPath: IndexPath) -> UITableViewCell {
let customCell = tableView.dequeueReusableCell(withIdentifier: "CustomCell") as! CustomCell
customCell.input1.tag = indexPath.row
customCell.input2.tag = indexPath.row
customCell.input1.delegate = self
customCell.input2.delegate = self
return customCell
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
return textField.endEditing(true)
}
func textFieldDidEndEditing(_ textField: UITextField) {
let indexPath = IndexPath(row: textField.tag, section: 0)
if let customCell = self.table.cellForRow(at: indexPath) as?
CustomCell {
let string1 = String(customCell.input1.text!)
let string2 = String(customCell.input2.text!)
inputRead.append(input(string1, string2))
}
}//end class
class CustomCell : UITableViewCell{
#IBOutlet weak var input1: UITextField!
#IBOutlet weak var input2: UITextField!
}
I have created a tableView with two different labels and one textfield. Depending on the indexPath in which is selected the labels will display different text according to the array. I have created a CocoTouch Class file and made it type TableViewCell.
TableViewCell.swift
import UIKit
class Driver_Navigation_TableViewCell: UITableViewCell {
#IBOutlet weak var orderTextField: UITextField!
#IBOutlet weak var adressLabel: UILabel!
#IBOutlet weak var nameLabel: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
ViewController.swift
class ViewController: UIViewController, MGLMapViewDelegate, UITableViewDataSource, UITableViewDelegate, UITextFieldDelegate {
var allCellsText = [String]()
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
array.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! Driver_Navigation_TableViewCell
cell.adressLabel.text = passengersAdress[indexPath.row]
cell.nameLabel.text = passengersName[indexPath.row]
cell.orderTextField.placeholder = "\(indexPath.row + 1)."
return(cell)
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath as IndexPath) as! Driver_Navigation_TableViewCell
cell.orderTextField.tag = indexPath.row
cell.orderTextField.delegate = self // theField is your IBOutlet UITextfield in your custom cell
return cell
}
func textFieldDidEndEditing(textField: UITextField) {
allCellsText.append(textField.text!)
print(allCellsText)
}
}
You cant have duplicate UITableViewDataSource and UITableViewDelegate methods it won't work like that.
Also in your code you have not set the delegate for the textField in both the cellForRowAtIndexPath() methods.
If you want to have two table view in a single controller try the following
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return array.count
}
func tableView(_ tableView: UITableView, cellForRowAtIndexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! Driver_Navigation_TableViewCell
if tableView == self.tableView1 {
cell.adressLabel.text = passengersAdress[indexPath.row]
cell.nameLabel.text = passengersName[indexPath.row]
cell.orderTextField.placeholder = "\(indexPath.row + 1)."
}
else {
cell.orderTextField.tag = indexPath.row
}
cell.orderTextField.delegate = self
return(cell)
}
Create outlets for the Table View
I want to be able to use a UIStepper that is located in each tableview row. I would like each UIStepper to update a label in the same tableview row that the UIStepper is in.
The code that I am trying is as follows
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cartListCell = tableView.dequeueReusableCell(withIdentifier: reuseCartListingCellIdentifier, for: indexPath) as? CartListingItemTableViewCell
cartListCell?.UI_STEPPER_NAME.value = VALUE_FROM_ARRAY
return cartListCell!
}
#IBAction func CartStoreQtyStepperAction(_ sender: UIStepper) {
// I need to update the value of a label in the tableview cell that tapped
}
UPDATED IMPLEMENTATION
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
cartListCell?.CartListingProductQtyStepperOutlet.tag = indexPath.row
cartListCell?.CartListingProductQtyStepperOutlet.addTarget(self, action: #selector(self.CartStoreQtyStepperAction(_:)), for: UIControlEvents.valueChanged)
cartListCell?.tag = Int(CartStoreItemIdArray [indexPath.row])!
return cartListCell!
}
#IBAction func CartStoreQtyStepperAction(_ sender: UIStepper)
{
let stepperValue = Int(sender.value)
let indexPath = IndexPath(row: stepperValue, section: 0)
print(stepperValue)
if let cell = yourTableView.cellForRow(at: indexPath) as? CartListingItemTableViewCell
{
print(cell?.tag)
}
}
I am not able to access the tableview cell and the label in that when I am doing it like this. Can someone guide me how to do this?
// Custom tableview cell code
class CartListingItemTableViewCell: UITableViewCell {
#IBOutlet weak var label: UILabel!
#IBOutlet weak var stepper: UIStepper!
}
// your view controller code (tableview data source)
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cartListCell = tableView.dequeueReusableCell(withIdentifier: reuseCartListingCellIdentifier, for: indexPath) as? CartListingItemTableViewCell
let stepperValue = yourArray[indexPath.row]
cartListCell?.label.text = String(stepperValue) ?? "0"
cartListCell?.stepper.value = Int(stepperValue) ?? 0
cartListCell?.stepper.tag = indexPath.row
cartListCell?.stepper.addTarget(self, action: #selector(self.stepperValueChanged(_:)), for: UIControlEvents.valueChanged)
return cartListCell!
}
// handle stepper value change action
#IBAction func stepperValueChanged(_ stepper: UIStepper) {
let stepperValue = Int(stepper.value)
print(stepperValue) // prints value
let indexPath = IndexPath(row: stepperValue, section: 0)
if let cell = yourTableView.cellForRow(at: indexPath) as? CartListingItemTableViewCell {
cell.label.text = String(stepperValue)
yourValueArray[index] = stepperValue
}
}
This is a solution for Swift 4 using NSKeyValueObservation
First of all you need a property in your data model to keep the current value of the stepper.
var counter = 0.0
In the custom cell create outlets for the stepper and the label and a property for the observation. Connect the outlets.
#IBOutlet weak var stepper : UIStepper!
#IBOutlet weak var label : UILabel!
var observation : NSKeyValueObservation?
In the controller in cellForRow add the observer, dataSourceArray is the data source array.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cartListCell = tableView.dequeueReusableCell(withIdentifier: reuseCartListingCellIdentifier, for: indexPath) as! CartListingItemTableViewCell
let item = dataSourceArray[indexPath.row]
cartListCell.stepper.value = item.counter
cartListCell.label.text = "\(item.counter)"
cartListCell.observation = cell.stepper.observe(\.value, options: [.new]) { (_, change) in
cartListCell.label.text = "\(change.newValue!)"
item.counter = change.newValue!
}
return cartListCell
}
Solution for Swift 4 swift4.
Add these lines in cellforRowAt.
cartListCell.UI_STEPPER_NAME.tag = indexPath.row
cartListCell.UI_STEPPER_NAME.addTarget(self, action: #selector(CartStoreQtyStepperAction), for: .touchUpInside)
To access cell items in the stepper function use this line.
let cartListCell = sender.superview?.superview as! CartListingItemTableViewCell
//check whether your superview is table cell.
//then use the cell instance to update the cell label
Use sender.tag in the function to access particular cell for example.
cartListCell.UI_STEPPER_NAME.text = "\(VALUE_FROM_ARRAY[sender.tag])"
//sender.tag is similar to indexPath.row as we pass the tag value in the cellforRowAt.
//It will work then please accept my answer๐๐ผ๐.
Assign tag value to sender of UIStepper in cellForRowAt and access the change of UIStepper value in CartStoreQtyStepperAction action using same tag.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cartListCell = tableView.dequeueReusableCell(withIdentifier: reuseCartListingCellIdentifier, for: indexPath) as? CartListingItemTableViewCell
//Assign tag value
cartListCell?.UI_STEPPER_NAME.tag = indexPath.row
cartListCell?.UI_STEPPER_NAME.value = VALUE_FROM_ARRAY
cartListCell?.stepper.addTarget(self, action: #selector(self.CartStoreQtyStepperAction(_:)), for: UIControlEvents.valueChanged)
return cartListCell!
}
#IBAction func CartStoreQtyStepperAction(_ sender: UIStepper) {
let index = sender.tag
yourValueArray[index] = sender.value
//Reload particular cell for tableView
}
I need to detect if the button has been clicked in the UITableViewController
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let LikesBtn = cell.viewWithTag(7) as! UIButton
}
The easiest and most efficient way in Swift is a callback closure.
Subclass UITableViewCell, the viewWithTag way to identify UI elements is outdated.
Set the class of the custom cell to the name of the subclass and set the identifier to ButtonCellIdentifier in Interface Builder.
Add a callback property.
Add an action and connect the button to the action.
class ButtonCell: UITableViewCell {
var callback : (() -> Void)?
#IBAction func buttonPressed(_ sender : UIButton) {
callback?()
}
}
In cellForRow assign the callback to the custom cell.
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ButtonCellIdentifier", for: indexPath) as! ButtonCell
cell.callback = {
print("Button pressed", indexPath)
}
return cell
}
When the button is pressed the callback is called. The index path is captured.
Edit
There is a caveat if cells can be added or removed. In this case pass the UITableViewCell instance as parameter and get the index path from there
class ButtonCell: UITableViewCell {
var callback : ((UITableViewCell) -> Void)?
#IBAction func buttonPressed(_ sender : UIButton) {
callback?(self)
}
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ButtonCellIdentifier", for: indexPath) as! ButtonCell
let item = dataSourceArray[indexPath.row]
// do something with item
cell.callback = { cell in
let actualIndexPath = tableView.indexPath(for: cell)!
print("Button pressed", actualIndexPath)
}
return cell
}
If even the section can change, well, then protocol/delegate may be more efficient.
First step:
Make Subclass for your custom UITableViewCell, also register protocol.
Something like this:
protocol MyTableViewCellDelegate: class {
func onButtonPressed(_ sender: UIButton, indexPath: IndexPath)
}
class MyTableViewCell: UITableViewCell {
#IBOutlet var cellButton: UIButton!
var cellIndexPath: IndexPath!
weak var delegate: MyTableViewCellDelegate!
override func awakeFromNib() {
super.awakeFromNib()
cellButton.addTarget(self, action: #selector(self.onButton(_:)), for: .touchUpInside)
}
func onButton(_ sender: UIButton) {
delegate.onButtonPressed(sender, indexPath: cellIndexPath)
}
}
In your TableViewController, make sure it conform to your just created protocol "MyTableViewCellDelegate".
Look at the code below for better understanding.
class MyTableViewController: UITableViewController, MyTableViewCellDelegate {
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if let cell = tableView.dequeueReusableCell(withIdentifier: "cellId", for: indexPath) as? MyTableViewCell {
cell.cellIndexPath = indexPath
cell.delegate = self
return cell
} else {
print("Something wrong. Check your cell idetifier or cell subclass")
return UITableViewCell()
}
}
func onButtonPressed(_ sender: UIButton, indexPath: IndexPath) {
print("DID PRESSED BUTTON WITH TAG = \(sender.tag) AT INDEX PATH = \(indexPath)")
}
}
Here is what I use:
First initialize the button as an Outlet and its action on your TableViewCell
class MainViewCell: UITableViewCell {
#IBOutlet weak var testButton: UIButton!
#IBAction func testBClicked(_ sender: UIButton) {
let tag = sender.tag //with this you can get which button was clicked
}
}
Then in you Main Controller in the cellForRow function just initialize the tag of the button like this:
class MainController: UIViewController, UITableViewDelegate, UITableViewDataSource, {
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell") as! MainViewCell
cell.testButton.tag = indexPath.row
return cell
}
}