4

How would I be able to arrange the cells by sections with a custom header

I have code set to arrange the cells in the CartVC by brand(placing the brand in the CartHeaderCell). When the data is passed from the HomeVC to the CartVC, I can't get my code arrange the cells into sections by brand from the code I created in the CartVC. (currently code passes data from HomeVC to CartVC without arranging the cells into sections)

How would I be arrange the sections by brand in the CartVC after data is passed into the Cart

Update:

Right Now the code in the CartViewController Extension arranges the cells into sections and passes the items into the cells by the brand, but scrambles all the cells into random sections or create a new section for the brand in the cells, and/or crashes the simulator when the CartBtn is pressed, or shows the same item/cell in multiple sections

image

extension HomeController: UITableViewDelegate, UITableViewDataSource {

    func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return itemSetup.count
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        guard let cell = tableView.dequeueReusableCell(withIdentifier: "HomeCell") as? HomeCell else { return UITableViewCell() }

        let item = itemSetup[indexPath.row]
        cell.configure(withItems: item)

        // passes data to the Cart Cells in the CartVC when ATC Btn is pressed in each HomeCell
        cell.addActionHandler = { (option: Int) in
            print("Option selected = \(option)")
            Tray.currentCart.cartItems.append(item)
            item.selectedOption = option
        }

        return cell
    }
}

import UIKit

class CartViewController: UIViewController {

    var items: Items!

    // arranges cells into sections
    var tray: [Tray] = []
    var sortedBrandsByName: [String] = []
    var sections: [[Tray]] = [[]]

    @IBOutlet weak var cartTableView: UITableView!

    override func viewDidLoad() {
        super.viewDidLoad()

        // arranges cells into sections
        let brandNames = tray.map { $0. titleBrandName }
        let uniqueBrandNames = Array(Set(brandNames))           
        let sortedBrandNames = uniqueBrandNames.sorted()
        let sections: [[Tray]] = sortedBrandNames.map { firstBrandNames in
            return tray
                .filter { $0. titleBrandName == firstBrandNames } 
                .sorted { $0.cart.brand < $1.cart.brand } // sort them
        }

        // Do any additional setup after loading the view.
        cartTableView.dataSource = self
        cartTableView.delegate = self

    }
}

extension CartViewController: UITableViewDataSource, UITableViewDelegate {

    func numberOfSections(in tableView: UITableView) -> Int {
        return Tray.currentCart.cartItems.count
    }

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {

        //allows data passed from the HomeVC populate the CartCells
        return Tray.currentCart.cartItems[section].count
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "CartCell", for: indexPath) as! CartCell

        // **Active code** that allows data passed from the HomeVC populate the CartCells
        let cart = Tray.currentCart.cartItems[indexPath.row]
        cell.configure(withItems: cart)

        return cell
    }

    func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
        let cartHeader = tableView.dequeueReusableCell(withIdentifier: "CartHeaderCell") as! CartHeaderCell
        cartHeader.storeName.text = Tray.currentCart.cartItems[section].brand
        return cartHeader
    }

    func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
        return 45
     } 
}

class CartHeaderCell: UITableViewCell {

    @IBOutlet weak var brandName: UILabel!

    override func awakeFromNib() {
        super.awakeFromNib()
        // Initialization code
    }
}

class CartCell: UITableViewCell {

    @IBOutlet weak var lblMealName: UILabel!
    @IBOutlet weak var imageUrl: UIImageView!
    @IBOutlet weak var lblSubTotal: UILabel!
    @IBOutlet weak var lblQty: UILabel!

    override func awakeFromNib() {
         super.awakeFromNib()
        // Initialization code
    }

    // allows the data to be passed into the cart cells

    func configure(withItems items: Items) {
        imageUrl.sd_setImage(with: URL(string: items.imageUrl))
        lblQty.text = "\(items.count)"
        let formatter = NumberFormatter()
        formatter.maximumFractionDigits = 2
        formatter.numberStyle = .decimal
        if items.selectedOption == 1 {
            lblSubTotal.text = "$\(formatter.string(for: items.price1 * Float(items.count))!)"
            lblMealName.text = "\(items.name) ● \(items.weight1)"
        } else if items.selectedOption == 2 {
            lblSubTotal.text = "$\(formatter.string(for: items.price2 * Float(items.count))!)"
            lblMealName.text = "\(items.name) ● \(items.weight2)"
        } else if items.selectedOption == 3 {
            lblSubTotal.text = "$\(formatter.string(for: items.price3 * Float(items.count))!)"
            lblMealName.text = "\(items.name) ● \(items.weight3)"
        }
    } 
}

// allows the code that is passed to the CartVC when an item is passed from the HomeVC to the CartVC
class Tray {
    static let currentCart = Tray()
    var cartItems = [Items]()
    var cart: Items!
    var sectionTitle: String!
}

extension Tray {
    var titleBrandName: String {
        return String(self.cart.brand[self.cart.brand.startIndex]).uppercased()
    }
}
Evelyn
  • 186
  • 1
  • 4
  • 25
  • The header for a section should not be implemented as a row. A better way to implement this would be - 1. Instead of an array for the data source, use a dictionary with brand name as key, and respective values in an array. 2. In `numberOfSections ` provide the key count of the dictionary. 3. In `viewForHeaderInSection ` configure the section header. – brainforked Nov 16 '19 at 18:57
  • I just added some code to my CartVC and Tray class (marked as "Test Code") for what I think would make the first step to your solution work. Sorry about the late response the wifi is down for my whole neighborhood and I finally was able to go to my local coffee shop to do this – Evelyn Nov 17 '19 at 02:49
  • I just tested this in the ```numberOfSections``` I placed ```return Tray.currentCart.cartItems.count``` and in the ```numberOfRowsInSection``` I placed ```return Tray.currentCart.cartItems[section].count```. Though it shows a header when data is passed and separates the cells into sections, its not accurate, actually its completely scrambles the items everywhere. And the brand fails to show in the header no what I try – Evelyn Nov 17 '19 at 04:53
  • Can you post the table data source? – brainforked Nov 17 '19 at 05:45
  • I think the model of Tray is wrong since it seems from the picture that there are multiple brands while you are using titleBrandName as a singular property and not an array. Besides this can you please post the model class of Items, so that I can have a closer look at how to arrange the items? – Shubham Ojha Nov 17 '19 at 14:33
  • just updated my code **@ShubhamOjha** – Evelyn Nov 19 '19 at 14:00

1 Answers1

2

The issue is you're not actually saving the sorts you're doing. And even if you were.. you're looking at a different part of the code for your data in the UITableViewDataSource methods.

First issue: you're not actually saving your data. Make sure the function that creates CartViewController actually passes in the tray before viewDidLoad: !!

In this part:

import UIKit

class CartViewController: UIViewController {

    var items: Items!

    // arranges cells into sections
    var tray: [Tray] = []
    var sortedBrandsByName: [String] = []
    var sections: [[Tray]] = [[]]

    @IBOutlet weak var cartTableView: UITableView!

    override func viewDidLoad() {
        super.viewDidLoad()

        // arranges cells into sections
        let brandNames = tray.map { $0. titleBrandName }
        let uniqueBrandNames = Array(Set(brandNames))           
        let sortedBrandNames = uniqueBrandNames.sorted()
        let sections: [[Tray]] = sortedBrandNames.map { firstBrandNames in
            return tray
                .filter { $0. titleBrandName == firstBrandNames } 
                .sorted { $0.cart.brand < $1.cart.brand } // sort them
        }

        // Do any additional setup after loading the view.
        cartTableView.dataSource = self
        cartTableView.delegate = self

    }
}

your viewDidLoad creates LOCAL versions of sortedBrandNames and sections. Remove the lets and try this instead:

override func viewDidLoad() {
    super.viewDidLoad()

    // arranges cells into sections
    let brandNames = tray.map { $0. titleBrandName }
    let uniqueBrandNames = Array(Set(brandNames)) 

    // MY CHANGES ARE BELOW (remove let)      
    sortedBrandNames = uniqueBrandNames.sorted()
    sections: [[Tray]] = sortedBrandNames.map { firstBrandNames in
        return tray
            .filter { $0. titleBrandName == firstBrandNames } 
            .sorted { $0.cart.brand < $1.cart.brand } // sort them
    }

    // Do any additional setup after loading the view.
    cartTableView.dataSource = self
    cartTableView.delegate = self

}

General feedback.. you should use THESE to fill in your table view NOT the static Tray.currentTray. Having data held on a value that you can read/write from anywhere in the code base is REALLY BAD.

Secondly.. you're reading from a different data set on the UITableViewDataSource methods...

Say we did manage to save our sorted data now... when you do Tray.currentCart you actually are not looking at the data you just sorted (that would be self.sections), at no point did you change the data in Tray.currentCart (again v bad don't make static stuff like this or singletons, its super easy to introduce bugs this way)

   func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {

        //allows data passed from the HomeVC populate the CartCells
        return Tray.currentCart.cartItems[section].count
    }

Instead... try this:

extension CartViewController: UITableViewDataSource, UITableViewDelegate {

    func numberOfSections(in tableView: UITableView) -> Int {
        return self.sections.count
    }

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return self.sections[section].count
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "CartCell", for: indexPath) as! CartCell

        let cart = self.sections[indexPath.section][indexPath.row]
        cell.configure(withItems: cart)

        return cell
    }

    func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
        let cartHeader = tableView.dequeueReusableCell(withIdentifier: "CartHeaderCell") as! CartHeaderCell
        cartHeader.storeName.text = self.sections[section].brand
        return cartHeader
    }

    func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
        return 45
     } 
}

That way you're looking at the data you just sorted in viewDidLoad. (Note i'm assuming your sorting logic works correctly)

gadu
  • 1,816
  • 1
  • 17
  • 31
  • okay I just ran the code and im getting errors In the viewDidLoad when I remove the ```Let```'s. In the cellForRowAt on```cell.configure(withItems: cart)``` is getting an error **Cannot convert value of type 'Tray' to expected argument type 'ProductList'** and the viewForHeaderInSection on ```cartHeader.storeName.text = self.sections[section].brand``` with the error ** Value of type '[Tray]' has no member 'brand'**. I'll do my best to work out the errors to see if I can make this work but I right now its not working – Evelyn Nov 21 '19 at 15:32
  • You're definitely right about the ```Tray.currentCart``` im working on how to restructure my code to have the ```currentCart``` removed from the Tray Class just going to take me sometime to rewrite my code since its property is used to pass data from my HomeVC (code not on here) from a closure to the CartVC – Evelyn Nov 21 '19 at 16:05
  • 1
    if you want to pass data back up use something like a delegate. init the `CartVC` on `HomeVC` set `cartVC.delegate = self` and then you can pass data back like that. be sure to google bc you're going to want to declare the delegate `weak` to avoid retain cycles and other best practices – gadu Nov 21 '19 at 17:23
  • tried that but it didn't work, since I have a **closure** that passes the data from the HomeVC to the CartVC – Evelyn Nov 22 '19 at 13:37
  • ran a bunch of tests to get this code to work, it works, sorry it took so long to figure out, bad internet lately and tons of work this past week – Evelyn Nov 24 '19 at 05:10
  • glad you got it! yeah there was def a bit of changes needed since the data you were sorting did not match exactly how the data in the `currentCart` but the idea was def correct. – gadu Nov 25 '19 at 14:15