Swift 5.1: How do I call map on a collection of one type and return another of a different type? - swift

I've been playing about with functional programming in Swift. However, I've encountered a problem.
If I call map on a collection of one type, how do I create a new collection of another type?
var fontFamilyMembers = [[Any]]()
//...
let postscriptNames = fontFamilyMembers.map {
[
"name": $0[0] as! String,
"weight": $0[2] as! Int,
"traits": $0[3] as! UInt
]
}
// Error: Value of type 'Any?' has no member 'count'
lengths = postscriptNames.map { $0["name"].count }
I understand I need to cast $0["name"] as a string. Why do I have to do that when I have already done it above? ("name": $0[0] as! String). Is that because postscriptNames is also type [[Any]]()?
I've tried this:
// Error: Cannot convert value of type 'Int' to closure result type 'String'
fontPostscriptNames = postscriptNames.map { ($0["name"] as! String).count }.joined(separator: "\n")
…but know I'm doubly confused.
How do I get map to return the count of each "name"?
Update
My original question still stands. However, I can avoid the problem altogether by using a struct as opposed to a dictionary which I assume is preferred in Swift.
let postscriptNames = fontFamilyMembers.map {
(
name: $0[0] as! String,
weight: $0[2] as! Int,
traits: $0[3] as! UInt
)
}
lengths = postscriptNames.map { $0.name.count }

Is that because postscriptNames is also type [Any]?
Yes. And as postscriptNames is of type [Any], you need to downcast $0.name to String
lengths = postscriptNames.compactMap { ($0.name as? String).count }
Downcast it to String to get the count.

Related

How to get key and value of Firestore mapped object

I have an app where users can rate films they have watched, and I want to be able to put these in a tableView. The rating is stored in Firestore, and I want to put both the KEY and value into a Struct so I can access it for the tableView.
However any site/tutorial/stack question I have seen only gets the Maps value, but not the key (in this case, the title name). I can access the value, but only by using the field key, but that is what I am trying to get (see attempt 1)
Struct:
struct Rating: Codable {
var ratedTitle: String
var ratedRating: Int
}
Variable:
var ratedList = [Rating]()
Load data function (attempt 1):
let dbRef = db.collection("Users").document(userID)
dbRef.getDocument { document, error in
if let error = error {
print("There was an error \(error.localizedDescription)")
} else {
if let docData = document!.data() {
let titleRating = docData["Title Ratings"] as? [String: Int]
let midnightMass = titleRating!["Midnight Mass"]
print("Rating given to Midnight Mass: \(midnightMass!) stars")
}
}
}
//Prints: Rating given to Midnight Mass: 2 stars
Also tried (but I don't know how to get this array onto a tableView and have the first index as the Title label, and the second index a Rating label for each movie in the array) attempt 2:
if let docData = document!.data() {
let titleRating = docData["Title Ratings"] as? [String: Int]
self.userRatedList = titleRating!
print("userRatedList: \(self.userRatedList)")
}
//Prints: userRatedList: ["Midnight Mass": 2, "Bly Manor": 5]
Attempt 3:
if let docData = document!.data() {
let titleRating = docData["Title Ratings"] as? [String: Int]
self.ratedList = [Rating(ratedTitle: <#T##String#>, ratedRating: <#T##Int#>)]
//Don't know what I would put as the ratedTitle String or ratedRating Int.
self.ratedList = [Rating(ratedTitle: titleRating!.keys, ratedRating: titleRating!.values)]
//Cannot convert value of type 'Dictionary<String, Int>.Keys' to expected argument type 'String'
//Cannot convert value of type 'Dictionary<String, Int>.Values' to expected argument type 'Int'
}
Firstly, I am not sure why you need the struct to conform to Codable?
Now, based off what I see, "Title Ratings" is a dictionary with a String key and an Int value. You are overcomplicating this. If you want to access the key and value of each element individually, use a for-in loop.
//Declare your global variable
var ratedList = [Rating]()
//If you are using an if let, there is not need to force unwrap
if let docData = document.data() {
if let userRatingList = docData["Title Ratings"] as? [String: Int] {
for (key, value) in userRatingList {
let rating = Rating(ratedTitle: key, ratedRating: value)
ratedList.append(rating)
}
//reload your tableView on the main thread
DispatchQueue.main.async {
tableView.reloadData()
}
}
}

Swift 3.0 Error: Ambiguous reference to member 'subscript' when setting a Dictionary value

I've got this method below which inserts a value into a dictionary. In Swift 2.x, it worked as is. When switching to Swift 3, I get the "Ambiguous reference to member 'subscript'" error.
Can anyone help me out in understanding why. Thanks
private func GetNames() -> AnyObject? {
var returnList : [NSDictionary] = []
let list = self.Names as! [NSDictionary]
for name in list {
var dict : Dictionary<NSObject, AnyObject> = [:]
dict["id"] = name["id"] // <-- ******Error*******
dict["name"] = name["name"] // <-- ******Error*******
returnList.append(dict as NSDictionary)
}
return returnList as AnyObject
}
In Swift 3 String is type of Structure, so it will not work with NSObject and you have declare dictionary thats key type is NSObject that is the reason you are getting that error. To solve the error simply change type of dictionary to [String:Any] form [NSObject:AnyObject] and you all set to go.
var dict : [String:Any] = [:]
Note: Also in Swift don't use NSDictionary and NSArray use Swift's native type dictionary and array.
The error occurs because Swift 3 needs to know all types which are subscripted by key or index.
Using Foundation collection types is counterproductive because they lack exactly that type information the compiler needs.
Returning an optional AnyObject rather than the distinct (non-optional) [[String:Any]] is counterproductive, too.
The solution is to use only (as) specific (as possible) native Swift types.
private func getNames() -> [[String:Any]] {
var returnList = [[String:Any]]()
let list = self.Names as! [[String:Any]]
for name in list {
var dict = [String:Any]()
dict["id"] = name["id"]
dict["name"] = name["name"]
returnList.append(dict)
}
return returnList
}
or swiftier if it's guaranteed that both keys name and id always exist
private func getNames() -> [[String:Any]] {
let list = Names as! [[String:Any]]
return list.map { ["id" : $0["id"]!, "name" : $0["name"]!] }
}

Swift: Getting dictionary value if I know the key

This should be super basic but I'm getting an error anyway.
Cannot subscript a value of type 'Dictionary<String, AnyObject>' with an index of type 'String'
Here's my code:
func createComments(attributes: [[String: AnyObject]], votes: [String: AnyObject], sid: Int) -> [Comment] {
var comments: [Comment] = [Comment]()
for commentAttributes in attributes {
let comment = Comment()
comment.commentId = commentAttributes["id"]
comments.append(comment)
}
return comments
}
I'm getting the error on this line:
comment.commentId = commentAttributes["id"]
As far as I can tell, commentAttributes should be a Dictionary with keys as Strings and values of AnyObject. I'm not sure how else to access the values of a Dictionary with a String key other than by using a String to subscript. What am I missing here?
Try to use if let and cast it to proper type.
for commentAttributes in attributes {
let comment = Comment()
if let id = commentAttributes["id"] as? Int {
comment.commentId = id
}
comments.append(comment)
}
Of course as soon as I ask the question I find the answer:
Cannot subscript a value of type '[String : AnyObject]' with an index of type 'String'
I needed to typecast the value of commentAttributes["id"] so that it matched the type of comment.commentId
comment.commentId = commentAttributes["id"] as! Int

Swift - matching class dynamically

I am trying to write an extension method on Dictionary with the following signature:
func firstNonNilObjectForKey(keys: [Key], ofClass aClass: Any.Type = String.self) -> Value?
This is meant to help me deal with JSON dictionaries, where I often need the first non-null value, but sometimes the JSON includes null itself as value, which is converted to NSNull in Cocoa.
The usage would be something like:
let dict: [String:AnyObject] = [...]
let myValue = dict.firstNonNilObjectForKey([ "key1", "key2" ]) as? String
The issue is implementational - how to match the class:
if let value = self[key] {
if value is aClass { ... } <--- error: aClass is not a type
if let castedValue = value as? aClass { ... } <--- error: ditto
let type = value.dynamicType
if type == aClass { ... } <--- no error, but doesn't handle subclasses!
// Cannot use value.isKindOfClass() since value may be Any
}
I have thought of an alternative:
func firstNonNilObjectForKey<ReturnValueType>(keys: [Key]) -> ReturnValueType?
which allows to be implemented as
if let value = self[key] as? ReturnValueType { ... }
But:
- Here I cannot set the default type (I mostly need String.Type).
- I'm not really sure how to invoke this:
let value = dict.firstNonNilObjectForKey([ "key1", "key2" ]) as? String <--- error: Cannot invoke 'firstNonNilObjectForKey' with an argument list of type '([String])'
let value = dict.firstNonNilObjectForKey<String>([ ... ]) <--- error: Cannot explicitly specialize a generic function
I hope this isn't a duplicate, but most similar questions here are simply handling a situation where you are matching against a known class.
I'm not sure I got the requirements exactly, but can something like this work for you?
extension Dictionary {
func firstNonNilObjectForKey(keys: [Key]) -> String? {
return self.firstNonNilObjectForKey(keys, ofClass: String.self)
}
func firstNonNilObjectForKey<T>(keys: [Key], ofClass aClass: T.Type) -> T? {
for k in keys {
if let v = self[k] as? T {
return v
}
}
return nil
}
}
let dict = ["key1": 2, "key2": "Foo"]
let aString = dict.firstNonNilObjectForKey(["key1", "key2"]) // "Foo"
let anInt = dict.firstNonNilObjectForKey(["key1", "key2"], ofClass: Int.self) // 2
The main gotcha here is that I'm using overloading as opposed to default parameters, which don't seem to get along well with the swift type checker.

Swift Check Dynamic Type of Array of Objects

I've an array of objects array: [AnyObject] and want to check their dynamic type against an array of classes how can I do that?
let array: [AnyObject] = ["hi", true, 12]
The above array is an example. I want it to work with any types passed for the array.
I expect to have another array of types to check against. But I don't know how to declare them.
You can keep Any instances and compare for Any.Type types with the == operator. Example based on the code in #lassej answer:
let array: [Any] = [UIView(), "hellow", 12, true]
let types: [(Any.Type, String)] = [
(UIView.self, "UIView"),
(String.self, "String"),
(Int.self, "Integer")
]
anyLoop: for any in array {
for (type, name) in types {
if any.dynamicType == type {
print( "type: \(name)")
continue anyLoop
}
}
print( "unknown type: \(any.dynamicType)")
}
// Prints:
// type: UIView
// type: String
// type: Integer
// unknown type: Bool
If you can limit the objects to subclasses of NSObject then this should work:
import Foundation
let array: [AnyObject] = ["hi", true, 12]
let types: [(AnyClass, String)] = [
(NSString.self, "String"),
(NSNumber.self, "Number"),
]
for obj in array {
for (type, name) in types {
if obj.isKindOfClass( type) {
println( "type: \(name)")
}
}
}
Not sure if there is a way to do this with Swift-Only objects.