using NSUserDefaults to save an array of objects - swift2

I am new to swift, and I am trying to build a game which will include a top 5 players name and score, if i just add the players to an array and than restart the game it "deletes" the players, so i am trying to use NSUserDefaults(i need to store just 5 strings and 5 integers), it does not work no matter what,
the code is:
class scoreController: UITableViewController {
var playersArray:[Player] = [Player]()
var nameFromGame = "" //name from game vc
var timeFromGame = 0 //time from game vc
let tmpPlayer = Player(playerName: "", scoreTime: 0)
let playerDefaults = NSUserDefaults.standardUserDefaults()
override func viewDidLoad() {
super.viewDidLoad()
let player1 = Player(playerName: "Bil", scoreTime: 50)
let player2 = Player(playerName: "Bob", scoreTime: 100)
playersArray.append(player1)
playersArray.append(player2)
tmpPlayer.playerName = nameFromGame
tmpPlayer.scoreTime = timeFromGame
playersArray.append(tmpPlayer)
playerDefaults.setObject(playersArray[11], forKey: "players")
print(playersArray )
}
}
I am just trying to save this for now and it crashes, does anyone know why? and also how can i store this in my app so it will save the data?
thank you!

Your player objects probably are the problem here. You must implement two methods in your player class (I am no swift master but it's probaby the same mistake):
- (void)encodeWithCoder:(NSCoder *)encoder;
- (id)initWithCoder:(NSCoder *)decoder;
That should work for you.
Hope it helps!!
PD: check this answer
How to store custom objects in NSUserDefaults

Your Player class needs to conform to NSCoding, and you'll need to store an archived data of your players array and unarchive it when extracting out the data.
Class:
class Player: NSObject, NSCoding {
var playerName: String
var scoreTime: Int
init(playerName: String, scoreTime: Int) {
self.playerName = playerName
self.scoreTime = scoreTime
}
func encodeWithCoder(aCoder: NSCoder) {
aCoder.encodeInteger(scoreTime, forKey: "score_time")
aCoder.encodeObject(playerName, forKey: "player_name")
}
required convenience init?(coder decoder: NSCoder) {
guard let playerName = decoder.decodeObjectForKey("player_name") as? String else {
return nil
}
self.init(playerName: playerName, scoreTime: decoder.decodeIntegerForKey("score_time"))
}
}
NSUserdefaults & Archiving/Unarchiving :
let player1 = Player(playerName: "Bil", scoreTime: 50)
let player2 = Player(playerName: "Bob", scoreTime: 100)
let playersArray = [player1, player2]
let playersData = NSKeyedArchiver.archivedDataWithRootObject(playersArray)
let defaults = NSUserDefaults.standardUserDefaults()
defaults.setObject(playersData, forKey: "players")
defaults.synchronize()
if let playersArrayData = defaults.objectForKey("players") as? NSData {
let unarchivedPlayers = NSKeyedUnarchiver.unarchiveObjectWithData(playersArrayData) as! [Player]
print(unarchivedPlayers)
}
Hope this helps, please remember to choose answer and up-vote if this solves your question.

You need to could call
playerDefaults.synchronize()
to save the data in the defaults.
EDIT
As facumenzella says, your other problem is storing custom class objects in the defaults. To solve that you need to implement methods in the custom Player class that tell it how to be converted to data. Then you should serialize the object before you store it in the defaults.

Related

How to use an array to fill the title text

I've created a customized keyboard using UIView. I'm trying to auto-fill the letters using a for loop but with no success.
func initializeSubviews() {
let xibFileName = "keyboardView" // xib extension not included b
let view = Bundle.main.loadNibNamed(xibFileName, owner: self, options: nil)![0] as! UIView
self.addSubview(view)
view.frame = self.bounds
setKeyboardText()
}
#IBOutlet var keyboardLetters: [myKeyboardBtn]!
func setKeyboardText() {
let str = "abcdefghijklmnopqrstuvwxyz"
let characterArray = Array(str)
for (Index, key) in keyboardLetters.enumerated() {
key.titleLabel?.text = String(characterArray[Index])
}
// [a,b,c,d,...]
}
what am I doing wrong?
According to Apple
"To set the actual text of the label, use setTitle(_:for:)
(button.titleLabel.text does not let you set the text)."

SceneKit Swift3 compiler-error

I'm trying to run animated "DAE" model in SceneKit:
let url = Bundle.main.url(forResource: "art.scnassets/Player(walking)", withExtension: "dae")
let sceneSource = SCNSceneSource(url: url!, options: [SCNSceneSource.LoadingOption.animationImportPolicy: SCNSceneSource.AnimationImportPolicy.playRepeatedly] )
let animationIds: NSArray = sceneSource?.identifiersOfEntries(withClass: CAAnimation)
for eachId in animationIds {
let animation: CAAnimation = (sceneSource?.entryWithIdentifier(eachId as! String, withClass: CAAnimation.self))!
animations.add(animation)
}
character?._walkAnimations = animations
But compiler It throws on the line:
let animationIds: NSArray = sceneSource?.identifiersOfEntries(withClass: CAAnimation)
and writes an error:
Cannot convert value of type '[String]?' to specified type 'NSArray'
Please help me to fix that problem.
Thanks in advance.
Why you are converting [String]? to NSArray and then convert each element to String again, no need of it simply use Swift native Array and wrapped the optional value with if let or guard let.
guard let animationIds = sceneSource?.identifiersOfEntries(withClass: CAAnimation) else {
return
}
for eachId in animationIds {
if let animation = sceneSource?.entryWithIdentifier(eachId, withClass: CAAnimation.self) {
animations.add(animation)
}
}
character?._walkAnimations = animations

NSCoder crash on decodeBool forKey (Xcode 8, Swift 3)

I have this simple class
import UIKit
class SimpleModel: NSObject, NSCoding {
var name : String!
var done : Bool!
init(name:String) {
self.name = name
self.done = false
}
internal required init?(coder aDecoder: NSCoder) {
self.name = aDecoder.decodeObject(forKey: "name") as! String
self.done = aDecoder.decodeBool(forKey: "done") // BUG HERE
}
func encode(with encoder: NSCoder) {
encoder.encode(self.name, forKey: "name")
encoder.encode(self.done, forKey: "done")
}
}
the save code:
let data = NSKeyedArchiver.archivedData(withRootObject: storageArray)
UserDefaults.standard.set(data, forKey: "storage")
UserDefaults.standard.synchronize()
the read code:
if let data = UserDefaults.standard.data(forKey: "storage") {
storageArray = NSKeyedUnarchiver.unarchiveObject(with: data) as! [SimpleModel]
}
the problem occurs when the NSKeyedUnarchiver does it's job. I can not understand where the problem comes from.
Thanks!
the trick is remove ! form the primitive types.
If you put ! you are saying "make an implicit-unwrapped optional" so the encoder will archive as NSNumber instead of Bool (or Int, Double).
If you remove ! the encoder will archive as Bool and things works as expected (I spent an "incident" and this solution is provided by Apple)
Bool and Int have new methods:
self.x = Int(decoder.decodeCInt(forKey: "Speed"))
self.y = decoder.decodeObject(forKey: "Serial") as! String
self.z = Bool(decoder.decodeBool(forKey: "Direction") )
I had the same Problem.
Try this:
self.done = aDecoder.decodeObject(forKey: "done") as? Bool ?? aDecoder.decodeBool(forKey: "done")

How to fix `Ambiguous use of 'subscript'` every time?

I'm using this class that was written in Swift 1.2 and now I want to use it with Swift 2.0.
I get an error: Ambiguous use of 'subscript' # let artist = result["name"] as! String
} else if let jsonArtists = jsonResult["artists"] as? NSDictionary {
if let results:NSArray = jsonArtists["items"] as? NSArray {
dispatch_async(dispatch_get_main_queue(), {
self.searching = false
var suggestionResults: [spotifySearchResult] = []
for result in results {
let artist = result["name"] as! String
var sugresult = spotifySearchResult()
sugresult.artist = artist
if !suggestionResults.contains(sugresult) {
suggestionResults.append(sugresult)
}
}
handler(suggestionResults)
})
}
}
}
I tried different fixes such as let artist = (result["name"] as! String) or let artist = (result["name"] as! String) as! String
But nothing worked.
I know that the question was already post 4 times but, I can't find anyone explaining how to fix it in every case, only case by case.
Can someone explain me how to investigate to fix it? Not just only a fix for my case. I would prefer fix it by myself with your hints!
BTW what does mean subscript? Is subscript the thing between quotation mark? IMHO the error message is a bit vague...
EDIT:
class spotifySearchResult : NSObject {
var artist=""
var track=""
var duration=0
var spotifyURL = NSURL()
var spotifyURI = NSURL()
override func isEqual(theObject: AnyObject?) -> Bool {
if let myObject = theObject as? spotifySearchResult {
return (myObject.artist.uppercaseString == self.artist.uppercaseString && myObject.track.uppercaseString == self.track.uppercaseString)
}
return false
}
}
Subscription means to use the shorter syntax item["key"] for item.objectForKey["key"]
results seems to be an array of dictionaries so I suggest to cast down to a more specific type
if let results = jsonArtists["items"] as? [[String:AnyObject]] {
or even, if all values are guaranteed to be strings
if let results = jsonArtists["items"] as? [[String:String]] {

Swift save multiple strings to core data

I can save 1 string to core data, but what is the best way to save multiple strings to core data with minimal code? This is what I am working with:
let appDel:AppDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
let context:NSManagedObjectContext = appDel.managedObjectContext!
let ent = NSEntityDescription.entityForName("Custom", inManagedObjectContext: context)
var newMessage = Custom(entity: ent!, insertIntoManagedObjectContext: context)
newMessage.words = "word1"
context.save(nil)
I've done this using the Transformable attribute type in CoreData, which maps to a variable of type id in Objective-C or AnyObject in Swift. You will be stuck with the overhead of having to unwrap and cast this to [String] or NSArray every time you want to work with it, but it'll get the job done.
Set up your entity like this:
And here's how your code would look:
class MyModel: NSManagedObject {
#NSManaged var strings: AnyObject?
}
var someModel = // Create your model
someModel.strings = [ "Hello", "World" ]
// Then save
// Later on, reload:
var reloadedModel = // Reload your model
if let strings = reloadedModel.strings as? [String] {
print( "Strings = \(strings)" )
}
Another way I've done this is to make a new CoreData entity that just has a "name" attribute which stores the string, and then the parent object has an NSSet or NSOrderedSet of those objects as a relationship. There is some cumbersome overhead to that as well, but it may be more appropriate depending on your needs.

Resources