How to write an array to NSUserDefaults in Swift - macos

I'm trying to write the contents of an array to NSUserDefaults, but the app hangs when I call setObject:withKey with the array as the object. Here's the relevant code:
class Contact: NSObject {
var name:String = ""
var number:String = ""
}
var contacts:[Contact]?
contacts = [Contact]()
let contact = Contact()
contact.name = "Joe"
contact.number = "123-4567"
contacts.append(contact)
let defaults = NSUserDefaults.standardUserDefaults()
// Never returns from this when I step over it in the debugger
defaults.setObject(contacts, forKey: "contacts")
defaults.synchronize()
What am I doing wrong?

Not sure exactly how it works on OS X, but if it's anything like it is on iOS, you can't store custom classes in NSUserDefaults, only Strings, Ints, Data, etc... so one work around is to convert your array to NSData and then store it as data and when you retrieve it cast it to be [Contact]. It may look something like this:
let data = NSKeyedArchiver.archivedDataWithRootObject(contacts)
let defaults = NSUserDefaults.standardUserDefaults()
// Storing the data
defaults.setObject(data, forKey: "contacts")
// Retrieving the data
if let data = defaults.objectForKey("contacts") as? NSData {
if let contacts = NSKeyedUnarchiver.unarchiveObjectWithData(data) as? [Contact] {
// Now you have your contacts
}
}
However, if you are storing large amounts of data, you should probably consider other forms of data management.

Related

using NSUserDefaults to save an array of objects

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.

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.

If statements in swift

I am new to swift and trying to understand this code that I am reading out of a book. Can somebody explain the if statement that sets the private searches Dictionary? Also what does the statement pairs as [String : String] mean? I am confused about the term as.Also I don't understand how the if statement is executed if you are declaring a constant and not comparing it? Shouldn't the if statement check for something then execute the following code if that is true?
import Foundation
// delegate protocol enables Model to notify controller when data changes
protocol ModelDelegate {
func modelDataChanged()
}
// this will manage the saved searches
class Model {
// keys used for storing the app's data in app's NSUserDefaults
private let pairsKey = "TwitterSearchesKVPairs"
private let tagsKey = "TwitterSearchesKeyOrder"
private var searches: Dictionary <String, String> = [:] // stores tag-query pairs
private var tags: Array<String> = [] // stores tags in user-specified order
private let delegate: ModelDelegate // delegate is MasterViewController
// initializes the Model object aka constructor
init(delegate: ModelDelegate) {
self.delegate = delegate
// get the NSUserDefaults object for the app
let userDefaults = NSUserDefaults.standardUserDefaults()
// get Dictionary of the app's tag-query pairs
if let pairs = userDefaults.dictionaryForKey(pairsKey) {
self.searches = pairs as [String : String]
}
// get Array with the app's tag order
if let tags = userDefaults.arrayForKey(tagsKey) {
self.tags = tags as [String]
}
"if let" is for conditional unwrapping and used to safely unwrap an optional value. "as" is used for casting but in this case you need to use as? (conditional casting). You can combine both in just one sentence if you want:
// get Dictionary of the app's tag-query pairs
if let pairs = userDefaults.dictionaryForKey(pairsKey) {
self.searches = pairs as [String : String]
}
// get Array with the app's tag order
if let tags = userDefaults.arrayForKey(tagsKey) {
self.tags = tags as [String]
}
can also be written as :
if let searches = NSUserDefaults.standardUserDefaults().dictionaryForKey(pairsKey) as? [String : String] {
println(searches.description)
}
if let tags = NSUserDefaults.standardUserDefaults().arrayForKey(tagsKey) as? [String] {
println(tags.description)
}
// [String : String] means a Dictionary where its keys and values are String types
NSUserDefaults.standardUserDefaults().setObject(["book":"brown","car":"black"], forKey: "searches")
if let searches = NSUserDefaults.standardUserDefaults().dictionaryForKey("searches") as? [String : String] {
println(searches["book"]!) // "brown"
println(searches["car"]!) // "black"
}

How do I view the actual string variable of a variable/constant?

Environment: Debugging Parse return object within Xcode 6.1
I can see the text within the object structure but can't adequately view its assigned variable.
Here's the code:
func retrieveAllMediaFromParse() {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), {
let myData:PFQuery = PFQuery(className: kParseMedia)
myData.findObjectsInBackgroundWithBlock{
(objects:[AnyObject]!, error:NSError!)->Void in
if !(error != nil){
// for object in objects {
let myObject = objects[0] as PFObject
let format = myObject.objectForKey("format") as String
let access = myObject.objectForKey("access") as String
let media = myObject.objectForKey("media") as String
let owner = myObject.objectForKey("owner") as String
let text = myObject.objectForKey("text") as String
let thumbNail = myObject.objectForKey("thumbnail") as NSString
}
}
});
}
let text = myObject.objectForKey("text") as String
When I take the 'po' of the command string I get the correct interpretation:
However, when I do the same for the assigned variable (constant), I get the following:
How do I view the variable/constant to display the actual string?
When program is paused in the debugger, you can find the values of PFObject fields by going to the estimatedData line in the debugger.

Creating NSData from NSString in Swift

I'm trying to ultimately have an NSMutableURLRequest with a valid HTTPBody, but I can't seem to get my string data (coming from a UITextField) into a usable NSData object.
I've seen this method for going the other way:
NSString(data data: NSData!, encoding encoding: UInt)
But I can't seem to find any documentation for my use case. I'm open to putting the string into some other type if necessary, but none of the initialization options for NSData using Swift seem to be what I'm looking for.
In Swift 3
let data = string.data(using: .utf8)
In Swift 2 (or if you already have a NSString instance)
let data = string.dataUsingEncoding(NSUTF8StringEncoding)
In Swift 1 (or if you have a swift String):
let data = (string as NSString).dataUsingEncoding(NSUTF8StringEncoding)
Also note that data is an Optional<NSData> (since the conversion might fail), so you'll need to unwrap it before using it, for instance:
if let d = data {
println(d)
}
Swift 4 & 3
Creating Data object from String object has been changed in Swift 3. Correct version now is:
let data = "any string".data(using: .utf8)
In swift 5
let data = Data(YourString.utf8)
Here very simple method
let data = string.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)
Swift 4
let data = myStringVariable.data(using: String.Encoding.utf8.rawValue)
// Checking the format
var urlString: NSString = NSString(data: jsonData, encoding: NSUTF8StringEncoding)
// Convert your data and set your request's HTTPBody property
var stringData: NSString = NSString(string: "jsonRequest=\(urlString)")
var requestBodyData: NSData = stringData.dataUsingEncoding(NSUTF8StringEncoding)!
To create not optional data I recommend using it:
let key = "1234567"
let keyData = Data(key.utf8)
Convert String to Data
extension String {
func toData() -> Data {
return Data(self.utf8)
}
}
Convert Data to String
extension Data {
func toString() -> String {
return String(decoding: self, as: UTF8.self)
}
}
Swift 4.2
let data = yourString.data(using: .utf8, allowLossyConversion: true)

Resources