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()
}
}
Related
I use an empty UITableView with custom cells and I add new items one by one without any problem. The tableView is scrollable, however when I add an item to the cell that is one index more from the last visible cell the app crashes.
When the app is loaded the numberOfRowsinSection is 1 and with every new entry it grows by 1. If the device has 10 visible cells it crashes on 11. If the device has 6 visible cells it crashes on 7. The app unexpectedly finds nil while unwrapping an Optional value.
Using advices from the question titled UITableview Not scrolling?
I tried each of the following lines in viewDidLoad and in my function:
self.myTableView.delegate = self
self.myTableView.autoresizingMask = UIView.AutoresizingMask.flexibleHeight;
self.myTableView.rowHeight = UITableView.automaticDimension
self.myTableView.bounces = true;
self.myTableView.reloadData()
without any positive result.
Here is the code:
var enterCounts: Int = 1
public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return enterCounts
}
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "TextInputCell") as! TextInputTableViewCell
return cell
}
#IBAction func enter(_ sender: Any) {
let activeRow = self.enterCounts - 1
let index = IndexPath(row: activeRow, section: 0)
let cell: TextInputTableViewCell = self.myTableView.cellForRow(at: index) as! TextInputTableViewCell
if cell.myTextField.text == "" {
"DO NOTHING"
} else {
"DO STUFF"
enterCounts += 1
self.myTableView.reloadData()
let nextIndex = IndexPath(row: activeRow + 1, section: 0)
"This is the line that finds nil and crashes when row is out of view"
let nextCell: TextInputTableViewCell = self.myTableView.cellForRow(at: nextIndex) as! TextInputTableViewCell
nextCell.myTextField.text = ""
nextCell.myTextField.becomeFirstResponder()
}
}
I would expect the UITableView to scroll and keep on loading as many cells the user enters, exactly as it does with the first/visible cells.Thank you.
After the 2 answers the code is:
#IBAction func enter(_ sender: Any) {
let activeRow = self.enterCounts - 1
let index = IndexPath(row: activeRow, section: 0)
let cell: TextInputTableViewCell = self.myTableView.cellForRow(at: index) as! TextInputTableViewCell
if cell.myTextField.text == "" {
"DO NOTHING"
} else {
"DO STUFF"
enterCounts += 1
let nextIndex = IndexPath(row: activeRow + 1, section: 0)
self.myTableView.insertRows(at: [nextIndex], with: .automatic)
self.myTableView.scrollToRow(at: nextIndex,at: .middle, animated: true)
//These lines are executed only when I am in visible cells
//when a new cell is added it is not ready to become first responder it is skipped and entered text is getting mixed up.
if let nextCell: TextInputTableViewCell = self.myTableView.cellForRow(at: nextIndex) as? TextInputTableViewCell {
nextCell.myTextField.text = ""
nextCell.myTextField.becomeFirstResponder()
}
}
}
With the code above the new cells appear wonderfully but textField become first responder only once, for the first cell that appears in view.
I declare my custom cell class in as below
#IBOutlet weak var myTextField: UITextField!
public func configure(text: String?, placeholder: String) {
myTextField.text = text
// myTextField.placeholder = placeholder
myTextField.accessibilityValue = text
// myTextField.accessibilityLabel = placeholder
}
override public func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override public func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
If I use a textField outside the tableView and keep the tableView only for displaying my entered values things are simple but having for entryField the last cell of the tableView creates problems when I try to make first responder the textField of the new inserted cell.
if you need to add new cell you can use this line to add it:
let indexPath = IndexPath(row: ... , section: ...)
tableView.insertRows(at: [indexPath], with: .automatic)
after that scroll to it
tableView.scrollToRow(at: indexPath,at: .middle, animated: true)
finally, you can use this cell
let cell = tableView.cellForRow(at: nextIndex) as! YourCustomCellClass
It is crashing because apple only keeps cells in memory that are visible, In your case you are access cell that is not in memory and instead to use optional you are forcing to unwrap which causes the crash.
After knowing this you should handle exception for cells that are not visible, like bewlow
#IBAction func enter(_ sender: Any) {
let activeRow = self.enterCounts - 1
let index = IndexPath(row: activeRow, section: 0)
let cell: TextInputTableViewCell = self.myTableView.cellForRow(at: index) as! TextInputTableViewCell
if cell.myTextField.text == "" {
"DO NOTHING"
} else {
"DO STUFF"
enterCounts += 1
self.myTableView.reloadData()
let nextIndex = IndexPath(row: activeRow + 1, section: 0)
//"This is the line that finds nil and crashes when row is out of view"
if let nextCell: TextInputTableViewCell = self.myTableView.cellForRow(at: nextIndex) as? TextInputTableViewCell
{
nextCell.myTextField.text = ""
nextCell.myTextField.becomeFirstResponder()
}
}
Or
if you want to make your textfield first responder first get cell in memory by scrolling to index Or by inserting it and then access it.
if you want to add new line in you tableview whenever a enter button is tapped this I think you should try doing it this way
I am assuming that u just want a textfield in your tableview but this could work with other thing also
in you custom class make an outlet for your textfield name it whatever you want I am naimg it tf and do this
iboutlet weak var tf: uitextfield!{
didSet{
tf.delegate = self
}}
and create a closure var like this
var textsaved: ((String) -> Void)?
then add textfield delegate to your customcell class like this
extension CustomCell: uitextfielddelegate{ }
then in your extension write :
func textfieldshouldreturn(_ textfield: uitextfield) -> Bool{
tf.resignFirstResponder()
retrun true }
func textfielddidendediting(_ textfield: uitextfield){
textsaved?(textfield.text!) }
then in your view controller create an empty array of string
var myarr = [String]()
make outlet for enter button and tableview
#iboutlet weak var mytableview: uitableView!{
didSet{
mytableview.delegate = self
mytableview.datasource = self }
#iboutlet weak var enterBtn(_ sender: uibutton) {
myarr.append("")
mytableview.reloaddata() }
in number of rows
return myarr.count
in cell for row at
let cell = tableview.dequereuseablecell(withidentifier: "cell", for :indexpath) as! customcell
cell.tf.text = myarr[indexpath.row]
cell.textsaved = { [unowned self] (text) in
self.myarr.remove(at: indexpath.row)
self.myarr.insert(text, at: indexpath.row)
sel.mytableview.reloaddata()
} return cell }
I'm a beginning iOS developer and I've been stuck on an issue for quite some time now.
Background:
I have a single viewcontroller in which I have placed a TableView (I cannot use a tableviewcontroller). It holds 4 dynamic prototype cells:
Cell 1 has an UITextField and a couple of labels, Cell 2-4 only have a label (with different types of information) and have to be hidden initially.
When an user enters a number (max. 3 digits) in the UITextField of the first cell, the number has to be compared to check if it is a correct/existing number. If this number proves correct, 2 things will have to happen: 1) the labels in the first cell will need to change layout (colour, font, size, ...) and set the data of one label. 2) the other cells in the tableview will have to appear beneath the first cell.
Problem:
I'm having trouble sending data back and forth between the first UITableViewCell and its UITableView. For the text input I use the shouldChangeCharactersInRange method in the cell class, which then limits the # of digits and calls a method in the tableView class where the data will be compared. For this I used delegation.
However, after checking whether the number is a match, I need to call a method in the cell class (from within the tableview class) that will change the layout and set the data of a label in the cell. Yet I can't seem to figure out a way to make this method call work and access the outlets.
Summarised: cell class sends number to tableview class, methods in tableview class run, tableview class sends bool to cell class where outlets need to be changed.
What I tried:
I tried setting up delegation in the other direction, but it wouldn't trigger. Using a normal method call wouldn't work either, because then the outlets are nil.
I believe my problem lies in the fact that I need to reference the same instance/object of the cell to access the outlets?
I selected and simplified the relevant pieces of code:
1) TableView class
class TableViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, updateUITableView {
var existingNumber = 200
var cellsRowHeight = false
#IBOutlet var tableView: UITableView!
//Started by InputTableViewCell when UITextField has a 3 digit-number
func checkNumberInput(inputNumber: Int) {
//Step 1: check if the number matches an existing one
let match = checkNumberMatch(inputNumber: inputNumber)
//Step 2: send a bool back to the cell class to change the layout through outlets
InputTableViewCell().changeLayout(numberMatch: match) // <--- problem
//Step 3: make the hidden cells appear
toggleCellsVisibility(numberMatch: match)
}
//Step 1
func checkNumberMatch(inputNumber: Int) -> Bool {
if inputNumber == existingNumber {
return true
} else {
return false
}
}
//Step 2
func toggleCellsVisibility(numberMatch: Bool) {
cellsRowHeight = numberMatch
if numberMatch == true { //cells appear
tableView?.beginUpdates()
tableView?.endUpdates()
//possible additional code
} else { //cells dissappear
tableView?.beginUpdates()
tableView?.endUpdates()
//possible additional code
}
}
//Step 3
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
switch (indexPath.row) {
case 0 where !cellsRowHeight || cellsRowHeight:
return 170
case 1 where !cellsRowHeight:
return 0
case 1 where cellsRowHeight:
return 54
//cases for other cells to appear/dissappear
default:
return 44
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.row == 0 {
//Create tableViewCell
let cellIdentifier = "InputCell"
let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as! InputTableViewCell
cell.delegate = self
//Customize lay-out of cell
cell.preservesSuperviewLayoutMargins = false
cell.separatorInset = UIEdgeInsets.zero
cell.layoutMargins = UIEdgeInsets.zero
return cell
}
//creation of other cells
}
// MARK: - Loading
override func viewDidLoad() {
super.viewDidLoad()
tableView.tableFooterView = UIView(frame: CGRect.zero)
}}
2) Cell class:
protocol updateUITableView: class {
func checkNumberInput(inputNumber: Int)
}
class InputTableViewCell: UITableViewCell, UITextFieldDelegate {
var delegate: updateUITableView?
#IBOutlet var textField: UITextField!
#IBOutlet var letterLabel: UILabel!
//Step 2: problem!
func changeLayout(numberMatch: Bool) {
if numberMatch == true {
print("Success") //this line triggers
letterLabel?.text = "A" //is nil
//other lay-out changes
}
else {
print("Fail, please provide an existing number")
//other lay-out changes
}
}
//Set maximum character limit in textField and dismiss keyboard when character limit is reached.
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let currentCharacterCount = textField.text?.characters.count ?? 0
let newLength = currentCharacterCount + string.characters.count - range.length
if (newLength == 3) {
textField.text = (textField.text! as NSString).replacingCharacters(in: range, with: string)
//Get text from textField
let numberInput: Int? = Int(textField.text!)
if numberInput != nil {
delegate?.checkNumberInput(number: numberInput!) //send number to tableview class
}
//Dismiss keyboard
textField.resignFirstResponder()
if (range.length + range.location > currentCharacterCount) {
return false
} else {
return true
}
}
return true
}
func viewDidLoad() {
}}
The actual problem is situated in "Step 2". Using this method I can perform the print statement, but the actual labels/outlets are nil because it is just a generic call.
Any help would be immensely appreciated! Thank you!
Pass your UITableViewCell instance in your delegate method like this
func checkNumberInput(senderCell: InputTableViewCell,inputNumber: Int)
and then you will be able to call any method on this cell's instance. So in your case.
func checkNumberInput(senderCell: InputTableViewCell,inputNumber: Int){
//...
senderCell.changeLayout(numberMatch: match)
//....
}
and you can call your method like
delegate?.checkNumberInput(senderCell: self, inputNumber: numberInput!)
You can also pass data using NSNotificationCenter.
Put this code in viewDidLoad
NotificationCenter.default.addObserver(self, selector: #selector(receivedDataFromNotification(notification:)), name: NSNotification.Name(rawValue: "receiveData"), object: nil)
This selector method for notification
func receivedDataFromNotification(notification : NSNotification) -> Void {}
Then Post Notification while you need to pass data.
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "receiveData"), object: YourObject)
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
}
Setup (Swift 1.2 / iOS 8.4):
I have UITableView custom cell (identifier = Cell) inside UIViewController. Have two buttons (increment/decrement count) and a label (display count) inside the custom TableView cell.
Goal:
Update the label as we press the increase count or decrease count button.
At present I am able to get the button Tag and call a function outside of the CellForRowAtIndexPath. The button press increases and decreases the count. But I am not able to display the count update in the label.
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell:FoodTypeTableViewCell = self.tableView!.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! FoodTypeTableViewCell
cell.addBtn.tag = indexPath.row // Button 1
cell.addBtn.addTarget(self, action: "addBtn:", forControlEvents: .TouchUpInside)
cell.subBtn.tag = indexPath.row // Button 2
cell.subBtn.addTarget(self, action: "subBtn:", forControlEvents: .TouchUpInside)
cell.countLabel.text = // How can I update this label
return cell
}
func addBtn(sender: AnyObject) -> Int {
let button: UIButton = sender as! UIButton
count = 1 + count
println(count)
return count
}
func subBtn(sender: AnyObject) -> Int {
let button: UIButton = sender as! UIButton
if count == 0 {
println("Count zero")
} else {
count = count - 1
}
println(count)
return count
}
I have seen this question here and there but was not able to find a clear answer in Swift. I would really appreciate if you could help answer it clearly so that other people can not just copy, but clearly understand what is going on.
Thank you.
Here is a solution that doesn't require tags. I'm not going to recreate the cell exactly as you want, but this covers the part you are asking about.
Using Swift 2 as I don't have Xcode 6.x anymore.
Let's start with the UITableViewCell subclass. This is just a dumb container for a label that has two buttons on it. The cell doesn't actually perform any specific button actions, it just passes on the call to closures that are provided in the configuration method. This is part of MVC. The view doesn't interact with the model, just the controller. And the controller provides the closures.
import UIKit
typealias ButtonHandler = (Cell) -> Void
class Cell: UITableViewCell {
#IBOutlet private var label: UILabel!
#IBOutlet private var addButton: UIButton!
#IBOutlet private var subtractButton: UIButton!
var incrementHandler: ButtonHandler?
var decrementHandler: ButtonHandler?
func configureWithValue(value: UInt, incrementHandler: ButtonHandler?, decrementHandler: ButtonHandler?) {
label.text = String(value)
self.incrementHandler = incrementHandler
self.decrementHandler = decrementHandler
}
#IBAction func increment(sender: UIButton) {
incrementHandler?(self)
}
#IBAction func decrement(sender: UIButton) {
decrementHandler?(self)
}
}
Now the controller is just as simple
import UIKit
class ViewController: UITableViewController {
var data: [UInt] = Array(count: 20, repeatedValue: 0)
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return data.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! Cell
cell.configureWithValue(data[indexPath.row], incrementHandler: incrementHandler(), decrementHandler: decrementHandler())
return cell
}
private func incrementHandler() -> ButtonHandler {
return { [unowned self] cell in
guard let row = self.tableView.indexPathForCell(cell)?.row else { return }
self.data[row] = self.data[row] + UInt(1)
self.reloadCellAtRow(row)
}
}
private func decrementHandler() -> ButtonHandler {
return { [unowned self] cell in
guard
let row = self.tableView.indexPathForCell(cell)?.row
where self.data[row] > 0
else { return }
self.data[row] = self.data[row] - UInt(1)
self.reloadCellAtRow(row)
}
}
private func reloadCellAtRow(row: Int) {
let indexPath = NSIndexPath(forRow: row, inSection: 0)
tableView.beginUpdates()
tableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation: .Automatic)
tableView.endUpdates()
}
}
When the cell is dequeued, it configures the cell with the value to show in the label and provides the closures that handle the button actions. These controllers are what interact with the model to increment and decrement the values that are being displayed. After changing the model, it reloads the changed cell in the tableview.
The closure methods take a single parameter, a reference to the cell, and from this it can find the row of the cell. This is a lot more de-coupled than using tags, which are a very brittle solution to knowing the index of a cell in a tableview.
You can download a full working example (Requires Xcode7) from https://bitbucket.org/abizern/so-32931731/get/ce31699d92a5.zip
I have never seen anything like this before so I am not sure if this will be the correct way to do. But I got the intended functionality using the bellow code:
For people who find it difficult to understand:
The only problem we have in this is to refer to the TableView Cell. Once you figure out a way to refer the cell, you can interact with the cell components.
func addBtn(sender: AnyObject) -> Int {
let button: UIButton = sender as! UIButton
let indexPath = NSIndexPath(forRow: sender.tag, inSection: 0) // This defines what indexPath is which is used later to define a cell
let cell = tableView.cellForRowAtIndexPath(indexPath) as! FoodTypeTableViewCell! // This is where the magic happens - reference to the cell
count = 1 + count
println(count)
cell.countLabel.text = "\(count)" // Once you have the reference to the cell, just use the traditional way of setting up the objects inside the cell.
return count
}
func subBtn(sender: AnyObject) -> Int {
let button: UIButton = sender as! UIButton
let indexPath = NSIndexPath(forRow: sender.tag, inSection: 0)
let cell = tableView.cellForRowAtIndexPath(indexPath) as! FoodTypeTableViewCell!
if count == 0 {
println("Count zero")
} else {
count = count - 1
}
cell.countLabel.text = "\(count)"
println(count)
return count
}
I hope someone will benefit from this.
PLEASE CORRECT ME IF THERE IS SOME PROBLEM IN THIS SOLUTION OR THERE IS A BETTER/PROPER WAY TO DO THIS.
Use tableView.reloadData() to reload your tableView content each time you click a button.
let text = "something"
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell:FoodTypeTableViewCell = self.tableView!.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! FoodTypeTableViewCell
cell.addBtn.tag = indexPath.row // Button 1
cell.addBtn.addTarget(self, action: "addBtn:", forControlEvents: .TouchUpInside)
cell.subBtn.tag = indexPath.row // Button 2
cell.subBtn.addTarget(self, action: "subBtn:", forControlEvents: .TouchUpInside)
cell.countLabel.text = something
return cell
}
func addBtn(sender: AnyObject) -> Int {
let button: UIButton = sender as! UIButton
count = 1 + count
println(count)
something = "\(count)"
self.tableView.reloadData()
return count
}
func subBtn(sender: AnyObject) -> Int {
let button: UIButton = sender as! UIButton
if count == 0 {
println("Count zero")
} else {
count = count - 1
}
println(count)
something = "\(count)"
self.tableView.reloadData()
return count
}
Update1
After your comments ...
you have an array (one value for each food) like this, and whenever you click on a button, you take the index of the row the contains that button, then use that index to retrive the value of count from your array, then reload the table view content.
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
}