Get items from array by the nearest day - ios

I'm adding items to my Firebase with date in this format:
var date: NSDate?
override func viewDidLoad() {
super.viewDidLoad()
date = NSDate()
}
// ......
#IBAction func save(_ sender: UIBarButtonItem) {
if let realDate = date {
fullDate = "\(String(describing: realDate))"
}
// ......
let wordItem = Word(word: word, translation: translation, date: fullDate, fullDate: trueDate, exOne: exOne, exTwo: exTwo, completed: false, keyRandom: randomString)
let wordItemRef = self.ref?.child("Users").child(uid).child("LearnedWords").child(randomString)
wordItemRef?.setValue(wordItem.toAnyObject())
presentingViewController?.dismiss(animated: true, completion: nil)
}
So, my Firebase for Date looks like this:
Then, I retrieve this data in another ViewController and add it to the array:
override func viewDidLoad() {
super.viewDidLoad()
// checking if user is in
guard let uid = Auth.auth().currentUser?.uid else {
return
}
// retrieving data from FireBase
ref = Database.database().reference()
databaseHandle = ref?.child("Users").child(uid).child("LearnedWords").observe(.value, with: { (snapshot) in
var newItems: [Word] = []
for item in snapshot.children {
let wordItem = Word(snapshot: item as! DataSnapshot)
newItems.append(wordItem)
}
newItems.sort(by: { $0.date.compare($1.date) == .orderedDescending})
self.words = newItems
self.getAllMessagesSent(snapshot: newItems)
})
}
// retrieve data from Firebase to the View
func getAllMessagesSent(snapshot: [Word]) {
int = snapshot.count - 1
array = snapshot
}
The question:
How is it possible to retrieve the items from array from the nearest day? If today is 16 of August, I should get all items of the nearest day (for example, 8 items from 12 of August, if this date was the last). And should I change the way I'm adding date to Firebase to achieve this?
Edit
I achieved this by comparing the last date in the array with all other dates:
// retrieve data from Firebase to the View
var dateToCompare: String?
var array: [Word] = []
func getAllMessagesSent(snapshot: [Word]) {
int = snapshot.count - 1
array = snapshot
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "MM/dd/yyyy"
dateFormatter.timeZone = TimeZone(secondsFromGMT: 0)
// get the nearest item's date and convert it to Date
dateToCompare = array[0].fullDate
let formattedDateToCompare = dateFormatter.date(from: dateToCompare!)
// make array of dates
var dateArray = [NSDate]()
var numberOfWordsThisDay = 1
// formatting all dates in the array
for i in 0..<array.count {
let date1 = dateFormatter.date(from: array[i].fullDate)
dateArray.append(date1! as NSDate)
}
// comparing and in case of cussces increase the number
for i in 1..<array.count {
if Calendar.current.compare(formattedDateToCompare!, to: dateArray[i] as Date, toGranularity: .day) == .orderedSame {
numberOfWordsThisDay += 1
}
}
self.numOfWords.placeholder = "From \(numberOfWordsThisDay) to \(array.count)"
}
It works, but it definitely doesn't look like efficient solution, because I loop over two huge arrays. Is it possible to improve my code? Thanks!

Related

updating values in a realm database swift

I am trying to update the values on a realm database. If a user selects a row containing values I want to be able to update the values of that row. Here is my code but instead of updating, it creates another value in the database
func updateTodoList(todoList: TodoListModel, name: String, description: String, createdDate: Date, remiderDate: Date, photo: Data, isCompleted: Bool) -> Void {
try! database.write {
if name != "" {
todoList.name = name
} else {
todoList.name = "No extra information"
}
todoList.desc = description
todoList.createdDate = createdDate
todoList.remiderDate = remiderDate
todoList.photo = photo
todoList.isCompleted = false
}
}
my did select row
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let todoList = todoItems?[indexPath.row]
let storyBoard: UIStoryboard = UIStoryboard(name: "AddTodoListSB", bundle: nil)
let newViewController = storyBoard.instantiateViewController(withIdentifier: Constants.ADD_TODO_SB) as! AddTodoListVC
newViewController.loadViewIfNeeded()
let min = Date()
let max = Date().addingTimeInterval(60 * 60 * 60 * 60)
guard let itemPhoto = UIImagePNGRepresentation(newViewController.imageView.image!) else {return}
newViewController.picker.minimumDate = min
newViewController.picker.maximumDate = max
// newViewController.showDateTimePicker(sender: <#T##AnyObject#>)
newViewController.picker.completionHandler = { date in
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss"
self.title = formatter.string(from: date)
let reminder = formatter.string(from: date)
TodoListFunctions.instance.updateTodoList(todoList: todoList!, name: newViewController.titleTxtField.text!, description: newViewController.moreInfoTxtView.text!, createdDate: (todoList?.createdDate)!, remiderDate: formatter.date(from: reminder)!, photo: itemPhoto, isCompleted: false)
}
tableView.reloadData()
self.present(newViewController, animated: true, completion: nil)
}
// TodolistModel
class TodoListModel: Object {
#objc dynamic var id = UUID().uuidString
#objc dynamic var name: String = ""
#objc dynamic var desc: String = "No Description"
#objc dynamic var photo: Data? = nil
#objc dynamic var createdDate: Date?
#objc dynamic var remiderDate: Date?
#objc dynamic var isCompleted = false
override static func primaryKey() -> String? {
return "id"
}
let parentCategory = LinkingObjects(fromType: CategoryModel.self, property: "items")
}
further codes would be supplied on request
To update an object it must have a primary key and after you edit it use
// if it doesn't exist it'll be added
database.add(editedObjc, update: true)
//
// create object 1 , note: r = database
let lista = TaskList()
lista.pid = 1
lista.name = "Whole List"
// create object 2
let lista2 = TaskList()
lista2.pid = 2
lista2.name = "Whole List 2"
// add to database by write
r.add([lista,lista2])
let stored = r.objects(TaskList.self)
print("before edit" , stored)
// edit name of object 2
lista2.name = "qqwwqwqwqwqwqwqwq"
// update the object after changing it's name
r.add(lista2, update: true)
let stored2 = r.objects(TaskList.self)
print("after edit" , stored2)

Swift Firebase Multithreading Issue

I'm trying to run a couple of for loops inside of a function that should return an array of strings.
Where I'm having trouble is with getting the correct results BEFORE the next for loop is run...and then again returning that results BEFORE I need to return the array of strings to complete the function.
In the first case, I have a for loop that's getting data from Firebase. I was able to use a dispatch group to get the value to print out - but then with the other loop after this - I was having issues from using the dispatch group in the prior task.
The code all works perfectly if executed with the correct values but I'm not sure how to go about this with regards to threading. Would really appreciate any help.
func findTopSpots() -> [String] {
var topFive = [String]()
var locationRatingDictionary = [String:Double]()
let myGroup = DispatchGroup()
let locationsArray = ["wyoming", "kansas", "arkansas", "florida", "california"]
// Use the days to find the most common month
let calendar = NSCalendar.current
var monthArray = [String]()
var date = self.departureDate!
let endDate = self.returnDate!
// Formatter for printing the month name
let fmt = DateFormatter()
fmt.dateFormat = "MMMM"
// Add each days month to an array
while date <= endDate {
date = calendar.date(byAdding: .day, value: 1, to: date)!
monthArray.append(fmt.string(from: date))
}
// Return the primary month from function
let primaryMonth = findMostCommonMonthInArray(array: monthArray).lowercased()
// Create a dictionary of location:rating for the primary month
for doc in locationsArray {
self.db.collection("locations").document(doc).collection("historic").document(primaryMonth).getDocument { (document, err) in
if let document = document, document.exists {
let rating = document["rating"] as? Double
locationRatingDictionary[doc] = rating
} else {
print("Document does not exist")
}
}
}
//---- THE CODE BELOW WILL NOT PRINT WITH ANY VALUES ----//
print(locationRatingDictionary)
// Sort the tuple array by rating
let locationRatingTupleArray = locationRatingDictionary.sorted{ $0.value > $1.value }
// Return 5 results
for (location,rating) in locationRatingTupleArray.prefix(5) {
print(location,rating)
topFive.append(location)
}
print("top five are \(topFive)")
return topFive
}
The issue here is that the firebase returns with query results asynchronously and you are not waiting for it to return.
I can see that you have instantiate DispatchGroup but have not used it. Lets try to use it to solve your issue. Also, you would need to change the method signature to take a closure. This avoids blocking thread to return function output.
func findTopSpots(completionHandler:([String])->Void) {
var topFive = [String]()
var locationRatingDictionary = [String:Double]()
let myGroup = DispatchGroup()
let locationsArray = ["wyoming", "kansas", "arkansas", "florida", "california"]
// Use the days to find the most common month
let calendar = NSCalendar.current
var monthArray = [String]()
var date = self.departureDate!
let endDate = self.returnDate!
// Formatter for printing the month name
let fmt = DateFormatter()
fmt.dateFormat = "MMMM"
// Add each days month to an array
while date <= endDate {
date = calendar.date(byAdding: .day, value: 1, to: date)!
monthArray.append(fmt.string(from: date))
}
// Return the primary month from function
let primaryMonth = findMostCommonMonthInArray(array: monthArray).lowercased()
// Create a dictionary of location:rating for the primary month
for doc in locationsArray {
myGroup.enter() self.db.collection("locations").document(doc).collection("historic").document(primaryMonth).getDocument { (document, err) in
if let document = document, document.exists {
let rating = document["rating"] as? Double
locationRatingDictionary[doc] = rating
} else {
print("Document does not exist")
}
myGroup.leave()
}
}
myGroup.notify(queue:.main) {
//---- THE CODE BELOW WILL NOT PRINT WITH ANY VALUES ----//
print(locationRatingDictionary)
// Sort the tuple array by rating
let locationRatingTupleArray = locationRatingDictionary.sorted{ $0.value > $1.value }
// Return 5 results
for (location,rating) in locationRatingTupleArray.prefix(5) {
print(location,rating)
topFive.append(location)
}
print("top five are \(topFive)")
completionHandler(topFive)
}
}
Your code is asynchronous fastest way is dispatchGroup with completion
//
func findTopSpots(completion:#escaping(_ arr:[string])->void){
let dispatchGroup = DispatchGroup()
var topFive = [String]()
var locationRatingDictionary = [String:Double]()
let locationsArray = ["wyoming", "kansas", "arkansas", "florida", "california"]
// Use the days to find the most common month
let calendar = NSCalendar.current
var monthArray = [String]()
var date = self.departureDate!
let endDate = self.returnDate!
// Formatter for printing the month name
let fmt = DateFormatter()
fmt.dateFormat = "MMMM"
// Add each days month to an array
while date <= endDate {
date = calendar.date(byAdding: .day, value: 1, to: date)!
monthArray.append(fmt.string(from: date))
}
// Return the primary month from function
let primaryMonth = findMostCommonMonthInArray(array: monthArray).lowercased()
// Create a dictionary of location:rating for the primary month
for doc in locationsArray {
dispatchGroup.enter()
self.db.collection("locations").document(doc).collection("historic").document(primaryMonth).getDocument { (document, err) in
if let document = document, document.exists {
let rating = document["rating"] as? Double
locationRatingDictionary[doc] = rating
} else {
print("Document does not exist")
}
dispatchGroup.leave()
}
}
dispatchGroup.notify(queue: .main) {
//---- THE CODE BELOW WILL NOT PRINT WITH ANY VALUES ----//
print(locationRatingDictionary)
// Sort the tuple array by rating
let locationRatingTupleArray = locationRatingDictionary.sorted{ $0.value > $1.value }
// Return 5 results
for (location,rating) in locationRatingTupleArray.prefix(5) {
print(location,rating)
topFive.append(location)
}
print("top five are \(topFive)")
completion(topFive)
}
}

How to query realm swift for a specific object

Edited to simplify my question...
I'm new to Realm and so far, it's pretty cool, but I'm having an extremely hard time figuring out how to querying my Realm DB to check if a specific item exists in it.
Here's my Realm Model:
import Foundation
import RealmSwift
class ChartCount: Object{
dynamic var date: Date = Date()
dynamic var count: Int = Int(0)
}
In my main ViewController I'm storing a series of ChartCount objects for the 7 days of the current week using the following function:
// function to check if this weeks days have been created in Realm DB yet and creates them if not
let realm = try! Realm()
lazy var visitors: Results<VisitorCount> = { self.realm.objects(VisitorCount.self)}()
let startOfWeekDate = Date().startOfWeek(weekday: 1)
let nextDay = 24 * 60 * 60
var startOfWeek = try! Realm().objects(VisitorCount.self)
func setThisWeeksDays(){
if charts.count == 0 {
try! realm.write() {
let defaultVisitorDates = [startOfWeekDate, startOfWeekDate + TimeInterval(nextDay), startOfWeekDate + TimeInterval(nextDay*2), startOfWeekDate + TimeInterval(nextDay*3), startOfWeekDate + TimeInterval(nextDay*4), startOfWeekDate + TimeInterval(nextDay*5), startOfWeekDate + TimeInterval(nextDay*6)]
for visitors in defaultChartrDates {
let newChartDate = ChartCount()
newChartDate.date = visitors
self.realm.add(newChartrDate)
}
}
visitors = realm.objects(ChartCount.self)
}
}
And this to create the StartOfWeekDate
// Finds the start/end of the current week ----------------------------------------------- //
extension Date {
func startOfWeek(weekday: Int?) -> Date {
var cal = Calendar.current
var component = cal.dateComponents([.yearForWeekOfYear, .weekOfYear], from: self)
component.to12am()
cal.firstWeekday = weekday ?? 1
return cal.date(from: component)!
}
func endOfWeek(weekday: Int) -> Date {
let cal = Calendar.current
var component = DateComponents()
component.weekOfYear = 1
component.day = -1
component.to12pm()
return cal.date(byAdding: component, to: startOfWeek(weekday: weekday))!
}
func monthDay() -> String? {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "MMM dd"
return dateFormatter.string(from: self)
}
}
internal extension DateComponents {
mutating func to12am() {
self.hour = 0 + 24
self.minute = 0
self.second = 0
}
mutating func to12pm(){
self.hour = 0
self.minute = 0
self.second = 0
}
}// </end> Finds the start/end of the current week ------------------------------------------ //
All I want to do is check the 'date' column of my ChartDate model to see if there is an object in it that contains the first day of this week (e.g. startOfWeekDate).
For anyone looking to access Realm objects by Primary Key (Like I was) here's the code:
let specificPerson = realm.object(ofType: Person.self, forPrimaryKey: myPrimaryKey)
You cannot access Realm in the initializer, but you already have the answer in your code. Make start of week lazy just like you have for visitors and it will not be initialized until its used, and by the time your init is done. This is the way most of the realm examples are done.
lazy var startOfWeek: Result<ChartCount> = {
return realm.objects(ChartCount.self).filter("date = 'startOfWeekDate'")
}()
Alternatively you can make start of week an implicitly unwrapped optional and initialize it in viewDidLoad or just make it a regular optional
var startOfWeek: Result<ChartCount>!
...
//ViewDidLoad
realm.objects(ChartCount.self).filter("date = 'startOfWeekDate'")
OK, I think I've got it working. This is what I did:
To check if the Realm DB has the startOfWeekDate record I created this function:
func searchForStartOfWeek(findDate: Date) -> ChartCount?{
let predicate = NSPredicate(format: "date = %#", findDate as CVarArg)
let dateObject = self.realm.objects(ChartCount.self).filter(predicate).first
if dateObject?.date == findDate{
return dateObject
}
return nil
}
Then I used it to check if it exists:
func setThisWeeksDays(){
if searchForStartOfWeek(findDate: startOfWeekDate) == nil {
try! realm.write() {
let defaultChartDates = [startOfWeekDate, startOfWeekDate + TimeInterval(nextDay), startOfWeekDate + TimeInterval(nextDay*2), startOfWeekDate + TimeInterval(nextDay*3), startOfWeekDate + TimeInterval(nextDay*4), startOfWeekDate + TimeInterval(nextDay*5), startOfWeekDate + TimeInterval(nextDay*6)] // 3
for charts in defaultChartDates {
let newChartDate = ChartCount()
newChartDate.date = charts
self.realm.add(defaultChartDates)
}
}
visitors = realm.objects(ChartCount.self)
}
}
Thanks for everyones help!

How to check NSDate yyyy/mm/dd in NSArray and compare it?

I have an NSArray, inside I have a var of kind NSDate
which give this format "timeStamp = "2015-08-18 16:58:31"
I want to compare in all the array the date only 2015-08-18
compare it and if same date only the first one show full NSDate
and the rest with same date show only the time on UI
This is what I did so far:
func getAllMessages() -> NSArray{
var allMessages = self.mutableSetValueForKey("messages").allObjects as NSArray
let timeStampSortDescriptor = NSSortDescriptor(key: "timeStamp", ascending: true)
var sortByTime = allMessages.sortedArrayUsingDescriptors([timeStampSortDescriptor])
println("\(sortByTime)")
return sortByTime
}
screen shot
http://i.stack.imgur.com/wcsSz.jpg
After some research i did made a solution as following
First this extension is very good and my way is depends on it
https://stackoverflow.com/a/27369380/5188737
If anyone have better solution or clean
I will appreciate if you can post it here
I'm sorry there's no comments
func loadMessages(){
if chatRoom!.messages.count > 0{
var i = 0
var tempMsgDate:NSDate?
var chatMessage:ChatMessage?
for message in chatRoom!.getAllMessages(){
let msg = message as! Message
if i == 0{
tempMsgDate = msg.timeStamp
chatMessage = ChatMessage(incoming: msg.income, text: msg.message, sentDate: convertNSDateToString(msg.timeStamp))
i++
}else{
//if the tempMsgDate (which is the first of the same
//date in the nsarray)
if checkIfSameDayDate(tempMsgDate!,date2: msg.timeStamp){
var tempDate = msg.timeStamp.time
let dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "HH:mm"
let date = dateFormatter.dateFromString(tempDate)
println("loadmessages method: \(date?.time)")
chatMessage = ChatMessage(incoming: msg.income, text: msg.message, sentDate: msg.timeStamp.time)
}else{//after he got to different date it save it as
//first of same date to follow
tempMsgDate = msg.timeStamp
chatMessage = ChatMessage(incoming: msg.income, text: msg.message, sentDate: convertNSDateToString(msg.timeStamp))
}
}
var msgCollection:[ChatMessage] = [ChatMessage]()
msgCollection.append(chatMessage!)
chat.loadedMessages.append(msgCollection)
}
}
}
func convertNSDateToString(date:NSDate) -> String{
let dateString = date.date + " " + date.time
println("in convert: \(dateString)")
return dateString
}
func checkIfSameDateFromSendMSGAndReciveMSG(date:NSDate) -> Bool{
for message in chatRoom!.getAllMessages(){
let msg = message as! Message
if msg.timeStamp.date == date.date{
return true
}
}
return false
}
//checks if the date1 equal to date2
func checkIfSameDayDate(date1:NSDate,date2:NSDate) -> Bool{
if date1.date == date2.date{
return true
}
return false
}

Parse.com Framework Sort Date Next Birthday

Need to sort on iOS by date using Parse.com but I need to order them based on the next birthday for a specific person. I tried to add the day and month with the current or next year and that helped but that will be a manual process, any recommendations to do this in an automated way.
As a side note I want to implement it in swift
Thanks all in advance.
The exact code will depend on your personal setup (I can imagine you've made a subclass of PFObject or similar), but try to experiment with the following code. I've also made another function called filterFriendsAfterBirthdays that might be useful for somebody reading this post.
//
// ViewController.swift
// ParseFun
//
// Created by Stefan Veis Pennerup on 20/06/15.
// Copyright (c) 2015 Kumuluzz. All rights reserved.
//
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
createDummyData()
queryForFriends() {
println("Unsorted birthdays: \($0)")
println("Sorted birthdays: \(self.filterFriendsAfterBirthdays($0))")
println("Closets upcoming birthday: \(self.filterFriendsBasedOnClosetsUpcomingBirthday($0))")
}
}
func createDummyData() {
for var i = 0; i < 25; i++ {
let myFriend = PFObject(className: "Friends")
let interval = Double(arc4random_uniform(UInt32.max))
myFriend["birthday"] = NSDate(timeIntervalSince1970: interval)
myFriend.saveInBackground()
}
}
func queryForFriends(completionHandler: ([PFObject]) -> ()) {
let friendsQuery = PFQuery(className: "Friends")
friendsQuery.findObjectsInBackgroundWithBlock { (result, error) in
if (error != nil) { return }
let pfArray = result as! [PFObject]
completionHandler(pfArray)
}
}
func filterFriendsAfterBirthdays(friends: [PFObject]) -> [PFObject] {
return friends.sorted {
return ($0["birthday"] as! NSDate).compare($1["birthday"] as! NSDate) == .OrderedAscending
}
}
func filterFriendsBasedOnClosetsUpcomingBirthday(friends: [PFObject]) -> [PFObject] {
let cal = NSCalendar.currentCalendar()
// Specifies the day unit
let dayUnit: NSCalendarUnit = .CalendarUnitDay
// Gets todays year
let today = NSDate()
let yearUnit: NSCalendarUnit = .CalendarUnitYear
let yearToday = cal.components(yearUnit, fromDate: today)
// Combined days and year units
let combinedUnits: NSCalendarUnit = .CalendarUnitYear | .CalendarUnitDay
return friends.sorted {
// Gets the birthday components since today
// Also uses the year unit to ensure that the day will be between -365 and 365
let birth1Components = cal.components(combinedUnits, fromDate: today, toDate: ($0["birthday"] as! NSDate), options: nil)
let birth2Components = cal.components(combinedUnits, fromDate: today, toDate: ($1["birthday"] as! NSDate), options: nil)
// Updates the days to a positive integer
if (birth1Components.day < 0) { birth1Components.day += 365 }
if (birth2Components.day < 0) { birth2Components.day += 365 }
return birth1Components.day <= birth2Components.day
}
}
}

Resources