Swift: Stuck with a compiler error - swift

While exploring a structure read from a json file, I've got this message on the “if let” line which I'm stuck with:
'String' is not a subtype of '(String, AnyObject)'
The code is as follows:
if let descriptions: Array<Dictionary<String,AnyObject>> = fields["description"] as? Array {
let description = descriptions[0]
if let text:String = description["text"] as? String { // where the error occurs
poi.description = text
}
}

You have to unwrap what's read from the description dictionary:
if let text:String = description["text"]! as? String { // where the error occurs
...
}
But that's not safe, because if the key is not found in the dict, it throws a runtime exception. A safer way is:
if let text:String = (description["text"] as AnyObject?) as? String { // where the error occurs
...
}
However, I presume that you're using NSJSONSerialization to deserialize your json data, so a better way to do that is to stick with obj-c types rather than pure swift data types with generics:
if let descriptions = fields["description"] as? NSArray {
let description = descriptions[0] as NSDictionary
if let text = description["text"] as? String {
let x = text
}
}
More compact and much easier to read.

Use the new syntax and less of it.
Test declarations: Dictionary of Array of Dictionary
// let testFields: [String:[[String:Any]]]
or
// let testFields: [String:[[String:String]]]
let testFields = ["description":[["text":"value"]]]
if let descriptions = testFields["description"] {
let description = descriptions[0]
if let text = description["text"] as String? {
println("text: \(text)")
}
}
Output:
text: value

Related

Swift cast if possible

I have this code
let jsonData = try JSONSerialization.jsonObject(with: data, options: []) as! [Any?]
if var first = jsonData[0] as! String?{
if(first=="Error"){
DispatchQueue.main.async(execute: {
self.postNotFoundLabel.isHidden = false
});
}else if(first=="Empty"){
print("Empty")
}
}
What i want to do is to cast jsonData[0] to String if it's possible and if it's not then move on.But instead when it's not possible application stops and gives me an error
Could not cast value of type '__NSDictionaryI' (0x1092054d8) to 'NSString' (0x108644508).
How can i cast only when it's possible?
You are trying to force-cast to an optional String. That's not what you want.
Change:
if var first = jsonData[0] as! String? {
to:
if var first = jsonData[0] as? String {
This tries to cast to String. If jsonData[0] isn't actually a String, you get nil and the if var fails.
And you probably want if let, not if var since you don't seem to be making any change to first.
First of all JSON objects will never return optional values so [Any?] is nonsense.
Second of all the error message says the type cast to string is inappropriate because the type of the result is actually a dictionary.
Solution: Check the type for both String and Dictionary
if let jsonData = try JSONSerialization.jsonObject(with: data) as? [Any],
let first = jsonData.first {
if let firstIsDictionary = first as? [String:Any] {
// handle case dictionary
} else if let firstIsString = first as? String {
// handle case string
}
}
PS: A type cast forced unwrap optional to optional (as! String?) is nonsense, too.
Here's the Swifty way to do what you're doing :)
guard let jsonData = try JSONSerialization.jsonObject(with: data, options: []) as? [Any?], let first = jsonData[0] as? String else {
DispatchQueue.main.async(execute: {
self.postNotFoundLabel.isHidden = false
});
return
}
if(first == "Empty") {
print(first)
}
Don't use as! if you are not sure that casting will succeed. The exclamation mark after the as keyword forces the casting, which throws an error if the casting does not succeed.
Use as? instead, which returns an optional variable of the type you were trying to casting to. If the casting fails, instead of throwing an error, it just returns nil.
let jsonData = try JSONSerialization.jsonObject(with: data) as? [Any]
if var first = jsonData.first as? String{
if(first=="Error"){
DispatchQueue.main.async(execute: {
self.postNotFoundLabel.isHidden = false
});
}else if(first=="Empty"){
print("Empty")
}
}

Swift Cannot assign through subscript: subscript is get only

I am fairly new to the Swift syntax and am receiving this error with my code "Cannot assign through subscript: subscript is get only"
This is from the line: friendDictionary[(friendUID as? String)!] = ["name": friendsData!["name"]]
Any advice on the correct way of doing it would be very helpful.
func getFriendsUIDs() {
if FBSDKAccessToken.currentAccessToken() == nil {
print("failed to start graph request")
return
}else{
}
if FBSDKAccessToken.currentAccessToken() != nil {
}
let parameters = ["fields": "name, id, picture"]
FBSDKGraphRequest(graphPath: "/me/friends", parameters: parameters).startWithCompletionHandler {
(NSURLConnection, result, requestError) in
let friendIds = result["id"] as? NSDictionary
let friendsData = friendIds!["data"] as? [NSDictionary]
var ref: FIRDatabaseReference!
ref = FIRDatabase.database().reference()
ref.child("users").child((FIRAuth.auth()?.currentUser?.uid)!).child("friendUIDs").observeEventType(.Value, withBlock: { (snapshot) in
self.FriendUIDs = NSArray()
self.FriendUIDs = (snapshot.value as? NSArray)!
print(self.FriendUIDs)
var friendDictionary = NSDictionary()
for friendUID in self.FriendUIDs {
friendDictionary[(friendUID as? String)!] = ["name": friendsData!["name"]]
}
self.fetchFriendFeed(friendDictionary)
}) { (error) in
print(error.localizedDescription)
}
}
}
func fetchFriendFeed(friendDictionary: NSDictionary) {
var ref: FIRDatabaseReference!
ref = FIRDatabase.database().reference()
for friendUID in FriendUIDs {
ref.child("users").child(friendUID as! String).child("Agenda").observeEventType(.ChildAdded, withBlock: { (snapshot) in
print(snapshot)
if let dictionary = snapshot.value as? [String: AnyObject] {
let friendPost = FriendPost()
friendPost.picture = friendDictionary[friendUID as! String]? ["picture"] as? String
friendPost.activity = dictionary["activity"] as? String
friendPost.date = dictionary["date"] as? String
friendPost.time = dictionary["time"] as? String
friendPost.friendname = friendDictionary[friendUID as! String]? ["name"] as? String
self.friendPosts.append(friendPost)
dispatch_async(dispatch_get_main_queue(), {
self.collectionView?.reloadData()
Nothing to do with Swift. You've elected to use Objective-C, in effect, by making friendDictionary an NSDictionary. NSDictionary is immutable; you can't assign into it or alter it in any way. That is simply a fact about Objective-C. The Swift var declaration makes no difference to this fact.
A better choice, since you are writing in Swift, would be to use a Swift dictionary, which is [AnyHashable:Any]() (in Swift 3). This will interchange with NSDictionary when you are talking to Objective-C, but it will give you a mutable dictionary because you (rightly) declared it with var.
Have you tried using NSMutableDictionary? That solved the issue for me.
For those who get stuck here, another reason for this happens when you try to assign something that does not conform the actual dictionary, in my example i was doing something like this:
var dict = [Date : UUID]()
let randomUUID = UUID()
dict[randomUUID] = Date.now
whereas I meant to write UUID : Date but I was sleepy so i made a mistake, and Swift gave me a misleading error saying subscript is get-only. So this error also appears with type mismatch for Swift 5.7.

Init has been renamed to init(describing) error in Swift 3

This code works fine in Swift 2:
guard let userData = responseData["UserProfile"] as? [String : AnyObject] else { return }
var userProfileFieldsDict = [String: String]()
if let profileUsername = userData["Username"] as? NSString {
userProfileFieldsDict["username"] = String(profileUsername)
}
if let profileReputationpoints = userData["ReputationPoints"] as? NSNumber {
userProfileFieldsDict["reputation"] = String(profileReputationpoints)
}
But, in Swift 3 it throws an error on userProfileFieldsDict["reputation"] saying
init has been renamed to init(describing:)
My question is why does it trigger on that line and not on the userProfileFieldsDict["username"] assignment line, and how to go about fixing it? I'm assuming it's because I'm casting a NSNumber to a String, but I can't really understand why that matters.
NSNumber is a very generic class. It can be anything from a bool to a long to even a char. So the compiler is really not sure of the exact data type hence it's not able to call the right String constructor.
Instead use the String(describing: ) constructor as shown below
userProfileFieldsDict["reputation"] = String(describing: profileReputationpoints)
Here's more info about it.
You need to drop your use of Objective-C types. This was always a bad habit, and now the chickens have come home to roost. Don't cast to NSString and NSNumber. Cast to String and to the actual numeric type. Example:
if let profileUsername = userData["Username"] as? String {
userProfileFieldsDict["username"] = profileUsername
}
if let profileReputationpoints = userData["ReputationPoints"] as? Int { // or whatever
userProfileFieldsDict["reputation"] = String(profileReputationpoints)
}

ambiguous use of subscript swift 2.2

I have a lot of issues in my code with this error. Hopefully if someone can help me here than I can figure out the rest of the problems. I have updated to xcode 7.3 and running swift 2.2.
I have read that the compiler has been "more restrictive", and I have to tell it what the "intermediary" objects are. This is causing me some confusion and would love further explanation.
func getMessage(dictionary:NSDictionary)->String{
var message = String()
if let dict = dictionary["aps"] {
if let message:String = dict["alert"] as? String {
return message
}
else{
message = ""
}
}
return message
}
Another example:
for object in objects {
let getDriver = object.objectForKey("driver")
if let picture = getDriver!["thumbnailImage"] as? PFFile {
self.profilePictures.append(picture)
}
self.requestsArray.append(object.objectId as String!)
}
The type of a dictionary value is always AnyObject. Cast the type to something more specific for example
if let dict = dictionary["aps"] as? [String:AnyObject] {
then the compiler knows that key subscripting is valid and possible
The second example is similar: object is a dictionary and the compiler needs to know that the value for key driver is also a dictionary
if let getDriver = object.objectForKey("driver") as? [String:AnyObject] {
if let picture = getDriver["thumbnailImage"] as? PFFile {
...

Extra argument 'error' in call - do/catch?

I know there is new error handling i.e. do/catch but not sure if it applies here and even if it does it's pretty difficult for me even going through the documentation. Could someone show me the correct code block please.
/*** error Extra argument 'error' in call ***/
var plistDic = NSPropertyListSerialization.propertyListWithData(plistData!,
options:Int(NSPropertyListMutabilityOptions.MutableContainersAndLeaves.rawValue),
format: nil, error: &error) as Dictionary<String, Dictionary<String, String>>
assert(error == nil, "Can not read data from the plist")
return plistDic
}
// END
EDIT:
let YALCityName = "name"
let YALCityText = "text"
let YALCityPicture = "picture"
private let kCitiesSourcePlist = "Cities"
class YALCity: Equatable {
var name: String
var text: String
var image: UIImage
var identifier: String
// MARK: Class methods
class internal func defaultContent() -> Dictionary<String, Dictionary<String, String>> {
let path = NSBundle.mainBundle().pathForResource(kCitiesSourcePlist, ofType: "plist")
let plistData = NSData(contentsOfFile: path!)
assert(plistData != nil, "Source doesn't exist")
do {
let plistDic = try NSPropertyListSerialization.propertyListWithData(plistData!,
options:NSPropertyListMutabilityOptions.MutableContainersAndLeaves,
format: nil
)
if let dictionary = plistDic as? Dictionary< String, Dictionary<String, String> > {
print("\(dictionary)")
}
else {
print("Houston we have a problem")
}
}
catch let error as NSError {
print(error)
}
return defaultContent()
}
init(record:CKRecord) {
self.name = record.valueForKey(YALCityName) as! String
self.text = record.valueForKey(YALCityText) as! String
let imageData = record.valueForKey(YALCityPicture) as! NSData
self.image = UIImage(data:imageData)!
self.identifier = record.recordID.recordName
}
}
func ==(lhs: YALCity, rhs: YALCity) -> Bool {
return lhs.identifier == rhs.identifier
}
Try this code:
do {
var plistDic = try NSPropertyListSerialization.propertyListWithData(plistData!,
options:NSPropertyListMutabilityOptions.MutableContainersAndLeaves,
format: nil
)
// plistDic is of type 'AnyObject'. We need to cast it to the
// appropriate dictionary type before using it.
if let dictionary = plistDic as? Dictionary<String, Dictionary<String, String>> {
// You are good to go.
// Insert here your code that uses dictionary (otherwise
// the compiler will complain about unused variables).
// change 'let' for 'var' if you plan to modify the dictionary's
// contents.
// (...)
}
else {
// Cast to dictionary failed: plistDic is NOT a Dictionary with
// the structure: Dictionary<String, Dictionary<String, String>>
// It is either a dictionary of a different internal structure,
// or not a dictionary at all.
}
}
catch let error as NSError {
// Deserialization failed (see console for details:)
print(error)
}
Note: I split the call to a function that throws (try...) and the casting to your specific type of Dictionary (if let...) because I'm not really sure exactly what would happen if the call succeeds but the cast fails, or if it would be clear which one failed from the debugger. Also, I don't like too many things happening in one line...
EDIT: I fixed the options parameter. In Swift, Ints and enums aren't interchangeable; you need to pass the right type (I missed it the first time when modifying your code).