Transferring data from a UITableViewCell cocoa touch class to a UIViewController class - ios

Okay, so what I am essentially trying to do is transfer data from a UITableViewCell class to a UIViewController class. Below is my UITableViewCell:
class OrderCell: UITableViewCell {
override func awakeFromNib() {
super.awakeFromNib()
}
var post: OrderModel!
var link: Link!
var addedList: [String:Float] = [:]
#IBOutlet weak var dishName: UILabel!
#IBOutlet weak var dishDescriptionAndPrice: UILabel!
#IBOutlet weak var numberOfOrders: UILabel!
#IBOutlet weak var addOrderBtn: UIButton!
#IBOutlet weak var subtractOderBtn: UIButton!
#IBAction func addButtonPressed(sender: AnyObject) {
if numberOfOrders.text == "9" {
numberOfOrders.text = "9"
} else {
numberOfOrders.text = String((Int(numberOfOrders.text!)! + 1))
}
addedList.updateValue(post.dishPrice, forKey: post.dishName)
print(addedList)
}
#IBAction func subtractButtonPressed(sender: AnyObject) {
if numberOfOrders.text == "0" {
numberOfOrders.text = "0"
} else {
numberOfOrders.text = String((Int(numberOfOrders.text!)! - 1))
}
}
func getOrders() -> Dictionary<String, Float> {
return addedList
}
func configureCell(post: OrderModel) {
self.post = post
self.dishName.text = post.dishName
self.dishDescriptionAndPrice.text = post.dishDescription
self.numberOfOrders.text = "0"
}
}
What I am trying to do, is have my other class (which is of UIViewController), retrieve 'addedList' from the above class. I obviously cannot use prepareForSegue, and I have had no luck find a way to do this that is compatible with these two classes. I am open to creating a new class to be the "bridge" between the two and transfer the data through, but I again cannot find a way to do this. Any and all help is greatly appreciated, thank you in advance.
To get more specific, the UIViewController holds my tableview, and the class that is above is just the object file for each individual cell. I need to transfer the data when the addButtonPressed IBaction is called.

You can do this by creating a delegate in your cell class. And in your view controller class, under cell for row at indexPath, add:
cell.delegate = self
And update your cell class like this:
protocol ClassNameDelegate:class {
func addButtonAction(addedList:[String:Float])
}
#IBOutlet weak var dishName: UILabel!
#IBOutlet weak var dishDescriptionAndPrice: UILabel!
#IBOutlet weak var numberOfOrders: UILabel!
#IBOutlet weak var addOrderBtn: UIButton!
#IBOutlet weak var subtractOderBtn: UIButton!
//Create a delegate
weak var delegate: ClassNameDelegate?
#IBAction func addButtonPressed(sender: AnyObject) {
if numberOfOrders.text == "9" {
numberOfOrders.text = "9"
} else {
numberOfOrders.text = String((Int(numberOfOrders.text!)! + 1))
}
addedList.updateValue(post.dishPrice, forKey: post.dishName)
print(addedList)
// Call this when added list is done and get the call to your view controller.
if delegate != nil {
delegate?.addButtonAction(addedList: addedList)
}
}
#IBAction func subtractButtonPressed(sender: AnyObject) {
if numberOfOrders.text == "0" {
numberOfOrders.text = "0"
} else {
numberOfOrders.text = String((Int(numberOfOrders.text!)! - 1))
}
}
func getOrders() -> Dictionary<String, Float> {
return addedList
}
func configureCell(post: OrderModel) {
self.post = post
self.dishName.text = post.dishName
self.dishDescriptionAndPrice.text = post.dishDescription
self.numberOfOrders.text = "0"
}
}
Finally import this delegate method in your view controller class, in the same way you import tableView delegate and datasource.

A cell is not the good place to store information like this. You should store the information in your view controller. And when your cell button is pressed, retrieve the information thanks to the index path of the cell.
In your cell:
func configureCell(post: OrderModel, delegate:UIViewController) {
self.dishName.text = post.dishName
self.dishDescriptionAndPrice.text = post.dishDescription
self.numberOfOrders.text = "0"
self.addButton.addTarget(delegate, action: #selector(delegate.addButtonPressed(_:)), for: UIControlEvents.touchUpInside)
}
In your view controller:
//store your data in your view controller
var data=[OrderModel]()
func addButtonPressed(_ button:UIButton){
let selectedCell=button.superview?.superview? as! OrderCell
let indexPath=tableView.indexPath(for: selectedCell)
let cellData=data[indexPath!.row]
if cellData.numberOfOrders==9{
selectedCell.numberOfOrders.text="9"
}else{
selectedCell.numberOfOrders.text=String(cellData.numberOfOrders+1)
}
}

Related

Table View Cell has no Initialiser

I am trying to display table view cell property into different table view cell when button I clicked . I am using story board . I added the Horizontal and vertical stack to contains the image and label properties . I want to display this property into another table view cell when user clicked the show button . In cellFor row function i defined the button action property . I am getting following error .Class 'DetailsViewCell' has no initializers. Cannot use instance member 'mc' within property initializer; property initializers run before 'self' is available
Here is the screenshot of the both view controller .
Here is the code .
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// guard let cell = tableView.dequeueReusableCell(withIdentifier: MovieCell.identifier, for: indexPath) as? MovieCell
// else { return UITableViewCell() }
let cell = tableView.dequeueReusableCell(withIdentifier: MovieViewCell.identifier, for: indexPath) as! MovieViewCell
cell.showButton.tag = indexPath.row
cell.showButton.addTarget(self, action: Selector("movieDetails"), for: .touchUpInside)
let row = indexPath.row
let title = presenter.getTitle(by: row)
let overview = presenter.getOverview(by: row)
let data = presenter.getImageData(by: row)
cell.configureCell(title: title, overview: overview, data: data)
return cell
}
}
Here is the code in identifier class with table view cell.
class MovieViewCell: UITableViewCell {
static let identifier = "MovieViewCell"
#IBOutlet weak var mainStackView: UIStackView!
#IBOutlet weak var movieImage: UIImageView!
#IBOutlet weak var movieTtile: UILabel!
#IBOutlet weak var movieOverview: UILabel!
#IBOutlet weak var showButton: UIButton!
#IBAction func movieDetails(_ sender: UIButton) {
var dc : DetailsViewCell
movieTtile = dc.movieTitle
movieOverview = dc.movieOverview
movieImage = dc.movieImage
}
func configureCell(title: String?, overview: String?, data: Data?) {
movieTtile.text = title
movieOverview.text = overview
if let imageData = data{
movieImage.image = UIImage(data: imageData)
}
}
}
Here is the code for Details view cell.
class DetailsViewCell: UITableViewCell {
#IBOutlet weak var movieTitle: UILabel!
#IBOutlet weak var movieOverview: UILabel!
#IBOutlet weak var movieImage: UIImageView!
var mc : MovieViewCell
movieTitle = mc.movieTtile
movieOverview = mc.movieOverview
movieImage = mc.movieImage
}
import UIKit
var loginData = ["", "", ""]
class LoginDataCell: UITableViewCell, UITextFieldDelegate {
#IBOutlet weak var txtLoginData: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
func textFieldDidEndEditing(_ textField: UITextField) {
if textField.tag == 0 {
loginData[0] = textField.text
} else if textField.tag == 1 {
loginData[1] = textField.text
} else if textField.tag == 2 {
loginData[2] = textField.text
}
}
}

How to implement event on click in button inside in a tableView to show other element?

I have a tableView with action buttons, one of them are hide until the user click the other button, I was looking how to do that and I found that I have to implement a delegate like the code below:
Class TableViewCell:
import UIKit
import FLAnimatedImage
protocol OnButtonsClickDelegate:AnyObject{
func onBtnDownloadClick(cell: ListadoTableViewCell)
}
class ListadoTableViewCell: UITableViewCell {
#IBOutlet weak var lblAnterior: UILabel!
#IBOutlet weak var lblCompras: UILabel!
#IBOutlet weak var lblDevolucion: UILabel!
#IBOutlet weak var lblSaldo: UILabel!
#IBOutlet weak var lblAbonos: UILabel!
#IBOutlet weak var lblNuevo: UILabel!
#IBOutlet weak var lblDiferido: UILabel!
#IBOutlet weak var lblCliente: UILabel!
#IBOutlet weak var lblNombreCliente: UILabel!
#IBOutlet weak var spinner: FLAnimatedImageView!
#IBOutlet weak var btnDowload: UIButton!
#IBOutlet weak var btnShare: UIButton!
var onButtonsClickDelegate : OnButtonsClickDelegate!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
#IBAction func onBtnDownloadClick(_ sender: AnyObject){
onButtonsClickDelegate.onBtnDownloadClick(cell: self)
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
Class Controller:
class ListadoController: NavigationViewController, UITableViewDelegate, UITableViewDataSource, RefreshScrollViewDelegate,OnButtonsClickDelegate {
#IBOutlet weak var tableView: RefreshTableView!
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cellListado") as! ListadoTableViewCell
let r = data[indexPath.row]
let compras = Functions.stringToFloat(str: r.compras)
let comprasn = Functions.stringToFloat(str: r.comprasn)
let abonos = Functions.stringToFloat(str: r.abonos)
let diferido = Functions.stringToFloat(str: r.diferido)
let saldomov = Functions.stringToFloat(str: r.saldomov)
cell.lblAnterior.text = Functions.moneyFormat(n: saldomov - compras - comprasn)
cell.lblCompras.text = Functions.moneyFormat(n: compras)
cell.lblDevolucion.text = Functions.moneyFormat(n: 0.0)
cell.lblSaldo.text = Functions.moneyFormat(n: saldomov - comprasn)
cell.lblAbonos.text = Functions.moneyFormat(n: abonos) + ""
cell.lblNuevo.text = Functions.moneyFormat(n: saldomov - comprasn - abonos) + ""
cell.lblDiferido.text = Functions.moneyFormat(n: diferido) + ""
cell.lblCliente.text = r.nombre.capitalized
cell.lblNombreCliente.text = r.cvecte
cell.onButtonsClickDelegate = self
if indexPath.row == data.count - 1 {
if (!last && !loading) {
loadData(page: currentPage)
}
}
return cell
}
func onBtnDownloadClick(cell: ListadoTableViewCell) {
cell.btnShare.isHidden = false
}
}
The problem is that it does not work correctly. When the user clicks the button, the other element is displayed but not only in the selected row, but also in other rows as well, how can I solve this problem?
The cell is being re-used and whatever is the state of that cell will still be there, in which case, try adding this to ListadoTableViewCell:
override func prepareForReuse() {
super.prepareForReuse()
btnShare.isHidden = true
}
You should try to save the state of the cell when you press the download button so that when the reloadData of the TableView is performed and the cellForRowAt function is called, the state of the changes in the cells is preserved, in your case the share button is shown if the download button has previously been pressed.
Here there is a project from my Github that make the functionality you need by applying MVVM Pattern https://github.com/JLPenaLopez/MyFiles I hope this helps you
This is a demostration: https://github.com/JLPenaLopez/MyFiles/blob/master/MyFilesGif.gif
I solved using an answer from another post:
Button action in custom UITableViewCell affects other cells
Thank you for your help.

how to pass data from one Tableview to another Tableview when button is pressed in cell?

Im trying to pass data from the ViewController to my CartViewController. The cells in the ViewController have 3 buttons(optionBtns) that have a price and weight label above each of them.
What Im trying to do is have the optionBtn selected pass the label data above it once the ATC button is pressed
the ATC button in the cell passes the data of image, name, category, and optionBtn data to the CartViewController cells(CartCell)
how would I be able to pass selected data to the CartVC when the ATC is pressed to present selected item Name, Image, and Category in cell with selected optionBtn data(Price & Weight)
I am also using Cloud Firestore to post data to populate my VC cells
class Cell: UITableViewCell {
weak var items: Items!
#IBOutlet weak var name: UILabel!
#IBOutlet weak var category: UILabel!
#IBOutlet weak var productImage: UIImageView!
#IBOutlet weak var weightOne: UILabel!
#IBOutlet weak var weightTwo: UILabel!
#IBOutlet weak var weightThree: UILabel!
#IBOutlet weak var priceOne: UILabel!
#IBOutlet weak var priceTwo: UILabel!
#IBOutlet weak var priceThree: UILabel!
#IBOutlet weak var addToCart: RoundButton!
#IBOutlet weak var optionBtn1: RoundButton!
#IBOutlet weak var optionBtn2: RoundButton!
#IBOutlet weak var optionBtn3: RoundButton!
var addActionHandler: (() -> Void)?
func configure(withItems items: Items) {
name.text = items.name
category.text = items.category
image.sd_setImage(with: URL(string: items.image))
priceOne.text = items.price1
priceTwo.text = items.price2
priceThree.text = items.price3
weightOne.text = items.weight1
weightTwo.text = items.weight2
weightThree.text = items.weight3
self.items = items
}
var lastSelectedButton = UIButton()
#IBAction func cartTypeSelected(_ sender: RoundButton) {
lastSelectedButton.isSelected = false; do {
self.lastSelectedButton.backgroundColor = UIColor.blue
lastSelectedButton = sender
sender.isSelected = true; do {
self.lastSelectedButton.backgroundColor = UIColor.lightGreen
}
}
#IBAction func atcBtn(_ sender: UIButton) {
self.addActionHandler?()
}
}
class CartViewController: UIViewController {
var items: Items!
#IBOutlet weak var cartTableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
cartTableView.dataSource = self
cartTableView.delegate = self
}
}
extension CartViewController: UITableViewDataSource, UITableViewDelegate {
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return Cart.currentCart.cartItems.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CartCell", for: indexPath) as! CartCell
let cart = Tray.currentCart.cartItems[indexPath.row]
cell.configure(withItems: cart)
return cell
}
}
class CartCell: UITableViewCell {
var selctedBtn: Cell?
#IBOutlet weak var lblMealName: UILabel!
#IBOutlet weak var imageUrl: UIImageView!
#IBOutlet weak var lblSubTotal: UILabel!
#IBOutlet weak var lblWeight: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
func configure(withItems items: Items) {
// lblWeight.text = "\(items.weight1)"
lblMealName.text = "\(items.category): \(items.name)"
let formatter = NumberFormatter()
formatter.maximumFractionDigits = 2
formatter.numberStyle = .decimal
// lblSubTotal.text = "$\(formatter.string(for: items.price1)!)"
imageUrl.sd_setImage(with: URL(string: items.imageUrl))
if selctedBtn?.optionBtn1.isSelected == true {
lblSubTotal.text = "$\(formatter.string(for: items.price1)!)"
lblWeight.text = "\(items.weight1)"
} else if selctedBtn?.optionBtn2.isSelected == true {
lblSubTotal.text = "$\(formatter.string(for: items.price2)!)"
lblWeight.text = "\(items.weight2)"
} else if selctedBtn?.optionBtn3.isSelected == true {
lblSubTotal.text = "$\(formatter.string(for: items.price3)!)"
lblWeight.text = "\(items.weight3)"
}
}
}
class Cart {
static let currentCart = Cart()
var cartItems = [Items]()
}
If the idea is for firebase to handle all of your data then the data should go through firebase and not through the viewController. ie your Firebase db should have an items collection (or perhaps one per store) and your user should have a cart collection. When the use taps the add to cart button you add the item in question to that users cart collection in firebase then show the cartViewController. The cartViewController should subscribe to the cart collection on the current user and then populate its tableview from that firebase collection.
TLDR the typical design of a firebase app is that the firebase DB is the source of truth for the app, so you should write all changes to firebase and then simply observe these collections elsewhere in the app. This also insures that if the user edits the cart on another device that it will update itself on the iOS device with the new cart items.
You can pass the values in closures.
So, in your Cell class you could change your closure var to:
var addActionHandler: ((Int) -> Void)?
Then, in your addToCart button action, something along these lines:
#IBAction func atcBtn(_ sender: UIButton) {
// pass back the user selected values
var i = 0
switch lastSelectedButton {
case optionBtn1:
i = 1
case optionBtn2:
i = 2
default:
i = 3
}
self.addActionHandler?(i)
}
Create a .selectedOption property of your Items class, you should add one (of type Int). You can use that to track the user's selection.
Modify cellForAt in Cell:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "Cell") as? Cell else { return UITableViewCell() }
// use var to make item mutable
var item = itemSetup[indexPath.row]
cell.configure(withItem: item)
cell.addActionHandler = { (option: Int) in
print("Option selected = \(option)")
item.selectedOption = option
Cart.currentCart.items.append(item)
}
return cell
}
In your CartCell you can make the labels like this:
if items.selectedOption == 1 {
lblSubTotal.text = "$\(formatter.string(for: items.price1)!)"
lblWeight.text = "\(items.weight1)"
} else if items.selectedOption == 2 {
lblSubTotal.text = "$\(formatter.string(for: items.price2)!)"
lblWeight.text = "\(items.weight2)"
} else if items.selectedOption == 3 {
lblSubTotal.text = "$\(formatter.string(for: items.price3)!)"
lblWeight.text = "\(items.weight3)"
}

swift - How to set UIStepper value from another class?

I have a tableview contains some tableviewcell with UIStepper. This is tableviewcell code:
class ShoppingCartItemCell: UITableViewCell {
static let reuseIdentifier = "ShoppingCartItemCell"
#IBOutlet weak var itemImage: UIImageView!
#IBOutlet weak var itemNameLabel: UILabel!
#IBOutlet weak var itemProviderLabel: UILabel!
#IBOutlet weak var itemPriceLabel: UILabel!
#IBOutlet weak var itemQuantityText: UITextField!
#IBOutlet weak var quantityStepper: UIStepper!
var oldValue = 0.0
var itemName = "" {
didSet {
itemNameLabel.text = itemName
}
}
var itemProvider = "" {
didSet {
itemProviderLabel.text = itemProvider
}
}
var itemPrice = "" {
didSet {
itemPriceLabel.text = itemPrice
}
}
var itemQuantity = "" {
didSet {
itemQuantityText.text = itemQuantity
}
}
}
And this is uistepper code in tableview:
class ShoppingCartViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var tableView: UITableView!
#IBAction func quantityStepper(_ sender: UIStepper) {
ShoppingCartItemCell.itemQuantity = "\(Int(sender.value))"// this way is not possible...
// How to update the quantityStepper value in ShoppingCartItemCell class here
}
}
How to update the quantityStepper value in ShoppingCartViewController class? Some people will tell me to create #IBAction of quantityStepper in ShoppingCartItemCell class but my business logic must to do this way. Please help. Thanks all.
I highly recommend to use Key-Value Observation in Swift 4, it's very easy to implement
In the cell create a NSKeyValueObservation property
var observation : NSKeyValueObservation?
In the view controller in cellForRowAt set the values for stepper and label add the observer
let item = dataSource[indexPath.row] // assuming `dataSource` is the data source array
cell.quantityStepper.value = item.quantity
cell.itemQuantityText.text = "\(item.quantity)"
cell.observation = cell.quantityStepper.observe(\.value, options: [.new]) { (stepper, change) in
cell.itemQuantityText.text = "\(change.newValue!)"
// update the data model for example
item.quantity = change.newValue!
}
Finally you have to remove the observer in didEndDisplaying after the cell has left the screen.
func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath) {
(cell as! ShoppingCartItemCell).observation = nil
}
No IBAction, no protocol / delegate. It's very simple and efficient.

Button on tableview cell is not working - swift

I have a button on my custom tableview cell that is showing the users name. When you click on it, you should be taken to his/her profile but nothing happens?
Here is my custom tableview cell class (commentsTableViewCell.swift) :
import UIKit
protocol commentsTableViewCellDelegate {
func namesIsTapped(cell: commentsTableViewCell)
}
class commentsTableViewCell: UITableViewCell {
#IBOutlet weak var nameButton: UIButton!
#IBOutlet weak var commentLabel: UILabel!
#IBOutlet weak var profilePic: UIImageView!
#IBOutlet weak var dateLabel: UILabel!
#IBOutlet weak var uidLabel: UILabel!
var delegate: commentsTableViewCellDelegate?
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
#IBAction func nameIsTapped(sender: AnyObject) {
//4. call delegate method
//check delegate is not nil
if let _ = delegate {
delegate?.namesIsTapped(self)
} else {
print("Delegate is \(delegate)")
}
}
}
Here is the important part of commentsTableViewController.swift:
class commentsTableViewController: UITableViewController, commentsTableViewCellDelegate, UITextViewDelegate {
#IBOutlet weak var commentLabel: UITextView!
#IBOutlet weak var theLabel: UILabel!
var dataGotten = "Nothing"
var updates = [CommentSweet]()
func namesIsTapped(cell: commentsTableViewCell) {
//Get the indexpath of cell where button was tapped
//let indexPath = self.tableView.indexPathForCell(cell)
let allDataSend = cell.nameButton.titleLabel?.text!
self.performSegueWithIdentifier("toDetailtableViewController", sender: allDataSend)
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "toDetailtableViewController" {
if let nextVC = segue.destinationViewController as? theProfileTableViewController {
nextVC.viaSegue = sender! as! String
}
}
}
override func viewDidLoad() {
Kindly set the delegate to self.
In cellforRowIndexPath
commentsTableViewCell.delegate = self
Where commentsTableViewCell is the custom UITableViewCell object
I suggest to handle the action of the cell's button in the viewController. You can recognize which button has been tapped by setting a tag for it.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell") as! TableViewCell
cell.myButton?.tag = indexPath.row
cell.myButton?.addTarget(self, action: #selector(), for: .touchUpInside)
return cell
}
func namesIsTapped(tappedButton: UIButton) {
// get the user (from users array for example) by using the tag, for example:
let currentUser = users[tappedButton.tag]
// do whatever you want with this user now...
}

Resources