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
Related
so I want to make this simple wishlist feature for when the user tapped the "heart" button it will add that data from view to wishlist view. just like this :
so when the user tapped that heart button, that movie will show in this wishlist view like this :
now, my question is how do I notify my wishlistVc so that it knows there's a new "wishlist" that the user tapped from the movie list. I have an idea that I should use a delegate, but still, I can't figure out how to implement a delegate in this case.
and I use "var movieList" to store all the data in HomeVc, and my idea is when the user tapped that heart button in tableview, that data that user tapped with will move into my "let wishlist", so i can populate it on my wishlistVC ( but I don't know how to do this so I need help)
so far this is my code :
class DefaultTableViewCell: UITableViewCell {
#IBOutlet weak var moviePosterImage: UIImageView!
#IBOutlet weak var movieTitleLabel: UILabel!
#IBOutlet weak var wishlistButton: UIButton!
var indexPath: IndexPath!
var delegate: DefaultTableViewDelegate?
var wishlistFlag:Bool = false
override func layoutSubviews() {
super.layoutSubviews()
wishlistButton.titleLabel?.text = ""
wishlistButton.addTarget(self, action: #selector(wishlistTapped(_:)), for: .valueChanged)
}
#IBAction func wishlistTapped(_ sender: UIButton) {
wishlistFlag = !wishlistFlag
delegate?.wishlistTrigger(row: indexPath.row)
if wishlistFlag == true {
wishlistButton.setImage(UIImage(named: "heart_fill"), for: .normal)
}else if wishlistFlag == false {
wishlistButton.setImage(UIImage(named: "heart"), for: .normal)
}
}
}
HomeVc (the vc that shows the movie list):
var movieList : [Movie] = []
extension HomeVC: UITableViewDataSource, UITableViewDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return movieList.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let data = movieList[indexPath.row]
let cell = tableView.dequeueReusableCell(withIdentifier: "DefaultTableViewCell", for: indexPath) as! DefaultTableViewCell
cell.indexPath = indexPath
cell.movieTitleLabel.text = data.title
cell.moviePosterImage.sd_setImage(with: data.imageUrl)
cell.delegate = self
return cell
}
}
protocol DefaultTableViewDelegate {
func wishlistTrigger(row: Int)
}
this is my wishlistVc:
let wishlist : [Movie] = []
extension WishlistVc: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return wishlist.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let data = wishlist[indexPath.row]
let cell = tableView.dequeueReusableCell(withIdentifier: "DefaultTableViewCell", for: indexPath) as! DefaultTableViewCell
cell.movieTitleLabel.text = data.title
cell.moviePosterImage.sd_setImage(with: data.imageUrl)
cell.wishlistButton.titleLabel?.text = ""
cell.indexPath = indexPath
return cell
}
}
I've been stuck for 2 whole days now I still don't know how to figure this out. I appreciate anyone that can help me. Thanks
Implement func like:
func wishlistTrigger(row: Int) {
self.myWishlistedItem.append(self.movieList[row]) //Add that wishlisted item in array
self.tableView.reloadData() //Now reload Table
}
This question already has an answer here:
Swift Using a UISlider in a UITableViewCell the right way?
(1 answer)
Closed 2 years ago.
My program is creating slider in dynamic cell. I can't just create IBAction using ctrl+"drag and drop" on slider because it is in cell, not in viewController. How can I create this IBAction in ViewController?
class SecondViewController: UIViewController {
var gradesNumber: Int?
var gradeArray: [Grade] = []
#IBOutlet weak var topLabel: UILabel!
#IBOutlet weak var gradesTableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
gradesTableView.dataSource = self
for i in 1...gradesNumber! {
gradeArray.append(Grade(sliderValue: 5, title: "Grade \(i)", grade: "5"))
}
gradesTableView.register(UINib(nibName: "TableViewCell", bundle: nil), forCellReuseIdentifier: "ReusableCell")
}
#IBAction func calculateButton(_ sender: UIButton) {
var avg = 0.0
for i in gradeArray {
avg += Double(i.grade)!
}
topLabel.text = "Avg: \(avg/Double(gradesNumber!))"
}
}
extension SecondViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return gradesNumber ?? 1
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ReusableCell", for: indexPath) as! TableViewCell
cell.gradeSlider.tag = indexPath.row
cell.gradeSlider.setValue(5, animated: true)
let slider: Grade = gradeArray[indexPath.row]
cell.gradeValue.text = slider.grade
cell.gradeNumber.text = slider.title
return cell
}
}
As Lucas says in his comment, you can create a custom subclass of UITableViewCell and attach the action to the cell class. Then you'd forward the message from the cell to the owning table view. (You'd probably need a delegate property on the cell class so you know who to forward the action to.)
Alternately, you could configure the IBAction in your cellForRowAt method:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ReusableCell", for: indexPath) as! TableViewCell
//other config code
//Make sure the slider doesn't already have the action attached.
if cell.gradeSlider.actions(forTarget: nil, forControlEvent: .valueChanged) == nil {
cell.gradeSlider.addTarget(self, action: #selector(actionSelector(:)), controlEvents: .valueChanged]
}
return cell
}
I'm trying to change the values of a variable in two different view controllers from the value of a textField but I don't understand how to use the delegate so that it works.
My Storyboard:
My Code:
MainView:
class GameCreatingViewController: UIViewController {
var newGame = Game()
override func viewDidLoad() {
super.viewDidLoad()
newGame = Game()
newGame.playerBook.NumberOfPlayers = 2
if let vc = self.children.first(where: { $0 is PlayersTableViewController }) as? PlayersTableViewController {
vc.currentGame = self.newGame
vc.tableView.reloadData()
}
if let vc = self.children.first(where: { $0 is GameViewController }) as? GameViewController {
vc.currentGame = self.newGame
}
}
func changeName(name: String, number: Int) {
self.newGame.playerBook.players[number].name = name
}
}
tableViewController:
class PlayersTableViewController: UITableViewController, UITextFieldDelegate {
var currentGame = Game()
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "playerCell", for: indexPath) as? PlayerNameTableViewCell else {fatalError("Wrong type of cell")}
// Configure the cell...
cell.playerName.delegate = self
let row = indexPath[1]+1
cell.numberOfPlayer = row
return cell
}
func changeName(name: String, number: Int) {
self.currentGame.playerBook.players[number].name = name
}
}
The Cell:
protocol changeNameDelegate: class {
func changeName(name: String, number: Int)
}
class PlayerNameTableViewCell: UITableViewCell, UITextFieldDelegate {
weak var delegate: changeNameDelegate? = nil
#IBOutlet weak var playerName: UITextField!
var numberOfPlayer: Int = Int()
#IBAction func changeName(_ sender: UITextField) {
delegate?.changeName(name: sender.text!, number: numberOfPlayer)
}
}
It seems like the action from the button executes but the fonctions from the other viewcontrollers don't.
Use the delegate to notify the other viewController.
Make sure isn't nil.
Usually protocols name the first letter is capitalized.
A good practice is to implement protocols in extensions.
Implement the changeNameDelegate protocol.
class PlayersTableViewController: UITableViewController, UITextFieldDelegate, changeNameDelegate {
And in the cell configuration set the delegate.
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "playerCell", for: indexPath) as? PlayerNameTableViewCell else {fatalError("Wrong type of cell")}
// Configure the cell...
cell.playerName.delegate = self
cell.delegate = self // This line is missing.
let row = indexPath[1]+1
cell.numberOfPlayer = row
return cell
}
I've searched for a solutions on this issue but none seem to work for my use case.
I have a table inside a viewcontroller and the issue I am facing is that when scrolling the UISwitch state is reset to OFF. I understand table cells are reused, but how do I implement a solution that will restore the state of UISwitch when a user scrolls based on my code below
import UIKit
class StirrViewController: UIViewController, UITableViewDelegate, UITableViewDataSource{
#IBOutlet weak var mylabel: UILabel!
var myString = String()
#IBAction func stirrBtn(_ sender: AnyObject) {
}
var timeSelected = String()
var selectedTimeArr = [String]()
override func viewDidLoad() {
super.viewDidLoad()
mylabel.text = myString
self.timeSelected = myString
}
func switchChanged(_ sender : UISwitch!){
print("table row switch Changed \(sender.tag)")
print("The switch is \(sender.isOn ? "ON" : "OFF")")
let kValue = (sender.tag + 1)
let keyValue = String(kValue)
if sender.isOn {
recipeSettings.boolStirrSwitch[keyValue] = true
recipeSettings.switchedOnArr.append(keyValue)
} else {
recipeSettings.boolStirrSwitch[keyValue] = false
}
}
public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
let stringNum = Int(self.timeSelected)
recipeSettings.recipeTimeSet2 = stringNum!
return(stringNum)!
}
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
var cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! UITableViewCell
//here is programatically switch make to the table view
let switchView = UISwitch(frame: .zero)
switchView.setOn(false, animated: true)
switchView.tag = indexPath.row // for detect which row switch Changed
switchView.addTarget(self, action: #selector(self.switchChanged(_:)), for: .valueChanged)
cell.accessoryView = switchView
// Process data displayed in rows(minutes)
let endTime = Int(self.timeSelected)
let startTime = Int(1)
// Recipe time array
let timeArray: [Int] = Array(startTime...endTime!)
let stringTimeArr = timeArray.map{String($0)}
// Save time array to global variable
recipeSettings.recipeTimeSetArr = stringTimeArr
// Create a boolean Array to hold all default false booleans
let defBool: Bool = false
var defBoolArr: [Bool] = []
// Fill the array with the defaults boolean
for _ in 0..<stringTimeArr.count{defBoolArr.append(defBool)}
// Map the array to global dictionary containing the Time in an array and default "false" value
for i in 0..<stringTimeArr.count {
recipeSettings.boolStirrSwitch[stringTimeArr[i]] = defBoolArr[i]
}
// Add the minutes to cell table
cell.textLabel?.text = stringTimeArr[indexPath.row]
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
As you can see in my code I do save the state of each UI switch in a global variable dictionary. How can I solve the issue of UISwitch changing states based on this code? All help is appreciated. Thanks in advance
var switchState = [String : Bool]()
your recipeSettings.boolStirrSwitch should be decleard like that.
As you are using timeSelected as numberOfRowsInSection as showing
your cell.textLabel from that so you don't need extra stringTimeArr
for that.
All the processing you do in cellForRowAt it will happen again and
again table cells are reused so for setting up data do it in another
function then reload TableView.
Solution for your problem should be look like that.
import UIKit
class StirrViewController: UIViewController, UITableViewDelegate, UITableViewDataSource{
//make tableView IBOutlet for reloading data
#IBOutlet weak var tableView: UITableView!
#IBOutlet weak var mylabel: UILabel!
var myString = String()
#IBAction func stirrBtn(_ sender: AnyObject) {
}
var timeSelected = String()
var selectedTimeArr = [String]()
override func viewDidLoad() {
super.viewDidLoad()
mylabel.text = myString
self.timeSelected = myString
self.setdefaultSwitchState()
}
//recipeSettings.boolStirrSwitch should be decleard like that
var switchState = [String : Bool]()
//setDeaultSwitchState
func setdefaultSwitchState(){
if let timeSelected = Int(self.timeSelected){
for value in 0..<timeSelected{
switchState["\(value)"] = false
//or
//recipeSettings.boolStirrSwitch["\(value)"] = false
}
}
self.tableView.reloadData()
}
#objc func switchChanged(_ sender : UISwitch!){
print("table row switch Changed \(sender.tag)")
print("The switch is \(sender.isOn ? "ON" : "OFF")")
let kValue = (sender.tag + 1)
let keyValue = String(kValue)
if sender.isOn {
switchState[keyValue] = true
} else {
switchState[keyValue] = false
}
}
public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
let stringNum = Int(self.timeSelected)
recipeSettings.recipeTimeSet2 = stringNum!
return(stringNum)!
}
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
var cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! UITableViewCell
//here is programatically switch make to the table view
let switchView = UISwitch(frame: .zero)
switchView.setOn(false, animated: true)
switchView.tag = indexPath.row // for detect which row switch Changed
switchView.addTarget(self, action: #selector(self.switchChanged(_:)), for: .valueChanged)
cell.accessoryView = switchView
cell.textLabel?.text = "\(indexPath.row + 1)"
if let switchState = switchState["\(indexPath.row)"] {
if switchState{
switchView.isOn = true
}else{
switchView.isOn = false
}
}else{
switchView.isOn = false
}
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
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)