If statements in swift - xcode

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

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

Passing Dictionary to Watch

I'm trying to pass data from iPhone -> Watch via Watch Connectivity using background transfer via Application Context method.
iPhone TableViewController
private func configureWCSession() {
session?.delegate = self;
session?.activateSession()
print("Configured WC Session")
}
func getParsePassData () {
let gmtTime = NSDate()
// Query Parse
let query = PFQuery(className: "data")
query.whereKey("dateGame", greaterThanOrEqualTo: gmtTime)
query.findObjectsInBackgroundWithBlock { (objects:[AnyObject]?, error:NSError?) -> Void in
if error == nil
{
if let objectsFromParse = objects as? [PFObject]{
for MatchupObject in objectsFromParse
{
let matchupDict = ["matchupSaved" : MatchupObject]
do {
try self.session?.updateApplicationContext(matchupDict)
print("getParsePassData iPhone")
} catch {
print("error")
}
}
}
}
}
}
I'm getting error twice printed in the log (I have two matchups in Parse so maybe it knows there's two objects and thats why its throwing two errors too?):
Configured WC Session
error
error
So I haven't even gotten to the point where I can print it in the Watch app to see if the matchups passed correctly.
Watch InterfaceController:
func session(session: WCSession, didReceiveApplicationContext applicationContext: [String : AnyObject]) {
let matchupWatch = applicationContext["matchupSaved"] as? String
print("Matchups: %#", matchupWatch)
}
Any ideas? Will post any extra code that you need. Thanks!
EDIT 1:
Per EridB answer, I tried adding encoding into getParsePassData
func getParsePassData () {
let gmtTime = NSDate()
// Query Parse
let query = PFQuery(className: "data")
query.whereKey("dateGame", greaterThanOrEqualTo: gmtTime)
query.findObjectsInBackgroundWithBlock { (objects:[AnyObject]?, error:NSError?) -> Void in
if error == nil
{
if let objectsFromParse = objects as? [PFObject]{
for MatchupObject in objectsFromParse
{
let data = NSKeyedArchiver.archivedDataWithRootObject(MatchupObject)
let matchupDict = ["matchupSaved" : data]
do {
try self.session?.updateApplicationContext(matchupDict)
print("getParsePassData iPhone")
} catch {
print("error")
}
}
}
}
}
}
But get this in the log:
-[PFObject encodeWithCoder:]: unrecognized selector sent to instance 0x7fbe80d43f30
*** -[NSKeyedArchiver dealloc]: warning: NSKeyedArchiver deallocated without having had -finishEncoding called on it.
EDIT 2:
Per EridB answer, I also tried just pasting the function into my code:
func sendObjectToWatch(object: NSObject) {
//Archiving
let data = NSKeyedArchiver.archivedDataWithRootObject(MatchupObject)
//Putting it in the dictionary
let matchupDict = ["matchupSaved" : data]
//Send the matchupDict via WCSession
self.session?.updateApplicationContext(matchupDict)
}
But get this error on the first line of the function:
"Use of unresolved identifer MatchupObject"
I'm sure I must not be understanding how to use EridB's answer correctly.
EDIT 3:
NSCoder methods:
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
//super.init(coder: aDecoder)
configureWCSession()
// Configure the PFQueryTableView
self.parseClassName = "data"
self.textKey = "matchup"
self.pullToRefreshEnabled = true
self.paginationEnabled = false
}
Error
You are getting that error, because you are putting a NSObject (MatchupObject) which does not conform to NSCoding inside the dictionary that you are going to pass.
From Apple Docs
For most types of transfers, you provide an NSDictionary object with
the data you want to send. The keys and values of your dictionary must
all be property list types, because the data must be serialized and
sent wirelessly. (If you need to include types that are not property
list types, package them in an NSData object or write them to a file
before sending them.) In addition, the dictionaries you send should be
compact and contain only the data you really need. Keeping your
dictionaries small ensures that they are transmitted quickly and do
not consume too much power on both devices.
Details
You need to archive your NSObject's to NSData and then put it in the NSDictionary. If you archive a NSObject which does not conform to NSCoding, the NSData will be nil.
This example greatly shows how to conform a NSObject to NSCoding, and if you implement these things then you just follow the code below:
//Send the dictionary to the watch
func sendObjectToWatch(object: NSObject) {
//Archiving
let data = NSKeyedArchiver.archivedDataWithRootObject(MatchupObject)
//Putting it in the dictionary
let matchupDict = ["matchupSaved" : data]
//Send the matchupDict via WCSession
self.session?.updateApplicationContext(matchupDict)
}
//When receiving object from the other side unarchive it and get the object back
func objectFromData(dictionary: NSDictionary) -> MatchupObject {
//Load the archived object from received dictionary
let data = dictionary["matchupSaved"]
//Deserialize data to MatchupObject
let matchUpObject = NSKeyedUnarchiver.unarchiveObjectWithData(data) as! MatchupObject
return matchUpObject
}
Since you are using Parse, modifying an object maybe cannot be done (I haven't used Parse in a while, so IDK for sure), but from their forum I found this question: https://parse.com/questions/is-there-a-way-to-serialize-a-parse-object-to-a-plain-string-or-a-json-string which can help you solve this problem easier than it looks above :)

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.

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.

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