1

I have an array of dictionaries [[String : AnyObject]] called rooms in a User object - each dictionary holds a name as the String and an id as the AnyObject.

I want to populate a table view with the names, so I'm trying to loop through the array and grab the String values from the dicts.

        if let roomDict = myUser.rooms as? [[String : AnyObject]] {
            for (roomNames, _) in roomDict {
                cell.textLabel?.text = roomNames
            }
        }

I'm relatively new and from what I've seen in tutorials and such when looping through dictionaries, you use the underscore to specify that you don't want the second value. So just grab all the first values (in this case, the names), and set them to the roomNames variable.

However I'm confused here because I'm not looping through a dictionary, I'm looping through an array of dictionaries. So I'm not sure how to do that. I did a search and the results I saw generally were asking about JSON, which isn't the case here. How can I do this?

Thanks for any help!

KingTim
  • 1,281
  • 4
  • 21
  • 29
  • 1
    You should search for the question before asking it, you will find many answers around this topic already. Good luck. –  Apr 04 '17 at 22:55
  • @Sneak I'm not trying to be rude, but did you read my question before posting this? – KingTim Apr 04 '17 at 22:58
  • Of course. Did you check the duplicate? If I missed something, or If it does not answer your question , update your question with what is different in your case and I can retract the duplicate if I dont see any other simliar thread. –  Apr 04 '17 at 23:02
  • How many dictionaries are in each element of the array? Also, it looks like you have this loop in `cellForRow(at:)` - you need to have this loop once, where you get your data, to build your array so that you can just access it in `cellForRow(at:)` – Paulw11 Apr 04 '17 at 23:02
  • @Paulw11 I'm not entirely sure if this is what you're asking, but the number of dictionaries in the array depends on how many rooms the user creates/is involved in. Each room has a name and ID. And yeah this is in `cellForRow`, I didn't realize it shouldn't be there - I'll do the loop somewhere else thanks for the heads up! – KingTim Apr 04 '17 at 23:06
  • @Sneak I did, I searched and saw that particular question but it was dealing with parsing through JSON, which is what I was talking about when I said `I did a search and the results I saw generally were asking about JSON`. I didn't see how I could apply that answer to my particular situation since I'm not dealing with JSON. Of course I can update the question if you want, but that line generally explains why my question is different i.e. not dealing with JSON. Let me know if you still want me to update further! – KingTim Apr 04 '17 at 23:09
  • KingTim A dictionary object does not care if it the key/values were a JSON in the beginning, a text document or a website URL and its brand name. Your question asks how to iterate through an **Array** containing **dictionaries** , if you read the duplicate or other simliar threads, the question is the same.There's no **Foundation Object** that is JSON, or **NSJSON**, apple didn't create this, so the use is https://developer.apple.com/reference/foundation/jsonserialization and https://developer.apple.com/reference/foundation/jsonserialization/1415493-jsonobject to return a **Foundation Object** –  Apr 04 '17 at 23:17
  • http://stackoverflow.com/questions/26727789/how-to-loop-through-arraydictionarystring-string-using-swift http://stackoverflow.com/questions/42548308/iterate-and-mutate-an-array-of-dictionaries-swift-3 http://stackoverflow.com/questions/28365939/how-to-loop-through-json-with-swiftyjson http://stackoverflow.com/questions/28203443/search-array-of-dictionaries-for-value-in-swift Let me know if you need more examples. –  Apr 04 '17 at 23:22
  • @Sneak My Stack-fu needs some work I admit. I'll delete the question if I really messed up, although I accepted Pauw's answer. Didn't mean to offend/upset/challenge you in any way so don't hate me. Programming's not coming as easily to me as it does to others. Let me know if you want me to delete. – KingTim Apr 04 '17 at 23:33
  • @KingTim No worries, not upset or anything. Keep the answer I will retract the flag. I have upvoted Paul since you solved the issue. I only wanted to show you that it makes no difference if it's json or whatever :) however, now you learned that atleast :P Good luck! –  Apr 04 '17 at 23:34

2 Answers2

6

Firstly, your dictionary structure isn't ideal. Rather than having the key as the room name and the value as the identifier, your dictionary should have known key names, with variable values. Keys should not be "data" in a dictionary.

So, rather than

["room1":1] 

it would be better if it were

["roomName":"room1", "roomID":1]

with your current structure, however, assuming that there is only one key per dictionary and that is the room name, you can get the names with:

if let rooms = myUser.rooms as? [[String : AnyObject]] {
   roomNames = rooms.map({ $0.keys.first!})
}

If you use the better structure I suggested then it would be

if let rooms = myUser.rooms as? [[String : AnyObject]] {
   roomNames = rooms.map({ $0["roomName"] as? String ?? ""})
}
Paulw11
  • 108,386
  • 14
  • 159
  • 186
  • Thanks, your solution worked - what would be the advantage of having the multiple elements in each dictionary as opposed to what I have now? I can try to change it if the current way is going to cause problems. – KingTim Apr 04 '17 at 23:27
  • 1
    `.keys.first` is an immediate red flag. Dictionary keys don't preserve ordering, so the `first` isn't defined. – Alexander Apr 04 '17 at 23:30
  • True, that is why I pointed out that the data structure is faulty. There is no way of knowing which key the user is after, since the key is variable. I have included a better approach – Paulw11 Apr 04 '17 at 23:38
  • @Paulw11 How is `key` `value` and `dictionary` random? And why wouldnt it be tagged you say? Doesnt it make reading easier or did I mess upp your answer in some way? Please let me know so I can avoid doing this in the future. –  Apr 04 '17 at 23:50
  • @Sneak key, value and dictionary, in the context of my answer, are just nouns. `let value = dictionary[key]` is code. Code tags are for *code*, not *emphasis* – Paulw11 Apr 04 '17 at 23:51
  • I see. I don't agree with you in this case since dictionary, key, and value is specific for the foundation object as you see https://developer.apple.com/reference/foundation/nsdictionary . But I will avoid editing your texts in the future as you wish. –  Apr 04 '17 at 23:58
  • Btw If the appe link is not convincing, you can check here. http://stackoverflow.com/questions/27144984/nslocalizedstring-shows-raw-key-instead-of-loading-a-string-from-another-languag/27145124#27145124 :) –  Apr 05 '17 at 00:05
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/139913/discussion-between-paulw11-and-sneak). – Paulw11 Apr 05 '17 at 00:06
1

I'm not clear about your data structure. If you have an array of dictionaries, and you want all the keys, you could use code like this:

let array = [
  ["key1": "value1",
   "key2": "value2",
   "key3": "value3",
   "key4": "value4"],
  ["key5": "value5",
   "key6": "value6",
   "key7": "value7",
   "key8": "value8"],
  ["key9": "value9",
   "key10": "value10",
   "key11": "value11",
   "key12": "value12"]
]
let keys = array
  .map{$0.keys}
  .reduce([], +)

print(keys)

That will give you an array of all the keys from all the dictionaries. The keys from each dictionary will be in a jumbled order however, since dictionaries are unordered. You'll get the keys from each inner dictionary in a jumbled order, followed by the keys from the next dictionary in the array, etc.

Sample output:

["key2", "key3", "key4", "key1", "key7", "key8", "key5", "key6", "key9", "key10", "key12", "key11"]

If you want to sort the keys, you can do that:

let keys = array
  .map{$0.keys}
  .reduce([], +)
  .sorted{$0.compare($1, options: .numeric) == .orderedAscending}

(In the above code I'm using the String class's compare(_:options:) function with an options value of .numeric so that "key11" sorts after "key10" instead of ["key1", "key11", "key12", "key2"], which you get from standard string ordering.)

The output of the sorted version (with the .numeric option) is:

["key1", "key2", "key3", "key4", "key5", "key6", "key7", "key8", "key9", "key10", "key11", "key12"]

Without the .numeric option, the output would be:

["key1", "key10", "key11", "key12", "key2", "key3", "key4", "key5", "key6", "key7", "key8", "key9"]

If your keys contain mixed upper/lower case and you want to ignore it, you'd use options of [.numeric, .caseInsensitive] (case insensitive sorting where numbers within strings are compared using numeric value.)

Duncan C
  • 128,072
  • 22
  • 173
  • 272
  • This is an awesome answer, thanks for this - I'm using Paul's suggested way but I'll definitely try this out too, I've never seen how to sort arrays like that so this will come in handy. – KingTim Apr 05 '17 at 13:37