I have an array of Person. The Person object has many fields between them inscriptionDate (a timestamp). I have to create a collectionView from this array but using sections. Every section has a header that is inscriptionDate as a date having this format dd/mm/yyyy. I have to sort the array by inscriptionDate but without time (only the format dd/mm/yyyy) in order to load the data in the collectionView (taking into consideration the sections). I have found from another question this solution. But how can I sort the array before doing this? How can I use this:
order = NSCalendar.currentCalendar().compareDate(now, toDate: olderDate,
toUnitGranularity: .Day)
in my case?
I agree with #Vasilii Muravev, you will need to clean your timestamp object first, either using extensions or functions. Timestamp isn't a valid Swift object btw, unless that is a custom class you created.
Then you can create a dictionary for your dataSource. I will use #Vasilii Muravev's extension:
//var myKeys : [Date] = []
let sortedPeople = persons.sorted { $0.inscriptionDate.noTime() < $1.inscriptionDate.noTime() }
//break your array into a dictionary([Date : [Person]])
//personsSortedByDateInSections : [Date : [Person]] = [:]
for person in sortedPeople {
if personsSortedByDateInSections[person.inscriptionDate] != nil {
personsSortedByDateInSections[person.inscriptionDate]!.append(person)
} else {
personsSortedByDateInSections[person.inscriptionDate] = [person]
}
}
myKeys = setKeyArray(personSortedByDateInSections.keys)
that will give you a dictionary object with all of your Person objects grouped(sectioned) by their inscriptionDate. Then you will just need to fill out your collectionView delegate and datasource methods.
override func numberOfSections(in: UICollectionView) -> Int {
return myKeys.count
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return personsSortedByDateInSections[myKeys[section]].count
}
UPDATE:
As stated in your comment there is a issue with grabbing a array of the keys with swift dictionaries(I don't think swift dictionaries had this issue in earlier version? I could be wrong)....anyway to workaround this I have used this function to set a class variable for the 'keyArray'.:
fileprivate func setKeysArray(_ keys: LazyMapCollection<Dictionary<Date, [Person]>, String) -> [Date]{
var keysArray = [Date]()
for key in keys {
keysArray.append(key)
}
return keysArray
}
First, you'll need to clean timestamp before the sorting. You can do that by using Calendar and Date extension:
extension Date {
func noTime() -> Date! {
let components = Calendar.current.dateComponents([.day, .month, .year], from: self)
return Calendar.current.date(from: components)
}
}
Then you'll just need to sort your array by date without time:
let sortedByDate = persons.sorted { $0.inscriptionDate.noTime() < $1.inscriptionDate.noTime() }
Note. Be careful with compareDate function of Calendar, since it comparing only specific component. If in this example: NSCalendar.currentCalendar().compareDate(now, toDate: olderDate, toUnitGranularity: .Day) you'll have same days in different months, the comparing result will show that dates are equal.
Assuming your inscriptionDate is a Date, why can't you sort on that? Since Date conforms to Comparable all you need is
let sortedByDate = persons.sorted { $0.inscriptionDate < $1.inscriptionDate }
I wright a code for you I thing this code useful for you.
//
// ViewController.swift
// sortData
//
// Created by Bijender singh on 26/01/18.
// Copyright © 2018 Bijender singh. All rights reserved.
//
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func viewDidAppear(_ animated: Bool) {
let myDataArray = NSMutableArray()
var myDataDic = NSMutableDictionary()
myDataDic.setValue("Bijender", forKey: "name")
myDataDic.setValue("1516965600", forKey: "date")
myDataArray.add(myDataDic)
myDataDic = NSMutableDictionary()
myDataDic.setValue("ben", forKey: "name")
myDataDic.setValue("1516965540", forKey: "date")
myDataArray.add(myDataDic)
myDataDic = NSMutableDictionary()
myDataDic.setValue("Deke", forKey: "name")
myDataDic.setValue("1516842180", forKey: "date")
myDataArray.add(myDataDic)
myDataDic = NSMutableDictionary()
myDataDic.setValue("Veer", forKey: "name")
myDataDic.setValue("1516842000", forKey: "date")
myDataArray.add(myDataDic)
myDataDic = NSMutableDictionary()
myDataDic.setValue("Ashfaq", forKey: "name")
myDataDic.setValue("1515981900", forKey: "date")
myDataArray.add(myDataDic)
print(myDataArray)
let sortedArray = self.sortDataWithDate(arrayData: myDataArray)
// sortedArray contane array which contan same date array
// you can use it according to you
// sortedArray.count is total diffrent dates in your array
// and (sortedArray.count[i] as! NSArray).count give you count of data of that date (0 < i < sortedArray.count)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func sortDataWithDate(arrayData : NSArray) -> NSArray {
// var chat: [ChatMessage]!
let returnArray = NSMutableArray()
var subArray = NSMutableArray()
let arrayDate = NSMutableArray()
for i in 0 ..< arrayData.count {
let msgDate = self.timeStampToDate(_timestamp: ((arrayData[i] as! NSDictionary).object(forKey: "date") as! String), _dateFormat: "dd/MM/yyyy")
print("dddt \(msgDate)")
// print("date array \(arrayDate) $$ msgDate \(msgDate)")
if arrayDate.contains(msgDate) {
subArray.add(arrayData[i])
}
else{
arrayDate.add(msgDate)
if arrayDate.count > 1 {
returnArray.add(subArray)
}
subArray = NSMutableArray()
subArray.add(arrayData[i])
}
}
if subArray != nil {
returnArray.add(subArray)
}
print(returnArray)
return returnArray
}
func timeStampToDate(_timestamp : String, _dateFormat : String) -> String{
// _dateFormat = "yyyy-MM-dd HH:mm:ss.SSSS"
var date = Date(timeIntervalSince1970: TimeInterval(_timestamp)!)
date += TimeInterval(Int(TimeZone.current.secondsFromGMT()) as NSNumber)
let dateFormatter = DateFormatter()
dateFormatter.timeZone = TimeZone(abbreviation: "GMT") //Set timezone that you want
dateFormatter.locale = NSLocale.current
dateFormatter.dateFormat = _dateFormat
let strDate = dateFormatter.string(from: date)
return strDate
}
}
In this code I insert array as hard coded
(
{
date = 1516965600;
name = Bijender;
},
{
date = 1516965540;
name = ben;
},
{
date = 1516842180;
name = Deke;
},
{
date = 1516842000;
name = Veer;
},
{
date = 1515981900;
name = Ashfaq;
}
)
And out put array is
(
(
{
date = 1516965600;
name = Bijender;
},
{
date = 1516965540;
name = ben;
}
),
(
{
date = 1516842180;
name = Deke;
},
{
date = 1516842000;
name = Veer;
}
),
(
{
date = 1515981900;
name = Ashfaq;
}
)
)
Related
I would like to sort my data by date from newest down to oldest. I am getting Transaction objects from Firestore in the form of "2020-12-29". I have seen previous answers on how to sort arrays by date but I am unsure how I can sort it with my current object structure.
func loadTransactions(){
if let catId = self.categoryId{
guard let user = Auth.auth().currentUser?.uid else { return }
db.collection("users").document(user).collection("Transactions")
.whereField("catId", isEqualTo: catId)
.getDocuments() {
snapshot, error in
if let error = error {
print("\(error.localizedDescription)")
} else {
self.budgetData.removeAll()
for document in snapshot!.documents {
let data = document.data()
let title = data["name"] as? String ?? ""
let date = data["date"] as? String ?? ""
let amount = data["amount"] as? Double ?? 0
let id = data["transId"] as? String ?? ""
let absolute = abs(amount)
let trans = Transaction(transId: id, catId:catId,title: title, dateInfo: date, image: UIImage.grocIcon, amount: absolute)
self.budgetData.append(trans)
// let testArray = ["25 Jun, 2016", "30 Jun, 2016", "28 Jun, 2016", //"2 Jul, 2016"]
// var convertedArray: [Date] = []
// var dateFormatter = DateFormatter()
// dateFormatter.dateFormat = "dd MM, yyyy"// yyyy-MM-dd"
// for dat in testArray {
// let date = dateFormatter.date(from: dat)
// if let date = date {
// convertedArray.append(date)
// }
// }
// var ready = convertedArray.sorted(by: { $0.compare($1) == .orderedDescending })
var tranSet = Set<Transaction>()
self.budgetData = self.budgetData.filter { (transaction) -> Bool in
if !tranSet.contains(transaction){
tranSet.insert(transaction)
return true
}
return false
}
DispatchQueue.main.async {
self.setTotalAmountOfCats()
self.tableView.reloadData()
}
}
}
}
}
}
You can sort the data in your Firebase query using order(by:): https://firebase.google.com/docs/firestore/query-data/order-limit-data
In your example, it'll probably look like:
db.collection("users").document(user).collection("Transactions")
.whereField("catId", isEqualTo: catId)
.order(by: "date")
If you implemented this, you could get rid of your tranSet and just append the Transactions in order (this is out of scope of the question, but I'd probably look at compactMap to transform snapshot.documents instead of appending them in order).
This assumes that your dates are in the format you listed (YYYY-MM-DD), which will alphabetize correctly, even though they aren't actually date/timestamp objects. If your data is in the format in your commented-out testArray section, than a different method would have to be used.
So i have created a class for a Day and for a Drink. and I'm trying to track how much you drink in a day, but I'm struggling with saving multiple days. I'm currently managing to save the current day(with the amount drunk that day) but i don't know how to save more than one day.
I want to save an array of type Day with all the days. how can i do this?
This is my Day class:
public class Day: NSObject {
var date: Date
var goalAmount: Drink
var consumedAmount: Drink
func saveDay() {
let formatting = DateFormatter()
formatting.dateFormat = "EEEE - dd/mm/yy"
UserDefaults.standard.set(formatting.string(from: date), forKey: "date")
UserDefaults.standard.set(goalAmount.amountOfDrink, forKey: "goal")
UserDefaults.standard.set(consumedAmount.amountOfDrink, forKey: "consumed")
}
func loadDay() {
let rawDate = UserDefaults.standard.value(forKey: "date") as? String ?? ""
let formatter = DateFormatter()
formatter.dateFormat = "EEEE - dd/mm/yy"
date = formatter.date(from: rawDate)!
goalAmount.amountOfDrink = UserDefaults.standard.float(forKey: "goal")
consumedAmount.amountOfDrink = UserDefaults.standard.float(forKey: "consumed")
}
}
This is my Drink class:
class Drink: NSObject {
var typeOfDrink: String
var amountOfDrink: Float
}
i am calling saveDay() when there are any changes made to the day, and then loadDay() when the app opens.
A better approach would be is to store the object of the class in userDefaults instead of storing particular properties of that class. And use [Date] instead of Date to save multiple days
For this first, you have Serialize the object to store in userDefaults and Deserialize to fetch the data from userDefaults.
import Foundation
class Day: Codable {
var date = Date()
var goalAmount: Drink
var consumedAmount: Drink
init(date: Date, goalAmount: Drink,consumedAmount: Drink ) {
self.date = date
self.goalAmount = goalAmount
self.consumedAmount = consumedAmount
}
static func saveDay(_ day : [Day]) {
do {
let object = try JSONEncoder().encode(day)
UserDefaults.standard.set(object, forKey: "days")
} catch {
print(error)
}
}
static func loadDay() {
let decoder = JSONDecoder()
if let object = UserDefaults.standard.value(forKey: "days") as? Data {
do {
let days = try decoder.decode([Day].self, from: object)
for day in days {
print("Date - ", day.date)
print("Goal Amount - ", day.goalAmount)
print("Consumed Amount - ",day.consumedAmount)
print("----------------------------------------------")
}
} catch {
print(error)
}
} else {
print("unable to fetch the data from day key in user defaults")
}
}
}
class Drink: Codable {
var typeOfDrink: String
var amountOfDrink: Float
init(typeOfDrink: String,amountOfDrink: Float ) {
self.typeOfDrink = typeOfDrink
self.amountOfDrink = amountOfDrink
}
}
Use saveAndGet() method to store and fetch details from userDefaults
func saveAndGet() {
// use any formats to format the dates
let date = Date()
let goalAmount = Drink(typeOfDrink: "Water", amountOfDrink: 5.0)
let consumedAmount = Drink(typeOfDrink: "Water", amountOfDrink: 3.0)
let day1 = Day(date: date, goalAmount: goalAmount, consumedAmount: consumedAmount)
let day2 = Day(date: date, goalAmount: goalAmount, consumedAmount: consumedAmount)
let day3 = Day(date: date, goalAmount: goalAmount, consumedAmount: consumedAmount)
let day4 = Day(date: date, goalAmount: goalAmount, consumedAmount: consumedAmount)
let days = [day1, day2, day3, day4]
Day.saveDay(days)
Day.loadDay()
}
1) You need to create array of object for this :
goalAmount = [Drink]()
var date = [Date]()
and append with each new element.
you can also add date variable inside your drink class.
2) you can also create array of dictionary:
var userData = [String : Any]()
key will be you date and Any contain related to drink data in Any you can store Anything.
I have a bunch of users on my message app, fetching my "messages" class I need to get sender's first and last name and profile image from their Parse profile on my app in order to show them in each message on the tableView.
I just want to show in tableView the name of users in class "messages" contained in the column "sender" wich contains pointers to PFUsers (of which I need "first_name", "last_name", "profile_picture")
my users class
my message class
update!
can't get where is the problem, if I downCast something, something else must be changed. here the updated code:
findTimeLineDataQuery.includeKey("sender")
findTimeLineDataQuery.findObjectsInBackgroundWithBlock({
(objects : [AnyObject]?, error : NSError?) in
if let objects = objects where error == nil {
var chats:(timelinechatsData: [String], chatsDate: [NSDate]) = ([], []) //date might be NSDate
var message: (messageObjts: [String], messageSender: [String]) = ([], [])
var datesToString: [String] {
get {
var stringDates:[String] = []
let formatter = NSDateFormatter()
formatter.dateFormat = "dd-MM-yyyy" //change the format as you wish
for date in dates {
stringDates.append(formatter.stringFromDate(date))
}
return stringDates
}
}
for singleObject in objects {
if let user = singleObject["sender"] as? PFObject {
let first_name = user["first_name"] as! String
let last_name = user["last_name"] as! String
//Cast it to how you saved it. I can't see it from the picture so I assumed you saved it as NSData
let picture = user["picture"] as! NSData
self.picturesProfilesArray.append(picture)
//once you fetch the user data, you can save it in a dictionary or however you want and later call it from cellForRowAtIndex
}
if let stringData = singleObject["message"] as? String {
chats.timelinechatsData.append(stringData)
if let messageDate = singleObject.createdAt {
chats.chatsDate.append(messageDate!)
}
}
//new
if let messageObject = singleObject["messageObject"] as? String {
message.messageObjts.append(messageObject)
}
//new senderNickname
if let messageSender = singleObject["senderNickname"] as? String {
message.messageSender.append(messageSender)
}
}
//update self
dispatch_async(dispatch_get_main_queue()) {
self.timelineChatsDataArray += chats.timelinechatsData
self.chatsDateArray += datesToString
self.messageObjectArray += message.messageObjts
self.messageSenderArray += message.messageSender
self.tableView.reloadData()
}
}
})
changes
so my
var chatsDateArray : [NSDate] = []
and
let dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "MM-dd-yyyy HH:mm"
let useDate = dateFormatter.stringFromDate(self.chatsDateArray[indexPath.row])
cell.dateMessageLabel.text = useDate
should become
var chatsDateArray : [String] = []
let dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "MM-dd-yyyy HH:mm"
let useDate = String(self.chatsDateArray[indexPath.row])
cell.dateMessageLabel.text = useDate
but this causes:
Initialization of immutable value 'first_name' was never used; consider replacing with assignment to '_' or removing it
Initialization of immutable value 'last_name' was never used; consider replacing with assignment to '_' or removing it
this disappears by changing var chat (chatsDate) back to string
, but this causes
Cannot convert value of type 'NSDate' to expected argument type 'String'
on
if let messageDate = singleObject.createdAt {
chats.chatsDate.append(messageDate!)
}
}
You can use 'includeKey' to access pointer values of related classes
findTimeLineDataQuery.includeKey("sender")
findTimeLineDataQuery.findObjectsInBackgroundWithBlock({
(objects : [AnyObject]?, error : NSError?) in
if let objects = objects where error == nil {
var chats:(timelinechatsData: [String], chatsDate: [NSDate]) = ([], []) //date might be NSDate
var message: (messageObjts: [String], messageSender: [String]) = ([], [])
var datesToString: [String] {
get {
var stringDates:[String] = []
let formatter = NSDateFormatter()
formatter.dateFormat = "dd-MM-yyyy" //change the format as you wish
for date in dates {
stringDates.append(formatter.stringFromDate(date))
}
return stringDates
}
}
for singleObject in objects {
if let user = singleObject["sender"] as! PFObject {
let first_name = user["first_name"] as! String
let last_name = user["last_name"] as! String
//Cast it to how you saved it. I can't see it from the picture so I assumed you saved it as NSData
let picture = user["picture"] as! NSData
picturesProfilesArray.append(picture)
//once you fetch the user data, you can save it in a dictionary or however you want and later call it from cellForRowAtIndex
}
if let stringData = singleObject["message"] as? String {
chats.timelinechatsData.append(stringData)
if let messageDate = singleObject.createdAt {
chats.chatsDate.append(messageDate)
}
}
//new
if let messageObject = singleObject["messageObject"] as? String {
message.messageObjts.append(messageObject)
}
//new senderNickname
if let messageSender = singleObject["senderNickname"] as? String {
message.messageSender.append(messageSender)
}
}
//update self
dispatch_async(dispatch_get_main_queue()) {
self.timelineChatsDataArray += chats.timelinechatsData
self.chatsDateArray += datesToString
self.messageObjectArray += message.messageObjts
self.messageSenderArray += message.messageSender
self.tableView.reloadData()
}
}
})
I have an array of dates. I need to find the latest one. Can someone show me an example?
You can make NSDate conform to Comparable, as shown here
Given this, you can then use maxElement to find the maximum (i.e. the latest).
import Foundation
extension NSDate: Comparable { }
public func ==(lhs: NSDate, rhs: NSDate) -> Bool {
return lhs.isEqualToDate(rhs)
}
public func <(lhs: NSDate, rhs: NSDate) -> Bool {
return lhs.compare(rhs) == .OrderedAscending
}
let dates = [NSDate(), NSDate()]
let maxDate = maxElement(dates)
Note, maxElements goes bang for empty arrays so you may want to guard it with isEmpty:
let maxDate = dates.isEmpty ? nil : Optional(maxElement(dates))
Or, if you don’t want to go the extension route:
if let fst = dates.first {
let maxDate = dropFirst(dates).reduce(fst) {
$0.laterDate($1)
}
}
or, to return an optional:
let maxDate = dates.reduce(nil) {
(lhs: NSDate?, rhs: NSDate?)->NSDate? in
lhs.flatMap({rhs?.laterDate($0)}) ?? rhs
}
You can make use of reduce:
guard let dates = dates, !dates.isEmpty else { return nil }
dates.reduce(Date.distantPast) { $0 > $1 ? $0 : $1 }
Edit: Handle empty or nil array
Swift has Array methods for getting both the min and max values for dates.
You can use the following:
let maxDate = myArrayOfDates.max()
let minDate = myArrayOfDates.min()
So if you have an array of dates like so:
And here is the code if you want to copy it:
let now = Date()
let dates = [
now,
now.addingTimeInterval(120),
now.addingTimeInterval(60)
]
let sut = dates.max()
print(sut!)
Hope this helps someone!
Run this in your playground
var dates = [NSDate]()
let dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "dd-MM-yyyy"
let date1 = dateFormatter.dateFromString("02-06-1987")
let date2 = dateFormatter.dateFromString("02-06-2001")
let date3 = dateFormatter.dateFromString("02-06-2010")
//var date1 = NSDate()
dates.append(date3!)
dates.append(date1!)
dates.append(date2!)
var maxDate = dates[0]
for i in 0...dates.count-1
{
if(maxDate.compare(dates[i]).rawValue == -1){
maxDate = dates[i]
println(maxDate)
}
println(maxDate)
}
println(maxDate.description)
have a good day :)
How to reset some NSUserDefaults everyday at 00:00
Ex.
if Time = 00:00/New day
{
NSUserDefaults.standardUserDefaults().removeObjectForKey("Data")
}
I suggest you to wrap NSUserDefaults into a Dao: it will perform the logic you need retrieving the saved data or resetting the data.
1: Adding isToday to NSDate
First of all let's add a property to NSDate
extension NSDate {
var isToday: Bool {
let unitFlags : NSCalendarUnit = [.Year, .Month, .Day]
let componentsSelf = NSCalendar.currentCalendar().components(unitFlags, fromDate: self)
let componentsNow = NSCalendar.currentCalendar().components(unitFlags, fromDate: NSDate())
return (componentsSelf.year,componentsSelf.month, componentsSelf.day) == (componentsNow.year,componentsNow.month, componentsNow.day)
}
}
2: The Dao for NSUserDefaults
class UserDefaultsDao {
static let sharedInstance = UserDefaultsDao()
private var storage: NSUserDefaults { return NSUserDefaults.standardUserDefaults() }
var data: String? {
set {
storage.setObject(newValue, forKey: "data")
storage.setObject(NSDate(), forKey: "lastSet")
}
get {
guard let lastSet = storage.objectForKey("dastSet") as? NSDate where lastSet.isToday else {
storage.removeObjectForKey("data")
return nil
}
return storage.objectForKey("data") as? String
}
}
}
3: Usage
This is how you save your data
UserDefaultsDao.sharedInstance.data = "hello world"
And this is how you print it
print(UserDefaultsDao.sharedInstance.data)
Since NSUserDefaults does NOT work properly in Playground, you should test this on a real project.