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
}
}
Related
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 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 text field that when I type a word in, and then press a button is supposed to add the word the the tableview. I know the array is being updated because after the button is pressed, the array, with its new value print fine in the console. I've tried reloadData() in several places but it's not doing anything. Here is my code:
import UIKit
class Arraytest: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var textField: UITextField?
var names = ["Jack", "Andy", "Guy", "Bruce", "Nick", "James", "Dominick"]
#IBAction func addButton(_ sender: Any) {
let name : String = textField!.text!
names.append(name)
textField?.text! = ""
for guy in names{
**print(guy)**
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell(style: UITableViewCellStyle.default, reuseIdentifier: "Cell")
cell.textLabel?.text = names[indexPath.row]
***tableView.reloadData()***
return cell
}
}
public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return names.count
}
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell(style: UITableViewCellStyle.default, reuseIdentifier: "Cell")
cell.textLabel?.text = names[indexPath.row]
return cell
}
}
Let's say I type John in the text field, here is what the console prints:
Jack
Andy
Guy
Bruce
Nick
James
Dominick
John
I know the array works fine, but not why the tableView won't reload when everyone claims reloadData() works(I'm sure it does, and I'm just making an easy mistake!)..Any ideas?
EDIT: Ok it turns out that you do have to drag the IBOutlet from the tableview. The reason I didn't do this earlier was because I watched a video and his worked without making the connection.
You should learn more about table view & how it works. Please see Apple Documentation Creating and Configuring a Table View
Here the method cellForRowAt is a data source of table view and it's get fired from tableview automatically when it's need to populate cell data. You couldn't manually call this method. Implementing this method inside #IBAction func addButton() does nothing. Function always needs to call from another method. So you should remove cellForRowAt inside #IBAction func addButton().
Solution: Get an table view outlet from storyboard. If need help see here Connect the UI to Code
#IBOutlet weak var tableView: UITableView?
Then set tableview datasource and delegate in viewDidLoad()
override func viewDidLoad() {
super.viewDidLoad()
tableView?.dataSource = self;
tableView?.delegate = self;
}
And finally update #IBAction func addButton() as below
#IBAction func addButton(_ sender: Any) {
let name : String = textField!.text!
names.append(name)
textField?.text! = ""
for guy in names{
print(guy)
}
tableView?.reloadData()
}
Full source may look like this:
import UIKit
class Arraytest: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var textField: UITextField?
#IBOutlet weak var tableView: UITableView?
var names = ["Jack", "Andy", "Guy", "Bruce", "Nick", "James", "Dominick"]
override func viewDidLoad() {
super.viewDidLoad()
tableView?.dataSource = self;
tableView?.delegate = self;
}
#IBAction func addButton(_ sender: Any) {
let name : String = textField!.text!
names.append(name)
textField?.text! = ""
for guy in names{
**print(guy)**
}
tableView?.reloadData()
}
public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return names.count
}
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell(style: UITableViewCellStyle.default, reuseIdentifier: "Cell")
cell.textLabel?.text = names[indexPath.row]
return cell
}
}
Your code has two very big mistakes (assuming that your tableview delegates are connected to your viewcontroller correctly).
First your a missing an IBOulet reference to your tableview. Create one and you can call the reloadData() method in that outlet.
Second, you should not call the public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell delegate method inside your IBAction. It is a delegate method, it is already listening for an event or command that will fire its logic, in this case reloadData().
So your IBaction should only take the value from the textfield and add it to the array, to finally call reloadData(), which will call public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
this code works... hope it helps:
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var tableview: UITableView!
#IBOutlet weak var textField: UITextField?
var names = ["Jack", "Andy", "Guy", "Bruce", "Nick", "James", "Dominick"]
// MARK: tableview delegate methods
public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return names.count
}
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell(style: UITableViewCellStyle.default, reuseIdentifier: "Cell")
cell.textLabel?.text = names[indexPath.row]
return cell
}
// MARK: ibactions
#IBAction func addButton(_ sender: Any) {
let name : String = textField!.text!
names.append(name)
textField?.text! = ""
tableview.reloadData()
}
}
You have to remove TableView delegate method from
#IBAction func addButton(_ sender: Any) {
}
method & put it outside of it. Create a property of this tableView form storyboard like that
#IBOutlet weak var tableView: UITableView!
In viewDidload method set datasource & delegate of it like that
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.datasource = self
self.tableView.delegate = self
}
Hope it helps.
You can achieve this by adding a property observer to the array of names. The didset method will run if there is any changes to the value.
class Arraytest: UIViewController, UITableViewDelegate UITableViewDatasource {
var names = ["Jack", "Andy", "Guy", "Bruce", "Nick", "James", "Dominick"] {
didSet {
self.tableView.reloadData();
}
}
}
The tableview func inside the addButton func doesnt run.. remove it and keep it like this:
#IBAction func addButton(_ sender: Any) {
let name : String = textField!.text!
names.append(name)
textField?.text! = ""
for guy in names{
**print(guy)**
}
}
You have to take your tableview func outside the #IBAction func addButton, as it's part of the uitableviewdatasource and is required to display your cell. After that, all you need is to put tableView.reloadData() after the for guy in names{ ... } loop.
Drag a connection from a split view controller to your view controller and use reloadData().
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 got a view controller which contains a tableview, this tableview contains dynamic cells done in storyboard. Most cells contain a textfield and in the view controller i need to detect when the textfields have been selected and which cell it was. (I load a popover pointing to the cell, and this must be called from the view controller).
Cell code
import UIKit
class AddNew_Date_Cell: UITableViewCell {
#IBOutlet var Label: UILabel!
#IBOutlet var textField: UITextField!
func loadItem(var data:NSArray) {
Label.text = data[0] as? String
}
}
ViewControllerCode
import UIKit
class AddNewDetailView: UIViewController, UITableViewDelegate, UITableViewDataSource, UIPopoverPresentationControllerDelegate, UITextFieldDelegate {
#IBOutlet var tableView: UITableView!
var items : NSMutableArray!
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func tableView(tableView: UITableView, estimatedHeightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return items.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
switch(items[indexPath.row][1] as! Int)
{
...
case 2:
var cell:AddNew_Date_Cell = tableView.dequeueReusableCellWithIdentifier("AN_Date_Cell") as! AddNew_Date_Cell
cell.loadItem(items[indexPath.row] as! NSArray, view: self)
cell.textField.delegate = self
return cell
...
}
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
}
func textFieldShouldBeginEditing(textField: UITextField) -> Bool {
//Need to know here which cell the textfield is attached to
turn true
}
}
Each time i select a textfield view I'm hitting a breakpoint in "textFieldShouldBeginEditing" however i have no idea which cell its in.
if i select the textfield in a cell "didSelectRowAtIndexPath" is never hit.
How do i find out which cell has been selected.
Thanks
You need to set the interactions from user off while the text field is not been edit, in cellForRowAtIndexPath add this line
cell.textField.userInteractionEnabled = false
In the didSelectRowAtIndexPath you will need to enable again for edition otherwise the user won't be to touche it if they want to and call first responder:
cell.textField.userInteractionEnabled = true
cell.textField.becomeFirstResponder()
once the user finish editing in textFieldShouldEndEditing you disable the interaction again:
cell.textField.userInteractionEnabled = false
This way the cellForRowAtIndexPath will always be called but will be up to you to set the user to use the correct UITextField.
I hope that helps you
You could use a custom cell class with a delegate protocol letting you know when a text field has started editing.
The custom cell would be the delegate of the text field and the view controller would be the delegate of the custom cell.
For a basic example, your cell class could look like the following:
import UIKit
protocol TextFieldCellDelegate {
func textFieldCellDidBeginEditing(cell: TextFieldCell)
}
class TextFieldCell: UITableViewCell {
#IBOutlet weak var textField: UITextField!
var delegate: TextFieldCellDelegate?
}
extension TextFieldCell: UITextFieldDelegate {
func textFieldDidBeginEditing(textField: UITextField) {
delegate?.textFieldCellDidBeginEditing(self)
}
}
And your view controller would implement the method like this:
extension ViewController: TextFieldCellDelegate {
func textFieldCellDidBeginEditing(cell: TextFieldCell) {
println(cell)
// Do something with cell
}
}
If you want to find out the index of your cell write the below code in func textFieldShouldBeginEditing(textField: UITextField)
var index = 0
var position: CGPoint = textField.convertPoint(CGPointZero, toView: self.<your_table_outlet_name>)
if let indexPath = self.<your_table_outlet_name>.indexPathForRowAtPoint(position)
{
index = indexPath.row
}