My Cart looks like this
What should be the best approach to do calculation of all cells and sums up total Amount label
Cart Working like this :
Increment in cell's item doubles the value of price label but when i dequeue new cell it already has that increment value
When tried to work with custom delegate , Delegate always shows nil
What should I do ? why my delegate is always nil ?
TableViewCell
class ShoppingCartCell: UITableViewCell {
#IBOutlet weak var cellView:UIView!
#IBOutlet weak var productImageView:UIImageView!
#IBOutlet weak var productName:UILabel!
#IBOutlet weak var brandName:UILabel!
#IBOutlet weak var productPrice:UILabel!
#IBOutlet weak var modifier1Lbl:UILabel!
#IBOutlet weak var modifier2Lbl:UILabel!
#IBOutlet var counterBtns:[UIButton]!
#IBOutlet weak var counterLbl:UILabel!
var delegate : cellDelegateFunc?
override func layoutMarginsDidChange() {
super.layoutMarginsDidChange()
contentView.frame = contentView.frame.inset(by: UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10))
productImageView.layer.cornerRadius = productImageView.frame.height / 4
cellView.roundUIViewWithShadow(cornerRadius: 4, shadowColor: .darkGray)
cellView.layer.masksToBounds = false
cellView.layer.shadowColor = UIColor.lightGray.cgColor
cellView.layer.shadowOpacity = 1
cellView.layer.shadowOffset = .zero
}
override func awakeFromNib() {
super.awakeFromNib()
cellView.layer.cornerRadius = cellView.frame.height / 16
productImageView.layer.cornerRadius = productImageView.frame.height / 16
}
#IBAction func counter(_ sender:UIButton){
self.delegate?.countItems(self)
}
}
CartViewController (Particular Portion)
class ShoppingBagVC: UIViewController , cellDelegateFunc {
func countItems(_ cell: ShoppingCartCell) {
print("print")
}
}
Protocol
protocol cellDelegateFunc : class {
func countItems(_ cell:ShoppingCartCell)
}
CellForRow
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if cartAllData[indexPath.row].deal.data != nil {
let cell = cartTableView.dequeueReusableCell(withIdentifier: "cell3", for: indexPath) as! ShoppingCartDealCell
cell.originalPrice = Int(cartAllData[indexPath.row].deal.data!.dealPrice)
cell.productName.text = cartAllData[indexPath.row].deal.data?.dealName
cell.productPrice.text = "Rs.\(String(cell.originalPrice))"
cell.freeItem.text = cartAllData[indexPath.row].deal.data?.freeProduct
cell.productImageView?.sd_setImage(with: URL(string: cartAllData[indexPath.row].deal.data!.imageURL), completed: nil)
return cell
} else {
let cell = cartTableView.dequeueReusableCell(withIdentifier: "cell2", for: indexPath) as! ShoppingCartCell
var originalPrice = Int()
var price : Int = 2{
didSet {
cell.productPrice.text = "Rs.\(String(price))"
}
}
var count : Int = 1{
didSet {
cell.counterLbl.text = String(count)
price = originalPrice * count
}
}
if let value = cartAllData[indexPath.row].deal.data?.quantity {
cell.counterLbl.text = String(value)
}
if let value = cartAllData[indexPath.row].product.data?.quantity {
cell.counterLbl.text = String(value)
}
originalPrice = Int(cartAllData[indexPath.row].product.data!.productBasePrice)
cell.productPrice.text = "Rs.\(String(originalPrice))"
cell.productName.text = cartAllData[indexPath.row].product.data?.productName
cell.productImageView?.sd_setImage(with: URL(string: cartAllData[indexPath.row].product.data!.imageURL), completed: nil)
cell.modifier1Lbl.text = cartAllData[indexPath.row].product.data?.modifier1
cell.modifier2Lbl.text = cartAllData[indexPath.row].product.data?.modifier2
return cell
}
}
As #joakim said in comment you are doing calculations in a UI! and it's not a correct way
When a UITableView scrolls every cell will reload because of reusing and every cell will lose its state because it loads again. so you must store state of each cell in a Model and pass it to your cell each time a cell loads.
As you requested The Best approach would be to use a ViewModel or a Presenter to store state of a View (here your cell) and in every load you feed that View (for example in your cellForRow) with the stored States or Properties
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 cannot figure this out since I do not know enough about table cells. I am building an invoice app for myself. In my tableview custom cell I made a label on the right side that is for amount due. when you fill out the invoice it prints out the amount in that label.
I have an empty label at the very top call totalDue that I want to have the sum of every amount in the table. I am struggling with this.
What I have is
import UIKit
var clientName = [String]()
var dueDate = [String]()
var projecDescript = [String]()
var dateStamp = Date()
var invoiceNum = [String]()
var amountDue = [String]()
var clientPicker = [""]
// Custom cell to make all input fields custom
class CustomCell: UITableViewCell {
//Make your outlets here, connect the outlets from cell in your storyboard
#IBOutlet var clientNameLabel: UILabel!
#IBOutlet var descriptionLabel: UILabel!
#IBOutlet var dateLabel: UILabel!
#IBOutlet var amountLabel: UILabel!
#IBOutlet var invoiceNum: UILabel!
#IBOutlet var dateStamp: UILabel!
}
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet var clientTableList: UITableView!
#IBOutlet var totalDue: UILabel!
#IBOutlet var totalBillsLabel: UILabel!
func calculateSum() {
var sum = 0
for amount in amountDue {
sum += amount
}
totalDue.text = "\(sum)"
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return (clientName.count)
return (dueDate.count)
return (projecDescript.count)
return (invoiceNum.count)
return (amountDue.count)
}
// This is the new items added into the inputs
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
let cell = tableView.dequeueReusableCell(withIdentifier: "clientCell", for: indexPath) as! CustomCell
// Adds Clients Name
let companyName = clientName[indexPath.row]
cell.clientNameLabel?.text = companyName
// Adds Clients Description
let descriptionName = projecDescript[indexPath.row]
cell.descriptionLabel?.text = descriptionName
// Adds the amount due
let amountName = amountDue[indexPath.row]
cell.amountLabel?.text = "$\(amountName)"
//Adds the total number of bills that you have in invoice
totalBillsLabel.text = "\(indexPath.row + 1)"
//Adding sum of all bills
sum += Int((amountName as NSString).floatValue)
//sum = Int((amountName as NSString).floatValue)
totalDue.text = "\(sum)"
//Adds DueDate
let invoiceDate = "Due \(dueDate[indexPath.row])"
cell.dateLabel?.text = invoiceDate
//Adds invoice Number
let invoiceNum = "Invoice #BMCS \(indexPath.row + 1)"
cell.invoiceNum.text = invoiceNum
//TimeStamp in the label datestamp
let timeStamp = "\(DateFormatter.localizedString(from: Date(), dateStyle: .short, timeStyle: .short))"
cell.dateStamp?.text = timeStamp
return cell
}
func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
let editAction = UITableViewRowAction(style: .default, title: "Edit") { (action, index) in
//tableView.isEditing = true
DispatchQueue.main.async() {
self.performSegue(withIdentifier: "EditDetails", sender: self)
}
print("Edit Button Pressed")
}
editAction.backgroundColor = UIColor.green
let deleteAction = UITableViewRowAction(style: .destructive, title: "Remove") { (action, indexPath) in
//Remove the labels in the custom cell
clientName.remove(at: indexPath.row)
//dueDate.remove(at: indexPath.row)
projecDescript.remove(at: indexPath.row)
amountDue.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: .fade)
//minus one total bill when deleting one
self.totalBillsLabel.text = "\(indexPath.row - 1)"
if indexPath.row == 0 {
self.totalBillsLabel.text = "0"
}
self.clientTableList.reloadData()
}
let emailAction = UITableViewRowAction(style: .default, title: "Email") { (action, index) in
print("Email Button Pressed")
}
emailAction.backgroundColor = UIColor.orange
let phoneCallAction = UITableViewRowAction(style: .default, title: "Call") { (action, index) in
print("Call Button Pressed")
}
phoneCallAction.backgroundColor = UIColor.blue
return [deleteAction,editAction,emailAction,phoneCallAction]
}
func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
return true
}
override func viewDidAppear(_ animated: Bool) {
clientTableList.reloadData()
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
Second controller
#IBAction func addInvoice(_ sender: Any) {
if clientNameInput.text != "" && descriptionNameInput.text != "" && amountInput.text != ""
{
clientName.append(clientNameInput.text!)
//clientInput.text = ""
projecDescript.append(descriptionNameInput.text!)
//descriptionFieldInput.text = ""
//dueDate.append(dateInput.text!)
//dateInput.text = ""
amountDue.append(amountInput.text!)
//amountList.text = ""
dueDate.append(newDueDateLabel.text!)
// After hit send this is the button that takes you back without having to back out yourself
_ = navigationController?.popViewController(animated: true)
}
}
Do not compute the total in cellForRowAt. That is called every time the row is made visible on the screen, so even if it were summing everything, it would be wrong. Create a separate function that computes the sum and return that to populate the label. Something like:
func calculateSum() {
var sum = 0
for amount in amountDue {
sum+= Int(amount) // more practical to convert to float here
}
totalDue.text = "\(sum)"
}
Then call this method in your viewDidLoad and other appropriate places, such as after a new row is added.
Please add this
var clientName = [String]()
var dueDate = [String]()
var projecDescript = [String]()
var dateStamp = Date()
var invoiceNum = [String]()
var amountDue = [String]()
var sum = 0.0
remove sum from other places in your viewcontroller
then in your second view controller
after amountDue.append(amountInput.text!)
add this
sum += Double(amountInput.text!)!
then in your ViewController
add this
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
totalDue.text = "\(sum)"
}
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")!
}
}
I need to display three or less products from an array in every custom cell of a tableview, I putted three image views and three labels in each cell to show the products. This is my TableviewDataSource Code.
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return products.count / 3
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return 1
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "HomeCell", for: indexPath) as! HomeTableViewCell
let sectionIndex = indexPath.section
if (sectionIndex + index + 2) <= products.count && (sectionIndex + index + 1) <= products.count && currentUser != nil {
cell.product1 = self.products[sectionIndex + index]
cell.product2 = self.products[sectionIndex + index + 1]
cell.product3 = self.products[sectionIndex + index + 2]
cell.selectionStyle = .none
index += 2
}
cell.delegate = self
return cell
}
And this is my TableviewCell Code.
protocol CustomCell : class {
func accessToProduct(product: Product)
func performSegueToProduct()
}
class HomeTableViewCell: UITableViewCell {
#IBOutlet weak var image1 : UIImageView!
#IBOutlet weak var image2 : UIImageView!
#IBOutlet weak var image3 : UIImageView!
#IBOutlet weak var label1: UILabel!
#IBOutlet weak var label2: UILabel!
#IBOutlet weak var label3: UILabel!
weak var delegate: CustomCell?
var product1: Product! {
didSet {
updateUI1()
}
}
var product2: Product! {
didSet {
updateUI2()
}
}
var product3: Product! {
didSet {
updateUI3()
}
}
var cache = SAMCache.shared()
func downloadPopularImages (product: Product, imageView: UIImageView, label: UILabel) {
imageView.image = nil
let productuid = product.uid
let profileImageKey = "\(productuid)"
if let image = cache?.object(forKey: profileImageKey) as? UIImage {
imageView.image = image
} else {
product.downloadPopularProductImage { [weak self] (image, error) in
if let error = error {
print(error.localizedDescription)
} else {
imageView.image = image
self?.cache?.setObject(image, forKey: profileImageKey)
}
}
}
}
// MARK: - Update the UI downloading product images and adding tapGestureRecognizer
func updateUI1() {
downloadPopularImages(product: product1, imageView: image1, label: label1)
label1.text = product1.name + " \(product1.subName)"
let tapGestureRecognizer1 = UITapGestureRecognizer(target: self, action: #selector(self.image1IsTapped))
image1.addGestureRecognizer(tapGestureRecognizer1)
}
func image1IsTapped () {
delegate?.accessToProduct(product: product1)
print(product1)
delegate?.performSegueToProduct()
}
func updateUI2() {
downloadPopularImages(product: product2, imageView: image2, label: label2)
label2.text = product2.name + " \(product2.subName)"
let tapGestureRecognizer2 = UITapGestureRecognizer(target: self, action: #selector(self.image2IsTapped))
image2.addGestureRecognizer(tapGestureRecognizer2)
}
func image2IsTapped() {
delegate?.accessToProduct(product: product2)
delegate?.performSegueToProduct()
}
func updateUI3() {
downloadPopularImages(product: product3, imageView: image3, label: label3)
label3.text = product3.name + " \(product3.subName)"
let tapGestureRecognizer3 = UITapGestureRecognizer(target: self, action: #selector(self.image3IsTapped))
image3.addGestureRecognizer(tapGestureRecognizer3)
}
func image3IsTapped() {
delegate?.accessToProduct(product: product3)
delegate?.performSegueToProduct()
}
}
However, if I have an array with a number of products that is not divisible by 3, does not correctly display all products. Imagine that we have 5 products, so in the first cell it will display the three first products and in the second cell it will display the two remaining products and there will be one image view and one label empties. But this doesn't happen, the code fills the above with one of the products already displayed.
I tried to use a collection view with three cells y each tableview cell but I need the cells of the uicollectionview to be fixed and not cut with the edges, so for that reason i implemented the three image views and three labels.
How can I fix this?
This is what I want:
I have a custom cell class that has an image, a few text labels(1 truncated), and a button:
class CustomTVC: UITableViewCell {
/* Outlets */
#IBOutlet weak var imageHolder: UIImageView!
#IBOutlet weak var concatenatedTitleHolder: UILabel!
#IBOutlet weak var localDateHolder: UILabel!
#IBOutlet weak var descriptionHolder: UILabel!
#IBOutlet weak var seeMoreButton: UIButton!
override func awakeFromNib() {
super.awakeFromNib()
}
}
When the user clicks on the button, it shows the full description of the truncated text label. However, the problem I have now is when the user clicks on the button for a specific cell, it shows the full description for the cell that the user clicked, and also the full description of another cell.
I know the reason that's happening is because the tableview reuses the cell via dequeueReusableCellWithIdentifier. How would I go about implementing a function that will make sure that when a user clicks on the button for a specific cell, only that cell's full description is shown?
Code for tableview:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = self.tableView.dequeueReusableCellWithIdentifier("customCell", forIndexPath: indexPath) as! CustomTVC
if listOfShows.count != 0 {
// Downloading and displaying picture
if let downloadPicture: UIImage = helperFunctions.downloadImage(listOfShows[indexPath.row].imageLink) {
cell.imageHolder.image = downloadPicture
}
// Enlarging / dismissing picture
cell.imageHolder.userInteractionEnabled = true
let newTapped = UITapGestureRecognizer(target: self, action: #selector(MainTVC.imagedTapped(_:)))
cell.imageHolder.addGestureRecognizer(newTapped)
// Concatenating channel + series + episode title
let concatenatedTitle = listOfShows[indexPath.row].channel + " " + listOfShows[indexPath.row].series + " " + listOfShows[indexPath.row].episodeTitle
// Converting into local date / time
let universalTime = helperFunctions.localDateAndTimeConverter(listOfShows[indexPath.row].originalAirDate)
/* Other labels */
cell.concatenatedTitleHolder.text = concatenatedTitle
cell.localDateHolder.text = universalTime
cell.descriptionHolder.text = listOfShows[indexPath.row].description
cell.seeMoreButton.tag = indexPath.row
cell.seeMoreButton.addTarget(self, action: #selector(MainTVC.buttonTapped(_:markedArray:)), forControlEvents: .TouchUpInside)
resetCellSettings(cell)
}
return cell
}
func buttonTapped(sender: UIButton, markedArray: [Bool]) {
let indexPath = NSIndexPath(forRow: sender.tag, inSection: 0)
let cell = tableView.cellForRowAtIndexPath(indexPath) as! CustomTVC
cell.seeMoreButton.hidden = true
cell.descriptionHolder.numberOfLines = 0
cell.descriptionHolder.lineBreakMode = NSLineBreakMode.ByWordWrapping
cell.descriptionHolder.sizeToFit()
}
func resetCellSettings(cell: CustomTVC) {
cell.seeMoreButton.hidden = false
cell.descriptionHolder.numberOfLines = 1
cell.descriptionHolder.lineBreakMode = NSLineBreakMode.ByTruncatingTail
cell.descriptionHolder.sizeToFit()
}
You should put buttonTapped func in CustomTVC class. And set outlet IBAction of seeMoreButton for that func when cell created.