I'm trying to get the data out of a notification in swift 3, using this tutorial: Developing Push Notifications for iOS 10 Unfortunately I get the following error:
private func getAlert(notification: [NSObject:AnyObject]) -> (String, String) {
let aps = notification["aps"] as? [String:AnyObject]
let alert = aps["alert"] as? [String:AnyObject]
let title = alert?["title"] as? String
let body = alert?["body"] as? String
return (title ?? "-", body ?? "-")
}
The issue is that notification is declared as a dictionary with keys of type NSObject. But you attempt to access that dictionary with a key of type String. String is not an NSObject. One solution is to cast your String to NSString.
Fixing that presents another error which is fixed on the next line. So your code ends up like this:
private func getAlert(notification: [NSObject:AnyObject]) -> (String, String) {
let aps = notification["aps" as NSString] as? [String:AnyObject]
let alert = aps?["alert"] as? [String:AnyObject]
let title = alert?["title"] as? String
let body = alert?["body"] as? String
return (title ?? "-", body ?? "-")
}
Having said all of that, that tutorial has a lot of mistakes and uses the wrong parameter types in many places. This getAlert method should not be using NSObject. It should be String.
Related
I have a database in firebase which looks as follows:
I need to get the values of nameID, tutorID, and imageURL and assign them to variables in Swift 4. Here is what I have so far in XCode:
let ref = Database.database().reference().child("students").child("student1")
ref.observe(.childAdded, with: { (snapshot) in
print(snapshot)
guard let dictionary = snapshot.value as? [String : AnyObject] else {
return
}
print (dictionary)
let Obj = studentInformation(nameID: " ", tutorID: " ", imageURL: " ")
Obj.imageURL = dictionary["photoID"] as? String
Obj.nameID = dictionary["nameID"] as? String
Obj.tutorID = dictionary["tutorID"] as? String
self.studentInfo.append(Obj)
}, withCancel: nil)
For the studentInformation class, I have declared it as such:
class studentInformation {
var nameID: String?
var tutorID: String?
var imageURL: String?
init(nameID: String?, tutorID: String?, imageURL: String?) {
self.nameID = nameID
self.tutorID = tutorID
self.imageURL = imageURL
}
}
I can't seem to get it to work correctly, as it's able to get the values from the database, but it is not able to assign it to the local variables I have in XCode. Any help would be appreciated. Thanks in advance
Create an optional initializer for in the Object and determine which variables should be optional (ex: only the imageURL is optional in the example below, and the nameID and tutorID have to be Strings otherwise the init will return nil):
init?(dictionary: [String : Any]) {
guard let nameId = dictionary["nameID"] as? String,
let tutorID = dictionary["tutorID"] as? String else { return nil }
let imageURL = dictionary["imageURL"] as? String
self.init(nameID: nameID, tutorID: tutorID, imageURL: imageURL)
}
Then, in the Firebase listener you can create the object like this:
// Returns Optional(obj)
let obj = studentInformation(dictionary: dictionary)
or
// Add object to array
if let obj = studentInformation(dictionary: dictionary) { self.studentInfo.append(obj) }
I am trying to pull articles from my database in firebase, but am getting a SIGABRT error in the let author part of this function, what is wrong?
func getAllArticles(handler: #escaping (_ articles: [Article])-> ()){
var articleArray = [Article]()
REF_ARTICLES.observeSingleEvent(of: .value) { (articleMessageSnapshot) in
guard let articleMessageSnapshot = articleMessageSnapshot.children.allObjects as? [DataSnapshot] else {return}
for article in articleMessageSnapshot {
let content = article.childSnapshot(forPath: "content").value as! String
let author = article.childSnapshot(forPath: "author").value as! String
let twitterHandle = article.childSnapshot(forPath: "twitterHandle").value as! String
let articleTitle = article.childSnapshot(forPath: "articleTitle").value as! String
let article = Article(content: content, author: author, twitterHandle: twitterHandle, ArticleTitle: articleTitle)
articleArray.append(article)
}
handler(articleArray)
}
}
I know nothing about Firebase, but from what you have posted, I don't think that is your problem.
This code article.childSnapshot(forPath: "author").value is returning NSNull meaning it found no values to match your criteria. The code as! String is literally saying to crash if the value is not a String. Since NSNull is not String you get the crash.
One solution:
let author = article.childSnapshot(forPath: "author").value as? String ?? "No Author"
Any maybe do that with all the lines. Also read about Swift optionals and Forced Unwrapping.
Please dont' mark as duplicate of "Value of type Any has no member value". I'm not asking how to solve the compiler error.
My question is: What is the best practise to handle nested Dictionaries with the new need to downcast everything?
My code in Swift 2 was:
if result != nil {
let token = String((result.value(forKey: "credentials")?.value(forKey: "token"))!)
let uid = String((result.value(forKey: "uid"))!)
let bio = String((result.value(forKey: "extra")?.value(forKey: "raw_info")?.value(forKey: "data")?.value(forKey: "bio"))!)
let followed_by = String((result.value(forKey: "extra")?.value(forKey: "raw_info")?.value(forKey: "data")?.value(forKey: "counts")?.value(forKey: "followed_by"))!)
let follows = String((result.value(forKey: "extra")?.value(forKey: "raw_info")?.value(forKey: "data")?.value(forKey: "counts")?.value(forKey: "follows"))!)
let media = String((result.value(forKey: "extra")?.value(forKey: "raw_info")?.value(forKey: "data")?.value(forKey: "counts")?.value(forKey: "media"))!)
let username = String((result.value(forKey: "user_info")?.value(forKey: "username"))!)
let image = String((result.value(forKey: "user_info")?.value(forKey: "image"))!)
self.saveAccount(token, uid: uid, bio: bio, followed_by: followed_by, follows: follows, media: media, username: username, image: image)
}
I know that the error appears because I need to downcast since Swift 3.
But since I'm querying a nested Dictionary, every single step needs to be downcasted as AnyObject and within 10 lines of code I cast as AnyObject like 50 times. And some lines are like 1 Billion characters long...
if result != nil {
let token = String(describing: (((result as! NSDictionary).value(forKey: "credentials") as AnyObject).value(forKey: "token"))!)
let uid = String(describing: ((result as! NSDictionary).value(forKey: "uid"))!)
let bio = String(describing: (((((result as! NSDictionary).value(forKey: "extra") as AnyObject).value(forKey: "raw_info") as AnyObject).value(forKey: "data") as AnyObject).value(forKey: "bio"))!)
let followed_by = String(describing: ((((((result as! NSDictionary).value(forKey: "extra") as AnyObject).value(forKey: "raw_info") as AnyObject).value(forKey: "data") as AnyObject).value(forKey: "counts") as AnyObject).value(forKey: "followed_by"))!)
let follows = String(describing: ((((((result as! NSDictionary).value(forKey: "extra") as AnyObject).value(forKey: "raw_info") as AnyObject).value(forKey: "data") as AnyObject).value(forKey: "counts") as AnyObject).value(forKey: "follows"))!)
let media = String(describing: ((((((result as! NSDictionary).value(forKey: "extra") as AnyObject).value(forKey: "raw_info") as AnyObject).value(forKey: "data") as AnyObject).value(forKey: "counts") as AnyObject).value(forKey: "media"))!)
let username = String(describing: (((result as! NSDictionary).value(forKey: "user_info") as AnyObject).value(forKey: "username"))!)
let image = String(describing: (((result as! NSDictionary).value(forKey: "user_info") as AnyObject).value(forKey: "image"))!)
self.saveAccount(token, uid: uid, bio: bio, followed_by: followed_by, follows: follows, media: media, username: username, image: image)
}
For example let followed_by
let followed_by =
String(describing: ((((((result as! NSDictionary)
.value(forKey: "extra") as AnyObject)
.value(forKey: "raw_info") as AnyObject)
.value(forKey: "data") as AnyObject)
.value(forKey: "counts") as AnyObject)
.value(forKey: "followed_by"))!)
I have another function where I query for 25+ objects and downcast like 100 times....
I know how to get the result, but is there a more advanced way to handle this scenario? Or at least to have it appear more readable? Help is very appreciated.
PS: It doesn't makes any difference if downcasted as NSDictionary or AnyObject, but since I know what I'm downcasting, I prefer to cast it as NSDictionary over AnyObject.
I guess the rationale there is that Swift tries to err on the side of certainty which makes dealing with NSDictionary data a little bit trickier.
It's hard to optimize not seeing the actual structure of 'result', but here's an attempt:
Assuming a data structure similar to:
let result = [
"credentials": [
"token": "token"
],
"uid": "uid",
"extra": [
"raw_info": [
"data": [
"bio": "bio",
"counts": [
"followed_by": 100,
"follows": 100,
"media": 100
]
]
]
],
"user_info": [
"username": "username",
"image": "image"
]
] as [String : Any]
This code can "parse" it into variables (tested in a playground):
if let result = result as? [String : Any] {
let token = (result["credentials"] as! [String:String])["token"]
let uid = result["uid"]
let data = (((result["extra"] as! [String : Any])["raw_info"] as! [String : Any])["data"] as! [String : Any])
let bio = data["bio"] as! String
let counts = data["counts"] as! [String:Int]
let followed_by = counts["followed_by"]
let follows = counts["follows"]
let media = counts["media"]
let userInfo = result["user_info"] as! [String : String]
let username = userInfo["username"]
let image = userInfo["image"]
// ...
}
The code could have been even more concise, had the structure of "extra" been known.
First of all and second Do not: Do not use NSDictionary in Swift. You throw away the type information.
And yes, you have to downcast the intermediate objects but always to something the compiler can safely work with (Swift Dictionary or Array).
Since all intermediate objects are obviously dictionaries cast them to [String:Any].
Here is a short example. For clarity a type alias for [String:Any] is used.
typealias JSONDictionary = [String:Any]
This extracts the objects in the extra node
if let result = result as? JSONDictionary {
let token = (result["credentials"] as! JSONDictionary)["token"] as! String
let uid = result["uid"] as! String
if let extra = result["extra"] as? JSONDictionary,
let rawInfo = extra ["raw_info"] as? JSONDictionary,
let data = rawInfo["data"] as? JSONDictionary {
let bio = data["bio"] as! String
if let counts = data["counts"] as! JSONDictionary{
let followed_by = counts["followed_by"] as! String
let follows = counts["follows"] as! String
let media = counts["media"] as! String
}
}
}
Of course this code is not tested but you get an impression how to parse nested dictionaries. If result is deserialized JSON consider to use a library like SwiftyJSON.
The way you are doing this is almost impossible to read, but also this is not how you use dictionaries in swift.
Is there a reason you are using NSDictionaries and value(forKey: ) instead of switching over to swift Dictionaries with Dictionary (swift 3 style guide wants us to type this as [String: AnyObect]) ? Are you trying to access the value(forKey: ) features of NSDictionary or just trying to get the value of result["uuid"]?
What I would do is create models or structs for each of those nested dictionaries. (Is this a JSON response?)
If I'm reading it correctly, followed_by is a variable within a nested dictionary(not sure if that is right)? So for instance- and this is one of the deeper nested dictionaries, but you could work your way up with this- You would create a class called "Counts"
That class would have properties, in your case those properties would be something like: followedBy(array of users?- users would also probably have custom class with properties like userName and image), follows(array of users?), media(whatever type of object this is)- and those properties would be initialized by that dictionary. So init(dictionary: [String: AnyObject]).
if let followersDictArray = dictionary["followed_by"] as? [[String: AnyObject]] {
var followers = Array<UserModel>()
for followersDict = followersDictArray {
if let follower = UserModel(dictionary: followersDict) {
followers.append(follower)
}
}
followedBy = followers
}
And continue like that with the rest of the properties.
You would have a class called Data with properties like bio(idk what type this is, but assuming String?) and counts(which would be of object type Counts.init(dictionary: ) and that Data class would be initialized by a dictionary which would hand the information to those properties.
You would work your way up like that to parse the data.
Essentially, you want structs or classes for nested dictionaries. You would NOT want to be doing all of that as! AnyObject stuff. The second you start seeing all of those exclamation points and ((((( while trying to access information from your JSON dictionary, you should take it as an alert that you are doing something very wrong.
In doing this the way you are doing it - you are not protecting yourself against JSON response errors and others will not be able to read your code.
Additionally, you should never be using snake case for a swift variable name.
followed_by should be followedBy
I apologise for the title of this question. I have no idea what else to call it.
So... When calling the following:
let testData: [NSObject : AnyObject] = getTestData()
print(testData)
I get this output:
[data: {"TypeId":7,"DataList":null,"TypeName":"This is a test"}]
How would I be able to access the value 7 for the key "TypeId"?
EDIT:
Please note that it's holding { } brackets, not only [ ], thus a cast to NSDictionary is not possible as far as I have tried.
Kind regards,
Anders
You can achieve plist-like nested structures using Any type for dictionary values which is Swift's somewhat counterpart to Objective-C's id type but can also hold value types.
var response = Dictionary()
response["user"] = ["Login": "Power Ranger", "Password": "Mighty Morfin'"]
response["status"] = 200
Any seems to be better than AnyObject because in the above code response["status"] is of type Swift.Int, while using value type of AnyObject it is __NSCFNumber.
The way most people do it is to parse annoying JSON data as custom objects. That should be done as soon as you get the JSON. Ideally, data as JSON should not be used outside your communication code, example:
First, let's define a class to hold your server data:
class MyServerObject {
let typeId: Int
let typeName: String
let dataList: [AnyObject]?
init(dictionary: Dictionary<String, AnyObject>) {
let dataDictionary = dictionary["data"] as! Dictionary<String, AnyObject>
self.typeId = dataDictionary["TypeId"] as! Int
self.typeName = dataDictionary["TypeName"] as! String
self.dataList = dataDictionary["DataList"] as? [AnyObject]
}
}
Note that init method is already parsing the JSON. This doesn't have to be done in init, you could also create a static parse method that will return a new instance.
Usage:
// demo data
let jsonString = "{\"data\": {\"TypeId\":7,\"DataList\":null,\"TypeName\":\"This is a test\"}}"
let jsonData = jsonString.dataUsingEncoding(NSUTF8StringEncoding)!
let json = try! NSJSONSerialization.JSONObjectWithData(jsonData, options: [])
// parsing
let myServerObject = MyServerObject(dictionary: json as! Dictionary<String, AnyObject>)
// now we can simply read data as properties
print(myServerObject.typeId)
print(myServerObject.typeName)
One of the good thing about this solution is that we can check the JSON format and all the properties are parsed with the correct types.
Parsing can be hierarchical, for example, if your dataList contains complex objects, let's call them DataListItem, your parsing method can parse each item separately and put them into a [DataListItem], e.g.
if let dataListJSON = dataDictionary["DataList"] as? [Dictionary<String, AnyObject>] {
self.dataList = dataListJSON.map({ DataListItem($0) })
}
Also note that when parsing as! will crash the app when the format is invalid. as? will return nil if the types don't match. as? is very useful for types that can be nil because they are parsed as NSNull instances.
taking in account your data ...
print(testData)
/*
[data: {
DataList = null;
TypeId = 7;
TypeName = "This is a test";
}]
*/
// DataList type should be declared somewhere
class DataList {}
// parse data or set default value, if 'key' doesn't exist
if let data = testData["data"] as? [String:AnyObject] {
let dataList = data["DataList"] as? DataList // nil
let typeId = data["TypeId"] as? Int ?? 0 // 7
let typeName = data["TypeName"] as? String ?? "" // This is test
}
I'm trying to pass a text that is correct api put in swift he arrives with the message of "optional". this is my code:
let accountValues:NSArray = account!.accountNumber.componentsSeparatedByCharactersInSet(NSCharacterSet(charactersInString: "-"))
self.accountTextField.text = accountValues.objectAtIndex(0) as? String
self.accountDigitTextField.text = accountValues.objectAtIndex(1) as? String
When retrieving the value, use ! after the value. The downcast should force unwrap any optionals. Anything set as? can be force unwrapped by using !.
For example,
let intString = 7 as? String
print(intString!)
This should print "7", rather than Optional("7")
please replace these lines :
self.accountTextField.text = accountValues.objectAtIndex(0) as? String
self.accountDigitTextField.text = accountValues.objectAtIndex(1) as? String
by :
self.accountTextField.text = (accountValues.objectAtIndex(0) as! String)
self.accountDigitTextField.text = (accountValues.objectAtIndex(1) as! String)
You're using Optional Value "As? String". So, it will return an Optional('value').
Try this:
let accountValues:NSArray = account!.accountNumber.componentsSeparatedByCharactersInSet(NSCharacterSet(charactersInString: "-"))
self.accountTextField.text = "\(accountValues.objectAtIndex(0))"
self.accountDigitTextField.text = "\(accountValues.objectAtIndex(1))"