custom cell delegate and UITextField (swift) - uitableview

the goal is to create new table row every time any other row is tapped. all rows contain UITextField and that is the problem.
main controller:
var subtasksArray = ["one", "two"]
var addRow = 0
class AddEditTaskController: UIViewController, UITableViewDataSource,
UITableViewDelegate, CustomCellDelegate {
func reloadTable() {
subtaskTable.reloadData()
}
#IBOutlet weak var subtaskTable: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
var tblView = UIView(frame: CGRectZero)
subtaskTable.tableFooterView = tblView
subtaskTable.tableFooterView?.hidden = true
subtaskTable.backgroundColor = UIColor.clearColor()
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return subtasksArray.count + addRow
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell: SubtaskCell = subtaskTable.dequeueReusableCellWithIdentifier("subtaskCell") as SubtaskCell
cell.delegate = self
return cell
}
custom cell:
protocol CustomCellDelegate {
func reloadTable()
}
class SubtaskCell: UITableViewCell, UITextFieldDelegate {
var delegate: CustomCellDelegate?
#IBOutlet weak var subtaskTextField: UITextField!
var subtasksArray = [String]()
override func awakeFromNib() {
super.awakeFromNib()
subtaskTextField.delegate = self
}
func textFieldDidBeginEditing(textField: UITextField) {
addRow += 1
delegate?.reloadTable()
}
func textFieldDidEndEditing(textField: UITextField) {
subtasksArray.append(subtaskTextField.text)
}
override func setSelected(selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
}
the problem is that if tap any row, a new one is created, but text field is not active.
if i don't use CustomCellDelegate, textfields in cells work fine, but i couldn't find any other way to reload table except cell delegate.

Related

Is there a simple way to delete specific custom cells from a UITableView?

I am trying to instantiate empty Buyer cells (custom cell) in my table view and then have the user populate the buyers' names. When the user presses the delete button for a row/cell, it should delete the corresponding row/cell regardless of whether or not the textfield for that row has been populated or not. Clearly, I am not getting the desired behavior. For example, when I press delete Row0 (whose textfield says "Buyer 0") and the tableview reloads, Buyer 0 is still there, but one of the empty Buyer cells at the end gets deleted instead.
import UIKit
class EntryAlertViewController: UIViewController {
//Fields/Table
#IBOutlet weak var itemField: UITextField!
#IBOutlet weak var priceField: UITextField!
#IBOutlet weak var tableView: UITableView!
//Visual Components
#IBOutlet weak var mainView: UIView!
#IBOutlet weak var titleView: UIView!
#IBOutlet weak var splitItemButton: UIButton!
#IBOutlet weak var cancelButton: UIButton!
#IBOutlet weak var addItemButton: UIButton!
//Commonly Used Objects/Variables
var potentialBuyers: [String] = []
override func viewDidLoad() {
super.viewDidLoad()
potentialBuyers.append("")
tableView.dataSource = self
tableView.register(UINib(nibName: "BuyerCell", bundle: nil), forCellReuseIdentifier: "ReusableCell")
}
override func viewWillAppear(_ animated: Bool) {
}
#IBAction func splitItemPressed(_ sender: UIButton) {
potentialBuyers.append("")
tableView.reloadData()
}
}
Here are the tableview datasource and the delete button delegate.
extension EntryAlertViewController: UITableViewDataSource, DeleteButtonDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return potentialBuyers.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ReusableCell", for: indexPath) as! BuyerCell
cell.deleteButtonDelegate = self
cell.indexPath = indexPath
cell.nameField.text = cell.buyerName
if potentialBuyers.count == 1 {
cell.deleteButton.isHidden = true
} else {
cell.deleteButton.isHidden = false
}
return cell
}
func deletePressed(index: Int) {
potentialBuyers.remove(at: index)
tableView.reloadData()
}
}
And here is my BuyerCell class with the UITextFieldDelegate as an extension.
import UIKit
protocol DeleteButtonDelegate {
func deletePressed(index: Int)
}
class BuyerCell: UITableViewCell {
#IBOutlet weak var deleteButton: UIButton!
#IBOutlet weak var nameField: UITextField!
var deleteButtonDelegate: DeleteButtonDelegate!
var indexPath: IndexPath!
var buyerName: String?
override func awakeFromNib() {
super.awakeFromNib()
self.nameField.delegate = self
}
#IBAction func deletePressed(_ sender: UIButton) {
//print the indexPath.row that this was pressed for
print("delet pressed for \(indexPath.row)")
self.deleteButtonDelegate?.deletePressed(index: indexPath.row)
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
extension BuyerCell: UITextFieldDelegate {
func textFieldDidBeginEditing(_ textField: UITextField) {
print("textFieldDidBeginEditing")
buyerName = nameField.text
}
func textFieldDidEndEditing(_ textField: UITextField) {
print("textFieldDidEndEditing")
buyerName = nameField.text
}
}
Your problem is in this line
cell.nameField.text = cell.buyerName
Cells are reused from a reuse pool, so you can't rely on the cell holding any particular state or value.
Your buyer name needs to come from your data model array.
Something like
cell.nameField.text = self.potentialBuyers[indexPath.row]
Reloading the whole tableview is a bit excessive when you have only deleted a single row; Just delete the relevant row.
You can also clean up your delegation protocol so that there is no need for the cell to track its indexPath -
protocol DeleteButtonDelegate {
func deletePressed(in cell: UITableViewCell)
}
In your cell:
#IBAction func deletePressed(_ sender: UIButton) {
self.deleteButtonDelegate?.deletePressed(in: self)
}
In your view controller:
func deletePressed(in cell: UITableViewCell) {
guard let indexPath = tableView.indexPath(for: cell) else {
return
}
potentialBuyers.remove(at: indexPath.row)
tableView.deleteRows(at:[indexPath], with: .automatic)
}
There is a major issue in your code. You are not updating the data model so the changes in the cells are lost when the user scrolls.
Rather then quite objective-c-ish protocol/delegate in Swift callback closures are much more convenient and efficient. You can use one callback for both updating the model and deleting the cell.
Replace the BuyerCell cell with
class BuyerCell: UITableViewCell {
#IBOutlet weak var deleteButton: UIButton!
#IBOutlet weak var nameField: UITextField!
var callback : ((UITableViewCell, String?) -> Void)?
override func awakeFromNib() {
super.awakeFromNib()
self.nameField.delegate = self
}
#IBAction func deletePressed(_ sender: UIButton) {
callback?(self, nil)
}
}
extension BuyerCell: UITextFieldDelegate {
func textFieldDidBeginEditing(_ textField: UITextField) {
print("textFieldDidBeginEditing")
callback?(self, nameField.text)
}
func textFieldDidEndEditing(_ textField: UITextField) {
print("textFieldDidEndEditing")
callback?(self, nameField.text)
}
}
In the controller in cellForRow assign the callback and handle the actions. The actions work also reliably if cells are reordered, inserted or deleted.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ReusableCell", for: indexPath) as! BuyerCell
let buyerName = potentialBuyers[indexPath.row]
cell.nameField.text = buyerName
cell.callback = { [unowned self] cCell, cName in
let currentIndexPath = tableView.indexPath(for: cCell)!
if let name = cName {
self.potentialBuyers[currentIndexPath.row] = name
} else {
self.potentialBuyers.remove(at: currentIndexPath.row)
tableView.deleteRows(at: [currentIndexPath], with: .fade)
}
}
cell.deleteButton.isHidden = potentialBuyers.count == 1
return cell
}

Table View Cell with a Textfield

I have a subclass, CustomCell, which inherits from my parent class, CreateEvent. The subclass describes the individual cells for the table view cell, which is on the CreateEvent View controller. In one specific cell, I have a textfield, that is linked to the CustomCell file, but I am having trouble getting the value from that textfield when a user enters into the textfield. I am also having trouble dismissing the keyboard with outside touches and pressing the return key, but I am primarily focused on getting the text from the textfield. I am familiar with doing these functionalities on a normal swift file but because this is a subclass, I'm not sure what to do. What I've tried is to use:
class CustomCell: UITableViewCell, UITextFieldDelegate {
#IBOutlet weak var entranceFeeTextField: UITextField!
override func awakeFromNib() {
super.awakeFromNib()
}
override func setSelected(selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
And:
class CreateEventVC: UIViewController, UITableViewDelegate, UITableViewDataSource, CustomCellDelegate, UITextFieldDelegate {
override func viewDidLoad() {
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let currentCellDescriptor = getCellDescriptorForIndexPath(indexPath)
let cell = tableView.dequeueReusableCell(withIdentifier: currentCellDescriptor["cellIdentifier"] as! String, for: indexPath) as! CustomCell
cell.entranceFeeTextField.delegate = self
entranceFeeAmount = cell.entranceFeeTextField.text!
}
This code doesn't run and I'm not exactly sure which textfield delegates I need to run in order to be able to get the Text value from the textfield.
You could use the UITextFieldDelegate methods textFieldShouldEndEditing(:) or textFieldShouldReturn(:) to get the results of the textfield.
for example:
func textFieldShouldEndEditing(textField: UITextField) -> Bool {
print("TextField should end editing method called")
let textFromCell = textField.text!
//do whatever you want with the text!
return true;
}
In this code snippet, textField will actually be your instance of entranceFeeTextField. Because somewhere, when that textfield stops editing, it calls self.delegate?.textFieldShouldEndEditing(entranceFeeTextField) and that method's implementation is inside your CreateEventVC.
Returning true will allow the textfield to end editing. This method will only get called when the user wants to stop editing. So you should remove entranceFeeAmount = cell.entranceFeeTextField.text! from your cellForRowAtIndexPath method because that's where you create your cell. At that point a user will not have typed into your textfield, so no use in getting the text from it as soon as it has been made.
All you have to do is implement one of those methods in CreateEventVC.
Here is the full code: (Xcode 8 swift 3)
(View Controller Class)
class ViewController: UIViewController,UITableViewDataSource,UITableViewDelegate,UITextFieldDelegate
{
#IBOutlet weak var tbl: UITableView!
var cell = TableViewCell()
override func viewDidLoad()
{
super.viewDidLoad()
}
override func didReceiveMemoryWarning()
{
super.didReceiveMemoryWarning()
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
cell = tbl.dequeueReusableCell(withIdentifier: "CELL") as! TableViewCell
cell.configure(text: "", placeholder: "EnterText")
return cell
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
return 1
}
func numberOfSections(in tableView: UITableView) -> Int
{
return 1
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool
{
print( cell.returnTextOfTextField() )
print(cell.txtField.text)
cell.txtField .resignFirstResponder()
return true
}
}
TableViewCell class (Custom cell):
class TableViewCell: UITableViewCell,UITextFieldDelegate
{
#IBOutlet weak var txtField: UITextField!
override func awakeFromNib()
{
super.awakeFromNib()
// Initialization code
}
public func configure(text: String?, placeholder: String) {
txtField.text = text
txtField.placeholder = placeholder
txtField.accessibilityValue = text
txtField.accessibilityLabel = placeholder
}
func returnTextOfTextField() -> String
{
print(txtField.text)
return txtField.text!
}
override func setSelected(_ selected: Bool, animated: Bool)
{
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
"CELL" is the identifier given to cell in Nib .
This is working code , I get the value from text field and even keyboard is resigned.
var cell = TableViewCell() // customCell
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
cell = tbl.dequeueReusableCell(withIdentifier: "CELL") as! TableViewCell
cell.configure(text: "", placeholder: "EnterText")
return cell
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
return 1
}
func numberOfSections(in tableView: UITableView) -> Int
{
return 1
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool
{
//cell = tbl.dequeueReusableCell(withIdentifier: "CELL") as! TableViewCell
print( cell.returnTextOfTextField() )
print(cell.txtField.text)
cell.txtField .resignFirstResponder()
return true
}
/// Custom cell class
class TableViewCell: UITableViewCell,UITextFieldDelegate
{
#IBOutlet weak var txtField: UITextField!
override func awakeFromNib()
{
super.awakeFromNib()
// Initialization code
}
public func configure(text: String?, placeholder: String) {
txtField.text = text
txtField.placeholder = placeholder
txtField.accessibilityValue = text
txtField.accessibilityLabel = placeholder
}
func returnTextOfTextField() -> String
{
print(txtField.text)
return txtField.text!
}
override func setSelected(_ selected: Bool, animated: Bool)
{
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}

tableview using array not working - xcode 7.3

I'm designing an app for my school and I am trying to make a simple directory using a tableview within a view controller. I began by trying to make objects, but then switched to a more simple array, still without any luck. I do not know if it is a problem with the code or with the storyboard. Here is the code for the viewcontroller:
import UIKit
class DirectoryViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var backButton2: UIButton!
#IBOutlet weak var directoryTableView: UITableView!
// #IBOutlet weak var nameLabel: NSLayoutConstraint!
// let searchController = UISearchController(searchResultsController: nil)
// var directoryObjects: NSMutableArray! = NSMutableArray()
var names = ["Teacher 1", "Teacher 2", "Teacher 3"]
override func viewDidLoad() {
super.viewDidLoad()
/*
self.directoryObjects.addObject("Teacher 1")
self.directoryObjects.addObject("Teacher 2")
self.directoryObjects.addObject("Teacher 3")
self.directoryTableView.reloadData()
*/
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
// Mark - tableview
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
return 3
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
let directoryCell = self.directoryTableView.dequeueReusableCellWithIdentifier("directoryCell", forIndexPath: indexPath) as! DirectoryTableViewCell
// directoryCell.directoryLabel.text = self.directoryObjects.objectAtIndex(indexPath.row) as? String
directoryCell.directoryLabel.text = names[indexPath.row]
return directoryCell
}
#IBAction func backButton2Tapped(sender: AnyObject) {
self.dismissViewControllerAnimated(true, completion: nil)
}
}
Here is the code for the DirectoryTableViewCell:
import UIKit
class DirectoryTableViewCell: UITableViewCell {
#IBOutlet weak var directoryLabel: 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
}
/*
func directoryTableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
}
*/
}
You have to set the delegates. The easiest way is in the code.
In viewDidLoad add:
directoryTableView.delegate = self
directoryTableView.dataSource = self
This will allow those tableView functions to get called.
I'd also recommend changing:
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
return 3
}
to:
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
return names.count
}

how to handle button click for each button in each row of UITableView

I have a UITableView (with a Custom class called CellModelAllNames for each row). Each Row has a Label and a button.
My question is: When btn_addRecording (i.e. the '+' button is clicked on any/each of the rows, how do I get the lbl_name.text, the label name shown, and show a pop up in the ViewController itself. I want to get additional information in the pop up and then save all the info (including the lbl_name to a database).
CellModelAllNames for each row layout:
import UIKit
class CellModelAllNames: UITableViewCell {
#IBOutlet weak var lbl_name: UILabel!
#IBOutlet weak var btn_addRecording: UIButton!
#IBAction func btnAction_addRecording(sender: AnyObject) {
println("clicked on button in UITableViewCell")
}
override func awakeFromNib() {
super.awakeFromNib()
}
override func setSelected(selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
func setCell(setBabyName: String) {
self.lbl_name.text = setBabyName
}
}
Here's the code of my ViewController:
import UIKit
class SecondViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var tbl_allNames: UITableView!
var arrayOfNames: [Name] = [Name]()
override func viewDidLoad() {
super.viewDidLoad()
self.tbl_allNames.delegate = self
self.tbl_allNames.dataSource = self
self.tbl_allNames.scrollEnabled = true
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell:CellModelAllNames = self.tbl_allNames.dequeueReusableCellWithIdentifier("CellModelAllNames") as! CellModelAllNames
let name = arrayOfNames[indexPath.row]
cell.setCell(name.name)
println("in tableView, cellforRowatIndex, returning new cells")
return cell
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return arrayOfNames.count
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
}
}
You can use standard UIKit methods to get the cell and its data:
func tappedButton(sender : UIButton) {
let point = sender.convertPoint(CGPointZero, toView: self.tableView)
let indexPath = self.tableView.indexPathForRowAtPoint(point)!
let name = arrayOfNames[indexPath.row]
// do something with name
}
You can add button action in your ViewController
1) In your function cellForRowAtIndexPath assign button's tag as index (ie. indexPath.row)
cell.btn_addRecording.tag = indexPath.row
2) Add target and action for your button :
cell.btn_addRecording.addTarget(self, action: "buttonPressed:", forControlEvents: .TouchUpInside)
3) Add action in ViewControler (ie. save info in database)
func buttonPressed(button: UIButton!)
{
// Add your code here
let name = arrayOfNames[button.tag]
}

dynamic tableview inside custom tableviewcell

I'm trying to create a tableview with custom cells that each one holds a tableview.
I want to show the inner tableview just when it have some data (most of the time it's empty). I've managed to display the cells but can't display their tableview if it's populated with data.
The problem also is that the cell height needs to be dynamic according to the amount of data to display.
The cell code:
class feedViewCell: UITableViewCell , UITableViewDataSource , UITableViewDelegate {
#IBOutlet var feedCellImage: UIImageView!
#IBOutlet var feedCellUserName: UILabel!
#IBOutlet var feedCellDate: UILabel!
#IBOutlet var feedCellComments: UILabel!
#IBOutlet var cardView: UIView!
#IBOutlet var repliesTableView: UITableView!
var repliesArray:[Reply] = []
#IBAction func addComment(sender: AnyObject) {
}
override func awakeFromNib() {
super.awakeFromNib()
var nib = UINib(nibName: "feedComment", bundle: nil)
repliesTableView.registerNib(nib, forCellReuseIdentifier: "commentCell")
}
override func setSelected(selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
override func layoutSubviews() {
cardSetup()
}
func cardSetup() {
cardView.layer.masksToBounds = false
cardView.layer.cornerRadius = 5
cardView.layer.shadowOffset = CGSizeMake(-0.2, 0.2)
cardView.layer.shadowRadius = 1
cardView.layer.shadowOpacity = 0.2
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return repliesArray.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell = repliesTableView.dequeueReusableCellWithIdentifier("commentCell", forIndexPath: indexPath) as CommentFeedCell
cell.commentCellUserName.text = repliesArray[indexPath.row].userName
return cell
}
}
And the Main controller code:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var comment = comments[indexPath.row]
let cell: feedViewCell = tableView.dequeueReusableCellWithIdentifier("feedCell", forIndexPath: indexPath) as feedViewCell
cell.feedCellUserName.text = comment.userName
cell.feedCellImage.backgroundColor = UIColor.redColor()
cell.feedCellComments.text = "\(comment.replies.count) COMMENTS"
cell.repliesArray = comment.replies
var dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "dd-MM-yyy"
cell.feedCellDate.text = dateFormatter.stringFromDate(NSDate())
if cell.repliesArray.count > 0 {
cell.repliesTableView.rowHeight = UITableViewAutomaticDimension
}
cell.repliesTableView.reloadData()
return cell
}
How to show the inner tableview only in cells which have comments (and hiding the tableview in cells with 0 comments)?
Call super in layoutSubviews and let us know what happens.
override func layoutSubviews() {
super.layoutSubviews()
cardSetup()
}

Resources