0

I have really nasty errors while working with something apparently simple.

I have an NSArray which I fill with words the first time the app launches.

Then at a certain point I want to use the array, so I declared another array inside the class:

var localArray = NSUserDefaults.standardUserDefaults().arrayForKey("array")!

Then I want to display a word in a label. And when a user presses a button (forward/backward) the next/previous word appears. Pretty simple, right? It is, but somehow I get really nasty errors.

I have 2 methods for the next/previous words:

    @IBAction func nextWord(sender: AnyObject) {
        if let currentIndex = localArray.indexOf(label.text!)
        {
            if currentIndex == localArray.count-1
            {
                label.text = localArray[0] as? String
            }
            else
            {
                label.text = localArray[currentIndex+1]
            }
        }
    }

The other method for going backwards is identical (with necessary modifications).

And in viewDidLoad I just set the label's text:

label.text = localArray[0] as? String

The problem is with the if let statements. Before using the user defaults, I just initialized localArray with a few strings. And everything worked fine. But once I added the user defaults, madness began. I had errors everywhere I used localArray saying that the array isn't unwrapped. So I added ! after the user defaults array when initializing the local one.

But the last 2 errors (in the if let statements) make no sense. Now the indexOf() method throws an error, a thing that didn't happen before. And it also tells me that it can't convert the string (label.text!) to @noescape (AnyObject) throws -> Bool.

So now I need to transform that string to an NSString for that to work or what? I found something about "bridging" related to arrays in user defaults. Is that my issue?

Now here's what bugs me: WHY do I have to explicitly downcast a string in the array to a string? WHY did the indexOf method change.

Doesn't an unwrapped optional act like a non-optional?

Lawrence413
  • 1,976
  • 1
  • 14
  • 28
  • 2
    Is this pure Swift code? You shouldn't be using `NSArray` at all in that case. Use the native collection types instead, which you can freely bridge to `NSArray` to store in `NSUserDefaults` (as long as the elements are obj-c bridgeable). – Hamish May 10 '16 at 16:23
  • It's Swift only. What do you mean by using native collection types? – Lawrence413 May 10 '16 at 16:26
  • Well... the [collection types that Swift itself provides](https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/CollectionTypes.html#//apple_ref/doc/uid/TP40014097-CH8-ID105)! If you want an array of strings, then you want `[String]`. `String` can be freely bridged to `NSString`, so you can freely bridge the array to `NSArray`. – Hamish May 10 '16 at 16:29
  • Well that's what the array is before I put it in the user defaults. Then I use `.setObject` to set the array in the user defaults. I think that's where the problem is. – Lawrence413 May 10 '16 at 16:32

3 Answers3

1

The object you are creating is not an array (at least the compiler doesn't know it is). The function arrayForKey("array")! is returning an AnyObject, not an Array. So from there on out the compiler thinks this object is an AnyObject. How I would solve this (may not be the correct way) would be to cast the Array immediately:

if let myArray: AnyObject! = NSUserDefaults.standardUserDefaults().objectForKey("array") {
    //Your array exists, cast it now and use it
    var localArray =  myArray as! Array<String>
}
else
{
    //Something bad happened, the array isn't there.
}

Then you will have to do unwrapping later on. If you know this object will always exist and the cast will always work you can change the ? to !.

Look to this answer for a little more information.

Community
  • 1
  • 1
Putz1103
  • 6,211
  • 1
  • 18
  • 25
0

This would be a lot better if it were implemented with the modulo operator.

let newIndex = (oldIndex + 1) % localArray.count

I need more information to answer the bulk of your question. What's the type of the localArray?

Alexander
  • 59,041
  • 12
  • 98
  • 151
  • I didn't specify it. But it should be `NSArray`. What should I replace to use `newIndex`? It's still using `currentIndex` so it fixes nothing. – Lawrence413 May 10 '16 at 16:21
  • I renamed `currentIndex` for clarity. – Alexander May 10 '16 at 16:23
  • What do you mean "I didn't specify it. But it should be NSArray"? You put that array in the user defaults, what type is it? I'm not asking what it **should** be, frankly, that's irrelevant. What **is** it? – Alexander May 10 '16 at 16:24
  • You asked about `localArray`. That one is initialized in the scene. The one which I put in the user defaults is `[String]`. – Lawrence413 May 10 '16 at 16:29
0

You should always avoid force unwrapping optionals (and for the most-part force downcasting). In order to safely retrieve your array from NSUserDefaults (with the correct type), you can do the following:

if let array = NSUserDefaults.standardUserDefaults().arrayForKey("array") as? [String] {
    // do something with your [String] array
} else {
    // do error handling
}

What this will do is get an [AnyObject]? from NSUserDefaults, representing your array. You can then use as? to conditionally downcast & bridge it back over to a Swift array of strings ([String]). If this was unsuccessful, you can safely handle that eventuality.

Community
  • 1
  • 1
Hamish
  • 78,605
  • 19
  • 187
  • 280