Label Data lost when scrolling in UITableView - swift

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])"
}
}

Related

How to append and remove selected row cells in tableview cell with a custom button?

protocol MealCellDelegate: AnyObject {
func didSelect(cell: MealCell)
}
class MealCell: UITableViewCell {
var delegate: MealCellDelegate?
#IBOutlet weak var checkBtn : UIButton!
}
I have a UITableViewCell for a select func
And I have a UIViewController like below.
class MealSearchViewController: UIViewController {
#IBOutlet weak var tableview : UITableView!
tableview.delegate = self
}
extension MealSearchViewController: MealCellDelegate {
func didSelect(cell: MealCell) {
if let indexPath = tableview.indexPath(for: cell) {
let selectedItem = meals[indexPath.row]
selectedMeals.append(selectedItem)
}
}
}
But This code dose not work.
I want to append to an empty array when I clicked a checking button (It's status is selected)
and I remove from the array when I deselect that button.
Help me plz
Cell code:
protocol MealCellDelegate: AnyObject {
func didSelect(cell: MealCell)
}
class MealCell: UITableViewCell {
var delegate: MealCellDelegate?
#IBOutlet weak var checkBtn: UIButton!
#IBAction func buttonClicked() {
delegate?.didSelect(cell: self)
}
}
UIViewController code:
class MealSearchViewController: UIViewController {
#IBOutlet weak var tableView : UITableView!
override func viewDidLoad() {
tableView.dataSource = self
}
}
extension MealSearchViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
cell = *create meal cell*
cell.delegate = self
return cell
}
}
extension MealSearchViewController: MealCellDelegate {
func didSelect(cell: MealCell) {
*it calls after you press button in cell*
}
}

swift data not transferred from UIBarButtonItem to another viewcontroller

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.

Updating Realm through a custom button on the UITableViewCell

I have a tableview of "Books" that are stored in Realm. I want to set the "CurrentBook" property to "True" when hitting a button on a custom UITableViewCell.
I believe my error has something to do with getting the correct book value in "func selectCurrentBook", when I use an optional like below nothing happens.
#objc func selectCurrentBook(sender: UIButton) {
try! realm.write {
book?.currentlyReading = true
}
}
When I don't use an optional for book and use book.currentlyReading = true I get the error "Unexpectedly found nil while implicitly unwrapping an Optional value:"
Am I incorrectly passing the book value somewhere? I can't seem to find out how. Maybe I'm delegating wrong?
My TableViewCell is:
import UIKit
import RealmSwift
protocol MyBooksDelegate {
func currentlyReadingButton()
}
class MyBooksTableViewCell: UITableViewCell {
let realm = try! Realm()
#IBOutlet weak var titleLabel: UILabel!
#IBOutlet weak var authorLabel: UILabel!
#IBOutlet weak var smallThumbnailImageView: UIImageView!
#IBOutlet weak var currentlyReadingButton: UIButton!
#IBAction func currentlyReadingButton(_ sender: Any) {
}
private var book: Book!
func loadImage(smallThumbnailURL: String) {
let imageURL = URL(string: smallThumbnailURL ?? "")
smallThumbnailImageView.sd_setImage(with: imageURL)
}
func configureCell(book: Book, delegate: MyBooksDelegate?) {
titleLabel.text = book.bookTitle
authorLabel.text = book.bookAuthor
loadImage(smallThumbnailURL: book.bookSmallThumbnailImageURL)
currentlyReadingButton.addTarget(self, action: #selector(selectCurrentBook(sender:)), for: .touchUpInside)
}
#objc func selectCurrentBook(sender: UIButton) {
try! realm.write {
book?.currentlyReading = true
}
}
}
My View Controller with TableView is :
import SwiftyJSON
import RealmSwift
class BooksViewController: UIViewController, UITextFieldDelegate, UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var myBooksTableView: UITableView!
let realm = try! Realm()
var books: Results<Book>?
// Search Bar Properties
var searchParameter = "intitle"
var booksArray: [Book] = []
override func viewDidLoad() {
super.viewDidLoad()
loadBooks()
// Setting up the TableView
self.myBooksTableView.delegate = self
self.myBooksTableView.dataSource = self
self.myBooksTableView.rowHeight = 120.0
// Setup Title
title = "My Books"
// navigationController?.navigationBar.prefersLargeTitles = true
}
override func viewWillAppear(_ animated: Bool) {
navigationController?.navigationBar.barStyle = .black
loadBooks()
}
func loadBooks() {
books = realm.objects(Book.self).sorted(byKeyPath: "DateCreated", ascending: false)
myBooksTableView.reloadData()
}
// TABLEVIEW
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return books?.count ?? 1
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if let cell = tableView.dequeueReusableCell(withIdentifier: "MyBooksTableViewCell", for: indexPath) as? MyBooksTableViewCell {
cell.configureCell(book: (books?[indexPath.row])!, delegate: self as? MyBooksDelegate)
// cell.selectionStyle = UITableViewCell.SelectionStyle.none
return cell
} else {
return UITableViewCell()
}
}
func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
return true
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
self.performSegue(withIdentifier: "myBooksTOMyBooksDetail", sender: self)
myBooksTableView.deselectRow(at: indexPath, animated: true)
}
And my Book Model is:
class Book: Object {
#objc dynamic var bookTitle: String!
#objc dynamic var bookAuthor: String!
#objc dynamic var bookSmallThumbnailImageURL: String!
#objc dynamic var bookThumbnailImageURL: String!
#objc dynamic var bookDescription: String!
#objc dynamic var bookISBN_13: String!
#objc dynamic var currentlyReading = false
#objc dynamic var DateCreated = Date()
#objc dynamic var WordID = UUID().uuidString
// words
let words = List<Word>()
override static func primaryKey() -> String? {
return "WordID"
}
}
The most compatible syntax is
currentlyReadingButton.addTarget(self, action: #selector(selectCurrentBook), for: .touchUpInside)
and
#objc func selectCurrentBook(_ sender: UIButton) {
However as the cell is custom anyway I'd prefer an IBAction over target/action
And the protocol MyBooksDelegate seems to be unused.
Side note:
Force unwrap the cell
let cell = tableView.dequeueReusableCell(withIdentifier: "MyBooksTableViewCell", for: indexPath) as! MyBooksTableViewCell
A crash – with report – reveals a design mistake which can be fixed instantly. With the if let you'll see nothing and have no clue why.
Update:
The crash occurs because you don't set book in the cell, add the first line after the {
func configureCell(book: Book, delegate: MyBooksDelegate?) {
self.book = book
titleLabel.text = book.bookTitle
...

How pass data from button in TableViewCell to View Controller?

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)
}
}

TableView within TableviewCell not displaying cells

I have a UITablaView inside UITableviewCell. Here is my code:
UITableViewCell:
class ProductViewCell: UITableViewCell {
#IBOutlet weak var totalOrderButton: UIButton!
#IBOutlet weak var productImage: UIImageView!
#IBOutlet weak var productNameLabel: UILabel!
#IBOutlet weak var productSubNameLabel: UILabel!
#IBOutlet weak var productBioLabel: UILabel!
#IBOutlet weak var mlButton: UIButton!
#IBOutlet weak var productQuantity: UILabel!
#IBOutlet weak var plusButton: UIButton!
#IBOutlet weak var minusButton: UIButton!
#IBOutlet weak var greenHeartButton: UIButton!
#IBOutlet weak var liquorsTableView: UITableView!
var fourLiquorStores: [LiquorStore] = []
var currentUser: User!
var currentProduct: Product! {
didSet {
updateUI()
}
}
func updateUI() {
downloadProductImage()
productNameLabel.text = currentProduct.name
productSubNameLabel.text = currentProduct.subName
productBioLabel.text = currentProduct.description
productQuantity.text = "\(currentProduct.quantity)"
updateGreenHeart()
}
var cache = SAMCache.shared()
weak var delegate: ProductViewCellDelegate!
override func awakeFromNib() {
super.awakeFromNib()
liquorsTableView.estimatedRowHeight = 65
liquorsTableView.rowHeight = 65
liquorsTableView.delegate = self
liquorsTableView.dataSource = self
fetchLiquorStores()
}
func fetchLiquorStores() {
LiquorStore.observeNewLiquorStore { (liquorStore) in
LiquorStore.observeNewLiquorStore { (liquorStore) in
if !self.fourLiquorStores.contains(liquorStore) {
self.fourLiquorStores.append(liquorStore)
self.delegate.updateTableView()
print(self.fourLiquorStores)
}
}
}
}
func downloadProductImage() {
let productuid = currentProduct.uid
let profileImageKey = "\(productuid)"
if let image = cache?.object(forKey: profileImageKey) as? UIImage {
productImage.image = image
} else {
currentProduct.downloadPopularProductImage { [weak self] (image, error) in
if let error = error {
print(error.localizedDescription)
} else {
self?.productImage.image = image
self?.cache?.setObject(image, forKey: profileImageKey)
}
}
}
}
// Ad o delete Quantities
#IBAction func plusDidTap() {
if currentProduct.quantity >= 1 && currentProduct.quantity <= 60 {
currentProduct.quantity += 1
delegate.updateTableView()
}
}
#IBAction func minusDidTap() {
if currentProduct.quantity > 1 {
currentProduct.quantity -= 1
delegate.updateTableView()
}
}
func updateGreenHeart() {
if let currentUser = currentUser {
if currentUser.favoriteProducts.contains(currentProduct) {
greenHeartButton.setImage(#imageLiteral(resourceName: "GreenHeartFilled"), for: [])
} else {
greenHeartButton.setImage(#imageLiteral(resourceName: "GreenHeart"), for: [])
}
}
}
// Ad or delete Favorite Products
#IBAction func greenHeartDidTap() {
if currentUser.favoriteProducts.contains(currentProduct) {
self.greenHeartButton.setImage(#imageLiteral(resourceName: "GreenHeart"), for: [])
self.currentUser.deleteFavoriteProduct(product: currentProduct)
} else {
self.greenHeartButton.setImage(#imageLiteral(resourceName: "GreenHeartFilled"), for: [])
self.currentUser.addFavoriteProduct(product: currentProduct)
}
}
}
extension ProductViewCell: UITableViewDataSource, UITableViewDelegate {
func numberOfSections(in tableView: UITableView) -> Int {
return fourLiquorStores.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = liquorsTableView.dequeueReusableCell(withIdentifier: "LiquorStoreViewCell", for: indexPath) as! LiquorStoresViewCell
cell.currentProduct = self.currentProduct
cell.liquorStore = self.fourLiquorStores[indexPath.section]
return cell
}
}
UITablViewCell -2 :
class LiquorStoresViewCell: UITableViewCell {
#IBOutlet weak var liquorStoreNameLabel: UILabel!
#IBOutlet weak var liquorStorePriceLabel: UILabel!
#IBOutlet weak var liquorStoreTimeLabel: UILabel!
#IBOutlet weak var starStackView: UIStackView!
#IBOutlet weak var imageViewBoard: UIImageView!
var currentProduct: Product!
var currentPriceIndex: Int = 0
var liquorStore: LiquorStore! {
didSet {
updateUI()
}
}
func updateUI() {
changeLiquorStoreProductsUid()
liquorStoreNameLabel.text = liquorStore.userName
let index = liquorStore.products.index(of: currentProduct)
if let index = index {
let productPrice = liquorStore.products[index].price[currentPriceIndex]
format(price: productPrice)
}
}
// Format Product Price to Currency
func format(price: Double) {
let formatter = NumberFormatter()
formatter.locale = Locale.current // Change this to another locale if you want to force a specific locale, otherwise this is redundant as the current locale is the default already
formatter.numberStyle = .currency
if let formattedTipAmount = formatter.string(from: price as NSNumber) {
liquorStorePriceLabel.text = "Tip Amount: \(formattedTipAmount)"
}
}
// Func to organize LiquorStores
func changeLiquorStoreProductsUid () {
for product in liquorStore.products {
if self.currentProduct.name == product.name && self.currentProduct.description == product.description && self.currentProduct.subName == product.subName {
let index = liquorStore.products.index(of: product)
if let index = index {
liquorStore.products[index].uid = currentProduct.uid
}
}
}
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
liquorStorePriceLabel.textColor = UIColor.white
liquorStoreNameLabel.textColor = UIColor.white
liquorStoreTimeLabel.textColor = UIColor.white
imageViewBoard.backgroundColor = #colorLiteral(red: 0.5239839702, green: 0.5239839702, blue: 0.5239839702, alpha: 1)
for view in starStackView.subviews {
let image = view as! UIImageView
image.image = #imageLiteral(resourceName: "WhiteStar")
}
}
}
The UITableView inside the UITableviewCell doesn't show the data :/
What's the problem?
This is what I'm building:
StoryBoard
An this appears in the simulator, as you can see, the second tableview is empty:
Simulator
PD: I'm downloading successfully the liquorStores from firebase.
While it's possible to do what you are trying to do, it would be much simpler to rethink your architecture entirely. If your view controller displays only a single product, plus a list of liquor stores, then the product view shouldn't really be in a cell at all--just make it an ordinary view, and make that view your table's header. The cells will then be the liquor stores, and you no longer have to deal with nesting a table within a cell. Alternately, you can keep the product view in a cell, but just add prototypes for the liquor stores as a second kind of cell in the same table.
EDIT: Now that you've posted your storyboard screenshot, the easiest thing for you to do is to move your liquor store prototype cell up to the top-level table, remove the subtable from the top-level cell, and in your data source, have
func tableView(_ tableView: UITableView, cellForRowAtIndexPath path: NSIndexPath) -> UITableViewCell {
if path.row == 0 {
return tableView.dequeueCellWithIdentifier("productDetailsCell")!
} else {
return tableView.dequeueCellWithIdentifier("liquorStoreCell")!
}
}