Swift save multiple strings to core data - xcode

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.

Related

Swift2 access component with string-name

Im more familiar with ActionScript3 and see many similarities in Swift2, kind of why i am trying out basic coding in Swift2 and Xcode.
Here's my example:
#IBOutlet weak var b1CurrSpeed: NSTextField!
I want to store b1CurrSpeed as a string so i could access the actual textfield component to set its default value when application is loaded.
I'm aiming for Swift2 for osx apps.
Here is a fictional example, not related to any actual code:
var tf:NSTextField = this.getItem("b1CurrSpeed");
tf.stringValue = "Hello world";
Reason to this approach is following...
I would like to store textfield value in NSUserDefaults, the key for defaults would be name of that textfield. So when looping thru the defaults, i would like to get key as string and when ive got that i'd have access to actual component to set its stringvalue property.
Tho, is that good approach in Swift / xCode ?
If you want to create a function for it, do someting like this:
func getStringForKey(key: String) -> String {
guard let result = NSUserDefaults.standardUserDefaults().objectForKey(key) as! String else { return "" }
return result
}
You can set the TextFields value with myTextField.text
Swift's Mirror type can get you close to it but it is limited to NSObject subclasses, can only access stored properties and is read-only.
Yet, there are ways around these limitations if your requirements will allow.
For example, here's an extension that will save and restore defaults values for all UITextfields on a view controller using the name you gave to each IBOutlet.
extension UIViewController
{
func loadDefaults(userDefaults: NSUserDefaults)
{
for prop in Mirror(reflecting:self).children
{
// add variants for each type/property you want to support
if let field = prop.value as? UITextField,
let name = prop.label
{
if let defaultValue = userDefaults.objectForKey(name) as? String
{ field.text = defaultValue }
}
}
}
func saveDefaults(userDefaults: NSUserDefaults)
{
for prop in Mirror(reflecting:self).children
{
if let field = prop.value as? UITextField,
let name = prop.label
{
if let currentValue = field.text
{ userDefaults.setObject(currentValue, forKey: name) }
}
}
}
}

Could not cast value of type 'RealmSwift.DynamicObject' to MyCustomObject during migration

During RealmSwift migration, i want to migrate from dynamic var customObject: CustomObject to let customObjectList = List<CustomObject>(). CustomObject is type of Object
This is the chunk of code within migration
let newList = List<CustomObject>()
if oldObject!["customObject"] != nil {
print(oldObject!["customObject"])
var obj = oldObject!["customObject"]
var result: CustomObject = obj as! CustomObject //Crash
farList.append(result)
}
newObject!["customObjectList"] = newList
Could not cast value of type 'RealmSwift.DynamicObject' (0x1015c82d0) to 'AppName.CustomObject' (0x1006e5550).
How do i achieve what i want? Currently what i can think of is to create a CustomObject & manually assign the values to it.
EDIT 1
I want to add a primaryKey to CustomObject. I keep getting duplicate primary key error, i'm pretty sure the key assigned is unique.
fatal error: 'try!' expression unexpectedly raised an error: Error Domain=io.realm Code=0 "Primary key property 'resultKey' has duplicate values after migration."
migration.deleteData(CustomObject.className())
if oldObject!["customObject"] != nil {
let oldSubFar = oldObject!["customObject"] as! MigrationObject
var newFarDict = oldSubFar.dictionaryWithValuesForKeys(["firstName","secondName"])
newFarDict["resultKey"] = NSUUID().UUIDString + "v1"
let newSubFar = migration.create(CustomObject.className(), value: newFarDict )
print(newSubFar) //its the updated object that i want
let subFarList = newObject!["customObjectList"] as! List<MigrationObject>
subFarList.append(newSubFar)
}
EDIT 2
I manage to figure out what is the error by making resultKey not a primary key. The app runs perfectly and when i open .realm to see the values, there are some fields with "" under resultKey -> The duplicated primary key. ><
I think what you'd like to do is like following:
Delete all CustomObject data if needed, because Migration List object cannot append existing objects.
Then you can enumerate User objects, and create each CustomObject from User's property. And new User object has customObject property, then append the CustomObject object to the list.
migration.deleteData(CustomObject.className()) // If needed
migration.enumerate(User.className()) { oldObject, newObject in
if let oldObject = oldObject,
let newObject = newObject {
let oldCustomObject = oldObject["customObject"] as! MigrationObject
let newCustomObject = migration.create(CustomObject.className(), value: oldCustomObject)
let customObjectList = newObject["customObjectList"] as! List<MigrationObject>
customObjectList.append(newCustomObject)
}
}

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"
}

casting arrays returned by NSURL.getResourceValue in Swift

I have spent all day trying to get useable results from NSURL.getResourceValue for NSURLTagNamesKey in swift. The function should take the path name as a string and return an array of strings for the user tags. I have a version of this that works in Objective C, but have not been able to re-write in Swift.
This is the current version of the code:
func listTags(filePath:String)->[String]{
//convert path string to NSURL
let theURL : NSURL = NSURL.fileURLWithPath(filePath)!
//get tags for NSURL -- should be NSArray of NSStrings hiding in an AnyObject?
var tags : AnyObject?
var anyError: NSError?
tags = theURL.getResourceValue(&tags, forKey:NSURLTagNamesKey, error: &anyError)
//unwrap tags object? This part never works
let tagArray = tags! as [AnyObject]
//insert every item in tag array into results array as a String
var results = [String]()
for object in tagArray{
results.append(object as String)
}
return results
}
The code will compile but breaks when it tries to convert the AnyObject to any other type. I have tried every combination I can think of -- [AnyObject], [String], NSArray, with/without exclamation points and question marks.
Am on verge of giving up on Swift.
You're going to kick yourself...
The method getResourceValue:forKey:error returns a value - a Bool, indicating whether the container you passed in as the first argument has been populated. Unfortunately you're assigning the value of this boolean to tags - your container! - which means that whatever was passed in to this container by Cocoa is immediately over-written. This worked for me...
var tags : AnyObject?
var anyError: NSError?
var success = theURL.getResourceValue(&tags,
forKey:NSURLTagNamesKey,
error: &anyError)
if success {
println("container contents \(tags as [String])") // -> [AutoLayout, Swift]
}
With Swift 2, getResourceValue(:forKey:) returns () throws, i.e., void type which throws errors, so the answer above will no longer work. It needs to be wrapped in a do {try} catch{} construction without the anyError variable:
do {
try theURL.getResourceValue(&tags, forKey: NSURLTagNamesKey)
return tags as! [String]
} catch {
// process the error here
}

How to write an array to NSUserDefaults in Swift

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.

Resources