TheAppleMan
TheAppleMan

Reputation: 11

Access a plist in Swift (error: "Expressions are not allowed at the top level")

I have a plist that I would like to access as a Swift Dictionary in Xcode. I'm able to define the contents of the file as an NSDictionary but every time I try to access a specific value/object from it, it shows the error "Expressions are not allowed at the top level". I've tried bracket notation, type casting and class methods (with dot notation) using autocompletion.

My plist contains dictionaries, which contain dictionaries that contain dictionaries with CGFloats and Strings.

I would like to access the instance's values.

Here's the code I use to define it, which is at the beginning of my GameScene.swift (it's a SpriteKit game):

let levelBlocks = NSDictionary(contentsOfFile: NSBundle.mainBundle().pathForResource("LevelBlocks", ofType: "plist"))

I would like to be able to do:

levelBlocks["Level1"]

EDIT: Added more information.

I've also tried putting it in the ViewDidLoad(), init() and using a generic that conforms to Hashable inside of a function instead of AnyObject/Any. I still can't access the String.

EDIT 2: I've tried this in the ViewDidLoad(), it returns the error 254:

let levelBlocks = NSDictionary(contentsOfFile: NSBundle.mainBundle().pathForResource("LevelBlocks", ofType: "plist")) as Dictionary<String, Dictionary<String, Dictionary<String, String>>>
let test = levelBlocks["Level1"] as Dictionary<String, Dictionary<String, String>>
let test1 = test["Block0"] as Dictionary<String, String>
let test2 = test1["color"] as String
println(test2)

Upvotes: 0

Views: 4572

Answers (2)

TheAppleMan
TheAppleMan

Reputation: 11

Here's the solution I found:

let levelBlocks = NSDictionary(contentsOfFile: NSBundle.mainBundle().pathForResource("LevelBlocks", ofType: "plist"))
let test: AnyObject = levelBlocks.objectForKey("Level1")
println(test) // Prints the value of test

I set the type of test to AnyObject to silence a warning about an unexpected inference that could occur.

Also, it has to be done in a class method.

To access and save a specific value of a known type:

let value = levelBlocks.objectForKey("Level1").objectForKey("amount") as Int
println(toString(value)) // Converts value to String and prints it

Upvotes: 1

Nate Cook
Nate Cook

Reputation: 93276

Xcode gives that error when you have an expression outside of a class or instance method. Declaring a global variable like levelBlocks is okay, but trying to call methods on a variable (which is what subscripting does) is an expression and not allowed there. If you're just testing things out, you can create another global:

let levelBlocks = NSDictionary(contentsOfFile: NSBundle.mainBundle().pathForResource("LevelBlocks", ofType: "plist"))
let level1 = levelBlocks["level1"]

but really any kind of manipulation should happen inside your class.

Note: This applies to ".swift" files -- playgrounds are built just for this sort of informal experimentation. Does your attempted code work in a playground?


Here's a breakdown of a Swift file that contains a class with the different levels scope shown in comments:

// MyViewController.swift

let globalNumber = 10           // global scope: can be accessed from 
                                // anywhere in this module.

class MyViewController : ViewController {
    var instanceNumber = 5      // instance scope: instances of this class each
                                // maintain their own value. should be
                                // accessed within methods using self.varname

    override func viewDidLoad() {
        super.viewDidLoad()

        var localNumber = self.instanceNumber * globalNumber
                                // local scope: will disappear after this method
                                // finishes executing (in most cases)
    }
}

In your case, you'll want to load the plist and access its values inside one of your class, probably the init or a ...DidLoad method.

As for pulling items out of the dictionary, keep in mind that an NSDictionary is bridged directly to Swift as a Dictionary, so you can load and parse it like this:

let path = NSBundle.mainBundle().pathForResource("LevelBlocks", ofType: "plist")
if let levelBlocks = NSDictionary(contentsOfFile: path) as? Dictionary<String, AnyObject> {
    let level1 = levelBlocks["level1"]                    // level1 is of type AnyObject?
    let level2 = levelBlocks["level2"] as NSString        // type NSString
    let level3: String = levelBlocks["level3"] as NSString     // type String
}

Upvotes: 0

Related Questions