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)")
Related
I'm making a tableview that allows users to select items, primary is the main item, and each primary item contains a secondary list, I created a custom cell and also inserted the other tableview inside, as shown in the picture link below.
Form picture
Here's my ViewController.
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, PrimaryDelegate {
#IBOutlet weak var tableView: UITableView!
//This is my data
var cardList = Card(userName: "Kevin", primaryList: [Card.Primary(primaryName: "Card1", secondaryList: [Card.Primary.Secondary(secondaryName: "Card1-1")]),Card.Primary(primaryName: "Card2", secondaryList: [Card.Primary.Secondary(secondaryName: "Card2-1"),Card.Primary.Secondary(secondaryName: "Card2-2")])])
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
tableView.reloadData()
}
#IBAction func enterAction(_ sender: Any) {
//I hope here can print the result
//How should I get the result from primaryList and secondaryList in Custom Cell ?
print(cardList)
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return cardList.primaryList.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CustomTableViewCell", for: indexPath) as! CustomTableViewCell
cell.primaryLabel.text = cardList.primaryList[indexPath.row].primaryName
cell.secondaryList = cardList.primaryList[indexPath.row].secondaryList
cell.primaryIndex = indexPath.row
cell.primaryDelegate = self
return cell
}
func primaryIndex(index:Int) {
//I use delegate to get index, but how to tell which secondaryList needs to be selected all?
print("primaryIndex\(index)")
}
}
Here's my Custom Cell, it includes the other tableview, that's why I feel complicated.
class CustomTableViewCell: UITableViewCell,UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var primaryBtn: UIButton!
#IBOutlet weak var secondaryBtn: UIButton!
#IBOutlet weak var primaryLabel: UILabel!
#IBOutlet weak var secondaryLabel: UILabel!
#IBOutlet weak var secondaryTableView: UITableView!
#IBOutlet weak var secondaryHeight: NSLayoutConstraint!
var primaryIndex:Int?
var primaryDelegate:PrimaryDelegate?
var secondaryList:[Card.Primary.Secondary]!{
didSet{
secondaryTableView.delegate = self
secondaryTableView.dataSource = self
secondaryTableView.reloadData()
secondaryHeight.constant = secondaryTableView.contentSize.height
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return secondaryList.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CustomTableViewCell", for: indexPath) as! CustomTableViewCell
cell.secondaryLabel.text = secondaryList[indexPath.row].secondaryName
return cell
}
#IBAction func primaryBtnAction(_ sender: UIButton) {
sender.isSelected = !primaryBtn.isSelected
primaryDelegate?.primaryIndex(index: primaryIndex!)
}
#IBAction func secondaryBtnAction(_ sender: UIButton) {
sender.isSelected = !secondaryBtn.isSelected
}
override func awakeFromNib() {
super.awakeFromNib()
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
}
I hope it can be...
1.When the user selects the primary item, it can automatically help me select all the secondary items. However, the primary item can only be selected "one", and when the user selects the next primary item, the previous one needs to be canceled including all secondary items.
2.When the user press enterAction, it can print the data that user have chosen.I need to know what if user not choose primary, How many secondary list's item is selected.
I mean like result is Card1-1 and Card2-1, they only choose item in secondary list.
How should I tell custom cell's tableview to select all when I choose primary item, and how did custom cell know which primary is selected and needs to reload Data?
If need further information, please let me know, this selection rule make me really confused.Thanks
You can use this at your primaryBtnAction :
if sender.isSelected{
for section in 0..<tableView.numberOfSections {
for row in 0..<tableView.numberOfRows(inSection: section) {
tableView.selectRow(at: IndexPath(row: row, section: section), animated: false, scrollPosition: .none)
}
}
} else { //Deselect statement
tableView.deselectRow(at: IndexPath(row: row, section: section), animated: false)
}
Hope it helps...
I have one table view and inside that i placed one main view. And inside that main view i placed one button.And when ever use click on my cell button. I need to get the cell title label.This is what i need. But i tried following below code. Not sure what i am missing out. It not at all calling my cell.add target line.
Code in cell for row at index:
cell.cellBtn.tag = indexPath.row
cell.cellBtn.addTarget(self, action:#selector(self.buttonPressed(_:)), for:.touchUpInside)
#objc func buttonPressed(_ sender: AnyObject) {
print("cell tap")
let button = sender as? UIButton
let cell = button?.superview?.superview as? UITableViewCell
let indexPath = tableView.indexPath(for: cell!)
let currentCell = tableView.cellForRow(at: indexPath!)! as! KMTrainingTableViewCell
print(indexPath?.row)
print(currentCell.cellTitleLabel.text)
}
I even added a breakpoint, still it not at calling my cell.addTarget line
Tried with closure too. In cell for row at index:
cell.tapCallback = {
print(indexPath.row)
}
In my table view cell:
var tapCallback: (() -> Void)?
#IBAction func CellBtndidTap(_ sender: Any) {
print("Right button is tapped")
tapCallback?()
}
Here that print statement is getting print in console.
import UIKit
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var list = [String]()
#IBOutlet weak var tableView: UITableView!
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return list.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! MyTableViewCell
cell.saveButton.tag = indexPath.row
//cell.saveButton.accessibilityIdentifier = "some unique identifier"
cell.tapCallback = { tag in
print(tag)
}
return cell
}
}
class MyTableViewCell: UITableViewCell {
// MARK: - IBOutlets
#IBOutlet weak var saveButton: UIButton!
// MARK: - IBActions
#IBAction func saveTapped(_ sender: UIButton) {
tapCallback?(sender.tag)
}
// MARK: - Actions
var tapCallback: ((Int) -> Void)?
}
Actually this is not a good programming practice to add the button (which contains in table view cell) target action in view controller. We should follow the protocol oriented approach for it. Please try to under stand the concept.
/*This is my cell Delegate*/
protocol InfoCellDelegate {
func showItem(item:String)
}
/*This is my cell class*/
class InfoCell: UITableViewCell {
//make weak reference to avoid the Retain Cycle
fileprivate weak var delegate: InfoCellDelegate?
//Outlet for views
#IBOutlet var showButton: UIButton?
override func awakeFromNib() {
super.awakeFromNib()
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
//This is the public binding function which will bind the data & delegate to cell
func bind(with: DataModel?, delegate: InfoCellDelegate?, indexPath: IndexPath) {
//Now the bind the cell with data here
//.....
//Assign the delegate
self.delegate = delegate
}
//Button action
#IBAction func rowSelected(sender: UIButton) {
self.delegate?.showItem(item: "This is coming from cell")
}
}
/*Now in your ViewController you need to just confirm the InfoCellDelegate & call the bind function*/
class ListViewController: UIViewController {
//Views initialisation & other initial process
}
//Table view Delegate & Data source
extension ListViewController: UITableViewDataSource, UITableViewDelegate {
/**
Configure the table views
*/
func configureTable() {
//for item table
self.listTable.register(UINib.init(nibName: "\(InfoCell.classForCoder())", bundle: nil), forCellReuseIdentifier: "\(InfoCell.classForCoder())")
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 5
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "InfoCell") as! InfoCell
cell.bind(with: DataModel, delegate: self, indexPath: indexPath)
return cell
}
}
extension ListViewController: InfoCellDelegate {
func showItem(item) {
print(item)
}
}
I'm practicing creating an app where I have a label that gets its text from an UITextField when the user presses a button. Now, I added another button and a tableview and I want to be able to "save" the label's text to the table cells with the same mechanism of stopwatch's laps.
So, to be clear, I want the button to transfer the label's text to the table view cells each time I press it.
After your save button, you need to store the texts somewhere and reload the table. (Or insert it with animation)
class ViewController: UIViewController {
#IBOutlet private var textField: UITextField!
#IBOutlet private var tableView: UITableView!
var texts: [String] = [] {
didSet { tableView.reloadData() }
}
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "SimpleCell")
tableView.dataSource = self
}
#IBAction func saveButtonTapped(_ sender: UIButton) {
guard let newText = textField.text else { return }
self.texts.append(newText)
}
}
And in tableView dataSource methods:
extension ViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return texts.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "SimpleCell", for: indexPath)!
cell.textLabel?.text = texts[indexPath.row]
return cell
}
}
I am trying to access each value of a text field in a prototype cell within a UITableView on Submit. I know I should be doing this in a better way (model) but for now, I just need to access these fields and cannot find a way to do this in Swift 3/4. Would anyone be able to assist?
Code:
import UIKit
import Firebase
class FormTableViewController: UITableViewController {
var formLabels = [String]()
var formPlaceholders = [String]()
override func viewDidLoad() {
super.viewDidLoad()
FirebaseApp.configure()
formLabels = ["Name","Email","Password", "Phone"]
formPlaceholders = ["John Smith","example#email.com","Enter Password", "8585551234"]
tableView.estimatedRowHeight = 30
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return formLabels.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = self.tableView.dequeueReusableCell(withIdentifier:
"FormTableCell", for: indexPath)
as! FormTableViewCell
let row = indexPath.row
cell.formLabel.font =
UIFont.preferredFont(forTextStyle: UIFontTextStyle.headline)
cell.formLabel.text = formLabels[row]
cell.formTextField.placeholder = formPlaceholders[row]
return cell
}
#IBAction func submitButtonPressed(_ sender: Any) {
// Need to do something with the Name, Email, Phone and Password fields here
}
}
You seem to acknowledge that updating the model directly probably makes sense. So why not do that? Just:
Have model collection for the responses;
Set up delegate for the text field in the cell;
Have cellForRowAt set that delegate; and
Make the table view controller conform to that class.
So, something quick and dirty, set up the cell to hook up editChanged event from the text field and set up protocol to inform the view controller:
protocol FormTableViewCellDelegate: class {
func fieldValueChanged(cell: UITableViewCell, textField: UITextField)
}
class FormTableViewCell: UITableViewCell {
weak var delegate: FormTableViewCellDelegate?
#IBOutlet weak var formLabel: UILabel!
#IBOutlet weak var formTextField: UITextField!
#IBAction func editingChanged(_ sender: UITextField) {
delegate?.fieldValueChanged(cell: self, textField: sender)
}
}
And then have the view controller set up model object and conform to your new protocol:
class FormTableViewController: UITableViewController {
var formLabels = [String]()
var formPlaceholders = [String]()
var values = [String?]()
override func viewDidLoad() {
super.viewDidLoad()
...
formLabels = ["Name","Email","Password", "Phone"]
formPlaceholders = ["John Smith","example#email.com","Enter Password", "8585551234"]
values = [nil, nil, nil, nil]
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "FormTableCell", for: indexPath) as! FormTableViewCell
let row = indexPath.row
cell.formLabel.font = .preferredFont(forTextStyle: .headline)
cell.formLabel.text = formLabels[row]
cell.formTextField.placeholder = formPlaceholders[row]
cell.formTextField.text = values[row]
cell.delegate = self // set the delegate, too
return cell
}
#IBAction func submitButtonPressed(_ sender: Any) {
print(#function, values)
}
}
// delegate protocol to update model as text fields change
extension FormTableViewController: FormTableViewCellDelegate {
func fieldValueChanged(cell: UITableViewCell, textField: UITextField) {
guard let indexPath = tableView.indexPath(for: cell) else { return }
values[indexPath.row] = textField.text
}
}
Then that's it, your model is updated as the text fields are updated. Plus this has the advantage that it now supports cell reuse, conforms to MVC patterns, etc.
If you want to just loop through cells, you can create an array of ‘IndexPath’.
let array = (0..<formLabels.count).map { IndexPath(row: $0, section:0) }
After that you can loop over this array and access individual cell using tableview method:- tableView.cellForIndexPath
Hope this helps. (Not on my laptop, so didn’t test the syntax)
I have a tableView with custom cell. in my custom cell I have a like button. for like Button I wrote a function to change state from .normal to .selected like this:
FeedViewCell
class FeedViewCell: UITableViewCell {
#IBOutlet weak var likeButton: UIButton!
var likes : Bool {
get {
return UserDefaults.standard.bool(forKey: "likes")
}
set {
UserDefaults.standard.set(newValue, forKey: "likes")
}
}
override func awakeFromNib() {
super.awakeFromNib()
self.likeButton.setImage(UIImage(named: "like-btn-active"), for: .selected)
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
#IBAction func likeBtnTouch(_ sender: AnyObject) {
print("press")
// toggle the likes state
self.likes = !self.likeButton.isSelected
// set the likes button accordingly
self.likeButton.isSelected = self.likes
}
}
FeedViewController :
class FeedViewController: UIViewController {
#IBOutlet var feedTableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
// Register Cell Identifier
let feedNib = UINib(nibName: "FeedViewCell", bundle: nil)
self.feedTableView.register(feedNib, forCellReuseIdentifier: "FeedCell")
}
func numberOfSectionsInTableView(_ tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.feeds.count
}
func tableView(_ tableView: UITableView, heightForRowAtIndexPath indexPath: IndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
func tableView(_ tableView: UITableView, cellForRowAtIndexPath indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "FeedCell", for: indexPath) as! FeedViewCell
return cell
}
}
But my problem is when I tap like button in cell with indexPath.row 0 the state of button in cell with indexPath.row 3 change state too.
where is my mistake?
thanks
You didn't post all your code, but I can tell you that for this to work the #IBAction func likeBtnTouch(_ sender: AnyObject) { } definition must be inside the FeedViewCell class definition to make it unique to a particular instance of the cell.
As a rule of thumb, I normally ensure that all the UI elements inside my cell are populated in cellForRowAtIndexPath when using dequeued cells. Also it should be set from an external source. I.o.w not from a property inside the cell. Dequeuing cells reuse them, and if not setup properly, it might have some leftovers from another cell.
For example, inside cellForRowAtIndexPath:
self.likeButton.isSelected = likeData[indexPath.row]