1

What I’m trying to do is group the sections in the TableView. By sorting them by the storeName so that I can separate the items to go to its proper section and arranged by StoreName.

Im trying to present a couple of different sections of items in the list for each store. By grouping the sections based on StoreName.

The Items Array contains all the items for each in each section and shows which store the item belongs to.

How would I be able to group the sections for specific stores?

I know that im close at grouping my sections but im just not sure how to make it work correctly as well as it connecting to my Custom HeaderCell. I have a function written in my StoreVC called attemptToAssembleStoreGroups()

I just want to know how would I be able to group my items together by their storeName to have their own section for each store with its own list of items.

struct ItemSelection {
    let store: String
    let productName: String
    let productImage: UIImage
    let quantity: String
    let total: String
}

struct ItemSection {
    let title : String
    let stores : [ItemSelection]
}

class StoreVC: UITableViewController {

      fileprivate let cellId = "id123"

        let items = [
          ItemSelection(store: "Walmart",
               productName: "Bionicle",
               productImage: #imageLiteral(resourceName: "Bionicle"),
               quantity: "4",
               total: "24"),
          ItemSelection(store: "Walmart",
               productName: "PokeBall",
               productImage: #imageLiteral(resourceName: "PokeBall"),
               quantity: "2",
               total: "30"),
          ItemSelection(store: "Target",
               productName: "Beer",
               productImage: #imageLiteral(resourceName: "Beer"),
               quantity: "2",
               total: "30"),
          ItemSelection(store: "Lego Store",
               productName: "Star Wars Set",
               productImage: #imageLiteral(resourceName: "Star_Wars_Set"),
               quantity: "4",
               total: "256"),
          ItemSelection(store: "Lego Store",
               productName: "Indiana Jones Set",
               productImage: #imageLiteral(resourceName: "Indiana_Jones_Set"),
               quantity: "2",
               total: "88"),
          ItemSelection(store: "Amazon",
               productName: "Coconut Milk",
               productImage: #imageLiteral(resourceName: "Coconut_Milk"),
               quantity: "4",
               total: "20"),
          ItemSelection(store: "Amazon",
               productName: "32 inch Tv",
               productImage: #imageLiteral(resourceName: "TV"),
               quantity: "1",
               total: "156"),
          ItemSelection(store: "Amazon",
               productName: "Amazon Echo",
               productImage: #imageLiteral(resourceName: "Amazon_Echo"),
               quantity: "1",
               total: "80"),
          ItemSelection(store: "Amazon",
               productName: "Grill",
               productImage: #imageLiteral(resourceName: "Grill"),
               quantity: "3",
               total: "90"),
          ItemSelection(store: "Amazon",
               productName: "Coconut Bar",
               productImage: #imageLiteral(resourceName: "coconuts"),
               quantity: "4",
               total: "240")
         ]

      var itemSelection = [[ItemSelection]]()
      var storeArray = [String]()
      var itemSections = [ItemSection]()

     override func viewDidLoad() {
        super.viewDidLoad()

        for index in self.items {
            storeArray.append(index.store)
        }

        let groupedDictionary = Dictionary(grouping: items, by: {String($0.store.prefix(1))})
        let keys = groupedDictionary.keys.sorted()
        itemSections = keys.map{ItemSection(title: $0, stores: groupedDictionary[$0]!.sorted(by: {$0.store < $1.store}))}
     }



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

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

     override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
         let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath) as! StoreCell
         let itemSelections = itemSelection[indexPath.section][indexPath.row]
         cell.itemSelction = itemSelections
         return cell
     }

     override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
         let cartHeader = tableView.dequeueReusableCell(withIdentifier: "Header") as! HeaderCell

         let stores = itemSelection[section]

         cartHeader.storeName.text = "Store: \(stores)"

         return cartHeader
     }

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

}

class StoreCell: UITableViewCell {

    @IBOutlet weak var itemQty: UILabel!
    @IBOutlet weak var itemName: UILabel!
    @IBOutlet weak var itemPrice: UILabel!
    @IBOutlet weak var itemImage: UIImage!

    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
    }

    var itemSelection: ItemSelection! {
        didSet {
            itemName.text = itemSelection.productName
            itemQty.text = "Qty: \(itemSelection.quantity)"
            itemPrice.text = "$\(itemSelection.total)"
            itemImage.image = itemSelection.productImage
        }

    }

}

class HeaderCell: UITableViewCell {

    @IBOutlet weak var storeName: UILabel!

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

    override func setSelected(_ selected: Bool, animated: Bool) {
        super.setSelected(selected, animated: animated)

        // Configure the view for the selected state
    }

}
Evelyn
  • 186
  • 1
  • 4
  • 25
  • 1
    I would separate your model for sections: https://stackoverflow.com/questions/40128423/how-to-deal-with-dynamic-sections-and-rows-in-ios-uitableview – koen Oct 17 '19 at 15:22
  • im aware of that way @Koen its just not the way im trying to go, im trying to separate the sections in the function attemptToAssembleStoreGroups() by seperating the sections that way since im trying to retrieve the data as if new data was put in and it can still show in the right store no matter where it is at in the array – Evelyn Oct 17 '19 at 16:22
  • 1
    Related: https://stackoverflow.com/questions/58397502/how-do-i-get-alphabetic-tableview-sections-from-an-object/58397733. The grouping condition can be anything. – vadian Oct 17 '19 at 16:27
  • ok ill give a look thank you @Vadian, and thanks for all the help that you've done for me in the past – Evelyn Oct 18 '19 at 02:35
  • **@Vidan** I just rewrote my code, to what it should for the link you sent me. its not working entirely but I think its because i don't know how to structure this with a custom header Cell in place of writing it programmatically. – Evelyn Oct 18 '19 at 12:09
  • @Evelyn It gets kinda messy when you jam everything into the view controller. I would break out the logic for displaying the data to a separate class or struct. – Adrian Oct 19 '19 at 13:59
  • its fine, I figured out the solution I tried using what Vidan recommended but it didn't really work so well so I had to modify it and use it with another to combine with this coding structure that I found – Evelyn Oct 19 '19 at 14:11

2 Answers2

1

Alternatively, you could break out your ItemSelection stuff into a separate class.

Dictionary(grouping:by:) enables you to turn an array into a dictionary grouped by whatever key you specify. The code below groups it by store name.

I make use of lazy vars so they're not all initialized when the class is initialized. Everything gets initialized when it's needed.

The storeNames are retrieved from the dictionary keys.

The elements for each store are retrieved by looking up a value from the dictionary I create from the initial array.

class StoreInfoDataManager {

    struct ItemSelection {
        let storeName: String
        let productName: String
        let productImage: UIImage
        let quantity: String
        let total: String
    }

    lazy var storeDictionary: [String: [ItemSelection]] = {
        return Dictionary(grouping: itemSelections) { $0.storeName }
    }()

    // These are the keys for your sections
    lazy var storeNames: [String] = {
        return Array(storeDictionary.keys).sorted()
    }()

    // This returns an array of items in the store
    func items(for store: String) -> [ItemSelection]? {
        return storeDictionary[store]
    }

    lazy var itemSelections: [ItemSelection] = {
        return [
            ItemSelection(storeName: "Walmart",
                          productName: "Bionicle",
                          productImage: #imageLiteral(resourceName: "Bionicle"),
                          quantity: "4",
                          total: "24"),
            ItemSelection(storeName: "Walmart",
                          productName: "PokeBall",
                          productImage: #imageLiteral(resourceName: "PokeBall"),
                          quantity: "2",
                          total: "30"),
            ItemSelection(storeName: "Target",
                          productName: "Beer",
                          productImage: #imageLiteral(resourceName: "Beer"),
                          quantity: "2",
                          total: "30"),
            ItemSelection(storeName: "Lego Store",
                          productName: "Star Wars Set",
                          productImage: #imageLiteral(resourceName: "Star_Wars_Set"),
                          quantity: "4",
                          total: "256"),
            ItemSelection(storeName: "Lego Store",
                          productName: "Indiana Jones Set",
                          productImage: #imageLiteral(resourceName: "Indiana_Jones_Set"),
                          quantity: "2",
                          total: "88"),
            ItemSelection(storeName: "Amazon",
                          productName: "Coconut Milk",
                          productImage: #imageLiteral(resourceName: "Coconut_Milk"),
                          quantity: "4",
                          total: "20"),
            ItemSelection(storeName: "Amazon",
                          productName: "32 inch Tv",
                          productImage: #imageLiteral(resourceName: "TV"),
                          quantity: "1",
                          total: "156"),
            ItemSelection(storeName: "Amazon",
                          productName: "Amazon Echo",
                          productImage: #imageLiteral(resourceName: "Amazon_Echo"),
                          quantity: "1",
                          total: "80"),
            ItemSelection(storeName: "Amazon",
                          productName: "Grill",
                          productImage: #imageLiteral(resourceName: "Grill"),
                          quantity: "3",
                          total: "90"),
            ItemSelection(storeName: "Amazon",
                          productName: "Coconut Bar",
                          productImage: #imageLiteral(resourceName: "coconuts"),
                          quantity: "4",
                          total: "240")
        ]
    }()
}
Adrian
  • 16,233
  • 18
  • 112
  • 180
0

found my solution

import Foundation

struct Items {
    let store: String
    let productName: String
    let productImage: UIImage
    let quantity: String
    let price: String
}

extension Items: Comparable {
    static func < (lhs: Items, rhs: Items) -> Bool {
        if lhs.store < rhs.store { return true }
        else { return lhs.store == rhs.store && lhs.productName < rhs.productName }
    }
}

extension Items {
    static let retail: [Items] = [
        .init(store: "Amazon",
              productName: "Care Bear",
              productImage: #imageLiteral(resourceName: "Bear"),
              quantity: "4",
              price: "156"),
        .init(....
    ]
}

import Foundation

class ItemDataSource: NSObject {
    var sections: [String: [Items]] = [:]

    var items: [String] {
        return sections.keys.sorted()
    }

    var indexes: [String] {
        return items
            .map { String($0.first!) }
            .reduce(into: Set<String>(), { $0.insert($1) })
            .sorted()
    }

    init(stores: [Items]) {
        for store in retail.sorted(by: <) {
            let items = store.items
            if var stores = sections[items] {
                stores.append(store)
                sections[items] = stores
            } else {
                sections[items] = [store]
            }
        }
    }
}

class StoreHeader: UITableViewCell {

    @IBOutlet weak var dispensaryName: UILabel!

    var store: String? {
        didSet { storeName.text = "Store: \(store!)" }
    }

}

class StoreCell: UITableViewCell {

    @IBOutlet weak var productImage: UIImageView!
    @IBOutlet weak var productQty: UILabel!
    @IBOutlet weak var productPrice: UILabel!
    @IBOutlet weak var productName: UILabel!

    var product: String? {
        didSet { productName.text = product }
    }
    var quantity: String? {
        didSet { productQty.text = "Qty: \(quantity!)" }
    }
    var price: String? {
        didSet { productPrice.text = "$\(price!)" }
    }
    var img: UIImage? {
        didSet { productImage.image = img }
    }

}

import UIKit

class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {

    let dataSource: ItemDataSource = .init(stores: Items.stores)

    @IBOutlet weak var tableView: UITableView!

    func numberOfSections(in tableView: UITableView) -> Int {
        return dataSource.sections.count
    }
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        let store = dataSource.items[section]
        return dataSource.sections[store]?.count ?? 0
    }
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
         let storeCell = tableView.dequeueReusableCell(withIdentifier: "StoreCell") as! StoreCell
        let store = dataSource.items[indexPath.section]
        let storeItem = dataSource.sections[store]?[indexPath.row]

        storeCell.product = storeItem?.productName
        storeCell.price = storeItem?.price
        storeCell.quantity = storeItem?.quantity
        storeCell.img = storeItem?.productImage

        return storeCell
    }
    func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
        let storeHeader = tableView.dequeueReusableCell(withIdentifier: "StoreHeader") as! StoreHeader
        storeHeader.store = dataSource.items[section]
        return storeHeader
    }
    func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
        return 45
    }
Evelyn
  • 186
  • 1
  • 4
  • 25
  • 1
    Kudos for figuring out a solution! Since you're getting started, I would dissuade you from force unwrapping variables (`!`). If it's `nil`, then your app will crash. Instead, use a `guard`, `if let`, or provide a default value (`aNumber ?? 1`). – Adrian Oct 19 '19 at 14:38
  • Throwing all the code in with no explanation is not a helpful answer and does not relate directly to the specific problem. – matt Oct 19 '19 at 14:38
  • 1
    you're right im still new to this and answering questions on stack, it is kind of difficult for me, barely got the hang of asking questions right, but I just posted up my solution so at least if someone gets similar problem like me they at least can see a possible solution. – Evelyn Oct 19 '19 at 14:44
  • @Evelyn As a suggestion, you could just create an optional var on your UITableViewCell class for your `storeItem` and utilize `didSet` on it to set the labels on the cell. That way you can offload code from your View Controller to where the work is actually done. – Adrian Oct 19 '19 at 14:54
  • how would I do that Adrian? ive never really grouped sections in my tableview this actually my first successful attempt – Evelyn Oct 19 '19 at 15:01