Fenda
Fenda

Reputation: 1385

Swift loop over a Dictionary plist with multidimensional array

I have a plist that is set up as shown below:

enter image description here

I'd like to load this into a variable and then loop over each of the items. This is the code I have so far, but to no avail I am seeing the error "Type 'AnyObject' does not conform to protocol 'SequenceType'".

func startTournament(sender: UIBarButtonItem) {
    var map: NSDictionary?
    if let path = NSBundle.mainBundle().pathForResource("knockout_single_8", ofType: "plist") {
        map = NSDictionary(contentsOfFile: path)
    }

    var matches = NSMutableDictionary()
    let rounds = map?["rounds"] as NSArray
    for match in rounds[0] { // Error from this line
        let mid = match["mid"]
        let match = ["names": ["testA", "testB"]]
        matches[mid] = match
    }
}

Upvotes: 1

Views: 1644

Answers (1)

Airspeed Velocity
Airspeed Velocity

Reputation: 40965

The problem you are having is that the foundation classes deal in AnyObjects, but for thinks like for loops that doesn't work:

import Foundation
let o: AnyObject = [1,2,3]

// this won't work, even though o _is_ an array
// error: type 'AnyObject' does not conform to protocol 'SequenceType'
for i in o {

}

// this is perhaps surprising given this does:
o[0]  // returns 1 as an AnyObject! (that's a syntactic ! not a surprise/shock ! :)

You may find it easier to convert earlier to Swift types and then use them directly. The downside of this is if you are storing heterogenous data in your plist (i.e. an array that has both strings and integers in it). But it doesn't look you do, so you could write your code like this:

func startTournament() {
    if let path = NSBundle.mainBundle().pathForResource("proplist", ofType: "plist") {
        if let map = NSDictionary(contentsOfFile: path) {

            var matches: [Int:[String:[String]]] = [:]

            if let rounds = map["rounds"] as? [[[String:Int]]] {

                for match in rounds[0] {
                    if let mid = match["mid"] {
                        let match = ["names": ["testA", "testB"]]
                        matches[mid] = match
                    }
                }
            }

        }
    }
}

Personally, I find this much easier to understand at a glance because the types you are dealing with at each level are easier to see and understand and you're not mashing away with as? and is etc. The big downside to this is if you want to handle multiple different types within the same collection. Then you need to leave things as AnyObject a bit longer, and do more fine-grained as? in the inner loops.

Once you have the above, there are various tricks you can do to handle the optionals better and avoid so many horrid if lets, replace loops with maps and filters etc., but it's probably better to get comfortable with this version first. Also, this code is missing various else clauses that could handle and report failure cases.

Upvotes: 3

Related Questions