This is an extension of an earlier question:- ios Swift Items do not get added to cart
The original issue is resolved. Now I have a supplementary hitch:-
The issue is in CartViewController - When I click on "Checkout(2)" rightBarButtonItem in the ProductViewController, it shows error in CartviewController's "numberOfRowsInSection" function - Kindly see image & codes below:-
class ProductViewController -
import UIKit
class ProductViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
let sections = ["Section A", "Section B"]
let rowspersection = [3,1]
fileprivate var cart = Cart()
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
//Workaround to avoid the fadout the right bar button item
self.navigationItem.rightBarButtonItem?.isEnabled = false
self.navigationItem.rightBarButtonItem?.isEnabled = true
//Update cart if some items quantity is equal to 0 and reload the product table and right button bar item
cart.updateCart()
self.navigationItem.rightBarButtonItem?.title = "Checkout (\(cart.items.count))"
tableView.reloadData()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "showCart" {
if let cartViewController = segue.destination as? CartViewController {
cartViewController.cart = self.cart
}
}
}
func numberOfSections(in tableView: UITableView) -> Int {
return sections.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return rowspersection[section]
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ProductTableViewCell") as! ProductTableViewCell
cell.delegate = self // original issue was here, now resolved.
var index = indexPath.row
if indexPath.section != 0, rowspersection.count > indexPath.section - 1{
index += rowspersection[indexPath.section - 1]
}
if index < productarray.count{
let data = productarray[index]
cell.name?.text = data.name
cell.imageView?.image = data.imagename
}
let product = productarray[indexPath.item]
cell.setButton(state: self.cart.contains(product: product))
return cell
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 44
}
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
switch(section) {
case 0:return "Section A"
case 1:return "Section B"
default :return ""
}
}
}
extension ProductViewController: CartDelegate {
// MARK: - CartDelegate
func updateCart(cell: ProductTableViewCell) {
guard let indexPath = tableView.indexPath(for: cell) else { return }
let product = productarray[indexPath.item]
//Update Cart with product
cart.updateCart(with: product)
self.navigationItem.rightBarButtonItem?.title = "Checkout (\(cart.items.count))"
}
}
The issue is in CartViewController - When I click on "Checkout(2)" rightBarButtonItem in the ProductViewController(see image above), it shows error in CartviewController's "numberOfRowsInSection" function - see CartViewController code below:-
import UIKit
class CartViewController: UIViewController {
#IBOutlet weak var tableView: UITableView!
#IBOutlet weak var totalView: UIView!
#IBOutlet weak var totalLabel: UILabel!
var cart: Cart? = nil
fileprivate let reuseIdentifier = "CartItemCell"
override func viewDidLoad() {
super.viewDidLoad()
tableView.tableFooterView = UIView(frame: .zero)
}
}
extension CartViewController: UITableViewDelegate, UITableViewDataSource {
// MARK: - Table view data source
func numberOfSections(in tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return (cart?.items.count)! /*Error - Thread 1: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)*/
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: reuseIdentifier, for: indexPath) as! CartItemTableViewCell
if let cartItem = cart?.items[indexPath.item] {
cell.delegate = self as CartItemDelegate
// cell.nameLabel.text = cartItem.product.name
// cell.priceLabel.text = cartItem.product.price
cell.quantityLabel.text = String(describing: cartItem.quantity)
cell.quantity = cartItem.quantity
// cell.contentView.backgroundColor = !cell.decrementButton.isEnabled ? .white : .blue
}
return cell
}
}
extension CartViewController: CartItemDelegate {
// MARK: - CartItemDelegate
func updateCartItem(cell: CartItemTableViewCell, quantity: Int) {
guard let indexPath = tableView.indexPath(for: cell) else { return }
guard let cartItem = cart?.items[indexPath.row] else { return }
//Update cart item quantity
cartItem.quantity = quantity
//Update displayed cart total
// guard let total = cart?.total else { return }
//totalLabel.text = String(total)
// print(total)
}
}
My Models -
struct Product -
import UIKit
struct Product:Equatable {
let name : String
var quantity : Int
var price : Double
let imagename: UIImage
// var subTotal : Double {
//return Double(quantity) * price }
}
var productarray = [Product(name: "a", quantity: 5, price: 5.0,imagename:#imageLiteral(resourceName: "CakeImage")),
Product(name: "b", quantity: 10, price: 10.0, imagename:#imageLiteral(resourceName: "PeasImge")),Product(name: "a", quantity: 5, price: 5.0,imagename:#imageLiteral(resourceName: "vectorlogo")),
Product(name: "b", quantity: 10, price: 10.0, imagename:#imageLiteral(resourceName: "blue")),]
class CartItem -
import Foundation
class CartItem {
var quantity : Int = 1
var product : Product
// var subTotal : Float { get { return Float(product.price) * Float(quantity) } }
init(product: Product) {
self.product = product
}
}
class Cart -
import Foundation
class Cart {
var items : [CartItem] = []
}
extension Cart {
/* var total: Float {
get { return items.reduce(0.0) { value, item in
value + item.subTotal
}
}
}*/
var totalQuantity : Int {
get { return items.reduce(0) { value, item in
value + item.quantity
}
}
}
func updateCart(with product: Product) {
if !self.contains(product: product) {
self.add(product: product)
} else {
self.remove(product: product)
}
}
func updateCart() {
for item in self.items {
if item.quantity == 0 {
updateCart(with: item.product)
}
}
}
func add(product: Product) {
let item = items.filter { $0.product == product }
if item.first != nil {
item.first!.quantity += 1
} else {
items.append(CartItem(product: product))
}
}
func remove(product: Product) {
guard let index = items.firstIndex(where: { $0.product == product }) else { return}
items.remove(at: index)
}
func contains(product: Product) -> Bool {
let item = items.filter { $0.product == product }
return item.first != nil
}
}
--
The UITableViewCells -
class ProductTableViewCell -
import UIKit
protocol CartDelegate {
func updateCart(cell: ProductTableViewCell) }
class ProductTableViewCell: UITableViewCell {
weak var myParent:ProductViewController?
#IBOutlet weak var name: UILabel!
#IBOutlet weak var price: UILabel!
#IBOutlet weak var imagename: UIImageView!
#IBOutlet weak var addToCartButton: UIButton!
var delegate: CartDelegate?
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
addToCartButton.layer.cornerRadius = 5
addToCartButton.clipsToBounds = true
}
func setButton(state: Bool) {
addToCartButton.isSelected = state
addToCartButton.backgroundColor = (!addToCartButton.isSelected) ? .black : .red
}
#IBAction func addToCart(_ sender: Any) {
setButton(state: !addToCartButton.isSelected)
self.delegate?.updateCart(cell: self)
}
}
class CartItemTableViewCell-
import UIKit
protocol CartItemDelegate {
func updateCartItem(cell: CartItemTableViewCell, quantity: Int)
}
class CartItemTableViewCell: UITableViewCell {
#IBOutlet weak var nameLabel: UILabel!
#IBOutlet weak var priceLabel: UILabel!
#IBOutlet weak var incrementButton: UIButton!
#IBOutlet weak var decrementButton: UIButton!
#IBOutlet weak var quantityLabel: UILabel!
var delegate: CartItemDelegate?
var quantity: Int = 1
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
incrementButton.layer.cornerRadius = 10
incrementButton.clipsToBounds = true
decrementButton.layer.cornerRadius = 10
decrementButton.clipsToBounds = true
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
#IBAction func updateCartItemQuantity(_ sender: Any) {
if (sender as! UIButton).tag == 0 {
quantity = quantity + 1
} else if quantity > 0 {
quantity = quantity - 1
}
decrementButton.isEnabled = quantity > 0
decrementButton.backgroundColor = !decrementButton.isEnabled ? .gray : .black
self.quantityLabel.text = String(describing: quantity)
self.delegate?.updateCartItem(cell: self, quantity: quantity)
}
}
The segues are connected properly. So, I suspect rightBarButtonItem i.e. Checkout(2) to another CartViewcontroller.
I have been thinking over it for quite some time.
I would sincerely appreciate your assistance. It would mean a lot to me.
It was a rookie mistake. I had named the segue incorrectly. It is “showCart” in the ProductViewController, while I was naming
it “ShowCart” i.e. upper case letter “S”. It is puny but formidable ! Anyways, thanks for looking into my problem.
Related
I made a table view with a label that increments and decrements on pressing a button and another button to show the text in another label outside the UItableView. Everything works fine but when I scroll the Tableview the value resets to zero!
Before Scrolling
After Scrolling
My ViewController class
class ViewController: UIViewController{
var numArray = [Value]()
var initialValue = 0
#IBOutlet weak var tableView : UITableView!
#IBOutlet weak var lblOutput : UILabel!
override func viewDidLoad() {
super.viewDidLoad()
for _ in 0 ... 100{
numArray.append(Value(number: initialValue))
}
self.lblOutput.text = "\(initialValue)"
tableView.delegate = self
tableView.dataSource = self
tableView.reloadData()
}
}
extension ViewController : UITableViewDelegate,UITableViewDataSource{
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return numArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "Cell",for: indexPath) as? ControllerTableViewCell else{fatalError("Error in creating cells")}
cell.delegate = self
cell.data = numArray[indexPath.row]
cell.lblInput.text = "\(cell.data.number)"
return cell
}
}
extension ViewController : MyTableViewCellDelegate{
func DidPrint(Data: String) {
self.lblOutput.text = "\(Data)"
}
}
My TableViewCell class
protocol MyTableViewCellDelegate : AnyObject {
func DidPrint(Data: String)
}
class ControllerTableViewCell: UITableViewCell {
weak var delegate : MyTableViewCellDelegate?
var data : Value!
private var counterValue = 0
#IBOutlet var lblInput : UILabel!
#IBOutlet var btnPrint : UIButton!
#IBOutlet var btnPlus : UIButton!
#IBOutlet var btnMinus : UIButton!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
#IBAction func DidPressPrint(){
self.data.number = counterValue
delegate?.DidPrint(Data: "\(data.number)")
print(data.number)
}
#IBAction func DidPressPlus(){
counterValue += 1
data.number = counterValue
self.lblInput.text = "\(data.number)"
}
#IBAction func DidPressMinus(){
if(counterValue > 0){
counterValue -= 1
data.number = counterValue
}
else{
counterValue = 0
data.number = 0
}
self.lblInput.text = "\(data.number)"
}
}
My Data Model
import Foundation
struct Value{
var number : Int
}
As #El Tomato suggested, you are not updating your data source, that's why your changes gets "forgotten" on scroll.
Try to move your didPressPlus, didPressMinus and didPressPrint in your ViewController class and redefine your table view delegate like below.
By passing the tag attributes to the buttons, you can then retrieve the index of the item pressed in the functions and edit the correct data source item.
Also remove the unnecessary MyTableViewCellDelegate.
class ViewController: UIViewController{
var numArray = [Value]()
var initialValue = 0
#IBOutlet weak var tableView : UITableView!
#IBOutlet weak var lblOutput : UILabel!
override func viewDidLoad() {
super.viewDidLoad()
for _ in 0 ... 100 {
numArray.append(Value(number: initialValue))
}
self.lblOutput.text = "\(initialValue)"
tableView.delegate = self
tableView.dataSource = self
}
}
extension ViewController : UITableViewDelegate, UITableViewDataSource
{
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return numArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as? ControllerTableViewCell else {fatalError("Error in creating cells")}
let indexItem = indexPath.row
let valueItem = numArray[indexItem]
cell.lblInput.text = valueItem.number
cell.btnMinus.tag = indexItem
cell.btnMinus.addTarget(self, action: #selector(didPressMinus(_:)), for: .touchUpInside)
cell.btnPlus.tag = indexItem
cell.btnPlus.addTarget(self, action: #selector(didPressPlus(_:)), for: .touchUpInside)
cell.btnPrint.tag = indexItem
cell.btnPrint.addTarget(self, action: #selector(didPressPrint(_:)), for: .touchUpInside)
return cell
}
#objc private func didPressPlus(_ sender: UIButton) {
let dataIndex = sender.tag
if numArray.count < dataIndex { return }
let numArrayItem = numArray[dataIndex]
if (numArrayItem.number >= 0) {
numArray[dataIndex].number -= 1
}
tableView.reloadData()
}
#objc private func didPressMinus(_ sender: UIButton) {
let dataIndex = sender.tag
if numArray.count < dataIndex { return }
numArray[dataIndex].number += 1
tableView.reloadData()
}
#objc private func didPressPrint(_ sender: UIButton) {
let dataIndex = sender.tag
if numArray.count < dataIndex { return }
self.lblOutput.text = "\(numArray[dataIndex].number)"
}
}
In order to move the three methods in the ViewController you'll need to remove the two correspondent IBAction from the UITableViewCell class.
Also, remove the linkage with the ControllerTableViewCell actions.
Here is the resulting ControllerTableViewCell:
class ControllerTableViewCell: UITableViewCell {
#IBOutlet var lblInput : UILabel!
#IBOutlet var btnPrint : UIButton!
#IBOutlet var btnPlus : UIButton!
#IBOutlet var btnMinus : UIButton!
override func awakeFromNib() {
super.awakeFromNib()
}
}
Your TableView's numberOfRowsInSection is using numArray as a source (numArray.count) and so is your cellForRowAt function, but your cell functions are updating your 'data' variable. Your 'data' variable is locally defined to your tableView and gets reset every time it is activated (including when you scroll).
You need to update the numArray or some other global resource to make it work. This involves using indexpath of the cell value inside the cell functions, meaning you need a way to refer to indexPath inside the cell. This article explains how to use tags or delegates, https://fluffy.es/handling-button-tap-inside-uitableviewcell-without-using-tag/.
Here's a solution using the existing delegate.
import UIKit
import Foundation
var initialValue = 0
var numArray = Array(repeating: initialValue, count: 100)
class ViewController: UIViewController {
#IBOutlet weak var tableView: UITableView!
#IBOutlet weak var lblOutput: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
self.lblOutput.text = "\(initialValue)"
tableView.delegate = self
tableView.dataSource = self
tableView.reloadData()
// Do any additional setup after loading the view.
}
}
extension ViewController : UITableViewDelegate,UITableViewDataSource{
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return numArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "Cell",for: indexPath) as? ControllerTableViewCell else{fatalError("Error in creating cells")}
cell.indexPath = indexPath
cell.delegate = self
cell.lblInput.text = String(numArray[indexPath.row])
return cell
}
}
extension ViewController : MyTableViewCellDelegate{
func DidPrint(Data: String) {
self.lblOutput.text = "\(Data)"
}
}
protocol MyTableViewCellDelegate : AnyObject {
func DidPrint(Data: String)
}
class ControllerTableViewCell: UITableViewCell {
weak var delegate : MyTableViewCellDelegate?
var indexPath : IndexPath?
private var counterValue = 0
#IBOutlet var lblInput : UILabel!
#IBOutlet var btnPrint : UIButton!
#IBOutlet var btnPlus : UIButton!
#IBOutlet var btnMinus : UIButton!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
#IBAction func DidPressPrint(){
delegate?.DidPrint(Data: "\(numArray[indexPath!.row])")
}
#IBAction func DidPressPlus(){
numArray[indexPath!.row] = numArray[indexPath!.row] + 1
self.lblInput.text = "\(numArray[indexPath!.row])"
}
#IBAction func DidPressMinus(){
if(numArray[indexPath!.row] > 0){
numArray[indexPath!.row] = numArray[indexPath!.row] - 1
}
else{
numArray[indexPath!.row] = 0
}
self.lblInput.text = "\(numArray[indexPath!.row])"
}
}
I am able to query the data and match it to my model but am not able to display it in my table view. I have 3 files I am working with apart from the storyboard.
Here is the main view controller:
class MealplanViewController: UIViewController {
var db: Firestore!
var mealplanArray = [Mealplan]()
#IBOutlet weak var mealplanTableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
mealplanTableView?.dataSource = self
mealplanTableView?.delegate = self
db = Firestore.firestore()
loadData()
// Do any additional setup after loading the view.
}
func loadData() {
userEmail = getUserEmail()
db.collection("Meal_Plans").getDocuments() {querySnapshot , error in
if let error = error {
print("\(error.localizedDescription)")
} else {
self.mealplanArray = querySnapshot!.documents.compactMap({Mealplan(dictionary: $0.data())})
print(self.mealplanArray)
DispatchQueue.main.async {
self.mealplanTableView?.reloadData()
}
}
}
}
func getUserEmail() -> String {
let user = Auth.auth().currentUser
if let user = user {
return user.email!
} else {
return "error"
}
}
}
// MARK: - Table view delegate
extension MealplanViewController: UITableViewDataSource, UITableViewDelegate {
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return mealplanArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
//let cell = tableView.dequeueReusableCell(withIdentifier: "MealplanTableViewCell", for: indexPath)
let mealplanRow = mealplanArray[indexPath.row]
let cell = tableView.dequeueReusableCell(withIdentifier: "MealplanTableViewCell") as! MealplanTableViewCell
cell.setMealplan(mealplan: mealplanRow)
return cell
}
}
And here is the cell where I am showing one of the queried values:
class MealplanTableViewCell: UITableViewCell {
#IBOutlet weak var mealplanNameLabel: UILabel!
func setMealplan(mealplan: Mealplan) {
// Link the elements with the data in here
mealplanNameLabel.text = mealplan.mpName
print(mealplan.mpName)
}
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
}
}
And finally, here is the data model:
import Foundation
import Firebase
protocol MealplanSerializable {
init?(dictionary:[String:Any])
}
struct Mealplan {
var mealplanId:String
var mpName:String
]
}
}
extension Mealplan : MealplanSerializable {
init?(dictionary: [String : Any]) {
guard let
let mealplanId = dictionary["mealplanId"] as? String,
let mpName = dictionary["mpName"] as? String,
else { return nil }
self.init(mealplanId: mealplanId, mpName: mpName)
}
}
I am getting just an empty table view with no data in it.
I have 2 ViewControllers, one of is called ProductListVC the other is MoreInfoVC. I have a tableView on ProductListViewController that shows cells multiple labels and buttons.
MoreInfoVC is a Modal pop-up VC with a few labels for the brand, Name, and description. I have all my data stored in Firestore and already have created class(ProductList) to help retrieve the data which presents the data in the tableview from the Cloud Firestore.
what I need to do is use the MoreInfo button in the individual TBV cell to pass the data into MoreInfoVC so that it can present the information of selected product
Now i can easily do this with either didSelectRowAt method or using indexPathForSelectedRow in prepare segue method. But both cases requires me to tap on the cell itself but not the button.
how would I be able to pass data from an individual tableview cell through the MoreInfo button onto the MoreInfoVC. I think I'm on the right path since it seems my MoreInfoVC is passing data but showing this at the moment
import UIKit
import Firebase
import FirebaseFirestore
class ProductListVC: UIViewController {
#IBOutlet weak var productListTableView: UITableView!
var productInventory: [ProductList] = []
var productSetup: [ProductList] = []
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
}
override func viewDidLoad() {
super.viewDidLoad()
productListTableView.dataSource = self
productListTableView.delegate = self
searchBar.delegate = self
fetchProducts { (products) in
self.productSetup = products
self.productListTableView.reloadData()
}
}
func fetchProducts(_ completion: #escaping ([ProductList]) -> Void) {
let ref = Firestore.firestore().collection("products")
ref.addSnapshotListener { (snapshot, error) in
guard error == nil, let snapshot = snapshot, !snapshot.isEmpty else {
return
}
completion(snapshot.documents.compactMap( {ProductList(dictionary: $0.data())} ))
}
}
}
extension ProductListVC: UITableViewDelegate, UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return productSetup.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "ProductListCell") as?
ProductListCell else { return UITableViewCell() }
cell.configure(withProduct: productSetup[indexPath.row])
cell.delegate = self
return cell
}
}
extension ProductListVC: ProductListCellDelegate {
func onTouchInfoButton(from cell: ProductListCell) {
self.selectedProduct = cell.product
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
self.performSegue(withIdentifier: "MoreInfo", sender: self)
}
}
import UIKit
import Firebase
class MoreInfoVC: UIViewController {
var products: ProductList?
#IBOutlet weak var productName: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
productName.text = "\(String(describing: products?.brand)): \(String(describing: products?.name))"
}
#IBAction func closeBtn(_ sender: Any) {
dismiss(animated: true, completion: nil)
print("Close More Information")
}
}
import UIKit
import SDWebImage
import Firebase
protocol ProductListCellDelegate: class {
func onTouchInfoButton(from cell: ProductListCell)
}
class ProductListCell: UITableViewCell {
weak var product: ProductList!
weak var delegate: ProductListCellDelegate?
#IBOutlet weak var productImage: UIImageView!
#IBOutlet weak var productName: UILabel!
#IBOutlet weak var categoryLabel: UILabel!
#IBOutlet weak var strain: UILabel!
#IBOutlet weak var moreInfo: RoundButton!
func configure(withProduct product: ProductList) {
productName.text = "\(String(describing: product.brand)): \(String(describing: product.name))"
categoryLabel.text = product.category
productImage.sd_setImage(with: URL(string: product.imageUrl))
strain.text = product.strain
self.product = product
}
#IBAction func infoButtonAction(_ sender: Any) {
self.delegate?.onTouchInfoButton(from: self)
}
}
Function #IBAction func infoButtonAction(_ sender: Any) {} should be in the ProductListCell
When that button is tapped, connect with the ProductListVC by delegate or closure to get the selected product.
Update
Using delegate:
Update your ProductListCell
import UIKit
import SDWebImage
import Firebase
protocol ProductListCellDelegate: class {
func onTouchInfoButton(from cell: ProductListCell)
}
class ProductListCell: UITableViewCell {
#IBOutlet weak var productImage: UIImageView!
#IBOutlet weak var dispensaryName: UILabel!
#IBOutlet weak var productName: UILabel!
#IBOutlet weak var thcPercent: UILabel!
#IBOutlet weak var cbdPercent: UILabel!
#IBOutlet weak var categoryLabel: UILabel!
#IBOutlet weak var categoryStrain: UILabel!
#IBOutlet weak var moreInfo: RoundButton!
weak var product: Product!
weak var delegate: ProductListCellDelegate?
func configure(withProduct product: ProductList) {
self.product = product
productName.text = "\(String(describing: product.brand)): \(String(describing: product.name))"
dispensaryName.text = product.dispensaryName
categoryLabel.text = product.category
productImage.sd_setImage(with: URL(string: product.imageUrl))
cbdPercent.text = product.cbd
thcPercent.text = product.thc
categoryStrain.text = product.strain
}
#IBAction func infoButtonAction(_ sender: Any) {
self.delegate?.onTouchInfoButton(from: self)
}
}
In your ProductListVC:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "ProductListCell") as?
ProductListCell else { return UITableViewCell() }
cell.configure(withProduct: productSetup[indexPath.row])
cell.delegate = self
return cell
}
extension ProductListVC: ProductListCellDelegate {
func onTouchInfoButton(from cell: ProductListCell) {
let selectedProduct = cell.product
// Do your stuff here
}
}
UPDATE
Because you use segue for navigation so let's create a variable to store your selected product in your ProductListVC
import UIKit
import Firebase
import FirebaseFirestore
class ProductListVC: UIViewController {
#IBOutlet weak var productListTableView: UITableView!
var productInventory: [ProductList] = []
var productSetup: [ProductList] = []
var selectedProduct: Product?
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
}
override func viewDidLoad() {
super.viewDidLoad()
productListTableView.dataSource = self
productListTableView.delegate = self
searchBar.delegate = self
fetchProducts { (products) in
self.productSetup = products
self.productListTableView.reloadData()
}
}
func fetchProducts(_ completion: #escaping ([ProductList]) -> Void) {
let ref = Firestore.firestore().collection("products")
ref.addSnapshotListener { (snapshot, error) in
guard error == nil, let snapshot = snapshot, !snapshot.isEmpty else {
return
}
completion(snapshot.documents.compactMap( {ProductList(dictionary: $0.data())} ))
}
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let vc = segue.destination as? MoreInforVC {
vc.product = self.selectedProduct
}
}
}
extension ProductListVC: UITableViewDelegate, UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return productSetup.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "ProductListCell") as?
ProductListCell else { return UITableViewCell() }
cell.configure(withProduct: productSetup[indexPath.row])
cell.delegate = self
return cell
}
}
extension ProductListController: ProductListCellDelegate {
func onTouchInfoButton(from cell: ProductListCell) {
self.selectedProduct = cell.product
self.performSegue(withIdentifier: "YourSegueIdentifier", sender: self)
}
}
Below is the CatalogViewController, which holds a tableview. The tableview has 1 prototype cell, ShopCell. When I print the items in the loop, they print correct, but when shown in the table, items are missing.
(Removing the shuffle() method does nothing & removing removeDuplicates(), items appear more than once). I didn't include the addToFavorites(cell: ShopCell) because I'm testing it. It does nothing.
protocol ShopCellDelegate {
func addToFavorites(cell: ShopCell)
}
class ShopCell: UITableViewCell {
#IBOutlet weak var productImageView: UIImageView!
#IBOutlet weak var titleLabel: UILabel!
#IBOutlet weak var priceLabel: UILabel!
#IBOutlet weak var descTV: UITextView!
#IBOutlet weak var favoriteButton: UIButton!
var delegate: ShopCellDelegate?
override func prepareForReuse() {
super.prepareForReuse()
self.productImageView.image = nil
self.titleLabel.text = ""
self.priceLabel.text = ""
self.descTV.text = ""
self.favoriteButton.isHidden = true
}
func setProduct(product: Product) {
productImageView.sd_setImage(with: URL(string: product.urlToImage!), placeholderImage: UIImage(named: "1024ELP.png"))
titleLabel.text = product.itemName!
priceLabel.text = product.priceTag!
descTV.text = product.itemDesc!
}
#IBAction func favOrUnfav(_ sender: UIButton) {
if let delegate = self.delegate {
delegate.addToFavorites(cell: self)
}
}
}
//
class CatelogViewController: UIViewController, GADInterstitialDelegate, SFSafariViewControllerDelegate, UITableViewDelegate, UITableViewDataSource, ShopCellDelegate {
#IBOutlet weak var tableView: UITableView!
static var shopType = String()
static var linkToVisit = String()
var myProducts = [Product]()
var productKeys = [String]()
var interstitial: GADInterstitial!
override func viewWillAppear(_ animated: Bool) {
visuals() // Sets Nav Bar color & changes cell size if device == ipad
}
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.delegate = self
self.tableView.dataSource = self
self.navigationController?.navigationBar.tintColor = UIColor.black
if CatelogViewController.shopType == "Apparel" {
self.title = NSLocalizedString("Shop Apparel", comment: "")
fetchProductLinks(child1: "ProductList", child2: "Products")
}else{
self.title = NSLocalizedString("Shop Others", comment: "")
fetchProductLinks(child1: "OtherList", child2: "OtherProducts")
//shuffleItems()
}
if let index = self.tableView.indexPathForSelectedRow{
self.tableView.deselectRow(at: index, animated: true)
}
}
// MARK: - Table view data source
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return myProducts.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! ShopCell
let product = myProducts[indexPath.row]
cell.delegate = self
cell.favoriteButton.isHidden = true
cell.setProduct(product: product)
return cell
}
func fetchProductLinks(child1: String, child2: String) {
let ref = Database.database().reference()
let prodRef = ref.child(child1).child(child2)
prodRef.observeSingleEvent(of: .value, with: { snapshot in
self.myProducts.removeAll()
for items in snapshot.children {
let item = items as! DataSnapshot
let product = item.value as! [String : String]
let name = product["Name"]
let link = product["Link"]
let img = product["urlToImage"]
let desc = product["Description"]
let price = product["Price"]
let newProduct = Product(urlToImage: img, itemName: name, itemLink: link, itemDesc: desc, priceTag: price)
self.myProducts.append(newProduct)
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
self.myProducts = self.shuffleArray(array: self.myProducts) as! [Product]
self.myProducts = self.myProducts.removeDuplicates()
})
ref.removeAllObservers()
}
extension Array where Element:Equatable {
func removeDuplicates() -> [Element] {
var result = [Element]()
for value in self {
if result.contains(value) == false {
result.append(value)
}
}
return result
}
}
You shuffle your array and you remove duplicates, but you don't reload data after it. So reload data of table view
self.myProducts = self.shuffleArray(array: self.myProducts) as! [Product]
self.myProducts = self.myProducts.removeDuplicates()
self.tableView.reloadData()
private let DBItemCellIdentifier = "ItemCellIdentifier"
private let DBItemSegueIdentifier = "ItemSegueIdentifier"
class DBItemsViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, DBItemTableViewCellDelegate {
#IBOutlet weak var tableView: UITableView!
#IBOutlet weak var previousButton: UIButton!
#IBOutlet weak var nextButton: UIButton!
#IBOutlet weak var categoryNameLabel: UILabel!
private var elements = [Any]()
private var currentItemIndex = 0
private var isFetching = false
private weak var currentCategory: DBCategory? {
didSet {
updateView()
}
}
var categories = [DBCategory]()
var currentCategoryIndex = 0
//MARK: - Class Methods
//MARK: - Initialization
override func viewDidLoad() {
super.viewDidLoad()
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 100.0
tableView.tableFooterView = UIView(frame: CGRectZero)
setupUserAndCartButtons()
fetchItems()
}
deinit {
print("deinit")
}
//MARK: - Actions
#IBAction func nextButtonTapped(sender: UIButton) {
currentCategoryIndex = min(currentCategoryIndex + 1, categories.count - 1)
fetchItems()
}
#IBAction func previousButtonTapped(sender: UIButton) {
currentCategoryIndex = max(currentCategoryIndex - 1, 0)
fetchItems()
}
//MARK: - Private
private func fetchItems() {
tableView.alpha = 0
currentCategory = nil
if !categories.isEmpty && !isFetching {
let category = categories[currentCategoryIndex]
currentCategory = DBCategory.findCategoryWithIdentifier(category.identifier)
if currentCategory == nil {
SVProgressHUD.show()
}
isFetching = true
DBNetworkClient.sharedClient().itemsForCategory(category, completionBlock: { error in
defer {
self.isFetching = false
SVProgressHUD.dismiss()
UIAlertController.showAlertFromError(error)
}
self.currentCategory = DBCategory.findCategoryWithIdentifier(category.identifier)
})
}
}
private func updateView() {
let category = categories[currentCategoryIndex]
title = category.menu.location.name
categoryNameLabel.text = category.name
previousButton.hidden = currentCategoryIndex == 0 ? true : false
nextButton.hidden = currentCategoryIndex == categories.count - 1 ? true : false
prepareElements()
tableView.reloadData()
UIView.animateWithDuration(0.5, animations: {
self.tableView.alpha = 1
})
}
private func prepareElements() {
elements.removeAll(keepCapacity: false)
if let items = currentCategory?.items {
for item in items {
elements.append(item)
}
}
if let sets = currentCategory?.sets {
for set in sets {
elements.append(set)
}
}
elements.sortInPlace {
let left = ($0 as? DBSet)?.position ?? ($0 as? DBItem)?.position
let right = ($1 as? DBSet)?.position ?? ($1 as? DBItem)?.position
return left < right
}
}
//MARK: - Overridden
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
let element = elements[currentItemIndex]
if segue.identifier == DBItemSegueIdentifier {
let itemViewController = segue.destinationViewController as! DBItemViewController
itemViewController.prepareWithElement(element)
}
}
//MARK: - UITableViewDataSource
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 0 //when I change to elements.count, deinit is not called
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier(DBItemCellIdentifier, forIndexPath: indexPath) as! DBItemTableViewCell
let element = elements[indexPath.row]
if let item = element as? DBItem {
cell.configureCellWithItem(item)
} else if let set = element as? DBSet {
cell.configureCellWithSet(set)
}
cell.delegate = self
return cell
}
//MARK: - UITableViewDelegate
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
currentItemIndex = indexPath.row
performSegueWithIdentifier(DBItemSegueIdentifier, sender: tableView.cellForRowAtIndexPath(indexPath))
}
//MARK: - DBItemTableViewCellDelegate
func itemTableViewCell(cell: DBItemTableViewCell, willPresentSetGroupsViewControllerForSet set: DBSet) {
presentSetOrderControllerWithOrder(DBSetOrder(set: set))
}
func itemTableViewCell(cell: DBItemTableViewCell, willPresentItemMealSizesViewControllerForItem item: DBItem) {
presentItemOrderControllerWithOrder(DBItemOrder(item: item))
}
}
Why my deinit is not called. I will offer 100 bounty once I will be able to do this, and award to that one, who help me solve this problem... I will offer a bounty even after solving the problem.
VERY IMPORTANT INFO:
this code calls deinit. IT IS WORKING. Because number of rows is 0. But I need to have there elements.count. When I change to this, deinit is not called.
EDIT:
func itemsForCategory(category: DBCategory, completionBlock: DBErrorHandler) {
let query = "locations/" + category.menu.location.identifier + "/categories/" + category.identifier
GET(query, parameters: nil, success: { operation, response in
if let error = NSError(response: response) {
completionBlock(error)
} else {
self.coreDataAssistant.parseAndSaveItemsToPersistentStore(response as? NSDictionary, completionBlock: { error in
completionBlock(error)
})
}
}) { operation, error in
let responseError = NSError(response: operation.responseObject)
completionBlock(responseError ?? error)
}
}
You are assigning self as your table view cell's delegate:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier(DBItemCellIdentifier, forIndexPath: indexPath) as! DBItemTableViewCell
let element = elements[indexPath.row]
if let item = element as? DBItem {
cell.configureCellWithItem(item)
} else if let set = element as? DBSet {
cell.configureCellWithSet(set)
}
// HERE
cell.delegate = self
return cell
}
The cell's delegate property is defined as follows:
var delegate: DBItemTableViewCellDelegate?
This creates a strong reference between the cell and the delegate (your view controller). The cell is also retained by the table view. This creates a retain cycle.
You will need to change the definition of the delegate property to be weak:
weak var delegate: DBItemTableViewCellDelegate?
Edit based on comment:
Your DBItemTableViewCellDelegate definition will need to be defined as a class-only protocol
protocol DBItemTableViewCellDelegate: class {
...
}