Sorting tableView data by timestamp with firebase - swift

I'm looking to sort the data / arrays in my tableView by which date it was created. I don't have any code to support this but I have the code for the cells and images from the database.
Database.database().reference().child("Projects").observe(.childAdded, with: { (snapshot) in
if let dictionary = snapshot.value as? [String : AnyObject]
{
let project = Project(dictionary: dictionary)
self.ProjectsArray.append(project)
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
else
{
print("fucked ")
}
self.FilterdProjectsArray = self.ProjectsArray
}, withCancel: nil)
Database

You just need to add one line:
self.ProjectsArray.append(project)
self.ProjectsArray = self.ProjectsArray.sorted { (lhs, rhs) in lhs.timestamp < timestamp }
where date is the name of the date property on your Project object
Update
You could extend Optional to conform to Comparable, but a simpler way to handle it might be:
self.ProjectsArray = self.ProjectsArray.sorted { (lhs, rhs) in
let lTimestamp = lhs.timestamp ?? NSNumber(integerLiteral: 0)
let rTimestamp = rhs.timestamp ?? NSNumber(integerLiteral: 0)
return lTimestamp < rTimestamp
}
Update 2
Turns out that NSNumbers aren't comparable. Looks like you'll need to use Int or Double, whichever makes more sense for your data (are you using whole or floating-point numbers?
self.ProjectsArray = self.ProjectsArray.sorted { (lhs: Project, rhs: Project) in
let lTimestamp: Int
if let lNum = lhs.timestamp {
lTimestamp = Int(lNum)
} else {
lTimestamp = 0
}
let rTimestamp: Int
if let rNum = rhs.timestamp {
rTimestamp = Int(rNum)
} else {
rTimestamp = 0
}
return lTimestamp < rTimestamp
}

Related

cannot find "count" in scope

please help me!!
func updateApps()
{
guard let count = UserDefaults().value(forKey: "count") as? Int else{
for x in 0 ..< count { //**cannot find "count" in scope**
if let task = UserDefaults().value(forKey: "task_\(x+1)") as? String{
Apps.append (tasks)
return
}
}
}
}
please help me!!
As #vadian mentioned integer(forKey returns default as 0, So you don’t need guard statement to check for nil values.
func updateApps()
{
let count = UserDefaults().integer(forKey: "count")
for x in 0 ..< count {
if let task = UserDefaults().string(forKey: "task_\(x+1)") {
Apps.append (tasks)
}
}
}
}
You can't because you are using guard. The else is being called when the UserDefaults().value(forKey: "count") as? Int value is nil
You can try this instead
func updateApps() {
if let count = UserDefaults().value(forKey: "count") as? Int {
for x in 0..< count {
if let task = UserDefaults().value(forKey: "task_\(x+1)") as? String{
Apps.append(task)
return
}
}
}
}
If you want to use guard then you can use this
func updateApps() {
guard let count = UserDefaults().value(forKey: "count") as? Int else {
return
}
for x in 0..< count {
if let task = UserDefaults().value(forKey: "task_\(x+1)") as? String{
Apps.append (task)
return
}
}
}

How to compare custom objects based on different properties using Equatable?

I am using the equatable protocol in order to compare two custom objects based on one property named mediaUID.
Is there a way to switch between comparing on different properties?
In func fetchNotificationsRemovedsometimes I need to compare by mediaUID or by likeUID.
var notificationsArray = [NotificationInformation]()
class NotificationInformation {
let type: String
let mediaUID: String?
let commentUID: String?
let likeUID:String?
}
extension NotificationInformation {
func fetchNotificationsRemoved(query: DatabaseQuery) {
NotificationInformation.observeNewNotificationsChildRemoved(query: query) { [weak self] (newNotification: NotificationInformation?) in
guard let strongSelf = self else {return}
guard let notification = newNotification else {return}
if notification.type == "like" {
// How to compare based on likeUID using equatable?
//compare based on likeUID
//get the index of the item of type 'like' in notificationsArray and do something with it
guard let index = strongSelf.notificationsArray.index(of: notification) else {return}
}else if notification.type == "media" {
// How to compare based on mediaUID using equatable?
//compare based on mediaUID
//get the index of the item of type 'media' in notificationsArray
guard let index = strongSelf.notificationsArray.index(of: notification) else {return}
} else if if notification.type == "commentUID" {
....
}
guard let index = strongSelf.notificationsArray.index(of: notification) else {return}
strongSelf.notificationsArray.remove(at: index)
let visibleIndexes = strongSelf.tableView.indexPathsForVisibleRows
let indexPathOfRemovedNotification = IndexPath(row: index, section: 0)
if let indexes = visibleIndexes,
indexes.contains(indexPathOfRemovedNotification) {
strongSelf.tableView.deleteRows(at: [indexPathOfRemovedNotification], with: .fade)
}
}
}
}//end extension
//enables us to compare two objects of type NotificationInformation
extension NotificationInformation: Equatable { }
func ==(lhs: NotificationInformation ,rhs: NotificationInformation) -> Bool {
guard let mediaUIDLeft = lhs.mediaUID else {return false}
guard let mediaUIDRight = rhs.mediaUID else {return false}
return mediaUIDLeft == mediaUIDRight
}
You could use a static var to establish the field you want to use for comparison:
class NotificationInformation: Equatable {
enum CompareField {
case type, mediaUID, commentUID, likeUID
}
static var compareField: CompareField = .mediaUID
let type: String
let mediaUID: String?
let commentUID: String?
let likeUID:String?
init(type: String, mediaUID: String? = nil, commentUID: String? = nil, likeUID: String? = nil) {
self.type = type
self.mediaUID = mediaUID
self.commentUID = commentUID
self.likeUID = likeUID
}
static func ==(lhs: NotificationInformation, rhs: NotificationInformation) -> Bool {
switch NotificationInformation.compareField {
case .type:
return lhs.type == rhs.type
case .mediaUID:
return lhs.mediaUID == rhs.mediaUID
case .commentUID:
return lhs.commentUID == rhs.commentUID
case .likeUID:
return lhs.likeUID == rhs.likeUID
}
}
}
Example:
let a = NotificationInformation(type: "foo", mediaUID: "123")
let b = NotificationInformation(type: "bar", mediaUID: "123")
NotificationInformation.compareField = .type
if a == b {
print("same type")
}
NotificationInformation.compareField = .mediaUID
if a == b {
print("same mediaUID")
}
Output:
same mediaUID
Comparing multiple fields using an OptionSet
If you replace the enum with an OptionSet, you could select multiple fields to compare:
struct CompareFields: OptionSet {
let rawValue: Int
static let type = CompareFields(rawValue: 1 << 0)
static let mediaUID = CompareFields(rawValue: 1 << 1)
static let commentUID = CompareFields(rawValue: 1 << 2)
static let likeUID = CompareFields(rawValue: 1 << 3)
}
static var compareFields: CompareFields = .mediaUID
static func ==(lhs: NotificationInformation, rhs: NotificationInformation) -> Bool {
var equal = true
if NotificationInformation.compareFields.contains(.type) {
equal = equal && (lhs.type == rhs.type)
}
if NotificationInformation.compareFields.contains(.mediaUID) {
equal = equal && (lhs.mediaUID == rhs.mediaUID)
}
if NotificationInformation.compareFields.contains(.commentUID) {
equal = equal && (lhs.commentUID == rhs.commentUID)
}
if NotificationInformation.compareFields.contains(.likeUID) {
equal = equal && (lhs.likeUID == rhs.likeUID)
}
return equal
}
Example
let a = NotificationInformation(type: "foo", mediaUID: "123", commentUID: "111")
let b = NotificationInformation(type: "bar", mediaUID: "123", commentUID: "111")
NotificationInformation.compareFields = .mediaUID
if a == b {
print("same mediaUID")
}
NotificationInformation.compareFields = [.mediaUID, .commentUID]
if a == b {
print("same mediaUID and commentUID")
}
Output
same mediaUID
same mediaUID and commentUID
Multithreaded issue
There is an issue if your code is modifying the compareFields value in another thread. The meaning of equals would change for all threads. One possible solution is to only change and use equality for NotificationInformation in the main thread.
...
} else if notification.type == "media" {
DispatchQueue.main.async {
NotificationInformation.compareFields = .mediaUID
guard let index = strongSelf.notificationsArray.index(of: notification) else {return}
// use index
...
}
}
...
Change your func to this:
func ==(lhs: NotificationInformation ,rhs: NotificationInformation) -> Bool {
guard let mediaUIDLeft = lhs.mediaUID else {return false}
guard let mediaUIDRight = rhs.mediaUID else {return false}
return (mediaUIDLeft == mediaUIDRight || lhs.likeUID == rhs.likeUID)
}
This means that two NotificationInformation are equal if they have the same mediaUID OR the same likeUID
If you need a conditional check, you can introduce a boolean variable:
class NotificationInformation {
let type: String
let mediaUID: String?
let commentUID: String?
let likeUID:String?
let checkByMediaUID: Bool = true
}
So change your Equatable:
func ==(lhs: NotificationInformation ,rhs: NotificationInformation) -> Bool {
guard let mediaUIDLeft = lhs.mediaUID else {return false}
guard let mediaUIDRight = rhs.mediaUID else {return false}
return (lhs.checkByMediaUID || rhs.checkByMediaUID) ? mediaUIDLeft == mediaUIDRight : lhs.likeUID == rhs.likeUID
}
In more readable way:
func ==(lhs: NotificationInformation ,rhs: NotificationInformation) -> Bool {
guard let mediaUIDLeft = lhs.mediaUID else {return false}
guard let mediaUIDRight = rhs.mediaUID else {return false}
if lhs.checkByMediaUID || rhs.checkByMediaUID{
return mediaUIDLeft == mediaUIDRight
}
return lhs.likeUID == rhs.likeUID
}
This means that if you want to check by mediaUID, just compare two object. If you want to check by likeUID, just change the variable of one of the object.
Example
let a: NotificationInformation = NotificationInformation()
let b: NotificationInformation = NotificationInformation()
//Check by `mediaUID`
if a == b{
....
}
//Check by `likeUID`
a.checkByMediaUID = false
if a == b{
....
}
You can check the type property of the NotificationInformation objects and compare objects according to that.
extension NotificationInformation: Equatable {
static func == (lhs: NotificationInformation, rhs: NotificationInformation) -> Bool {
guard lhs.type == rhs.type else {
print("Types of lhs and rhs are not same ")
return false
}
switch lhs.type {
case "like": return lhs.likeUID == rhs.likeUID
case "media": return lhs.mediaUID == rhs.mediaUID
case "commentUID": return lhs.commentUID == rhs.commentUID
default: return false
}
}
}
And you can use enum for the type property
class NotificationInformation {
enum NotificationType: String {
case like
case media
case commentUID
}
let type: NotificationType
let mediaUID: String?
let commentUID: String?
let likeUID:String?
}
extension NotificationInformation: Equatable {
static func == (lhs: NotificationInformation, rhs: NotificationInformation) -> Bool {
guard lhs.type == rhs.type else {
print("Types of lhs and rhs are not same ")
return false
}
switch lhs.type {
case .like: return lhs.likeUID == rhs.likeUID
case .media: return lhs.mediaUID == rhs.mediaUID
case .commentUID: return lhs.commentUID == rhs.commentUID
}
}
}
Usage
extension NotificationInformation {
func fetchNotificationsRemoved(query: DatabaseQuery) {
NotificationInformation.observeNewNotificationsChildRemoved(query: query) { [weak self] newNotification in
guard let strongSelf = self else {return}
guard let notification = newNotification else {return}
guard let index = strongSelf.notificationsArray.index(of: notification) else {return}
strongSelf.notificationsArray.remove(at: index)
let visibleIndexes = strongSelf.tableView.indexPathsForVisibleRows
let indexPathOfRemovedNotification = IndexPath(row: index, section: 0)
if let indexes = visibleIndexes, indexes.contains(indexPathOfRemovedNotification) {
strongSelf.tableView.deleteRows(at: [indexPathOfRemovedNotification], with: .fade)
}
}
}
}

Convert to string an Any value

This fails (Non-nominal type 'Any' cannot be extended)
extension Any {
func literal() -> String {
if let booleanValue = (self as? Bool) {
return String(format: (booleanValue ? "true" : "false"))
}
else
if let intValue = (self as? Int) {
return String(format: "%d", intValue)
}
else
if let floatValue = (self as? Float) {
return String(format: "%f", floatValue)
}
else
if let doubleValue = (self as? Double) {
return String(format: "%f", doubleValue)
}
else
{
return String(format: "<%#>", self)
}
}
}
as I would like to use it in a dictionary (self) to xml string factory like
extension Dictionary {
// Return an XML string from the dictionary
func xmlString(withElement element: String, isFirstElement: Bool) -> String {
var xml = String.init()
if isFirstElement { xml.append("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n") }
xml.append(String(format: "<%#>\n", element))
for node in self.keys {
let value = self[node]
if let array: Array<Any> = (value as? Array<Any>) {
xml.append(array.xmlString(withElement: node as! String, isFirstElemenet: false))
}
else
if let dict: Dictionary<AnyHashable,Any> = (value as? Dictionary<AnyHashable,Any>) {
xml.append(dict.xmlString(withElement: node as! String, isFirstElement: false))
}
else
{
xml.append(String(format: "<%#>", node as! CVarArg))
xml.append((value as Any).literal
xml.append(String(format: "</%#>\n", node as! CVarArg))
}
}
xml.append(String(format: "</%#>\n", element))
return xml.replacingOccurrences(of: "&", with: "&amp", options: .literal, range: nil)
}
}
I was trying to reduce the code somehow, as the above snippet is repeated a few times in a prototype I'm building but this is not the way to do it (a working copy with the snippet replicated works but ugly?).
Basically I want to generate a literal for an Any value - previously fetched from a dictionary.
It seems like you can't add extensions to Any. You do have some other options though - either make it a function toLiteral(value: Any) -> String, or what is probably a neater solution; use the description: String attribute which is present on all types that conform to CustomStringConvertible, which includes String, Int, Bool, and Float - your code would be simplified down to just xml.append(value.description). You then just have make a simple implementation for any other types that you might get.
Ok, finally got this working. First the preliminaries: each of your objects needs to have a dictionary() method to marshal itself. Note: "k.###" are struct static constants - i.e., k.name is "name", etc. I have two objects, a PlayItem and a PlayList:
class PlayItem : NSObject {
var name : String = k.item
var link : URL = URL.init(string: "http://")!
var time : TimeInterval
var rank : Int
var rect : NSRect
var label: Bool
var hover: Bool
var alpha: Float
var trans: Int
var temp : String {
get {
return link.absoluteString
}
set (value) {
link = URL.init(string: value)!
}
}
func dictionary() -> Dictionary<String,Any> {
var dict = Dictionary<String,Any>()
dict[k.name] = name
dict[k.link] = link.absoluteString
dict[k.time] = time
dict[k.rank] = rank
dict[k.rect] = NSStringFromRect(rect)
dict[k.label] = label ? 1 : 0
dict[k.hover] = hover ? 1 : 0
dict[k.alpha] = alpha
dict[k.trans] = trans
return dict
}
}
class PlayList : NSObject {
var name : String = k.list
var list : Array <PlayItem> = Array()
func dictionary() -> Dictionary<String,Any> {
var dict = Dictionary<String,Any>()
var items: [Any] = Array()
for item in list {
items.append(item.dictionary())
}
dict[k.name] = name
dict[k.list] = items
return dict
}
}
Note any value so marshal has to be those legal types for a dictionary; it helps to have aliases so in the PlayItem a "temp" is the string version for the link url, and its getter/setter would translate.
When needed, like the writeRowsWith drag-n-drop tableview handler, I do this:
func tableView(_ tableView: NSTableView, writeRowsWith rowIndexes: IndexSet, to pboard: NSPasteboard) -> Bool {
if tableView == playlistTableView {
let objects: [PlayList] = playlistArrayController.arrangedObjects as! [PlayList]
var items: [PlayList] = [PlayList]()
var promises = [String]()
for index in rowIndexes {
let item = objects[index]
let dict = item.dictionary()
let promise = dict.xmlString(withElement: item.className, isFirstElement: true)
promises.append(promise)
items.append(item)
}
let data = NSKeyedArchiver.archivedData(withRootObject: items)
pboard.setPropertyList(data, forType: PlayList.className())
pboard.setPropertyList(promises, forType:NSFilesPromisePboardType)
pboard.writeObjects(promises as [NSPasteboardWriting])
}
else
{
let objects: [PlayItem] = playitemArrayController.arrangedObjects as! [PlayItem]
var items: [PlayItem] = [PlayItem]()
var promises = [String]()
for index in rowIndexes {
let item = objects[index]
let dict = item.dictionary()
let promise = dict.xmlString(withElement: item.className, isFirstElement: true)
promises.append(promise)
items.append(item)
}
let data = NSKeyedArchiver.archivedData(withRootObject: items)
pboard.setPropertyList(data, forType: PlayList.className())
pboard.setPropertyList(promises, forType:NSFilesPromisePboardType)
pboard.writeObjects(promises as [NSPasteboardWriting])
}
return true
}
What makes this happen are these xmlString extensions and the toLiteral function - as you cannot extend "Any":
func toLiteral(_ value: Any) -> String {
if let booleanValue = (value as? Bool) {
return String(format: (booleanValue ? "1" : "0"))
}
else
if let intValue = (value as? Int) {
return String(format: "%d", intValue)
}
else
if let floatValue = (value as? Float) {
return String(format: "%f", floatValue)
}
else
if let doubleValue = (value as? Double) {
return String(format: "%f", doubleValue)
}
else
if let stringValue = (value as? String) {
return stringValue
}
else
if let dictValue: Dictionary<AnyHashable,Any> = (value as? Dictionary<AnyHashable,Any>)
{
return dictValue.xmlString(withElement: "Dictionary", isFirstElement: false)
}
else
{
return ((value as AnyObject).description)
}
}
extension Array {
func xmlString(withElement element: String, isFirstElemenet: Bool) -> String {
var xml = String.init()
xml.append(String(format: "<%#>\n", element))
self.forEach { (value) in
if let array: Array<Any> = (value as? Array<Any>) {
xml.append(array.xmlString(withElement: "Array", isFirstElemenet: false))
}
else
if let dict: Dictionary<AnyHashable,Any> = (value as? Dictionary<AnyHashable,Any>) {
xml.append(dict.xmlString(withElement: "Dictionary", isFirstElement: false))
}
else
{
xml.append(toLiteral(value))
}
}
xml.append(String(format: "<%#>\n", element))
return xml
}
}
extension Dictionary {
// Return an XML string from the dictionary
func xmlString(withElement element: String, isFirstElement: Bool) -> String {
var xml = String.init()
if isFirstElement { xml.append("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n") }
xml.append(String(format: "<%#>\n", element))
for node in self.keys {
let value = self[node]
if let array: Array<Any> = (value as? Array<Any>) {
xml.append(array.xmlString(withElement: node as! String, isFirstElemenet: false))
}
else
if let dict: Dictionary<AnyHashable,Any> = (value as? Dictionary<AnyHashable,Any>) {
xml.append(dict.xmlString(withElement: node as! String, isFirstElement: false))
}
else
{
xml.append(String(format: "<%#>", node as! CVarArg))
xml.append(toLiteral(value as Any))
xml.append(String(format: "</%#>\n", node as! CVarArg))
}
}
xml.append(String(format: "</%#>\n", element))
return xml
}
func xmlHTMLString(withElement element: String, isFirstElement: Bool) -> String {
let xml = self.xmlString(withElement: element, isFirstElement: isFirstElement)
return xml.replacingOccurrences(of: "&", with: "&amp", options: .literal, range: nil)
}
}
This continues another's solution, the toLiteral() suggestion above, in hopes it helps others.
Enjoy.

how to convert null string to null swift

I am new to Swift. I tried with this Swift link Detect a Null value in NSDictionaryNSDictionary, but I failed to do so.
Data:
"end_time" = "<null>"
Here is my code:
if endTime["end_time"] is NSNull {
print("your session still available ")
}
else{
print("your session end \(endTime["end_time"])")
}
Every time it is going to else statement. May be I need to convert string to null or alternative solution. Could you help me please?
Thank you.
Here's how you check null in swift:
let time = endTime["end_time"]
if time != "<null>" {
print("time is not <null>")
}
else
{
print("time is <null>")
}
You can create a NilCheck controller to check nil or null for various datatypes. For example i have created a function to remove null [if any] from the dictionary and store the array of dictionary in Userdefaults. Please be free to ask your queries :)
func removeNilAndSaveToLocalStore(array : [[String:Any]]) {
var arrayToSave = [[String:Any]]()
for place in array {
var dict = [String:Any]()
dict["AreaId"] = NilCheck.sharedInstance.checkIntForNil(nbr: place["AreaId"]! as? Int)
dict["AreaNameAr"] = NilCheck.sharedInstance.checkStringForNil(str: place["AreaNameAr"]! as? String)
dict["AreaName"] = NilCheck.sharedInstance.checkStringForNil(str: place["AreaName"]! as? String)
dict["GovernorateId"] = NilCheck.sharedInstance.checkIntForNil(nbr: place["GovernorateId"]! as? Int)
arrayToSave.append(dict)
}
LocalStore.setAreaList(token: arrayToSave)
}
class NilCheck {
static let sharedInstance : NilCheck = {
let instance = NilCheck()
return instance
}()
func checkStringForNil(str : String?) -> String {
guard let str = str else {
return String() // return default string
}
return str
}
func checkIntForNil(nbr : Int?) -> Int {
guard let num = nbr else {
return 0 // return default Int
}
return num
} }

How to check dictionary value to be exactly a Bool?

Let's say we have something like this:
static func convertBoolToString(source: [String: AnyObject]) -> [String:AnyObject]? {
var destination = [String:AnyObject]()
for (key, value) in source {
switch value {
case is Bool:
destination[key] = "\(value as! Bool)"
default:
destination[key] = value
}
}
if destination.isEmpty {
return nil
}
return destination
}
The problem is that if value is Double or Int or anything convertible to Bool it will pass the first case.
Pls, check the doc: https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/TypeCasting.html
How to check the value to be exactly and only a Bool?
This is a tricky problem. Note that neither Bool, Double or Int are AnyObject, they are all value types. That means they are represented in the dictionary as NSNumber. However, NSNumber can convert any value it holds to a Bool.
Checking which type is inside NSNumber is not easy. One way to check is to compare references with the result of NSNumber(bool:) constructors because NSNumber returns always the same instance:
func convertBoolToString(source: [String: AnyObject]) -> [String:AnyObject]? {
var destination = [String:AnyObject]()
let theTrue = NSNumber(bool: true)
let theFalse = NSNumber(bool: false)
for (key, value) in source {
switch value {
case let x where x === theTrue || x === theFalse:
destination[key] = "\(value as! Bool)"
default:
destination[key] = "not a bool"
}
}
if destination.isEmpty {
return nil
}
return destination
}
let dictionary: [String: AnyObject] = ["testA": true, "testB": 0, "testC": NSNumber(bool: true)]
print("Converted: \(convertBoolToString(dictionary))")
For other options, see get type of NSNumber
Swift 3 version:
static func convertBoolToString(_ source: [String: Any]?) -> [String:Any]? {
guard let source = source else {
return nil
}
var destination = [String:Any]()
let theTrue = NSNumber(value: true)
let theFalse = NSNumber(value: false)
for (key, value) in source {
switch value {
case let x as NSNumber where x === theTrue || x === theFalse:
destination[key] = "\(x.boolValue)"
default:
destination[key] = value
}
}
return destination
}