groupBy array by two values in swift - swift

I'm using this extension of Array to groupBy:
func groupBy <U> (groupingFunction group: (Element) -> U) -> [U: Array] {
var result = [U: Array]()
for item in self {
let groupKey = group(item)
// If element has already been added to dictionary, append to it. If not, create one.
if result.has(groupKey) {
result[groupKey]! += [item]
} else {
result[groupKey] = [item]
}
}
return result
}
And trying to make a dictionary from teamId and rank (custom properties of my object from array).
self.items = myArray.groupBy {
team in
if team.rank == nil {
return team.teamId!
} else {
return team.rank!
}
}
My case looks something like this:
[{
"team_id": "4",
"team_name": "T16",
"rank": "3"
},
,{
"team_id": "4",
"team_name": "T16",
"rank": "2"
},
{
"team_id": "4",
"team_name": "T16",
"rank": "1"
}
,{
"team_id": "5",
"team_name": "T16",
"rank": null
}]
desired output:
let teams : [String: TeamItem] = [ "4" : TeamItem.rank3, "4-2" : TeamItem.rank2, "4-1" : TeamItem.rank1, "5" : TeamItem]
EDIT 2
func requestTeamData(listener:([TeamItem]) -> ()) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
let baseURL: String = self._appSettings.getPathByName(EndPoint.BASE_URL.rawValue) as! String
let paths: NSDictionary = self._appSettings.getPathByName(EndPoint.PATH.NAME.rawValue) as! NSDictionary
let teamPaths: NSDictionary = paths.objectForKey(EndPoint.PATH.TEAM.KEY.rawValue) as! NSDictionary
let path = teamPaths.valueForKey(EndPoint.PATH.TEAM.PATH.rawValue) as! String
request(.GET, baseURL + path, parameters:[:])
.responseCollection { (_, _, team: [TeamItem]?, error) in
if let team = team {
dispatch_async(dispatch_get_main_queue()) {
listener(team)
}
}
}
}
}
Calling the API, straightforward
DataProvider.getInstance().requestTeamData({(items:[TeamItem]) -> () in
//Getting the items from the API
})
EDIT 1
#objc final class TeamItem: Equatable, ResponseObjectSerializable, ResponseDictionarySerializable {
let firstName: String?
let lastName: String?
let seasonId: String?
let seasonName: String?
let teamId: String?
let teamName: String?
let rank: String?
let positionId: String?
let positionName: String?
let number: String?
let birthDate: String?
let birthPlace: String?
let citizenship: String?
let height: String?
let weight: String?
let maritalStatus: String?
let model: String?
let hobby: String?
let food: String?
let book: String?
let movie: String?
let wish: String?
let message: String?
let biography: String?
let imageURL: String?
let teamImageURL: String?
required init?(response: NSHTTPURLResponse, representation: AnyObject) {
self.firstName = representation.valueForKeyPath(EndPoint.Keys.Team.FirstName.rawValue) as? String
self.lastName = representation.valueForKeyPath(EndPoint.Keys.Team.LastName.rawValue) as? String
self.seasonId = representation.valueForKeyPath(EndPoint.Keys.Team.SeasonId.rawValue) as? String
self.seasonName = representation.valueForKeyPath(EndPoint.Keys.Team.SeasonName.rawValue) as? String
self.teamId = representation.valueForKeyPath(EndPoint.Keys.Team.TeamId.rawValue) as? String
self.teamName = (representation.valueForKeyPath(EndPoint.Keys.Team.TeamName.rawValue) as? String)!
self.rank = representation.valueForKeyPath(EndPoint.Keys.Team.Rank.rawValue) as? String
self.positionId = representation.valueForKeyPath(EndPoint.Keys.Team.PositionId.rawValue) as? String
self.positionName = representation.valueForKeyPath(EndPoint.Keys.Team.PositionName.rawValue) as? String
self.number = representation.valueForKeyPath(EndPoint.Keys.Team.Number.rawValue) as? String
self.birthDate = representation.valueForKeyPath(EndPoint.Keys.Team.BirthDate.rawValue) as? String
self.birthPlace = (representation.valueForKeyPath(EndPoint.Keys.Team.BirthPlace.rawValue) as? String)!
self.citizenship = representation.valueForKeyPath(EndPoint.Keys.Team.Citizenship.rawValue) as? String
self.height = representation.valueForKeyPath(EndPoint.Keys.Team.Height.rawValue) as? String
self.weight = representation.valueForKeyPath(EndPoint.Keys.Team.Weight.rawValue) as? String
self.maritalStatus = representation.valueForKeyPath(EndPoint.Keys.Team.MaritalStatus.rawValue) as? String
self.model = representation.valueForKeyPath(EndPoint.Keys.Team.Model.rawValue) as? String
self.hobby = representation.valueForKeyPath(EndPoint.Keys.Team.Hobby.rawValue) as? String
self.food = representation.valueForKeyPath(EndPoint.Keys.Team.Food.rawValue) as? String
self.book = representation.valueForKeyPath(EndPoint.Keys.Team.Book.rawValue) as? String
self.movie = representation.valueForKeyPath(EndPoint.Keys.Team.Movie.rawValue) as? String
self.wish = representation.valueForKeyPath(EndPoint.Keys.Team.Wish.rawValue) as? String
self.message = representation.valueForKeyPath(EndPoint.Keys.Team.Message.rawValue) as? String
self.biography = representation.valueForKeyPath(EndPoint.Keys.Team.Biography.rawValue) as? String
self.imageURL = representation.valueForKeyPath(EndPoint.Keys.Team.ImageURL.rawValue) as? String
self.teamImageURL = representation.valueForKeyPath(EndPoint.Keys.Team.TeamImageURL.rawValue) as? String
}
#objc static func collection(#response: NSHTTPURLResponse, representation: AnyObject) -> [TeamItem] {
var teamItemArray: [TeamItem] = []
if let representation = representation as? [[String: AnyObject]] {
for teamItemRepresentation in representation {
if let teamItem = TeamItem(response: response, representation: teamItemRepresentation) {
teamItemArray.append(teamItem)
}
}
}
return teamItemArray
}
}
func ==(lhs: TeamItem, rhs: TeamItem) -> Bool {
return lhs.lastName == rhs.lastName
}
}
EDIT
TeamItem.rank1 means actually that the value of the rank is 1 (the same for others)

This is what've need, hope will help someone:
#objc static func dictionary(#response: NSHTTPURLResponse, representation: AnyObject) -> [String: Array<TeamItem>] {
var teamItemDictionary: [String: Array<TeamItem>] = [:]
if let representation = representation as? [[String: AnyObject]] {
for teamItemRepresentation in representation {
if let teamItem = TeamItem(response: response, representation: teamItemRepresentation) {
if teamItem.rank != nil {
let teamKey = teamItem.teamId! + "-" + teamItem.rank!
if teamItem.teamId != EndPoint.Keys.Team.Type.rawValue {
if let team = teamItemDictionary[teamKey] as [TeamItem]? {
teamItemDictionary[teamKey]?.push(teamItem)
} else {
teamItemDictionary[teamKey] = []
teamItemDictionary[teamKey]?.push(teamItem)
}
}
} else {
if teamItem.teamId != EndPoint.Keys.Team.Type.rawValue {
if let team = teamItemDictionary[teamItem.teamId!] as [TeamItem]? {
teamItemDictionary[teamItem.teamId!]?.push(TeamItem)
} else {
teamItemDictionary[teamItem.teamId!] = []
teamItemDictionary[teamItem.teamId!]?.push(TeamItem)
}
}
}
}
}
}
return teamItemDictionary
}

Related

Casting JSON array to a struct model swift

I am parsing a json array of object into a model which works. In this object, there is an array to a value and I created another model to handle that array but when ever I pass this object, the internal array returns nil after casting as the model class. Any help is appreciated
JSON Sample
[
{
"code": "AF",
"code3": "AFG",
"dial_code": "+93",
"name": "Afghanistan",
"capital": "Kabul",
"region": "Asia",
"subregion": "Southern Asia",
"states": [
{
"code": "BDS",
"name": "Badakhshān",
"subdivision": null
},
{
"code": "BGL",
"name": "Baghlān",
"subdivision": null
}
]
}
}
]
MODEL
public struct LocaleInfo {
public var locale: Locale?
public var id: String? {
return locale?.identifier
}
public var country: String
public var code: String
// public var phoneCode: String
public var states: [LocalStateInfo]
public var flag: UIImage? {
return UIImage(named: "Countries.bundle/Images/\(code.uppercased())", in: Bundle.main, compatibleWith: nil)
}
public var currencyCode: String? {
return locale?.currencyCode
}
public var currencySymbol: String? {
return locale?.currencySymbol
}
public var currencyName: String? {
guard let currencyCode = currencyCode else { return nil }
return locale?.localizedString(forCurrencyCode: currencyCode)
}
init(country: String, code: String/*, phoneCode: String*/, states: [LocalStateInfo]) {
self.country = country
self.code = code
self.states = states
self.locale = Locale.availableIdentifiers.map { Locale(identifier: $0) }.first(where: { $0.regionCode == code })
}
}
public struct LocalStateInfo {
public var code: String
public var name: String
public var subdivision: String
}
Passing the JSON Body
func getInfo(completionHandler: #escaping (FetchResults) -> ()) {
let bundle = Bundle(for: LocalePickerViewController.self)
let path = "Countries.bundle/Data/CountryCodes"
guard let jsonPath = bundle.path(forResource: path, ofType: "json"),
let jsonData = try? Data(contentsOf: URL(fileURLWithPath: jsonPath)) else {
let error: (title: String?, message: String?) = (title: "ContryCodes Error", message: "No ContryCodes Bundle Access")
return completionHandler(FetchResults.error(error: error))
}
if let jsonObjects = (try? JSONSerialization.jsonObject(with: jsonData, options: JSONSerialization.ReadingOptions.allowFragments)) as? Array<Any> {
var result: [LocaleInfo] = []
for jsonObject in jsonObjects {
guard let countryObj = jsonObject as? Dictionary<String, Any> else { continue }
guard let country = countryObj["name"] as? String,
let code = countryObj["code"] as? String/*,
let phoneCode = countryObj["dial_code"] as? String*/ else {
fatalError("Broken here")
continue
}
log("countryObj state \(countryObj["states"] as? [LocalStateInfo])", .fuck)
log("countryObj \(countryObj)", .fuck)
let states = countryObj["states"] as? [LocalStateInfo] ?? [LocalStateInfo]()
let new = LocaleInfo(country: country, code: code/*, phoneCode: phoneCode*/, states: states)
result.append(new)
}
return completionHandler(FetchResults.success(response: result))
}
let error: (title: String?, message: String?) = (title: "JSON Error", message: "Couldn't parse json to Info")
return completionHandler(FetchResults.error(error: error))
}
let states = countryObj["states"] as? [LocalStateInfo] ?? [LocalStateInfo]()
is presumably the line that isn't working for you. But countryObj is just a dictionary straight from JSON:
guard let countryObj = jsonObject as? Dictionary<String, Any> else { continue }
Why would casting it to an array of LocalStateInfo work at at all? It's an array of dictionaries, and you need to parse each one out individually.
You've said using Codable would alter the "entire scope" of the library, I don't understand how this is the case. You can implement codable (or even just Decodable) without affecting any other file.

How to map the nested data in a document from Firestore by Swift?

I have a document data structure on Firestore like this:
pic1
pic2
So there are 2 map-objects inside the document and a collection and a another document inside this document
Then I create 3 model swift files for this document
task:
struct task {
var Name: String
var Address: String
var Car: CarModel
car Price: PriceModel
var dictionary: [String:Any] {
return [
"Name" : Name,
"Address" : Address,
"Car" : CarModel,
"Price" : PriceModel
]
}
init?(data: [String:Any]) {
guard let Name = dictionary["Name"] as? String,
let Address = dictionary["Address"] as? String,
let Car = ditionary["car"] as? CarModel,
let Price = dictionary["price"] as? PriceModel else{
return nil
}
self.Name = Name
self.Address = Address
self.Car = Car
self.Price = Price
}
}
CarModel:
struct CarModel {
var brand: String
var model: String
var year: String
var dictionary: [String:Any] {
return [
"brand" : brand,
"model" : model,
"year" : year,
]
}
init?(data: [String:Any]) {
guard let brand = dictionary["brand"] as? String,
let model = dictionary["model"] as? String,
let year = ditionary["year"] as? String else{
return nil
}
self.brand = brand
self.model = model
self.year = year
}
}
PriceModel:
struct PriceModel {
var basic: Int
var extra: Int
var dictionary: [String:Any] {
return [
"basic" : basic,
"extra" : extra,
]
}
init?(data: [String:Any]) {
guard let basic = dictionary["basic"] as? Int,
let extra = ditionary["extra"] as? Int else{
return nil
}
self.basic = basic
self.extra = extra
}
}
Then download the data with this following code:
func loadDataFromFirestore(completion: #escaping (Bool) -> ()) {
var success: Bool = false
DispatchQueue.global(qos: .userInteractive).async {
let downloadGroup = DispatchGroup()
let colRef = db.collection("tasks")
downloadGroup.enter()
colRef.getDocuments() { (querySnapshot, error) in
if let error = error {
print("Error: \(error)")
return
}
for document in querySnapshot!.documents{
let result = document.data()
print (result)
if let data = task(data: result){
print(data)
}
}
success = true
downloadGroup.leave()
}
downloadGroup.wait()
DispatchQueue.main.async {
completion(success)
}
}
}
I can get the data with comment the CarModel and PriceModel, but if I uncomment these two, it will let my app crash!
So how could I get the map-object to adapt to my code?
And the second question is: How can I get the document inside a document's collection with this kind of code?

Posts Being Uploaded Randomly in Collection View - Swift & Firebase

I have been refactoring my code and now I'm having trouble with the posts.
Whenever I add a new post to the collection view, it is being added in a random cell and out of order, instead of in the first post.
I know the reason is the fetchuser function and from what I'm being told due to the asynchronous loading, but don't know what to do in order to correct this.
Could someone help me figure out what to do so that my posts are added in the first cell?
#objc func observePostsAdoption() {
let postsRef = Database.database().reference().child("posts")
postsRef.queryOrdered(byChild: "postType").queryEqual(toValue: "adopt").observe(.value) { (snapshot) in
var tempPost = [Posts]()
for child in snapshot.children {
if let childSnapshot = child as? DataSnapshot {
let dict = childSnapshot.value as? [String: Any]
let newAdoptiondPost = Posts.transformPost(dict: dict!)
//This will look up all users at once
self.fetchUser(userid: newAdoptiondPost.userid!, completed: {
tempPost.insert(newAdoptiondPost, at: 0)
DispatchQueue.main.async {
self.postsadoption = tempPost
self.adoptionCollectionView.reloadData()
self.refresherAdoption.endRefreshing()
}
})
}
}
}
}
func fetchUser(userid: String, completed: #escaping ()-> Void ) {
Database.database().reference().child("users").child(userid).observeSingleEvent(of: .value) { (snapshot) in
if let dict = snapshot.value as? [String: Any] {
let user = UserProfile.transformUser(dict: dict)
self.users.insert(user, at: 0)
completed()
}
}
}
Here's my Post Struct
class Posts {
//UserView
var uid: String?
var author: UserProfile?
var timestamp: Date?
var userid: String?
func getDateFormattedString() -> String {
let formatter = DateFormatter()
formatter.dateFormat = "MMM d, HH:mm"
return formatter.string(from: self.timestamp!)
}
//Image
var photoUrl: URL?
//PostInformation View
var city: String?
var municipality: String?
var name: String?
var breed : String?
var phone : String?
var address : String?
var petType: String?
var genderType: String?
var comments: String?
}
extension Posts {
static func transformPost(dict: [String: Any]) -> Posts {
let post = Posts()
//Post Picture
let photoUrl = dict["photoUrl"] as? String
post.photoUrl = URL(string: photoUrl!)
//INFO POSTS
post.userid = dict["userid"] as? String
post.city = dict["city"] as? String
post.municipality = dict["municipality"] as? String
post.name = dict["name"] as? String
post.breed = dict["breed"] as? String
post.phone = dict["phone"] as? String
post.address = dict["address"] as? String
post.comments = dict["comments"] as? String
post.petType = dict["petType"] as? String
post.genderType = dict["gender"] as? String
let timestamp = dict["timestamp"] as? Double
post.timestamp = Date(timeIntervalSince1970: timestamp!/1000)
return post
}
}
If you already have the posts ordered by post type you can just do sorting depending on the timestamp. For example
#objc func observePostsAdoption() {
let postsRef = Database.database().reference().child("posts")
postsRef.queryOrdered(byChild: "postType").queryEqual(toValue: "adopt").observe(.value) { (snapshot) in
var tempPost = [Posts]()
for child in snapshot.children {
if let childSnapshot = child as? DataSnapshot {
let dict = childSnapshot.value as? [String: Any]
let newAdoptiondPost = Posts.transformPost(dict: dict!)
//This will look up all users at once
self.fetchUser(userid: newAdoptiondPost.userid!, completed: {
tempPost.insert(newAdoptiondPost, at: 0)
DispatchQueue.main.async {
self.postsadoption = tempPost
self.postsadoption.sort { (p1, p2) -> Bool in
return p1.timeStamp?.compare(p2.timeStamp!) == .orderdDescending
}
self.adoptionCollectionView.reloadData()
self.refresherAdoption.endRefreshing()
}
})
}
}
}
}
With that the posts adoption array will be sorted depending on the timestamp that you have.

UITableView Null Value

I have a list of JSON data downloaded from server:
(DataModal.swift)
class DataModal {
var orderAutoid: Int?
var orderId: String?
var orderName: String?
var orderQty: String?
var orderStatus: String?
init(bOrder_autoid: Int, bOrder_id: String, bOrder_name: String, bOrder_qty: String, bOrder_status: String){
self.orderAutoid = bOrder_autoid
self.orderId = bOrder_id
self.orderName = bOrder_name
self.orderQty = bOrder_qty
self.orderStatus = bOrder_status
}
(OrderStructureDownloadProtocol.swift)
protocol OrderStructureDownloadProtocol: class {
func newItemDownload(items: Array<Any>)
}
....
var jsonElement = Dictionary<String, Any>()
var newOrders = Array<Any>()
for i in 0..<jsonResult.count {
jsonElement = jsonResult[i] as! Dictionary
let newOrder_autoid = jsonElement["orderAutoid"] as? Int ?? 0
let newOrder_id = jsonElement["orderId"] as? String ?? ""
let newOrder_name = jsonElement["orderName"] as? String ?? ""
let newOrder_qty = jsonElement["orderQty"] as? String ?? ""
let newOrder_status = jsonElement["orderStatus"] as? String ?? ""
let newOrder = BMSDataModal(bOrder_autoid: newOrder_autoid, bOrder_id: newOrder_id, bOrder_name: newOrder_name, bOrder_qty: newOrder_qty, bOrder_status: newOrder_status)
newOrders.append(newOrder)
}
DispatchQueue.main.async (
execute: { () -> Void in
self.delegate.newItemDownload(items: newOrders as! Array<Any>)
})
(tableview.swift)
var newOrdersArray = [BMSDataModal]()
func newItemDownload(items: Array<Any>) {
newOrdersArray = items as! [BMSDataModal]
newOrderLookupTableView.reloadData()
}
(tableview.swift another part)
let cell = tableView.dequeueReusableCell(withIdentifier: "orderLookupCell", for: indexPath) as! NewOrderTableViewCell
let item = newOrdersArray[indexPath.row]
cell.newHMNumber?.text = item.orderId ?? "-"
cell.newMP?.text = item.orderName ?? "-"
cell.newQTY?.text = item.orderQty ?? "-"
return cell
}
having all the old NS-style changed. The app is running okay, there are some items that need to reset. As my data-source always contain Double, but I declared it as a String, as I won't deal with calculation so I treated it as 'String'.

Swift - How to init an array in struct?

I'm not sure how to init the array in Struct. I'm not able to fetch data from array, meanwhile I was manage to get the result from object (platform.status).
Am I init it wrongly ?
Any ideas ?
Here is Network Request :
func fetchServicePlatform(token: String, _key: String) {
let selectedUrl = URL(string: "\(mainUrl)/get_service")
let parameters: [String: String] = ["_key": _key]
var serviceList = [ServiceList]()
URLSession.shared.dataTask(with: setupURLRequest(selectedURL: selectedUrl!, parameters: parameters, token: token, email: "test#gmail.com")) { (data, response, error) in
if let err = error {
print("Failed to fetch API: \(err.localizedDescription)")
}
guard let data = data else { return }
do {
guard let json = try JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [String: Any] else { return }
let platform = Platform(json: json)
if platform.status == "success" {
self.serviceList = platform.service_list
DispatchQueue.main.async {
self.collectionView.reloadData()
}
}
} catch let jsonErr {
print("Failed to fetch service platform: ", jsonErr.localizedDescription)
}
}.resume()
}
Here is JSON :
{
"status": "pass",
"service_list": [
{
"title": "Plumber",
"description": "Plumber",
"image": "https://s3-ap-southeast-1.heroku.com.png"
},
{
"title": "Cleaner",
"description": "Cleaner",
"image": "https://s3-ap-southeast-1.heroku.com.png"
}
]
}
Here is Struct :
struct Platform {
let service_list: [ServiceList]
let status: String
init(json: [String: Any]) {
service_list = [ServiceList(json: json["service_list"] as? [String: Any] ?? [:])]
status = json["status"] as? String ?? ""
}
}
struct ServiceList {
let title: String
let description: String
let image: String
init(json: [String: Any]) {
title = json["title"] as? String ?? ""
description = json["description"] as? String ?? ""
image = json["image"] as? String ?? ""
}
}
In your data json["service_list"] is an array of dictionaries,
You Can try out.
struct Platform {
var service_list: [ServiceList] = []
var status: String
init(json: [String: Any]) {
if let jsonArray = json["service_list"] as? [[String: Any]] {
for service in jsonArray {
service_list.append(ServiceList(json: service))
}
}
else{
service_list = []
}
status = json["status"] as? String ?? ""
}
}
init an array in the struct
struct MyData {
var dataArray:[Any] = []
var latitude: Float
var longitude: Float
}
You have to unwrap the dictionary as an array of dictionaries and then loop through it with a map or flatmap, where you use the value $0 as the value
guard let serviceList = (json["service_list"] as? [[String: Any]])?.flatmap({ServiceList(withDictionary: $0)})
Go with the below approach, this way it is simpler
Create your structure with Codable
struct Platform: Codable {
let service_list: [ServiceList]
let status: String
enum CodingKeys: String, CodingKey {
case service_list, status
}
}
struct ServiceList: Codable {
let title: String
let description: String
let image: String
enum CodingKeys: String, CodingKey {
case title, description, image
}
}
Your json data object
let json = """
{
"status": "pass",
"service_list": [
{
"title": "Plumber",
"description": "Plumber",
"image": "https://s3-ap-southeast-1.heroku.com.png"
},
{
"title": "Cleaner",
"description": "Cleaner",
"image": "https://s3-ap-southeast-1.heroku.com.png"
}
]
}
""".data(using: .utf8)!
Use the JSONDecoder to map the json object to structure
let decoder = JSONDecoder()
let platform = try! decoder.decode(Platform.self, from: json)
if platform.status == "pass"{
for service in platform.service_list{
print(service.title)
}
}