Text fields in Table View Cells - ios

I am working on this app, and I am new to Swift. Only two weeks of knowledge. I am supposed to create a table view with 12 cells - 3 of which are supposed to be text fields and user can type what they want. I have made two prototype cells with two different identifiers. I am using an array called "items" which has strings to be represented in the cells. If the string is blank, that cell is supposed to be a text field, which I have done. Problem occurs after that when I try to type in those fields and scroll the cells.
Please help me in understanding and solving the following issues:
How can I delegate the text field which I added as a subview to my tableview cell?
How can I make sure that whatever I type in the textfield remains there, even after I scroll the cells up and down as I wish?
How can I make sure user can edit whatever they type in the text field?
Here is my code:
var items = ["Apple", "Fish", "Dates", "Cereal", "Ice cream", "Lamb", "Potatoes", "Chicken", "Bread", " ", " "," "]
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var iD = "normal"
if (items[indexPath.row] == " ") {
iD = "textField"
let cell = tableView.dequeueReusableCellWithIdentifier(iD, forIndexPath: indexPath)
cell.textLabel?.text = items[indexPath.row]
let textField = UITextField(frame: CGRect(x: 20, y: 10, width: 150, height: 30))
textField.backgroundColor = UIColor.brownColor()
textField.placeholder = ""
cell.contentView.addSubview(textField)
return cell
}
else {
let cell = tableView.dequeueReusableCellWithIdentifier(iD, forIndexPath: indexPath)
cell.textLabel?.text = items[indexPath.row]
return cell
}
////
func tableView(tableView: UITableView, moveRowAtIndexPath sourceIndexPath: NSIndexPath, toIndexPath destinationIndexPath: NSIndexPath) {
if (sourceIndexPath.row != destinationIndexPath.row){
let temp = items[sourceIndexPath.row]
items.removeAtIndex(sourceIndexPath.row)
items.insert(temp, atIndex: destinationIndexPath.row)
}
tableView.reloadData()
}

I would rather create a subclass of UITableviewCell and add a textfield there as a subview. Then you set the delegate of the textfield to the cell itself.
Here is some sample code. I did not try to run it, but i think it should work:
class InputCell: UITableViewCell, UITextFieldDelegate {
private let textField = UITextField()
private var resultBlock: ((changedText: String) -> ())? = nil
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.textField.delegate = self
self.contentView.addSubview(self.textField)
// additional setup for the textfield like setting the textstyle and so on
}
func setup(initalText: String, resultBlock: (changedText: String) -> ()) {
self.textField.text = initalText
self.resultBlock = resultBlock
}
func textFieldDidEndEditing(textField: UITextField) {
if let block = self.resultBlock, let text = textField.text {
block(changedText: text)
}
}
}
In your view controller i would change the items to be a dictionary, so you can directly link them to the indexpaths of the tableview. And you need to let the tableview register your custom cell class.
var items = [0: "Apple", 1: "Fish", 2: "Dates", 3: "Cereal", 4: "Ice cream", 5: "Lamb", 6: "Potatoes", 7: "Chicken", 8: "Bread", 9: " ", 10: " ", 11: " "]
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
// the position of the cell with a textfield is fixed right? Just put the row number here then
if indexPath.row > 8 {
let cell = tableView.dequeueReusableCellWithIdentifier("CellWithTextField", forIndexPath: indexPath) as! InputCell
if let initialText = items[indexPath.row] {
cell.setup(initialText) { [weak self] text in
self?.items[indexPath.row] = text
}
}
return cell
} else {
let cell = tableView.dequeueReusableCellWithIdentifier("NormalCell", forIndexPath: indexPath)
cell.textLabel?.text = items[indexPath.row]
return cell
}
}

first, add the textfield protocol UITextFielDelegate, and in cell for row atindex write textfield.delegate = self.
And add delegate method to manage the text.
Hope this help you.

You have to save the textfield data in some object. As UITableView reuses same cells your textfield text will be lost on scroll as the cell will be reused. I would suggest creating some array and storing the data in that for each indexPath.
You should implement the UITextField delegate methods inside cell class and whenever some text changes, you should call another delegate method of yours (CellDelegate) which should be implemented in ViewController to update data.
This delegate method will pass text and indexPath of cell.

In the UITableViewCell add the UItextfield delegate for textChange.
You can alter your items array to contain model objects having the real value and the editing value.
let apple = Item()
apple.realValue = "Apple"
apple.editedValue = ""
var item = [0: apple, .............
Pass the model object in the tableview cell and update the edited value on text change delegate call. In cellForRow add the editedValue in the textfield text property
let item = items[indexPath.row]
cell.textLabel?.text = item.realValue
if item.editedValue{
// add the textFieldText
}

Inherit UITextFieldDelegate to your controller
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var iD = "normal"
if (items[indexPath.row] == " ") {
iD = "textField"
let cell = tableView.dequeueReusableCellWithIdentifier(iD, forIndexPath: indexPath)
cell.textLabel?.text = items[indexPath.row]
let textField = UITextField(frame: CGRect(x: 20, y: 10, width: 150, height: 30))
textField.backgroundColor = UIColor.brownColor()
textField.placeholder = ""
cell.contentView.addSubview(textField)
textField.delegate = self // set delegate
textField.tag= indexPath.row
return cell
}
else {
let cell = tableView.dequeueReusableCellWithIdentifier(iD, forIndexPath: indexPath)
cell.textLabel?.text = items[indexPath.row]
return cell
}
}
//delegate methods
func textFieldDidBeginEditing(textField: UITextField!) { //delegate method
print(textField.tag)//you will get the row. items[textField.tag] will get the object
}
func textFieldShouldEndEditing(textField: UITextField!) -> Bool { //delegate method
print(textField.tag)//you will get the row. items[textField.tag] will get the object
items[textField.tag]= textField.text
return false
}
func textFieldShouldReturn(textField: UITextField!) -> Bool { //delegate method
textField.resignFirstResponder()
return true
}

Related

How do I send tableview data on submit button?

I have a UITableView with 2 sections.
Each section has different xib cells (with multiple textfields, radio buttons etc.).
The user can enter those values using the keyboard.
How can I get the value of each text field at submit button using model?
Here is my model class for cell for detail section:
class DataModel {
var name: String?
var gender: String?
init(name: String?, gender: String?)
{
self.name = name
self.gender = gender
}
}
How I use it in my tableview:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCell(withIdentifier:"DetailCell") as? DetailCell
if cell == nil{
let arrNib:Array = Bundle.main.loadNibNamed("DetailCell",owner: self, options: nil)!
cell = arrNib.first as? DetailCell
}
let model: DataModel
model = DataModel [indexPath.row]
//displaying values
cell?.name_lbl.text = model.name
cell?.gender_lbl.text = model.gender
return cell!
}
First, you name_lbl and gender_lbl shoule be UITextField, not the UILabel.
You have to add target for the name_lbl and gender_lbl:
cell?.name_lbl.text = model.name
cell?.name_lbl.tag = indexPath.section
cell?.name_lbl.addTarget(self, action: #selector(textFieldNameDidChange(_:)), for: UIControl.Event.editingChanged)
cell?.gender_lbl.text = model.gender
cell?.name_lbl.tag = indexPath.section
cell?.name_lbl.addTarget(self, action: #selector(textFieldGenderDidChange(_:)), for: UIControl.Event.editingChanged)
and you add methods textFieldNameDidChange and textFieldGenderDidChange as:
#objc func textFieldGenderDidChange(sender: UITextField) {
var model = DataModel[sender.tag]
model.gender = sender.text
}
You should do the same for the "name".
You can give tag to textfield and use UITextFieldDelegate for this like,
func textFieldDidEndEditing(_ textField: UITextField) {
if textField.tag == 0 {
model.name = textField.text ?? ""
}
}
on you button submit, you can access model value like
#IBAction func submitButtonTapped(_ sender: Any) {
self.view.endEditing(true)
let name = model.name
}
For this u need to set tag to textfield in cell for row. In shouldChageCharacter delegate method set text to data array with respective indexpath and create the object of uitableviewcell and set text to the respective label. for get data you can get data from array. In above answer may be if u scroll table then TableCell will mix with eachother.

Add cell to UITableView if textfield of previous cell is filled in

I have a UITableView in my ViewController. Every cell contains a textfield where someone can enter new text (a to do list). Now, I want to add a new cell, which already contains a new textfield, only if the textfield of the previous cell contains text.
Currently I use a UIButton to add a new cell and this works and resizes the table view so all cells are visible, but I want this to be automated after filling in the previous cell so it becomes more user friendly.
This is what my Storyboard looks like.
And this is the code I currently use to add a new row:
#IBAction func btnAddRow_TouchUpInside(_ sender: Any) {
toDoTableView.beginUpdates()
amountCells.append("")
self.toDoTableView.insertRows(at: [IndexPath.init(row: self.amountCells.count - 1, section: 0)] , with: .automatic)
toDoTableView.endUpdates()
tblHeight.constant = toDoTableView.contentSize.height
}
Any idea on how I can accomplish this? Thanks in advance.
EDIT: I have tried to use EditingDidEnd
class TextFieldsCell: UITableViewCell {
#IBOutlet weak var txtToDo: UITextField!
#IBAction func txtToDo_EditingDidEnd(_ sender: Any) {
if(!(txtToDo.text?.isEmpty)! {
print("Insert Code to Add New Row")
}
}
}
When I do this, I cannot access the toDoTableView from my ViewController. Another issue this might result in is that when there already are 5 rows and the first one is simply edited, another row will insert while I would not want that.
You can check, whether textfield is empty or not in viewController itself. You just confirm the delegate for textfield from storyboard and in cellForRowAt and give the tag to that text field
e.g,
cell.yourTextField.delegate = self
cell.yourTextField.tag = indexPath.row
and check the textfield is empty or not in textField delegate method. Create object of cell in method like
func textFieldDidEndEditing(_ textField: UITextField) {
rowBeingEditedInt = textField.tag
let indexPath = IndexPath(row:rowBeingEdited! , section:0 )
let cell = yourTableView.cellForRow(at: indexPath) as! yourTableViewCell
// check cell.yourtextField.text isempty and do your condition
}
Add this to you textfield delegate and Replace tableview with your tableview name
func textFieldDidEndEditing(textField: UITextField!){
var cell: UITableViewCell = textField.superview.superview as UITableViewCell
var table: UITableView = cell.superview as UITableView
let textFieldIndexPath = table.indexPathForCell(cell)
if textFieldIndexPath.row == (yourdataArray.count - 1)
{
print("came to last row")
if ((textField.text?.count)! > 0){
print("text available")
toDoTableView.beginUpdates()
amountCells.append("")
self.toDoTableView.insertRows(at: [IndexPath.init(row:
self.amountCells.count - 1, section: 0)] , with: .automatic)
toDoTableView.endUpdates()
tblHeight.constant = toDoTableView.contentSize.height
}
}
}
Fixed it eventually by combining answers from #Mauli and #Vicky_Vignesh / #Kuldeep.
func textFieldDidEndEditing(_ textField: UITextField) {
let rowBeingEditedInt = textField.tag
let indexPath = IndexPath(row:rowBeingEditedInt , section:0 )
let cell = toDoTableView.cellForRow(at: indexPath) as! TextFieldsCell
let table: UITableView = cell.superview as! UITableView
let textFieldIndexPath = table.indexPath(for: cell)
if textFieldIndexPath?.row == (amountCells.count - 1) {
if(!(cell.txtToDo.text?.isEmpty)!) {
toDoTableView.beginUpdates()
amountCells.append("")
self.toDoTableView.insertRows(at: [IndexPath.init(row: self.amountCells.count - 1, section: 0)] , with: .automatic)
toDoTableView.endUpdates()
tblHeight.constant = toDoTableView.contentSize.height
}
}
}
Thanks guys! Much appreciated.
You can try this.
In cellForRowAt method set UITableViewCell textField delegate first.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell:TableViewCell = self.tblvw.dequeueReusableCell(withIdentifier: "TableViewCell", for: indexPath) as! TableViewCell
cell.txtField.delegate = self
cell.txtField.tag = indexPath.row
cell.txtField?.text = amountCells[indexPath.row]
return cell
}
In textFieldDidEndEditing you just need to check below and based on that you perform your further task.
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
return textField.resignFirstResponder()
}
func textFieldDidEndEditing(_ textField: UITextField) {
if textField.text?.count == 0 || textField.tag != self.amountCells.count - 1 {
}
else {
self.tblvw.beginUpdates()
amountCells.append("")
self.tblvw.insertRows(at: [IndexPath.init(row: self.amountCells.count - 1, section: 0)] , with: .automatic)
self.tblvw.endUpdates()
}
}

Swift 4 UITableView make one cell as UITextView

I have a dynamic Table as UITableView and all cells are as normal (retrieved form my array)
However I need only one cell as TextView to be able input text. On text Change I need to retrieve the text user input.
How to make this?
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return array.count+1 //to allow this input field
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if(indexPath.row < array.cont){
//normal cell from array
let cell = Table.dequeueReusableCell(withIdentifier: "myCell")
cell?.textLabel?.text = array[indexPath.row]
cell?.isUserInteractionEnabled = true;
cell?.textLabel?.adjustsFontSizeToFitWidth = true
cell?.textLabel?.textAlignment = .center;
return cell!;
}else{
//create input text field (DON'T KNOW HOW)
}
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if(indexPath.row < array.cont){
//.. perform action ..
}else{
//retrieve input text (DONT know How)
}
}
Creating UITextView inside UITableViewCell is quite simple :
let textView: UITextView = UITextView(frame: CGRect(x: 0, y: 20, width: 311.00, height: 50.00)) // Set frames as per requirements
textView.textAlignment = NSTextAlignment.justified
cell.contentView.addSubView(textView)
But this would lead to incorrect values while scrolling the table. Best approach would be to create a custom cell and add UITextView there. Here is the custom cell. Keep the constraints intact.
Before using the custom cell, you need to register it in your table. So :
let nib = UINib(nibName: "TextCell", bundle: nil)
Table.register(nib, forCellReuseIdentifier: "TextCell")
Don't forget to put identifier of cell in xib.
Now in cellForRowAtIndexPath :
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if(indexPath.row < array.cont){
//normal cell from array
let cell = Table.dequeueReusableCell(withIdentifier: "myCell")
cell?.textLabel?.text = array[indexPath.row]
cell?.isUserInteractionEnabled = true;
cell?.textLabel?.adjustsFontSizeToFitWidth = true
cell?.textLabel?.textAlignment = .center;
return cell!;
}else{
//create input text field (DON'T KNOW HOW)
let cell = tableView.dequeueReusableCell(withIdentifier: "TextCell", for: indexPath) as! TextCell
// Access UITextView by cell.textView
return cell
}
}
The main issue is - dynamic cell size as per UITextView height. But that entirely depends on your requirement. You can follow this post for that.
You can achieve this with delegation pattern or NSNotification.
Here's the solution for this using delegation pattern.
Create new UITableViewCell using xib and add the textView on contentView of cell, set the reuse identifier and than register the xib in the ViewController with
tableView.register(UINib(nibName: "name of the xib file", bundle: nil), forCellReuseIdentifier: "Identifier here")
Now define protocol anywhere outside of the class
// You can give any name
// Here we are confirming to class to avoid any retain cycles
protocol CustomCellDelegate :class {
func returnText(text :String)
}
Now initialise " var delegate : CustomCellDelegate? " in same class of UITableViewCell that we created above while creating xib.
and confirm to protocol UITextViewDelegate and than in the cell class write this
override func awakeFromNib() {
super.awakeFromNib()
textView.delegate = self
}
after that add these functions in same class of tableViewCell
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
if(text == "\n") {
textView.resignFirstResponder()
return false
}
return true
}
func textViewDidEndEditing(_ textView: UITextView) {
delegate.returnText(text : textView.text ?? "")
}
Now in the ViewController class
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.row == array.count { // this will call the cell with TextView at the end , you can play with any indexPath to call the cell
let cell = tableView.de... // Deque the cell with TextView here using reuse identifier
cell.delegate = self
return cell
}
else {
// deque other cells
}
}
we'll write an extension of ViewController and confirm to our custom protocol
extension ViewController : CustomCellDelegate {
// this function will get called when you end editing on textView
func returnText(text :String) {
print(text) // you may save this string in any variable in ViewController class
}
}
Add a TextView in to your custom cell, hide it, show when you need
if you want to have the textView on the top of all cells, drag and drop a UIView inside the tableView before the cell.
this view will scroll with cells.
design this view as you need insert a textView inside it, and use textView's delegate methods to perform operations.

Tableview cells from Firebase

I have followed this tutorial. To add a cell to the tableview the code looks like this toDoItems.append(ToDoItem(text: "CHILDS NAME"))
This is toDoItem :
func toDoItemAddedAtIndex(index: Int) {
let toDoItem = ToDoItem(text: "")
toDoItems.insert(toDoItem, atIndex: index)
tableView.reloadData()
// enter edit mode
var editCell: TableViewCell
let visibleCells = tableView.visibleCells as! [TableViewCell]
for cell in visibleCells {
if (cell.toDoItem === toDoItem) {
editCell = cell
editCell.label.becomeFirstResponder()
break
}
}
}
And this is ToDoItems :
func tableView(tableView: UITableView,
cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! TableViewCell
cell.selectionStyle = .None
cell.textLabel?.backgroundColor = UIColor.clearColor()
let item = toDoItems[indexPath.row]
// cell.textLabel?.text = item.text
cell.delegate = self
cell.toDoItem = item
return cell
}
And my firebase database looks like this :
JSON :
"Mission-list" : {
"vJGnbbJX9lbBYwgesjHfNqdzmJs2" : {
}
}
Question : How do I automatically add a tableview cell for each child added bellow Mission-list? And set the cells name to the childs name.
Thanks!
My guess is that toDoItem is your dataSource variable which carries the data retrieved from Firebase Database. So when you append your data to your Global variable:-
toDoItems.append(ToDoItem(text: "CHILDS NAME"))
yourTableView.reloadData()

How to get Text from a label in custom cell in Table View

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = self.tbl_vw.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath)as!cuscellTableViewCell
cell.txt_lbl.text = self.section[indexPath.row];
cell.sel_btn.addTarget(self, action: #selector(self.switchChanged), forControlEvents: .ValueChanged)
return cell
}
func switchChanged(sender: AnyObject) {
let switchControl: UISwitch = sender as! UISwitch
print("The switch is \(switchControl.on ? "ON" : "OFF")")
if switchControl.on {
print("The switch is on lets martch")
}
}
I have a switch and a label in a custom cell in tableview. When I ON the switch i need to get the text in the label, can any one help how to make it possible.
Use the tag property of UISwitch to store the position and use it in your handler to get the actual text form section array.
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = self.tbl_vw.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath)as!cuscellTableViewCell
cell.txt_lbl.text = self.section[indexPath.row];
cell.sel_btn.tag = indexPath.row
cell.sel_btn.addTarget(self, action: #selector(self.switchChanged), forControlEvents: .ValueChanged)
return cell
}
func switchChanged(sender: AnyObject)
{
let switchControl: UISwitch = sender as! UISwitch
print("The switch is \(switchControl.on ? "ON" : "OFF")")
let text = self.section[switchControl.tag]
print(text)
if switchControl.on
{
print("The switch is on lets martch")
}
}
If you have multiple sections with multiple rows you can use a dictionary and store a tuple.
var items = [Int:(Int,Int)]()
...
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = self.tbl_vw.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath)as!cuscellTableViewCell
cell.txt_lbl.text = self.section[indexPath.row];
cell.sel_btn.tag = indexPath.row
let tuple = (section: indexPath.section, row: indexPath.row)
self.items[cell.sel_btn.hashValue] = tuple
cell.sel_btn.addTarget(self, action: #selector(self.switchChanged), forControlEvents: .ValueChanged)
return cell
}
func switchChanged(sender: AnyObject)
{
let switchControl: UISwitch = sender as! UISwitch
print("The switch is \(switchControl.on ? "ON" : "OFF")")
let tuple = self.items[switchControl.hashValue]
print(tuple.0) // this is the section
print(tuple.1) // this is the row
if switchControl.on
{
print("The switch is on lets martch")
}
}
In your case move func switchChanged(sender: AnyObject) to your custom class and assign selector in customCell for UISwitch. You will start receiving your switchChange event in your customCell now you are customCell you have access to your UISwitch & UILabel objects, also keep a boolean flag to maintain switch state if required.
Step 1: Move your switchChanged to customCell class.
Step 2: In customCell awakeFromNib or init method assign selector to UISwitch object to cell itself to receive switch change event in cell.
Step 3: Once your switch change event fires update your UILabel as you have access to it inside customCell.
Let me know if you have any doubt.
what you need to do is use block
from Get button click inside UI table view cell
first move your action method to table cell
in tableview cell add block property
#property (copy, nonatomic) void (^didTapButtonBlock)(id sender);
with your action method
call block
- (void)didTapButton:(id)sender {
if (self.didTapButtonBlock)
{
self.didTapButtonBlock(sender);
}
}
and receive that from cell , you have both section and row there
[cell setDidTapButtonBlock:^(id sender)
{
// Your code here
}];
Hope it helps
Set tag of sel_btn(UISwitch) in cellForRowAtIndexPath method.
sel_btn.tag = indexPath.row
On switchChanged method, get the tag of sel_btn and thus text of label.
let switchControl: UISwitch = sender as! UISwitch
let tag = switchControl.tag
let cell : cuscellTableViewCell = tbl_vw.cellForRowAtIndexPath(NSIndexPath(forRow: tag, inSection: 0)) as? cuscellTableViewCell
print(cell.txt_lbl.text)
If you want to get data from model not from view, then override the above two lines with below line:-
let textValue = self.section[tag];
In tableview delegate method -
tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath)
add index path value to switch tag value -
cell.sel_btn.tag = indexPath.row
In Switch Value change method just get selected text when that switch is ON
func switchChanged(sender: AnyObject)
{
let switchControl: UISwitch = sender as! UISwitch
print("The switch is \(switchControl.on ? "ON" : "OFF")")
// If switch ON
if switchControl.on
{
print("The switch is ON”)
// Get Selected Label Text
let selectedText = self.section[switchControl.tag]
print(selectedText)
}
}

Resources