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)
Related
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 working on the iOS application with Swift 4. In that project I have requirement like, I have to create controls dynamically along with the proper alignment.
For example, I have a button when I click on that button I am hitting the service from that I am getting json data which contains 4 objects. Based on that I have to create controls dynamically and dynamic alignment also should do. I tried lot of examples and tutorials. I didn’t find any solution.
You can use UITableView for that and here is example:
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var tableview: UITableView!
var nameArr :[String] = []
override func viewDidLoad() {
super.viewDidLoad()
tableview.delegate = self
tableview.dataSource = self
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func four_btn(_ sender: Any) {
nameArr.removeAll()
let nameData = ["First Name","Middle Name","Last Name","DOB"]
nameArr += nameData
tableview.reloadData()
}
#IBAction func eight_btn(_ sender: Any) {
nameArr.removeAll()
let nameData = ["Salutation","First Name","Middle Name","Last Name","DOB","Gender","Mobile","Email"]
nameArr += nameData
tableview.reloadData()
}
}
extension ViewController: UITableViewDataSource, UITableViewDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return nameArr.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! tableviewCells
cell.nameLabel.text = nameArr[indexPath.row]
return cell
}
}
class tableviewCells : UITableViewCell{
#IBOutlet weak var nameLabel: UILabel!
}
You can use UITableView for the same
Your scenario is like, it may possible that one user having 5 records however another may have 10 or 12 records means you've to work dynamically
if there are 2 buttons which calls 2 different APIs then just manage 2 different array like this
var arr1 = NSArray()
var arr2 = NSArray()
var isAPI1Called = Bool()
save response of both apis in different array
then just manage flag on button tap and in suitable view like this
#IBAction func btn1(_ sender: Any) {
isAPI1Called = true
self.API1Called()
}
#IBAction func btn2(_ sender: Any) {
isAPI1Called = false
self.API1Called()
}
Now use flag in UITableview Delegate like this
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if isAPI1Called
{
return arr1.count
}
else
{
return arr2.count
}
}
Load UITableviewCell as per your requirement if UI changed
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if isAPI1Called
{
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell1", for: indexPath) as! UITableviewCell
//Do your required stuff here
return cell
}
else
{
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell1", for: indexPath) as! UITableviewCell
//Do your required stuff here
return cell
}
}
Hope it will help you
Comment if not get any point
I have a table view representing an underlying array. The cells have a label and a slider which should show the value of the percentage property of the array.
I want to use key-value observing to update the label whenever the percentage property changes. (I know KVO is overkill in this example but eventually sliding one slider will affect the other cells including the position of the slider and the underlying array will be set from multiple places in the app and at any time so KVO is the way to go.)
I've had a bunch of help from this answer, but I can't get it to fire and update the label. I'm including all my code here. Not sure where I'm going wrong.
import UIKit
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, CustomCellDelegate {
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
tableView.dataSource = self
tableView.delegate = self
for i in 0...4 {
items.append(Items(ID: i, percentage: 50))
}
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return items.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if let cell = tableView.dequeueReusableCell(withIdentifier: myTableViewCell.ID) as? myTableViewCell {
cell.object = items[indexPath.row]
cell.mySlider.tag = indexPath.row
return cell
} else {
return UITableViewCell()
}
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 100
}
#IBAction func sliderValueChanged(_ sender: UISlider) {
items[sender.tag].percentage = Double(sender.value)
print("percentage at \(items[sender.tag].ID) is \(items[sender.tag].percentage)")
}
func didUpdateObject(for cell: UITableViewCell) {
if let indexPath = tableView.indexPath(for: cell) {
tableView.reloadRows(at: [indexPath], with: .automatic)
print("hello")
}
}
}
class myTableViewCell: UITableViewCell {
static let ID = "myCell"
weak var delegate: CustomCellDelegate?
private var token: NSKeyValueObservation?
var object: Items? {
willSet {
token?.invalidate()
}
didSet {
myLabel.text = "\(object?.percentage ?? 0)"
token = object?.observe(\.percentage) { [weak self] object, change in
if let cell = self {
cell.delegate?.didUpdateObject(for: cell)
}
}
}
}
override func awakeFromNib() {
super.awakeFromNib()
}
#IBOutlet weak var myLabel: UILabel!
#IBOutlet weak var mySlider: UISlider!
}
class Items: NSObject {
let ID: Int
#objc dynamic var percentage: Double
init(ID: Int, percentage: Double){
self.ID = ID
self.percentage = percentage
super.init()
}
}
var items: [Items] = []
protocol CustomCellDelegate: class {
func didUpdateObject(for cell: UITableViewCell)
}
To do the KVO in Swift 4, you have to declare the property as dynamic and call observe(_:options:changeHandler:) on that object, saving the resulting NSKeyValueObservation token. When that token falls out of scope (or replaced with another token), the original observer will automatically be removed.
In your case, you have your observer calling the delegate, which then reloads the cell. But you never appear to set that delegate property, so I suspect that method isn't getting called.
But this all seems a bit fragile. I'd be inclined to just update the label directly in the observer's changeHandler. I also think you can do a more direct updating of the cell (I'd put the "value changed" IBAction in the cell, not the table view), and eliminate that rather awkward use of the tag to identify which row in the model array had its slider updated (which can be problematic if you insert or delete rows).
So consider this object:
class CustomObject: NSObject {
let name: String
#objc dynamic var value: Float // this is the property that the custom cell will observe
init(name: String, value: Float) {
self.name = name
self.value = value
super.init()
}
}
You could then have a table view controller that populates an array of objects with instances of this model type. The details here are largely unrelated to the observation (which we'll cover below), but I include this just to provide a complete example:
class ViewController: UITableViewController {
var objects: [CustomObject]!
override func viewDidLoad() {
super.viewDidLoad()
// self sizing cells
tableView.estimatedRowHeight = 60
tableView.rowHeight = UITableViewAutomaticDimension
// populate model with random data
let formatter = NumberFormatter()
formatter.numberStyle = .spellOut
objects = (0 ..< 1000).map {
CustomObject(name: formatter.string(for: $0)!, value: 0.5)
}
}
}
// MARK: - UITableViewDataSource
extension ViewController {
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return objects?.count ?? 0
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CustomCell", for: indexPath) as! CustomCell
cell.object = objects[indexPath.row]
return cell
}
}
Having done that, you can now have the base class for your cell (a) update the model object if the slider changes; and (b) observe changes to that dynamic property, in this example updating the label when the value changes are observed in the model object:
class CustomCell: UITableViewCell {
#IBOutlet weak var nameLabel: UILabel!
#IBOutlet weak var valueLabel: UILabel!
#IBOutlet weak var valueSlider: UISlider!
static private let formatter: NumberFormatter = {
let _formatter = NumberFormatter()
_formatter.maximumFractionDigits = 2
_formatter.minimumFractionDigits = 2
_formatter.minimumIntegerDigits = 1
return _formatter
}()
private var token: NSKeyValueObservation?
weak var object: CustomObject? {
didSet {
let value = object?.value ?? 0
nameLabel.text = object?.name
valueLabel.text = CustomCell.formatter.string(for: value)
valueSlider.value = value
token = object?.observe(\.value) { [weak self] object, change in
self?.valueLabel.text = CustomCell.formatter.string(for: object.value)
}
}
}
#IBAction func didChangeSlider(_ slider: UISlider) {
object?.value = slider.value
}
}
That yields:
For more information, see the "Key-Value Observing" section of the Using Swift with Cocoa and Objective-C: Adopting Cocoa Patterns.
hi #sean problem is in UITableview cell class you have already make diSet Method , so you dont need to pass value for cell.lable and slider Just try below code
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if let cell = tableView.dequeueReusableCell(withIdentifier: myTableViewCell.ID) as? myTableViewCell {
//pass the object to which you wanna add observer to cell
cell.object = items[indexPath.row]
return cell
} else {
return UITableViewCell()
}
}
I have tableview which has custom cells.
Each cell has 3 textfields: dayInWeek, startTime, endTime.
In below image, it has 2 rows. But user can click + button to add more rows.
If user click Submit button, I want to loop to every rows, collect 3 textfields data, and store in array or whatever.
Custom TableViewCell:
import UIKit
class RegularScheduleCell: UITableViewCell {
#IBOutlet weak var dayInWeek: UITextField!
#IBOutlet weak var startTime: UITextField!
#IBOutlet weak var endTime: UITextField!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
}
And a view controller:
import UIKit
class RegularScheduleVC: UIViewController, UITableViewDelegate,UITableViewDataSource {
#IBOutlet weak var tableView: UITableView!
var numOfRow = 1
override func viewDidLoad() {
super.viewDidLoad()
tableView.dataSource = self
tableView.delegate = self
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return numOfRow
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cellIdentifier = "RegularScheduleCell"
let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as! RegularScheduleCell
return cell
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if (editingStyle == UITableViewCellEditingStyle.delete) {
numOfRow -= 1
self.tableView.deleteRows(at: [indexPath], with: UITableViewRowAnimation.right)
tableView.reloadData()
}
}
func insertNewRow(_ sender: UIBarButtonItem) {
if numOfRow < 7 {
numOfRow += 1
tableView.reloadData()
}
}
override func setEditing(_ editing: Bool, animated: Bool) {
super.setEditing(editing, animated: animated)
tableView.setEditing(editing, animated: animated)
}
}
At this moment, I try to use UITextFieldDelegate
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cellIdentifier = "RegularScheduleCell"
let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as! RegularScheduleCell
cell.dayInWeek.delegate = self
cell.startTime.delegate = self
cell.endTime.delegate = self
return cell
and
func textFieldDidEndEditing(_ textField: UITextField) {
allCellsText.append(textField.text!) //allCellsText is an array
print(allCellsText)
}
so that when user finish editing, then add that data to array.
However, this does not satisfy my requirement, because:
on the same cell: can not know if the data is belong to dayOfWeek, or startTime, or endTime
on 2 different cells: can not know if data is belong to, let say, dayOfWeek of 1st cell or dayOfWeek of 2nd cell.
Therefore, How can I loop to all cells, get all 3 text fields data?
Thanks
Method 1:
Make an array of key pairs as-
var arrayOfKeyPairs = [[String:Any]]()
arrayOfKeyPairs.append(["header":"xx",
"value" : "",
“id”: "dsd",
"order" : 0])
We are just replacing the default values with user input values as-
func textFieldDidEndEditing(_ textField: UITextField) {
let center: CGPoint = textField.center
let rootViewPoint: CGPoint = textField.superview!.convert(center, to: tableView)
let indexPath: IndexPath = tableView.indexPathForRow(at: rootViewPoint)! as IndexPath
arrayOfKeyPairs[indexPath.row ]["value"] = textField.text//here you are appending(replacing) data to array
}
On click of submit button, cross check what you received as-
func tapsOnNext(){
self.view.endEditing(true)//for adding last text field value with dismiss keyboard
print(arrayOfKeyPairs)
}
Method 2:
We can get cell data by accessing the cell with particular indexpath as
func tapsOnNext(){
let indexpath = IndexPath(row: 0, section: 0)
let cell = tableView.cellForRow(at: indexPath) as! CustomTableViewCell
print(cell.myTextField.text)
}
You can get text of UITextField with adding target to UITextField
cell.YOUR_TEXTFIELD.addTarget(self, action: "textFieldDidChange:", forControlEvents: UIControlEvents.EditingChanged)
//EditingChanged is one of the events and will be fired whenever the user changes any character in that UITextField.
After that, you can call your function like this:
func textFieldDidChange(textField: UITextField) {
//your code
}
Don't forget to create class for UITableViewCell and to create IBOutlets of all your UITextField in that custom cell class
You can do it the following way.
First of all you can create an object for example "History"
make its properties like daysInWeek, startTime, endTime.
In your viewDidLoad method you define an array of Objects. Populate the data in the array, or save those objects of "History" that you created in this array.
Set the dataSource of the table view to this array.
in your method cellForRowAtIndexPath you can access the elements of the array you created above.
When you are tapping the plus button, you can create a new object of History, save this object in the array and reload the table view.
If you can share the git repo of this code, i will show how this is being done.
Simple code: On button click get cell data from cell's text field
let indexpath = IndexPath(row: 0, section: 1)
let cell = ItemtableView.cellForRow(at: indexPath) as! CustomTableViewCell
print("text \(cell.myTextField.text)")
I'm going to do something like this https://i.stack.imgur.com/jAGsk.png
So if user input points - it'll save points to the user's name. How to do it? I paste textField in the tableViewCell with a functions.
Here is code from the tableViewCell file
#IBOutlet weak var inputScore: UITextField!
public func configure(text: Int?, placeholder: String) {
inputScore.text = String(text!)
inputScore.placeholder = placeholder
inputScore.accessibilityValue = String(text!)
inputScore.accessibilityLabel = placeholder
}
And here is code from the VC file
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "InputScore") as! InputScoreTableViewCell
cell.textLabel?.text = usersIn[indexPath.row]
cell.configure(text: 100, placeholder: "Score")
return cell
}
public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return usersIn.count
}
So how to save it to the user's name?
Use DidSelectRowAtIndexPath method to get cell textLable text in textField.
Below Sample Code for That:
import UIKit
class ViewController: UIViewController,UITableViewDelegate,UITableViewDataSource {
#IBOutlet var btnOK: UIButton!
#IBOutlet var txtValue: UITextField!
#IBOutlet var tblData: UITableView!
let arrResult = NSMutableArray()
override func viewDidLoad() {
super.viewDidLoad()
tblData.dataSource = self
tblData.delegate = self
btnOK.tag = 57775
btnOK.addTarget(self, action: #selector(applyEdit(sender:)), for: .touchUpInside)
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return arrResult.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell(style: .default, reuseIdentifier: "cell")
cell.textLabel?.text = arrResult[indexPath.row] as? String ?? ""
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
btnOK.tag = indexPath.row
let cell: UITableViewCell = tableView.cellForRow(at: indexPath)!
txtValue.text = cell.textLabel?.text
setTitle()
}
func setTitle() {
if btnOK.tag == 57775 {
btnOK.setTitle("Add", for: .normal)
}else{
btnOK.setTitle("Update", for: .normal)
}
}
func applyEdit(sender: UIButton) {
if sender.tag == 57775 {
arrResult.add(txtValue.text ?? "")
}else{
arrResult.removeObject(at: sender.tag)
arrResult.insert(txtValue.text ?? "", at: sender.tag)
sender.tag = 57775
setTitle()
}
txtValue.text = ""
tblData.reloadData()
}
}
output:
You have to create a data model for your users:
class User: NSObject {
var points = 0
}
And then create an array of users in your view controller:
var users = [User]()
That way, you can do something like this
var user = users[indexPath.row]
user.points = 100
print(user.points) // 100
You can then display your users' points in your table view. You can also assign a tag to your text fields equal to the indexPath.row so that you can easily work with them.
In top of use user model provided by #Cesare we need to modified the cellForRowAtIndexPath method and your cell's implementation, adding a closure for data change event, and using it
#IBOutlet weak var inputScore: UITextField!
fileprivate var fnDataWasUpdated : (Int?) -> Void = {_ in} //closure for data change notification
public func configure(text: Int?, placeholder: String,_ fnListener: #escaping (Int?) -> Void) {
inputScore.text = String(text!)
inputScore.placeholder = placeholder
inputScore.accessibilityValue = String(text!)
inputScore.accessibilityLabel = placeholder
//added delegate implementation for UITextField
inputScore.delegate = self
self.fnDataWasUpdated = fnListener
}
also is needed that your cell adopts UITextFieldDelegate protocol
extension InputScoreTableViewCell : UITextFieldDelegate
{
func textFieldDidEndEditing(_ textField: UITextField)
{
if let intValue = Int(textField.text)
{
self.fnDataWasUpdated(intValue)
}
}
}
Finally we use the new closure in your cell
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "InputScore") as! InputScoreTableViewCell
let currUser = self.users[indexPath.row]
cell.configure(text: currUser.points, placeholder: "Score",{ (newIntValue) in
currUser.points = newIntValue
})
return cell
}
This code was not tested but I had been using the main concept in several projects, so if you have any kind of problems please let me know
I hope this helps you