How to show multiple items of same time in FirebaseDatabase - ios

I am building a calendar app and I have developed it so that when you click on a date it will show the event corresponding to that date. I have it so that the date will appear in the tableview and when you click on the event it will then segue to a detail view controller and show the details of that event. I am stuck because I am not sure how to get firebase to load multiple events onto one date or how to show multiple events with the same date.
My Code:
View Did Load:
override func viewDidLoad() {
super.viewDidLoad()
calendar.dataSource = self
calendar.delegate = self
self.calendar.calendarHeaderView.backgroundColor = UIColor.blue
navigationController?.navigationBar.barTintColor = UIColor.blue
calendar.appearance.titleWeekendColor = UIColor.red
retrieveEventsFromDatabase()
print(eventsArray)
}
How I am getting the Data:
func retrieveEventsFromDatabase() {
dbReference = Database.database().reference().child("calendarevents")
dbReference.observe(DataEventType.value, with: { [weak self] (snapshot) in
guard snapshot.childrenCount > 0 else { return }
var events: [EventsDataModel] = []
for event in snapshot.children.allObjects as! [DataSnapshot]
{
let object = event.value as? [String: AnyObject]
let eventName = object?["eventName"]
let eventDate = object?["eventDate"]
let eventColor = object?["eventColor"]
let event = EventsDataModel(eventName: eventName as! String, eventDate: eventDate as! String,eventColor: eventColor as! String)
events.append(event)
self?.eventsArray = events
//print(events)
}
DispatchQueue.main.async {
self?.eventsTable.reloadData()
self?.calendar.reloadData()
}
})
}
How i am showing it in my table and calendar:
func calendar(_ calendar: FSCalendar, didSelect date: Date, at monthPosition: FSCalendarMonthPosition) {
let formatter = DateFormatter()
formatter.dateFormat = "dd-MM-yyyy"
let dateString = self.dateFormatter2.string(from: date)
print("selected dateString = \(dateString)")
tableArray.removeAll()
for event in eventsArray {
if event.eventDate.contains(dateString) {
selectedDateLabel.text = ("The selected date is \(dateString)")
print("The event for this date is \(event.eventName)")
tableArray.append(event)
break;
} else {
selectedDateLabel.text = "select a date to see the events for this day"
print("=select a date to see the events for this day")
}
}
self.eventsTable.reloadData()
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let friend = tableArray[indexPath.row].eventName
let cell = eventsTable.dequeueReusableCell(withIdentifier: "eventCell", for: indexPath) as! EventsTitleTableViewCell
cell.eventTitleLabel?.text = friend
return cell
}
Firebase Database:
{
"2021-01-12" : {
"eventColor" : "red",
"eventDate" : "2021-01-12",
"eventName" : "SignUp Day"
},
"2021-01-15" : {
"eventColor" : "red",
"eventDate" : "2021-01-15",
"eventName" : "First Day"
},
"electionday" : {
"eventColor" : "red",
"eventDate" : "2021-02-20",
"eventName" : "Election Day "
},
"groundhogday" : {
"eventColor" : "red",
"eventDate" : "2021-02-20",
"eventName" : "GroundHog Day"
}
}
Picture of my App for more details:

I think that a better and more logical approach would be to store the event, rather than dates, and then every event has a corresponding date.
When the user taps on the day, you fetch all the events that have the date property equal to that date.

So for anybody that is looking at this in the future. I want to answer my own question. For any references I was using this with FSCalendar and Firebase. The answer to my question was that there was a "break;" as seen after the tableArray(append). That was only allowing one of my events to show on the table. When i commented that out, then both the events would show on my table. Thanks!

Related

Group Array to introduce sections to UITableView in Swift

I have the following class:
ChatMessage: Codable {
var id: Int?
var sender: User?
var message: String?
var seen: Int?
var tsp: Date?
}
The tsp is formatted like this: YYYY-MM-DD hh:mm:ss
I would like to "group" messages sent on the same day to end up with something like in this example:
let groupedMessages = [ [ChatMessage, ChatMessage], [ChatMessage, ChatMessage, ChatMessage] ]
I ultimately want to use groupedMessages in a UITableViewController to introduce sections like so:
func numberOfSections(in tableView: UITableView) -> Int {
return groupedMessages.count
// would be 2 int the above
}
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return chatMessages[section].count
// would be 2 for the first and 3 for the second section
}
What would be the most performant way of getting the sorting done? - i.e. something that also works well once the number of chatMessages to be sorted increases
You could try:
let cal = Calendar.current
let groupedMessages = Dictionary(grouping: self.chatMessages, by: { cal.startOfDay($0.tsp) })
let keys = groupedMessages.keys.sorted(by: { $0 > $1 })
This however would give you a dictionary like:
[
SomeDateHere: [
ChatMessage(id: 0, sender: User?, message: "String", seen: 1),
ChatMessage(id: 0, sender: User?, message: "String", seen: 1)
],
AnotherDateHere: [
ChatMessage(id: 0, sender: User?, message: "String", seen: 1)
]
You could then use keys to return the section count:
return keys.count
And to get the array of messages for each dictionary item like so:
let key = keys[indexPath.section]
let messages = groupedMessages[key].sorted(by: { $0.tsp > $1.tsp })
Or to get just the count:
let key = keys[indexPath.section]
return groupedMessages[key].count
Grouping array by date you could find here How to group array of objects by date in swift?
How to connect this to UITableView you will find here https://www.ralfebert.de/ios-examples/uikit/uitableviewcontroller/grouping-sections/
I would probably start by introducing a new type:
public struct CalendarDay: Hashable {
public let date: Date
public init(date: Date = Date()) {
self.date = Calendar.current.startOfDay(for: date)
}
}
extension CalendarDay: Comparable {
public static func < (lhs: CalendarDay, rhs: CalendarDay) -> Bool {
return lhs.date < rhs.date
}
}
Then I would extend your message:
extension ChatMessage {
var day: CalendarDay {
return CalendarDay(date: tsp)
}
}
(I am assumming tsp is not an optional, there is no reason for it to be optional).
Now, let's define a section:
struct Section {
var messages: [ChatMessage] = []
let day: CalendarDay
init(day: CalendarDay) {
self.day = day
}
}
Let's group easily:
let messages: [ChatMessage] = ...
var sections: [CalendarDay: Section] = [:]
for message in messages {
let day = message.day
var section = sections[day] ?? Section(day: day)
section.messages.append(day)
sections[day] = section
}
let sectionList = Array(sections.values).sorted { $0.day < $1.day }
If your messages are not originally sorted, you might need to also sort messages in every section separately.
let testArray = ["2016-06-23 09:07:21", "2016-06-23 08:07:21", "2016-06-22 09:07:21", "2016-06-22 08:07:21"]
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "YYYY-MM-DD hh:mm:ss"
let groupDic = Dictionary(grouping: testArray) { (pendingCamera) -> DateComponents in
let date = Calendar.current.dateComponents([.day, .year, .month], from: dateFormatter.date(from: pendingCamera)!)
return date
}
print(groupDic)
You can take groupDic and return in numberOfSections and titleForHeaderInSection.

Convert UNIX time from json import (swift struct) to date as string and populate table

I have a json file being imported to my project (https://api.myjson.com/bins/ywv0k). The json attributes are decoded and stored in my struct class "News", which has the same attributes like the json file.
in a second step I populate a table with the string attribute "timestamp" from my struct class "News", which is actually a UNIX time.
My problem now is that I am lost how to change this UNIX time to a string of format "dd/mm/yy HH:mm:ss", since I get an error when I try to put a function
let date = NSDate(timeIntervalSince1970: timestamp) //error since timestamp is currently defined as string. If I make it a long variable, I cannot populate the table with it any more, since the label requires a text with string format.
let dayTimePeriodFormatter = NSDateFormatter()
dayTimePeriodFormatter.dateFormat = "dd/mm/yy HH:mm:ss"
let dateString = dayTimePeriodFormatter.stringFromDate(date)
into the do-encoding-loop as well as when I put it into this table function: func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell.
Swift 4
import UIKit
// structure from json file
struct News: Codable{
let type: String
let timestamp: String // UNIX format, eg. "1531294146340"
let title: String
let message: String
}
class HomeVC: BaseViewController, UITableViewDelegate, UITableViewDataSource {
var myNewsItems: [News] = []
#IBOutlet weak var myNewTableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
let nibName = UINib(nibName: "CustomTableViewCell", bundle: nil)
myNewTableView.register(nibName, forCellReuseIdentifier: "tableViewCell")
// JSON Decoding
let url=URL(string:"https://api.myjson.com/bins/ywv0k")
let session = URLSession.shared
let task = session.dataTask(with: url!) { (data, response, error) in
guard let data = data else { return }
do {
let myNewsS = try
JSONDecoder().decode([News].self, from: data)
print(myNewsS)
self.myNewsItems = myNewsS
DispatchQueue.main.async {
self.myNewTableView.reloadData()
}
} catch let jsonErr {
}
}
task.resume()
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return myNewsItems.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "tableViewCell", for: indexPath) as!
CustomTableViewCell
// populate table with json content
cell.commonInit(timestamp: myNewsItems[indexPath.row].timestamp, message: myNewsItems[indexPath.row].message)
return cell
}
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
cell.backgroundColor = UIColor(white: 1, alpha: 0.5)
}
}
First of all the date format is wrong. It has to be "dd/MM/yy HH:mm:ss"
The most efficient solution – if you are responsible for the JSON – send the value for timestamp as Double. Then it's sufficient to declare timestamp
let timestamp: Date // UNIX format, eg. 1531294146340
and add the date decoding strategy
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .millisecondsSince1970
Another solution is to put the date conversion code into the struct
struct News: Codable{
let type: String
let timestamp: String // UNIX format, eg. "1531294146340"
let title: String
let message: String
enum CodingKeys: String, CodingKey { case type, timestamp, title, message}
let dateFormatter : DateFormatter = {
let formatter = DateFormatter()
formatter.dateFormat = "dd/MM/yy HH:mm:ss"
return formatter
}()
var dateString : String {
let timeInterval = TimeInterval(timestamp)!
let date = Date(timeIntervalSince1970: timeInterval / 1000)
return dateFormatter.string(from:date)
}
}
The computed property dateString contains the date string.
Further you could declare type as enum
enum Type : String, Codable {
case organizational, planning
}
struct News: Codable{
let type: Type
...
You should be able to convert the timestamp to date and then format it to specific format and convert back to String to display on UILabel. See if the following helps
func string(from timestamp: String) -> String {
if let timeInterval = TimeInterval(timestamp) {
let date = Date(timeIntervalSince1970: timeInterval)
let formatter = DateFormatter()
formatter.dateFormat = "dd/MM/yy HH:mm:ss"
return formatter.string(from: date)
}
return "" //return empty if somehow the timestamp conversion to TimeInterval (Double) fails
}
1) As a first suggestion, do NOT store date as string for a number or reasons.
(Apple says to use the very basic type... so use a 64bit for UnixTimestamp OR NSDate.. far more flexible, for example performing calcs, difference, localisations and so on... (and far better memory usage.. (Ints do not even use ARC...))
(and use optional for fields.... far more secure..)
2) so use an extension to save as Date (for example)
Let's start from a int unixTimestamp:
(I added a complete sample for a controller...)
//
// ViewController.swift
// sampleDate
//
// Created by ing.conti on 16/08/2018.
// Copyright © 2018 com.ingconti. All rights reserved.
//
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
//sample with int...
if let dm =
// Thursday, January 1, 2015 12:00:00 AM GMT+01:00
(1420066800000/1000).fromUnixTimeStamp(){
// note: usual timestamp from server come with milliseincods..
//now You get a date... use and format!
print(dm.description)
}
let testString = "1420066800"
if let n = Int(testString), let dm = n.fromUnixTimeStamp(){
print(dm.description)
}
}
}
extension Int {
func fromUnixTimeStamp() -> Date? {
let date = Date(timeIntervalSince1970: TimeInterval(self))
return date
}
}
so use extension AND change your to use date.
A final note: codable is fine, but not fine for "edge cases"ad apple says in
"Reflecting on Reflection" (https://developer.apple.com/swift/blog/?id=37) sometimes is better to write parser by hand... for a small piece of JSON.
So for example use:
(I rewrote a bit your class...)
typealias Dict = [String : Any]
struct News{
let type: String?
// NO! let timestamp: String // UNIX format, eg. "1531294146340"
let timestamp: Date?
let title: String?
let message: String?
init?(dict : Dict?) {
guard let d = dict else{
return nil
}
if let s = d["timestamp"] as? String, let n = Int(s) {
timestamp = n.fromUnixTimeStamp()
}else{
timestamp = nil // or other "default" ..
}
// go on parsing... other fields..
if let s = d["type"] as? String{
type = s
}else{
type = nil // or other "default" ..
}
if let s = d["title"] as? String {
title = s
}
else{
title = nil // or other "default" ..
}
if let s = d["message"] as? String {
message = s
}else{
message = nil // or other "default" ..
}
}
}
so use in this way:
...
let new = News(dict: dict)
I usually extract data form JSON in this way:
...
guard let json = try? JSONSerialization.jsonObject(with: data, options: []) as? Dict
else{
return
}
guard let dict = json else{
return
}
..
let new = News(dict: dict)

iOS: UICollectionView sections from array with dates

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;
}
)
)

Firebase query sort order in swift?

When I load the following Firebase Database data into my tableView, the data is sorted in ascending order by date. How can I order this by descending (show the newest post at the top)?
Query in Xcode:
let ref = self.rootRef.child("posts").queryOrderedByChild("date").observeEventType(.ChildAdded, withBlock: { (snapshot) -> Void in
JSON export:
"posts" : {
"-KMFYKt7rmfZINetx1hF" : {
"date" : "07/09/16 12:46 PM",
"postedBy" : "sJUCytVIWmX7CgmrypqNai8vGBg2",
"status" : "test"
},
"-KMFYZeJmgvmnqZ4OhT_" : {
"date" : "07/09/16 12:47 PM",
"postedBy" : "sJUCytVIWmX7CgmrypqNai8vGBg2",
"status" : "test"
},
Thanks!!
EDIT: Below code is the entire solution thanks to Bawpotter
Updated query:
let ref = self.rootRef.child("posts").queryOrderedByChild("date").observeEventType(.ChildAdded, withBlock: { (snapshot) -> Void in
let post = Post.init(key: snapshot.key, date: snapshot.value!["date"] as! String, postedBy: snapshot.value!["postedBy"] as! String, status: snapshot.value!["status"] as! String)
self.posts.append(post)
self.tableView.insertRowsAtIndexPaths([NSIndexPath(forRow: self.posts.count-1, inSection: 0)], withRowAnimation: .Automatic)
tableView cellForRowAtIndexPath
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("PostCell", forIndexPath: indexPath) as! PostCell
self.posts.sortInPlace({$0.date > $1.date})
self.tableView.reloadData()
Post.swift:
import UIKit
class Post {
var key: String
var date: String
var postedBy: String
var status: String
init(key: String, date: String, postedBy: String, status: String){
self.key = key
self.date = date
self.postedBy = postedBy
self.status = status
}
}
When Firebase loads the data into your tableView data source array, call this:
yourDataArray.sortInPlace({$0.date > $1.date})
Swift 3 Version:
yourDataArray.sort({$0.date > $1.date})
Swift 4 Version:
yourDataArray.sort(by: {$0.date > $1.date})
While I do recommend doing what was posted and creating a Class and doing it that way, I will give you another way to do sort it.
Since you already have it sorted in Ascending order from Firebase and you know the amount of records, you can do this:
guard let value = snapshot.children.allObjects as? [FIRDataSnapshot] else {
return
}
var valueSorted: [FIRDataSnapshot] = [FIRDataSnapshot]()
var i: Int = value.count
while i > 0 {
i = i - 1
valueSorted.append(value[i])
}
Simply use .reverse() before using data
Example:
myRef.observe(.value) { (snapshot) in
guard var objects = snapshot.children.allObjects as? [DataSnapshot] else {
return
}
objects.reverse() //<========= HERE
//Now use your `objects`
...
}
Swift 5:
Add reversed() to your objects after sorting by the required field.
For example, let's assume you have a day of the month in the "day" field in FireStore.
Something like this will do the trick (call loadData() function in viewDidLoad to see the output):
let db = Firestore.firestore()
func loadData() {
db.collection("FireStoreCollectionName").order(by: "day").getDocuments { (querySnapshot, error) in
if let e = error {
print("There was an issue retrieving data from Firestore, \(e)")
} else {
for document in querySnapshot!.documents.reversed() {
let data = document.data()
let fDay = data["day"] as! Int
print(fDay)
}
}
}
}

How do you properly order data from Firebase chronologically

I'm trying to order my data from Firebase so the most recent post is at the top (like Instagram), but I just can't get it to work properly. Should I be using a server timestamp? Is there a "createdAt" field?
func getPosts() {
POST_REF.observeEventType(.Value, withBlock: { snapshot in
guard let posts = snapshot.value as? [String : [String : String]] else {
print("No Posts Found")
return
}
Post.feed?.removeAll()
for (postID, post) in posts {
let newPost = Post.initWithPostID(postID, postDict: post)!
Post.feed?.append(newPost)
}
Post.feed? = (Post.feed?.reverse())!
self.tableView.reloadData()
}, withCancelBlock: { error in
print(error.localizedDescription)
})
}
Using only reverse() for your array is not enough way to encompass everything. There are different things you need to think about:
Limit while retrieving data, use append() and then reverse() to save time. You don't need to delete all array for each time.
Scroll trigger or willDisplay cell method loading
Let's start. You can create a child for your posts timestamp or date/time being global. To provide like Instagram seconds, weeks I advice you using UTC time. So I will call this: (timeUTC)
For sorting your all post, use since1970 timestamp. So I will call this (timestamp) and then also you can keep another node as (reversedTimestamp) adding - prefix to timestamp. So when you use queryOrdered to this node. You can handle latest 5 post using with yourQuery.queryLimited(toFirst: 5).
1.Get UTC date/time for timeUTC node in Swift 3:
let date = Date()
let formatter = DateFormatter()
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss ZZZ"
formatter.timeZone = TimeZone(abbreviation: "UTC")
let utcTimeZoneStr = formatter.string(from: date)
+0000 means it's universal time, look at http://time.is/tr/UTC
2.Get since1970 timestamp to sort posts in Swift 3:
let timestamp = (Date().timeIntervalSince1970 as NSString).doubleValue
let reversedTimestamp = -1.0 * timestamp
Now, you can save them on your Firebase posts like this.
"posts" : {
"-KHLOy5mOSq0SeB7GBXv" : {
"timestamp": "1475858019.2306"
"timeUTC" : "2012-02-04 12:11:56 +0000"
},
"-KHLrapS0wbjqPP5ZSUY" : {
"timestamp": "1475858010.1245"
"timeUTC" : "2014-02-04 12:11:56 +0000"
},
I will retrieve five by five post, so I'm doing queryLimited(toFirst: 5) in viewDidLoad:
let yourQuery = ...queryOrdered(byChild: "reverseTimestamp")
.queryEnding(atValue: "\(self.pageOnTimestamp)", childKey: "reverseTimestamp")
yourQuery.observeSingleEvent(of: .value, with: { (snapshot) in
if snapshot.value is NSNull {
print("There is no post.")
}
else {
yourQuery.queryLimited(toFirst: 5).observeSingleEvent(of: .value, with: { (snapshot) in
self.posts.removeAll(keepingCapacity: true)
for (i, snap) in snapshot.children.enumerated() {
if let postAllDict = snapshot.value as? [String: AnyObject] {
if let postDict = postAllDict[(snap as AnyObject).key as String] as? [String: AnyObject] {
let post = Post(key: (snap as AnyObject).key as String, postDict: postDict)
self.posts.append(post)
}
}
}
completion(true)
})
}
})
If user reached latest post, you can handle it with willDisplay method like below, then you can call loadMore function.
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
if self.posts.count - 1 == indexPath.row {
// call loadMore function.
}
}
In loadMore() function you can handle latest post's timestamp, then start-end query as with that, so you can easily continue with next first 5 posts while appending before array.
For Swift 3 conversion as nice formatted, take a look here: Swift 3 - UTC to time ago label, thinking 12h / 24h device time changes
Based on #tobeiosdev answer i define my data structure like this:
"posts" : {
"-KcOa8GXBl08V-WXeX8P" : {
"author" : "#giovanny.piñeros",
"hashtags" : [ "Hello", "World" ],
"text" : "Hola Mundo!!!!!",
"timestamp" : 5.08180914309278E8,
"title" : "Hello World",
"userid" : "hmIWbmQhfgh93"
}
As you can see i've added a timestamp attribute, when i query my data with a child added event, i pass that data to a post object, then i append that object to an array of posts from which my table view will feed:
self.posts.append(post)
self.posts.sort(by: {$0.timestamp! > $1.timestamp!})
self.tableViewFeed.reloadData()
With the sort method i've managed to order my data in the desire order of posts, from the newest to the oldest. I Hope this approach could help anyone :).

Resources