Loop through Array of items with key value pair

I have this function named “addToBasketButtonPressed()” to add item Id in my basket with default quantity 1 and the function name “incrementQty” called after else is checking if item to append already exist in basket and if it does then just increase the qty by 1.

    @objc func addToBasketButtonPressed() {
    //check if user is logged in or show login view
    if MUser.currentUser() != nil {
        
        downloadBasketFromFirestore(MUser.currentId()) { (basket) in
            if basket == nil {
                self.createNewBasket()
            }else {
               
                self.incrementQty(basketId: basket!.id, itemToUpdate: self.item!.id, deltaQty: 1)
                
                let qty: Int = 1
                let item = self.item.id

                let dataTosave = (id: item!, qty: qty)
                
                basket?.itemIds.append("\(dataTosave)")
    
                print("basket id is \(basket!.id)")
                print("current item name is \(self.item.name)")
                
                self.updateBasket(basket: basket!, withValues: [kITEMIDS: basket!.itemIds!])

            }

Here’s the function to increment qty by 1 which is erroring.

Error 1 = Value of optional type '[String : Any]?' must be unwrapped to a value of type '[String : Any]' on line = for item in itemArray { which I can fix by adding ! but I then get below error.

Error 2 = Value of tuple type '(key: String, value: Any)' has no member 'subscript' on last 3 lines

    func incrementQty (basketId: String, itemToUpdate: String, deltaQty: Int) {

    FirebaseReference(.Basket).document(basketId).getDocument { (documentSnapshot, error) in
    if let error = error {
                   print(error.localizedDescription)
                   return
        }
        
        if let documentData = documentSnapshot?.data() {
            
            print("item exists. update qty")
            print("document data in itemIds", documentData)
            let itemArray = documentData["itemIds"] as? [String: Any]
            print("items in item array \(itemArray)")
              

            for item in itemArray {

                if  item["Id"] == itemToUpdate {
                    print("checking document with id", item["Id"])
                    (item["Qty"] as! Int) + deltaQty
                }
            }

If I comment out the last 4 lines of code and amend below codes by commenting out “as? [String: Any]’ as shown below then in the debug window I can see the list of items. If I don’t comment out “as? [String: Any]” then it’s nil.

                let itemArray = documentData["itemIds"] // as? [String: Int]
        print("items in item array \(itemArray)")

My last question is after correcting above errors, how do I loop through this list to check if an item user is appending to basket already exists and if it does then just increase the quantity by 1 else add with default quantity 1?

note: data is extracted from Firebase Database.

your help on this will be greatly appreciated as I've spent few days searching for answers/solution but no luck.

I think you should create a struct to store the data, instead of storing tuples.

For example:

struct ItemData {
  let id: String
  let quantity: Int
}

Then, instead of doing:

let dataTosave = (id: item!, qty: qty)

You can do:

let dataToSave = ItemData(id: item!, quantity: qty)

You will also have to change the type of basket.itemIds to [ItemData]. Then, you can loop over this array and do something with each item.

Alternatively, you can make it a dictionary instead (perhaps [String: Int]) if you want. It depends on your use case and how the data is supposed to be passed/stored.

Thanks for your input on this. do you mean change below code:
basket?.itemIds.append("(dataToSave)")

and how would re-write to append an item?

currently to add a new item for user to add to basket I have below item class:

class Item {
var id: String!
var categoryId: String!
var name: String!
var description: String!
var price: Double!
var imageLinks: [String]!
var kitchenName: String!
var quantity: Int!

init() {
}

init(_dictionary: NSDictionary) {
    id = _dictionary [kOBJECTID]  as? String
    categoryId = _dictionary [kCATEGORYID] as? String
    name = _dictionary[kNAME] as? String
    quantity = _dictionary [kQUANTITY] as? Int
    kitchenName = _dictionary[kKITCHENNAME] as? String
    description = _dictionary[kDESCRIPTION] as? String
    price = _dictionary[kPRICE] as? Double
    imageLinks = _dictionary[kIMAGELINKS] as? [String]
}

}

func saveItemToFirestore(_ item: Item) {

FirebaseReference(.Items).document(item.id).setData(itemDictionaryFrom(item) as! [String : Any])

}

...and class for basket below:
class Basket {

var id: String!
var ownerId: String!
var itemIds: [String]!
var delivery: Float!
var admin: Float!
var quantity: Int!

// var itemId: String

init() {
}

init(_dictionary: NSDictionary) {
    id = _dictionary[kOBJECTID] as? String
    ownerId = _dictionary[kOWNERID] as? String
    itemIds = _dictionary[kITEMIDS] as? [String]
    delivery = _dictionary[kDELIVERY] as? Float
    admin = _dictionary[kADMIN] as? Float
    quantity = _dictionary[kQUANTITY] as? Int
  //  itemId = _dictionary[kITEM] as? String
}

}

func saveBasketToFirestore(_ basket: Basket) {

FirebaseReference(.Basket).document(basket.id).setData(basketDictionaryFrom(basket) as! [String: Any])

}

func basketDictionaryFrom(_ basket: Basket) -> NSDictionary {

return NSDictionary(objects: [basket.id, basket.ownerId, basket.itemIds,basket.quantity, basket.delivery, basket.admin], forKeys: [kOBJECTID as NSCopying, kOWNERID as NSCopying, kITEMIDS as NSCopying, kQUANTITY as NSCopying,kDELIVERY as NSCopying, kADMIN as NSCopying])

}

//MARK: - Update basket
func updateBasketInFirestore(_ basket: Basket, withValues: [String : Any], completion: @escaping (_ error: Error?) -> Void) {

FirebaseReference(.Basket).document(basket.id).updateData(withValues) { (error) in
    completion(error)
}

}

You need to first update the Basket class:

class Basket {
 ...
 var itemIdAndQuantityMap: [String: Int] = [:]
 ...
}

or if you want to use a struct:

struct ItemData {
  let id: String
  let quantity: Int
}

class Basket {
 ...
 var itemData: [ItemData] = []
 ...
}

I would also advise to use struct for all these types (Basket, etc) as well (if possible) and get rid of the force-unwraps, but you can do that later.

Anyway, now once you've updated the Basket class, you can do:

  1. basket.itemIdAndQuantityMap[item.id] = qty if you use the dictionary approach.
  2. basket.itemData.append(ItemData(id: item.id, quantity: qty)) if you use the array approach.

I really appreciate your help on this. I have searched the web for days for a solution but no luck so really thank you for your help. I'm also new to Swift coding so a lot of things not clear to me yet.

I built this app following source code of a similar app which is based on dictionary so it is best I keep this structure otherwise I will have to change pretty much everything which for me will be very confusing/messy.

You mentioned to add below code in Basket class. where exactly I need to add this line and any other code need changing there?

 var itemIdAndQuantityMap: [String: Int] = [:]

below 2 functions I mentioned in my first post are written in ItemViewController separate from the Basket class.

@objc func addToBasketButtonPressed()
func incrementQty (basketId: String, itemToUpdate: String, deltaQty: Int)

below is everything I have in my Basket class. if you can tell me which line of code I should change/add for appending document "itemIds".

class Basket {

var id: String!
var ownerId: String!
var itemIds: [String]!
var delivery: Float!
var admin: Float!
var quantity: Int!

// var itemId: String

init() {
}

init(_dictionary: NSDictionary) {
    id = _dictionary[kOBJECTID] as? String
    ownerId = _dictionary[kOWNERID] as? String
    itemIds = _dictionary[kITEMIDS] as? [String]
    delivery = _dictionary[kDELIVERY] as? Float
    admin = _dictionary[kADMIN] as? Float
    quantity = _dictionary[kQUANTITY] as? Int
  //  itemId = _dictionary[kITEM] as? String
}

}

//MARK: - Download items
func downloadBasketFromFirestore(_ ownerId: String, completion: @escaping (_ basket: Basket?)-> Void) {

FirebaseReference(.Basket).whereField(kOWNERID, isEqualTo: ownerId).getDocuments { (snapshot, error) in
    
    guard let snapshot = snapshot else {
        
        completion(nil)
        return
    }
    
    if !snapshot.isEmpty && snapshot.documents.count > 0 {
        let basket = Basket(_dictionary: snapshot.documents.first!.data() as NSDictionary)
        completion(basket)
    } else {
        completion(nil)
    }
}

}

//MARK: - Save to Firebase
func saveBasketToFirestore(_ basket: Basket) {

FirebaseReference(.Basket).document(basket.id).setData(basketDictionaryFrom(basket) as! [String: Any])

}

//MARK: Helper functions

func basketDictionaryFrom(_ basket: Basket) -> NSDictionary {

return NSDictionary(objects: [basket.id, basket.ownerId, basket.itemIds,basket.quantity, basket.delivery, basket.admin], forKeys: [kOBJECTID as NSCopying, kOWNERID as NSCopying, kITEMIDS as NSCopying, kQUANTITY as NSCopying,kDELIVERY as NSCopying, kADMIN as NSCopying])

}

//MARK: - Update basket
func updateBasketInFirestore(_ basket: Basket, withValues: [String : Any], completion: @escaping (_ error: Error?) -> Void) {

FirebaseReference(.Basket).document(basket.id).updateData(withValues) { (error) in
    completion(error)
}

}

You need to replace var itemIds: [String]! with var itemIdAndQuantityMap: [String: Int] = [:]. I renamed to make it more clear.

You would also need to make other changes, like changing itemIds = _dictionary[kITEMIDS] as? [String] to itemIdAndQuantityMap = _dictionary[kITEMIDS] as? [String: Int] ?? [:].

If you're new to Swift and you're building this app by copying another app's code, I do highly recommend you spend some time learning Swift properly first (i.e. tutorials on YouTube, read a book, etc) otherwise you will find it very difficult to work on this app further.

I totally agree. I in fact have been reading and researching a lot lately and by doing that I have managed to make lot of changes and added new features to app I'm building. Will test your method and see if it gives me the result I wanted.