Swift: Sort list object by property - swift

after a lot of research on the net, I tried several solutions but it does not work? I want to sort my array according to the leaguecode property here is my code:
var teams = [Team]()
dbReference.child(FBDatabaseKeys.teams).observeSingleEvent(of: .value) { (snapshot) in
for nameTeam in snapshot.children.allObjects as! [DataSnapshot] {
let value = nameTeam.value as? NSDictionary
let team = Team(
capitanId: value?[FBDatabaseKeys.Teams.captainID] as! String,
leagueCode: value?[FBDatabaseKeys.Teams.leagueCode] as? String ?? value?["leageCode"] as! String,
playersIds: [""], //todo retrieve data to array
teamName: value?[FBDatabaseKeys.Teams.teamName] as! String,
imageUrl: value?[FBDatabaseKeys.Teams.imageUrl] as! String,
groupMoto: value?[FBDatabaseKeys.Teams.teamMoto] as! String,
totalScore: String(format: "%#", value?[FBDatabaseKeys.Teams.totalScore] as? CVarArg ?? "") ,
totalTickets: String(format: "%#", value?[FBDatabaseKeys.Teams.totalTickets] as? CVarArg ?? "")
)
if team.leagueCode == self.currentPlayer.leagueCode{
teams.append(team)
}
}
teams = teams.sort {
Int($0.leagueCode)! < Int($1.leagueCode)!
} as! [Team]
//Sort array by property score
completion(teams)
}
Error:
Error:(204, 27) cannot assign value of type '()' to type '[Team]'
Here is my object:
struct Team {
let capitanId: String
let leagueCode: String
let playersIds: [String]
let teamName: String
let imageUrl: String
let groupMoto: String
let totalScore: String
let totalTickets: String
Thanks for your help

The error message
cannot assign value of type '()'
clearly says that the sort function doesn't return anything. The array is sorted in place.
And the conversions to Int are not necessary. You can sort strings numerically with localizedStandardCompare
teams.sort {
$0.leagueCode.localizedStandardCompare($1.leagueCode) == .orderedAscending
}

You need
teams = teams.sorted {
Int($0.leagueCode)! < Int($1.leagueCode)!
}
Or only mutating
teams.sort {
Int($0.leagueCode)! < Int($1.leagueCode)!
}
https://developer.apple.com/documentation/swift/array/2296815-sorted
https://developer.apple.com/documentation/swift/array/2296801-sort
//
dbReference.child(FBDatabaseKeys.teams).observeSingleEvent(of: .value) { (snapshot) in
for nameTeam in snapshot.children.allObjects as! [DataSnapshot] {
let value = nameTeam.value as? NSDictionary
let team = Team(
capitanId: value?[FBDatabaseKeys.Teams.captainID] as! String,
leagueCode: value?[FBDatabaseKeys.Teams.leagueCode] as? String ?? value?["leageCode"] as! String,
playersIds: [""], //todo retrieve data to array
teamName: value?[FBDatabaseKeys.Teams.teamName] as! String,
imageUrl: value?[FBDatabaseKeys.Teams.imageUrl] as! String,
groupMoto: value?[FBDatabaseKeys.Teams.teamMoto] as! String,
totalScore: String(format: "%#", value?[FBDatabaseKeys.Teams.totalScore] as? CVarArg ?? "") ,
totalTickets: String(format: "%#", value?[FBDatabaseKeys.Teams.totalTickets] as? CVarArg ?? "")
)
if team.leagueCode == self.currentPlayer.leagueCode{
teams.append(team)
}
}
teams.sort {
Int($0.leagueCode)! < Int($1.leagueCode)!
}
completion(teams)
}

Related

passing string from firebase realtime database request

How can I pass the string userImage which I get from a firebase realtime database request? I can not just use return userImage. It says Unexpected non-void return value in void function. I want the userImage as result and paste it to XXXXXX.
...
func datenBankAbfrage() {
print("datenbankabfrage getriggert")
dic = [:]
for name in myFeed.myArray1[0..<myFeed.myArray1.count] {
ref = Database.database().reference().child("placeID/\(name)")
ref.observe(.value) { (snapshot) in
self.dic[name] = []
var specificNameVideos = [importComment]()
for video in snapshot.children.allObjects as! [DataSnapshot] {
let Object1 = video.value as? [String: AnyObject]
let timestampDate = NSDate(timeIntervalSince1970: Double(Object1?["userTime"] as! NSNumber)/1000)
let dateFormatter = DateFormatter()
dateFormatter.timeZone = NSTimeZone.local
dateFormatter.dateFormat = "dd.MM.YY hh:mm"
let time = dateFormatter.string(from: timestampDate as Date)
print(time + "Uhr")
let userName = Object1?["userName"]
...
let kommentarCount = Object1?["kommentarCount"]
let userID = Object1?["userID"]
//print(userID!)
ViewComments.commentIDNew = commentId as! String
func wertBerechnen(completion: #escaping (String) -> Void) {
self.ref = Database.database().reference()
self.ref.child("user/\(userID!)").observeSingleEvent (of: .value, with: { (snapshot) in
let value = snapshot.value as? NSDictionary
let userImage = value?["picture"] as? String ?? ""
completion(userImage)
})
}
wertBerechnen { userImage in
let video = importComment(
userName: userName as! String, userGroup: userGroup as! String,
userComment: userComment as! String, userTime: userTime as! String,
userLikes: userLikes as! Int, commentId: commentId as! String, placeID: placeID as! String, kommentarCount: kommentarCount as? Int, userImage: userImage)
specificNameVideos.append(video)
}
}
self.dic[name] = specificNameVideos
let tem = Array(self.dic.values).flatMap { $0 } // You can also sort allComments with userTime property
self.table = tem.sorted { $0.userTime! > $1.userTime! }
self.tableView.reloadData()
}
myFeed.myArray1 = []
}
...
You can use a completion handler to return the userImage string:
func wertBerechnen(completion: #escaping (String) -> Void) {
self.ref = Database.database().reference()
self.ref.child("user/\(userID!)").observeSingleEvent (of: .value, with: { (snapshot) in
let value = snapshot.value as? NSDictionary
let userImage = value?["picture"] as? String ?? ""
completion(userImage)
})
}
Then you can call it like this:
wertBerechnen { userImage in
let video = importComment(
userName: userName as! String, userGroup: userGroup as! String,
userComment: userComment as! String, userTime: userTime as! String,
userLikes: userLikes as! Int, commentId: commentId as! String, placeID: placeID as! String, kommentarCount: kommentarCount as? Int, userImage: userImage)
specificNameVideos.append(video)
}
self.dic[name] = specificNameVideos
let tem = Array(self.dic.values).flatMap { $0 }
self.table = tem.sorted { $0.userTime! > $1.userTime! }
self.tableView.reloadData()
}
}
You need to reload the tableView data inside the wertBerechnen completion handler:
func wertBerechnen(completion: #escaping (String) -> Void) {
self.ref = Database.database().reference()
self.ref.child("user/\(userID!)").observeSingleEvent (of: .value, with: { (snapshot) in
let value = snapshot.value as? NSDictionary
let userImage = value?["picture"] as? String ?? ""
completion(userImage)
})
}
Then you can call it like this:
wertBerechnen { userImage in
let video = importComment(
userName: userName as! String, userGroup: userGroup as! String,
userComment: userComment as! String, userTime: userTime as! String,
userLikes: userLikes as! Int, commentId: commentId as! String, placeID: placeID as! String, kommentarCount: kommentarCount as? Int, userImage: userImage)
specificNameVideos.append(video)
self.tableView.reloadData()
}
self.dic[name] = specificNameVideos
let tem = Array(self.dic.values).flatMap { $0 }
self.table = tem.sorted { $0.userTime! > $1.userTime! }
}
}

How do I loop through a firestore document that has an array of maps?

I mostly work with dictionaries since I am fairly new but here we have an embedded struct that I need to loop through. With this, I need to be able to populate expanding cells in UITableView.
My struct looks like this:
struct Complain: Codable {
let eMail, message, timeStamp, userEmail: String
let status: Bool
let planDetails: PlanDetails
enum CodingKeys: String, CodingKey {
case eMail = "E-mail"
case message = "Message"
case timeStamp = "Time_Stamp"
case userEmail = "User_Email"
case status, planDetails
}
}
// MARK: - PlanDetails
struct PlanDetails: Codable {
let menuItemName: String
let menuItemQuantity: Int
let menuItemPrice: Double
}
And my query looks like this:
db.collection("Feedback_Message").getDocuments() { documentSnapshot, error in
if let error = error {
print("error:\(error.localizedDescription)")
} else {
for document in documentSnapshot!.documents {
let eMail = document.get("E-mail")
let message = document.get("Message")
let timeStamp = document.get("Time_Stamp")
let userEmail = document.get("User_Email")
let planDetails = document.get("planDetails")
// how to loop through planDetails
}
}
I have gotten this far:
db.collection("Feedback_Message").getDocuments() { documentSnapshot, error in
if let documentSnapshot = documentSnapshot {
for document in documentSnapshot.documents {
guard let planDetails = document.get("planDetails") as? [[String : Any]] else {
let email = document.get("E-mail")
let message = document.get("Message")
let timeStamp = document.get("Time_Stamp")
let userEmail = document.get("User_Email")
let status = false
}
for plan in planDetails {
if let menuItemName = plan["menuItemName"] as? String {
// not sure I populate the menuItemsName, menuItemsQuantity and menuItemsPrice within the array
}
}
}
}
}
UPDATE 2:
Final query: This is finally working as expected. But am not able to populate the expanding cells.
db.collection("Feedback_Message").getDocuments() { documentSnapshot, error in
if let documentSnapshot = documentSnapshot {
for document in documentSnapshot.documents {
guard let planDetails = document.get("planDetails") as? [[String : Any]],
let email = document.get("E-mail") as? String,
let message = document.get("Message") as? String,
let timeStamp = document.get("Time_Stamp") as? String,
let userEmail = document.get("User_Email") as? String,
let status = document.get("status") as? Bool else {
continue
}
for plan in planDetails {
guard let menuItemName = plan["menuItemName"] as? String,
let menuItemQuantity = plan["menuItemQuantity"] as? Int,
let menuItemPrice = plan["menuItemPrice"] as? Double else {
continue
}
let planDetails = PlanDetails(menuItemName: menuItemName, menuItemQuantity: menuItemQuantity, menuItemPrice: menuItemPrice)
let complaint = Complain(eMail: email, message: message, timeStamp: timeStamp, userEmail: userEmail, status: status, planDetails: planDetails)
self.messageArray.append(complaint)
print(self.messageArray)
}
}
}
}
EDIT
// OPTION 1 - FULL REQUIREMENT
db.collection("Feedback_Message").getDocuments() { documentSnapshot, error in
if let documentSnapshot = documentSnapshot {
for document in documentSnapshot.documents {
// all of these properties are required to parse a single document
guard let planDetails = document.get("planDetails") as? [[String : Any]],
let email = document.get("E-mail") as? String,
let message = document.get("Message") as? String,
let timeStamp = document.get("Time_Stamp") as? String,
let userEmail = document.get("User_Email") as? String,
let status = document.get("status") as? Bool else {
continue // continue this loop
}
for plan in planDetails {
// all of these properties are required to instantiate a struct
guard let name = plan["menuItemName"] as? String,
let price = plan["menuItemName"] as? Double,
let quantity = plan["menuItemQuantity"] as? Int else {
continue // continue this loop
}
let plan = PlanDetails(menuItemName: name, menuItemQuantity: quantity, menuItemPrice: price)
// then you will likely append this plan to an array
}
}
}
}
// OPTION 2 - PARTIAL REQUIREMENT
db.collection("Feedback_Message").getDocuments() { documentSnapshot, error in
if let documentSnapshot = documentSnapshot {
for document in documentSnapshot.documents {
// all of these properties are required to parse a single document
guard let planDetails = document.get("planDetails") as? [[String : Any]],
let email = document.get("E-mail") as? String,
let message = document.get("Message") as? String else {
continue // continue this loop
}
// these properties are not required
let timeStamp = document.get("Time_Stamp") as? String // optional (can be nil)
let userEmail = document.get("User_Email") as? String // optional (can be nil)
let status = document.get("status") as? Bool ?? false // not optional because it has a default value of false
for plan in planDetails {
// all of these properties are required to instantiate a struct
guard let name = plan["menuItemName"] as? String,
let price = plan["menuItemName"] as? Double,
let quantity = plan["menuItemQuantity"] as? Int else {
continue // continue this loop
}
let plan = PlanDetails(menuItemName: name, menuItemQuantity: quantity, menuItemPrice: price)
// then you will likely append this plan to an array
}
}
}
}
There are a number of other ways you can do this, these are just a couple. For example, you can instantiate the struct with the [String: Any] dictionary itself.
I finally figured out what the problem was. Had to do with the way I setup the struct and how I populate the array.
Right way is as such i.e. the planDetails need to be cast as an array:
struct Complain: Codable {
let eMail, message, timeStamp, userEmail: String
let status: Bool
let planDetails: [PlanDetails]
enum CodingKeys: String, CodingKey {
case eMail = "E-mail"
case message = "Message"
case timeStamp = "Time_Stamp"
case userEmail = "User_Email"
case status, planDetails
}
}
// MARK: - PlanDetails
struct PlanDetails: Codable {
let menuItemName: String
let menuItemQuantity: Int
let menuItemPrice: Double
}
And then I need to iterate through each plan item, add it to the array and then add the array to the top level Complain:
db.collection("Feedback_Message").getDocuments() { documentSnapshot, error in
if let documentSnapshot = documentSnapshot {
for document in documentSnapshot.documents {
guard let planDetails = document.get("planDetails") as? [[String : Any]],
let email = document.get("E-mail") as? String,
let status = document.get("status") as? Bool else {
continue
}
for plan in planDetails {
guard let menuItemName = plan["menuItemName"] as? String,
let menuItemQuantity = plan["menuItemQuantity"] as? Int else {
continue
}
let singlePlan = PlanDetails(menuItemName: menuItemName, menuItemQuantity: menuItemQuantity)
self.planDetailsArray.append(singlePlan)
}
let complain = Complain(eMail: email, status: status, planDetails: self.planDetailsArray)
self.planDetailsArray.removeAll()
self.messageArray.append(complain)
}
}
// Print the main array or load table
}
}
bsod's answer stays as the correct answer because without his help, I would not have been able to arrive at his conclusion.

Swift 4 Unwrapping Dictionary from Firebase

Here is the output of "print(dict)"...
["2018-10-17 11:19:51": {
firstname = Brooke;
id = 40vI7hApqkfX75SWsqIR6cdt7xV2;
lastname = Alvarez;
message = hshahyzhshbsbvash;
username = poiii;
}]
["2018-10-17 11:20:31": {
firstname = Trevor;
id = 40vI7hApqkfX75SWsqIR6cdt7xV2;
lastname = Bellai;
message = hey;
username = br9n;
}]
["2018-10-17 11:20:44": {
firstname = Amy;
id = 40vI7hApqkfX75SWsqIR6cdt7xV2;
lastname = Ikk;
message = hey;
username = nine9;
}]
My code...
Database.database().reference().child("recent-msgs").child(uid!).observe(.childAdded) { (snapshot: DataSnapshot) in
if let dict = snapshot.value as? [String: Any] {
print(dict)
// Store data in user.swift model
let firstnameData = dict[0]["firstname"] as! String
let idData = dict["id"] as! String
let lastnameData = dict["lastname"] as! String
let messageData = dict["message"] as! String
let usernameData = dict["username"] as! String
let rankData = dict["rank"] as! String
let propicrefData = dict["propicref"] as! String
let convoinfo = RecentConvo(firstnameString: firstnameData, idString: idData, lastnameString: lastnameData, messageString: messageData, usernameString: usernameData, rankString: rankData, propicrefString: propicrefData)
self.recentconvos.append(convoinfo)
print(self.recentconvos)
self.tableView.reloadData()
}
}
I'm trying to retrieve the dictionary within the first dictionary which is the value to the key which is the date associate with it. For example: 2018-10-17 11:19:51. However I cannot use this exact string to call it because I must do this without the knowledge of that string.
I tried this:
let firstnameData = dict[0]["firstname"] as! String
But it returns an error:
Cannot subscript a value of type '[String : Any]' with an index of type 'Int'
The error noted above is showing up because you were trying to access the element at a certain position (0) from the dictionary. Dictionaries are not ordered lists, and hence won't have a fixed order of elements to be accessed.
The logged dictionary doesn't really look like a dictionary. Assuming that it is a dictionary, and its keys are the date strings, you can use the following code snippet to parse the dictionary.
class RecentConversation {
var id: String?
var firstName: String?
var lastName: String?
var message: String?
var username: String?
var rank: String?
var propicref: String?
init?(dictionary: [String: Any]?) {
guard let dict = dictionary else {
// Return nil in case the dictionary passed on is nil
return nil
}
id = dict["id"] as? String
firstName = dict["firstname"] as? String
lastName = dict["lastname"] as? String
message = dict["message"] as? String
username = dict["username"] as? String
rank = dict["rank"] as? String
propicref = dict["propicref"] as? String
}
}
Usage:
let dateStrings = dict.keys.sorted {
// Sort in chronological order (based on the date string; if you need to sort based on the proper date,
// convert the date string to Date object and compare the same).
//
// Swap the line to $0 > $1 to sort the items reverse chronologically.
return $0 < $1
}
var conversations: [RecentConversation] = []
for date in dateStrings {
if let conversation = RecentConversation(dictionary: (dict[date] as? [String: Any])) {
conversations.append(conversation)
}
}
You were all very helpful, so I would like to start off by saying thank you. I went ahead and applied the method that lionserdar explained. (.allKeys)
// Fetch Recent Messages
func fetchRecentMsgs() {
// Direct to database child
Database.database().reference().child("recent-msgs").child(uid!).observe(.childAdded) { (snapshot: DataSnapshot) in
if let dict = snapshot.value as? NSDictionary {
print(dict)
print(dict.allKeys)
let keys = dict.allKeys
for key in keys {
print(key)
if let nestedDict = dict[key] as? [String: Any] {
print(nestedDict)
let firstnameData = nestedDict["firstname"] as! String
let idData = nestedDict["id"] as! String
let lastnameData = nestedDict["lastname"] as! String
let messageData = nestedDict["message"] as! String
let usernameData = nestedDict["username"] as! String
Worked for me so I hope this will help others too!

Swift Ambiguous use of 'subscript'

I had recently added new pods to my project and updated the previous ones, this code had all worked before and now I am getting "Ambiguous use of 'subscript'" I have been checking other answers and it seems like the way I have it currently set up is the correct way. I am not sure why all of a sudden these lines of code would get flagged as errors.
databaseRef.child("following").queryOrderedByKey().observeSingleEvent(of: .value, with: { (postsSnapshot) in
let posts = postsSnapshot.value as! [String: AnyObject]
for (_, post) in posts {
for (_, postInfo) in post as! [String: AnyObject] {
if let followingID = postInfo["uid"] as? String {
for each in self.following {
if each == followingID {
guard let uid = postInfo["uid"] as! String? else {return}
guard let name = postInfo["businessName"] as! String? else {return}
guard let address = postInfo["businessStreet"] as! String? else {return}
guard let state = postInfo["businessState"] as! String? else {return}
guard let city = postInfo["businessCity"] as! String? else {return}
let data = ["uid":postInfo["uid"] as! String, "businessName":postInfo["businessName"] as! String, "businessStreet":postInfo["businessStreet"] as! String, "businessState":postInfo["businessState"] as! String, "businessCity":postInfo["businessCity"] as! String,"businessZIP":postInfo["businessZIP"] as! String, "businessPhone":postInfo["businessPhone"] as! String, "businessLatitude":postInfo["businessLatitude"] as! String, "businessLongitude":postInfo["businessLongitude"] as! String, "businessWebsite":postInfo["businessWebsite"] as! String, "facebookURL":postInfo["facebookURL"] as! String, "foursquareURL":postInfo["foursquareURL"] as! String, "googleURL":postInfo["googleURL"] as! String, "instagramURL":postInfo["instagramURL"] as! String, "snapchatURL":postInfo["snapchatURL"] as! String, "twitterURL":postInfo["twitterURL"] as! String, "yelpURL":postInfo["yelpURL"] as! String]
The problem is your use of AnyObject. postInfo is being declared as AnyObject but then you attempt to use it like a dictionary, hence the error.
If post is a dictionary of dictionaries, then declare is as such:
Change:
for (_, postInfo) in post as! [String: AnyObject] {
to:
for (_, postInfo) in post as! [String: [String: AnyObject]] {
This will change postInfo to be [String: AnyObject] instead of AnyObject.

possible to cast this Alamofire result to an array of dictionaries

I am not an iOS dev and have to make a few changes to a Swift / AlamoFire project (not mine) and am a bit lost.
I have the following JSON:
{"metro_locations":
[
{
"name":"Ruby Red"
},
{
"name":"Blue Ocean"
}
]
}
class (I know that there are issues here):
class Location{
var name=""
init(obj:tmp){
self.name=tmp["name"]
}
}
and need to make an AlamoFire call
Alamofire.request(.GET, "https://www.domain.com/arc/v1/api/metro_areas/1", parameters: nil)
.responseJSON { response in
if let dataFromNetworking = response.result.value {
let metroLocations = dataFromNetworking["metro_locations"]
var locations: [Location]=[]
for tmp in metroLocations as! [Dictionary] { // <- not working, Generic Paramter 'Key' could not be inferred
let location=Location.init(obj: tmp)
locations.append(location)
}
}
}
I have included the error msg, the "not working" but feel that there are issues in other parts too (like expecting a dictionary in the initialization). What does the 'Key' could not be inferred mean and are there other changes I need to make?
edit #1
I have updated my Location to this to reflect your suggestion:
init?(dictionary: [String: AnyObject]) {
guard let id = dictionary["id"] else { return nil }
guard let name = dictionary["name"] else { return nil }
guard let latitude = dictionary["latitude"] else { return nil }
guard let longitude = dictionary["longitude"] else { return nil }
self.name = name as! String
self.id = id as! Int
self.latitude = latitude as! Double
self.longitude = longitude as! Double
}
but I get the error:
Could not cast value of type 'NSNull' (0x10f387600) to 'NSNumber' (0x10f77f2a0).
like this:
I would think that the guard statement would prevent this. What am I missing?
You can cast metroLocations as an array of dictionaries, namely:
Array<Dictionary<String, String>>
Or, more concisely:
[[String: String]]
Thus:
if let dataFromNetworking = response.result.value {
guard let metroLocations = dataFromNetworking["metro_locations"] as? [[String: String]] else {
print("this was not an array of dictionaries where the values were all strings")
return
}
var locations = [Location]()
for dictionary in metroLocations {
if let location = Location(dictionary: dictionary) {
locations.append(location)
}
}
}
Where
class Location {
let name: String
init?(dictionary: [String: String]) {
guard let name = dictionary["name"] else { return nil }
self.name = name
}
}
Clearly, I used [[String: String]] to represent an array of dictionaries where the values were all strings, as in your example. If the values included objects other than strings (numbers, booleans, etc.), then you might use [[String: AnyObject]].
In your revision, you show us a more complete Location implementation. You should avoid as! forced casting, and instead us as? in the guard statements:
class Location {
let id: Int
let name: String
let latitude: Double
let longitude: Double
init?(dictionary: [String: AnyObject]) {
guard let id = dictionary["id"] as? Int,
let name = dictionary["name"] as? String,
let latitude = dictionary["latitude"] as? Double,
let longitude = dictionary["longitude"] as? Double else {
return nil
}
self.name = name
self.id = id
self.latitude = latitude
self.longitude = longitude
}
}