I have a UITableView, in which, each cell has UITextField embedded. I have an array of values that I want to change based on which UITextField gets edited. Currently, I am using the function:
func textFieldDidEndEditing(_ textField: UITextField) {
editedValue = textField.text!
}
But what I want to do is set a generic string, and if a user changes the text in a UITextField, then I can index the string and change that specific value. Is there a way to find the tag of the cell containing the UITextField that was edited?
in your cellForRowAtIndexPath, do this:
cell.yourTextField.tag = indexPath.row // so we are setting the tag of your textfield to indexPath.row, so that we can use this tag property to get the indexpath.
cell.yourTextField.addTarget(self, action: #selector(textChanged(textField:)), for: .editingChanged)
now in your view controller declare this function:
#objc func textChanged(textField: UITextField) {
let index = textField.tag
// this index is equal to the row of the textview
}
You can get parent cell of your textField and then find IndexPath
func textFieldDidEndEditing(_ textField: UITextField) {
if let cell = textField.superview as? UITableViewCell {
let indexpath = tableview.indexPath(for: cell)
// indexpath.row - row index, indexpath.section - section index
}
}
First define a protocol/delegate in your UITableViewCell
protocol CustomCellDelegate: class {
func getTextFeildData(cell: CustomCell, text: String)
}
class CustomCell: UITableViewCell {
weak var delegate: CustomCellDelegate!
func textFieldDidEndEditing(_ textField: UITextField) {
delegate.getTextFeildData(cell: self, text: textField.text ?? "")
}
}
In your cellForRowAtIndexPath function set the delegate of the cell:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CustomCell", for: indexPath) as! CustomCell
cell.delegate = self
return cell
}
Then confirm the delegate method to ViewController:
extension ViewController: CustomCellDelegate {
func getTextFeildData(cell: CustomCell, text: String) {
guard let tappedIndexPath = self.tableView.indexPathForCell(cell) else {return}
print("Tapped IndexPath: \(tappedIndexPath)")
print("Text Field data: \(text)")
// update your model here
}
}
Related
I'm doing a form through a UITableView, using custom UITableViewCells which contain a UITextField each.
I'm using textFieldShouldReturn to jump to the next textField but I cannot understand why what I typed in one textField appears randomly (actually, there is weird pattern) into another textField.
Here the custom UITableViewCell
class RPTableViewCell: UITableViewCell {
#IBOutlet weak var titleLabel: UILabel!
#IBOutlet weak var textField: DictionaryTextField!
#IBOutlet weak var errorLabel: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
titleLabel.textColor = Constants.secondaryColor
textField.textColor = Constants.secondaryColor
contentView.isUserInteractionEnabled = false
errorLabel.isHidden = true
}
func setTag(tag: Int) {
textField.tag = 100 + tag
}
}
Then in my FormViewController I do
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let field = formFields?[indexPath.row] else { return UITableViewCell() }
if let cell = tableView.dequeueReusableCell(withIdentifier: "cellID") as? RPTableViewCell {
cell.titleLabel.text = field["displayText"]?.uppercased
cell.textField.text = field["userAnswer"] as? String // This was missing, but is key
cell.textField.placeholder = field["placeholder"] as? String
cell.setTag(tag: indexPath.row)
cell.textField.delegate = self
return cell
} else {
return UITableViewCell()
}
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
guard let cell = tableView.cellForRow(at: indexPath) as? RPTableViewCell else { return }
tableView.scrollToRow(at: indexPath, at: .middle, animated: true)
cell.textField.becomeFirstResponder()
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
if let nextTextField = tableView.viewWithTag(textField.tag + 1) as? UITextField {
tableView.deselectRow(at: IndexPath(row: textField.tag - 100, section: 0), animated: false)
tableView.selectRow(at: IndexPath(row: textField.tag - 99, section: 0),
animated: false, scrollPosition: .middle)
nextTextField.becomeFirstResponder()
} else {
textField.resignFirstResponder()
}
return false
}
EDIT:
In viewDidLoad I load the formFiels like this
// Read from a JSON file
guard let visitorPaths = Constants.configDict()?["visitorPaths"] as? [Dictionary<String, AnyObject>] else {
print("Error: no visitorPaths found in the configuration")
return
}
formFields = visitorPaths.first!["fields"]! as? [[String: AnyObject]]
Your are using the following snippet which does not work:
if let nextTextField = tableView.viewWithTag(textField.tag + 1) as? UITextField
The textfield is not a subview of your tableView. The Textfield is a subview of the TableViewCell.
You can acces the cell in the textFieldShouldReturn(_ textField: UITextField) delegate method like the following:
let nextIndexPath = IndexPath(row: textField.tag + 1, section: 0)
if let cell = tableView.cellForRow(at: nextIndexPath) as? RPTableViewCell {
cell.textField.becomeFirstResponder()
}
Edit for the text jumping:
Add the following textField delegate method to store the new text:
func textFieldDidEndEditing(_ textField: UITextField) {
formFields?[textField.tag - 100]["displayText"] = textField.text
}
Please review the code below. I want to get cell index when textfield should begin editing.
class SingleLineText: UITableViewCell, UITextFieldDelegate {
func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool {
let cell: SingleLineText = textField.superview!.superview as! SingleLineText
let table: UITableView = cell.superview as! UITableView
let textFieldIndexPath = table.indexPath(for: cell)
print(textFieldIndexPath as Any)
return true
}
}
In cellForRowAt method just add:
youCell.textField.tag = indexPath.row
and use it Like:
func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool {
let indexPath: IndexPath = IndexPath(row: textField.tag, section: 0)
//here is your indexPath
return true
}
}
I have a tableview with one textfield in each cell. I added a target like this:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "customLevelCell") as! LevelTableViewCell
cell.cellTextField.addTarget(self, action: #selector(ViewController.TextfieldEditAction), for: .editingDidEnd)
return cell
}
But found out that I'm not able to use the indexpath.row / sender.tag to get the specific textfield text
#objc func TextfieldEditAction(sender: UIButton) {
}
So my question is how can I get the text after the user has edited one of the textfields.
Also how can i get the indexpath.row or sender.tag which will be used to collect the text they added to that specific textfield.
The easiest way to handle this is probably to use a delegate protocol…
In your cell
protocol LevelTableViewCellDelegate: class {
func levelTableViewCell(_ levelTableViewCell: LevelTableViewCell, didEndEditingWithText: String?)
}
class LevelTableViewCell: UITableViewCell {
#IBOutlet private weak var cellTextField: UITextField!
var delegate: LevelTableViewCellDelegate?
override func awakeFromNib() {
cellTextField.addTarget(self, action: #selector(didEndEditing(_:)), for: .editingDidEnd)
}
#objc func didEndEditing(_ sender: UITextField) {
delegate?.levelTableViewCell(self, didEndEditingWithText: sender.text)
}
}
In your view controller
class TableViewController: UITableViewController {
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "LevelTableViewCell") as! LevelTableViewCell
cell.delegate = self
return cell
}
}
extension TableViewController: LevelTableViewCellDelegate {
func levelTableViewCell(_ levelTableViewCell: LevelTableViewCell, didEndEditingWithText: String?) {
let indexPath = tableView.indexPath(for: levelTableViewCell)
// Now you have the cell, indexPath AND the string
}
Also, note that the view outlet is be private. You'll find that you write cleaner code if you follow this rule
Following is the extension of UIView that can be used to get the cell or indexPath of the cell enclosing textField
extension UIView {
var tableViewCell : UITableViewCell? {
var subviewClass = self
while !(subviewClass is UITableViewCell){
guard let view = subviewClass.superview else { return nil }
subviewClass = view
}
return subviewClass as? UITableViewCell
}
func tableViewIndexPath(_ tableView: UITableView) -> IndexPath? {
if let cell = self.tableViewCell {
return tableView.indexPath(for: cell)
}
return nil
}
}
Example :-
#objc func TextfieldEditAction(sender: UITextField) {
//replace tableView with the name of your tableView
guard let indexPath = sender.tableViewIndexPath(tableView) else {return}
}
I have tableview which has custom cells.
Each cell has 3 textfields: dayInWeek, startTime, endTime.
In below image, it has 2 rows. But user can click + button to add more rows.
If user click Submit button, I want to loop to every rows, collect 3 textfields data, and store in array or whatever.
Custom TableViewCell:
import UIKit
class RegularScheduleCell: UITableViewCell {
#IBOutlet weak var dayInWeek: UITextField!
#IBOutlet weak var startTime: UITextField!
#IBOutlet weak var endTime: UITextField!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
}
And a view controller:
import UIKit
class RegularScheduleVC: UIViewController, UITableViewDelegate,UITableViewDataSource {
#IBOutlet weak var tableView: UITableView!
var numOfRow = 1
override func viewDidLoad() {
super.viewDidLoad()
tableView.dataSource = self
tableView.delegate = self
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return numOfRow
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cellIdentifier = "RegularScheduleCell"
let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as! RegularScheduleCell
return cell
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if (editingStyle == UITableViewCellEditingStyle.delete) {
numOfRow -= 1
self.tableView.deleteRows(at: [indexPath], with: UITableViewRowAnimation.right)
tableView.reloadData()
}
}
func insertNewRow(_ sender: UIBarButtonItem) {
if numOfRow < 7 {
numOfRow += 1
tableView.reloadData()
}
}
override func setEditing(_ editing: Bool, animated: Bool) {
super.setEditing(editing, animated: animated)
tableView.setEditing(editing, animated: animated)
}
}
At this moment, I try to use UITextFieldDelegate
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cellIdentifier = "RegularScheduleCell"
let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as! RegularScheduleCell
cell.dayInWeek.delegate = self
cell.startTime.delegate = self
cell.endTime.delegate = self
return cell
and
func textFieldDidEndEditing(_ textField: UITextField) {
allCellsText.append(textField.text!) //allCellsText is an array
print(allCellsText)
}
so that when user finish editing, then add that data to array.
However, this does not satisfy my requirement, because:
on the same cell: can not know if the data is belong to dayOfWeek, or startTime, or endTime
on 2 different cells: can not know if data is belong to, let say, dayOfWeek of 1st cell or dayOfWeek of 2nd cell.
Therefore, How can I loop to all cells, get all 3 text fields data?
Thanks
Method 1:
Make an array of key pairs as-
var arrayOfKeyPairs = [[String:Any]]()
arrayOfKeyPairs.append(["header":"xx",
"value" : "",
“id”: "dsd",
"order" : 0])
We are just replacing the default values with user input values as-
func textFieldDidEndEditing(_ textField: UITextField) {
let center: CGPoint = textField.center
let rootViewPoint: CGPoint = textField.superview!.convert(center, to: tableView)
let indexPath: IndexPath = tableView.indexPathForRow(at: rootViewPoint)! as IndexPath
arrayOfKeyPairs[indexPath.row ]["value"] = textField.text//here you are appending(replacing) data to array
}
On click of submit button, cross check what you received as-
func tapsOnNext(){
self.view.endEditing(true)//for adding last text field value with dismiss keyboard
print(arrayOfKeyPairs)
}
Method 2:
We can get cell data by accessing the cell with particular indexpath as
func tapsOnNext(){
let indexpath = IndexPath(row: 0, section: 0)
let cell = tableView.cellForRow(at: indexPath) as! CustomTableViewCell
print(cell.myTextField.text)
}
You can get text of UITextField with adding target to UITextField
cell.YOUR_TEXTFIELD.addTarget(self, action: "textFieldDidChange:", forControlEvents: UIControlEvents.EditingChanged)
//EditingChanged is one of the events and will be fired whenever the user changes any character in that UITextField.
After that, you can call your function like this:
func textFieldDidChange(textField: UITextField) {
//your code
}
Don't forget to create class for UITableViewCell and to create IBOutlets of all your UITextField in that custom cell class
You can do it the following way.
First of all you can create an object for example "History"
make its properties like daysInWeek, startTime, endTime.
In your viewDidLoad method you define an array of Objects. Populate the data in the array, or save those objects of "History" that you created in this array.
Set the dataSource of the table view to this array.
in your method cellForRowAtIndexPath you can access the elements of the array you created above.
When you are tapping the plus button, you can create a new object of History, save this object in the array and reload the table view.
If you can share the git repo of this code, i will show how this is being done.
Simple code: On button click get cell data from cell's text field
let indexpath = IndexPath(row: 0, section: 1)
let cell = ItemtableView.cellForRow(at: indexPath) as! CustomTableViewCell
print("text \(cell.myTextField.text)")
I want to make a table view with textfields in each cell,
I have a custom class in a swift file:
import UIKit
public class TextInputTableViewCell: UITableViewCell{
#IBOutlet weak var textField: UITextField!
public func configure(#text: String?, placeholder: String) {
textField.text = text
textField.placeholder = placeholder
textField.accessibilityValue = text
textField.accessibilityLabel = placeholder
}
}
Then in my ViewController I have
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell{
let cell = tableView.dequeueReusableCellWithIdentifier("TextInputCell") as! TextInputTableViewCell
cell.configure(text: "", placeholder: "Enter some text!")
text = cell.textField.text
return cell
}
That works well:
But when the user enters text in the textfield and press the button I want to store the strings of each textfield in an array.
I have tried with
text = cell.textField.text
println(text)
But it prints nothing like if it was empty
How can I make it work?
In your view controller become a UITextFieldDelegate
View Controller
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, UITextFieldDelegate {
var allCellsText = [String]()
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! CustomTableViewCell
cell.theField.delegate = self // theField is your IBOutlet UITextfield in your custom cell
cell.theField.text = "Test"
return cell
}
func textFieldDidEndEditing(textField: UITextField) {
allCellsText.append(textField.text!)
println(allCellsText)
}
}
This will always append the data from the textField to the allCellsText array.
this method is init the cell,u have not model to save this datas,so
text = cell.textfield.text
is nothing!
you can init var textString in viewcontroller,an inherit UITextFieldDelegate
optional func textFieldDidEndEditing(_ textField: UITextField)
{
textString = _textField.text
}
optional func textFieldShouldReturn(_ textField: UITextField) -> Bool{
return true
}
and cell.textField.text = textString
create a variable
var arrayTextField = [UITextField]()
after this in your func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell{}
add
self.arrayTextField.append(textfield)
before returning cell, after this to display the values using
for textvalue in arrayTextField{
println(textvalue.text)
}
So here is a way to do it using the suggestion of having a textfield array. It uses the tag of the textfield to ensure it does not get added multiple times when you scroll.
// Assumes that you have a label and a textfield as a part of you tableViewCell
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "cell") as? BodyStatsTableViewCell else {
return UITableViewCell()
}
cell.rowLabel.text = bodyTableArray[indexPath.row].rowTitle
let tagValue = indexPath.row + 100
var newRow = true
for textvalue in arrayTextField{
if textvalue.tag == tagValue {
newRow = false
}
}
if newRow {
cell.rowTextField.tag = tagValue
self.arrayTextField.append(cell.rowTextField)
cell.rowTextField.text = bodyTableArray[indexPath.row].rowValue
}
return cell
}
// And then you cam get the values when you want to save with where tag - 100 will be the array index value to update
for textvalue in arrayTextField{
print(textvalue.text)
}