Swift Firebase Multithreading Issue - ios

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

Related

Unwrapping an Optional value in swift and realm

I wrote a working function for the application, but the error came out "The nil value was unexpectedly found when an optional value was implicitly deployed" limit Limit label.the text I can't fix.
Properties:
#IBOutlet weak var limitLabel: UILabel!
Function:
func leftLabels(){
let limit = self.realm.objects(Limit.self)
guard limit.isEmpty == false else {return}
limitLabel.text = limit[0].limitSum //Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value
let calendar = Calendar.current
let formatter = DateFormatter()
formatter.dateFormat = "yyyy/MM/dd HH:mm"
let firstDay = limit[0].limitDate as Date
let lastDay = limit[0].limitLastDate as Date
let firstComponent = calendar.dateComponents([.year, .month, .day], from: firstDay)
let lastComponent = calendar.dateComponents([.year, .month, .day], from: lastDay)
let startDate = formatter.date(from: "\(firstComponent.year!)/\(firstComponent.month!)/\(firstComponent.day!) 00:00")
let endDate = formatter.date(from: "\(lastComponent.year!)/\(lastComponent.month!)/\(lastComponent.day!) 23:59")
let filterLimit: Int = realm.objects(SpendingDB.self).filter("self.date >= %# && self.date <= %#", startDate ?? "", endDate ?? "").sum(ofProperty: "cost")
ForThePeriod.text = "\(filterLimit)"
let a = Int(limitLabel.text!)!
let b = Int(ForThePeriod.text!)!
let c = a - b
availableForSpending.text = "\(c)"
I will be glad if you tell me the correct code
As from comments if appears that your view is not yet loaded and some of your views are still nil. Your app crashes because in line limitLabel.text = limit[0].limitSum the limitLabel is nil. It would crash regardless of Realm even by calling limitLabel.text = "Hello world!"
You can always guard data that you need to avoid changes in your code. Simply add
guard let limitLabel = limitLabel else { return nil }
guard let ForThePeriod = ForThePeriod else { return nil }
and so on.
I tried to clean up your code a bit. It is hard to understand what exactly are you trying to achieve but something like the following may seem a bit more appropriate:
func leftLabels() {
// Elements needed for method to execute.
guard let limitLabel = limitLabel else { return }
guard let forThePeriodLabel = forThePeriodLabel else { return }
guard let availableForSpendingLabel = availableForSpendingLabel else { return }
// Items that will be reused throughout the method later on
let limits: [Limit]
let firstLimit: Limit
let dates: (start: Date?, end: Date?)
let filterLimit: Int
limits = self.realm.objects(Limit.self)
guard limits.isEmpty == false else { return }
firstLimit = limits[0]
// limitLabel
limitLabel.text = firstLimit.limitSum
// Date components
dates = {
let calendar = Calendar.current
let formatter = DateFormatter()
formatter.dateFormat = "yyyy/MM/dd HH:mm"
let firstDay = firstLimit.limitDate as Date
let lastDay = firstLimit.limitLastDate as Date
let firstComponent = calendar.dateComponents([.year, .month, .day], from: firstDay)
let lastComponent = calendar.dateComponents([.year, .month, .day], from: lastDay)
let startDate = formatter.date(from: "\(firstComponent.year!)/\(firstComponent.month!)/\(firstComponent.day!) 00:00")
let endDate = formatter.date(from: "\(lastComponent.year!)/\(lastComponent.month!)/\(lastComponent.day!) 23:59")
return (startDate, endDate)
}()
// forThePeriodLabel
filterLimit = realm.objects(SpendingDB.self).filter("self.date >= %# && self.date <= %#", startDate ?? "", endDate ?? "").sum(ofProperty: "cost")
forThePeriodLabel.text = String(filterLimit)
// availableForSpendingLabel
availableForSpendingLabel.text = {
guard let a = Int(firstLimit.limitSum) else { return "" }
let b = filterLimit
let c = a - b
return String(c)
}()
}
Note some practices which help you better to structure and solve your code.
Guard dangerous data at first
Create a list of reusable items for your method (there should be as fewer as possible, in most cases none). Note how these can be later assigned to. And if you try using it before assigning to it, you will be warned by your compiler.
Wrap as much code into closed sections such as availableForSpendingLabel.text = { ... code here ... }()
Use tuples such as let dates: (start: Date?, end: Date?)
Don't be afraid of using long names such as availableForSpendingLabel
I would even further try and break this down into multiple methods. But I am not sure what this method does and assume that you have posted only part of it...
========== EDIT: Adding alternate approach ==========
From comments this is a financial application so probably at least dealing with Decimal numbers would make sense. Also introducing approach with adding a new structure which resolves data internally. A formatter is also used to format the number. And some other improvements:
struct Limit {
let amount: Decimal
let startDate: Date
let endDate: Date
}
struct Spending {
let cost: Decimal
let date: Date
}
struct LimitReport {
let limitAmount: Decimal
let spendingSum: Decimal
let balance: Decimal
init(limit: Limit) {
let limitAmount: Decimal = limit.amount
let spendingSum: Decimal = {
let calendar = Calendar.autoupdatingCurrent // Is this OK or should it be some UTC or something?
func beginningOfDate(_ date: Date) -> Date {
let components = calendar.dateComponents([.day, .month, .year], from: date)
return calendar.date(from: components)!
}
let startDate = beginningOfDate(limit.startDate)
let endDate = calendar.date(byAdding: .day, value: 1, to: startDate)
let spendings: [Spending] = realm.objects(Spending.self).filter { $0.date >= startDate && $0.date < endDate }
return spendings.reduce(0, { $0 + $1.cost })
}()
let balance = limitAmount - spendingSum
self.limitAmount = limitAmount
self.spendingSum = spendingSum
self.balance = balance
}
}
func leftLabels() {
// Elements needed for method to execute.
guard let limitLabel = limitLabel else { return }
guard let forThePeriodLabel = forThePeriodLabel else { return }
guard let availableForSpendingLabel = availableForSpendingLabel else { return }
guard let limit = self.realm.objects(Limit.self).first else { return }
let formatter = NumberFormatter()
formatter.numberStyle = .currency
formatter.currencySymbol = "$"
let report = LimitReport(limit: limit)
limitLabel.text = formatter.string(from: report.limitAmount)
forThePeriodLabel.text = formatter.string(from: report.spendingSum)
availableForSpendingLabel.text = formatter.string(from: report.balance)
}
Matic provided a good, comprehensive answer to your question (voted). I thought I'd provide an answer narrowly focused on your crash and a "short and sweet" way to fix it:
The line in question could crash 2 different ways:
limitLabel.text = limit[0].limitSum //Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value
Your limitLabel IBOutlet is declared as an "implicitly unwrapped Optional" (Note the ! after the type, UILabel:
#IBOutlet weak var limitLabel: UILabel!
An implicitly unwrapped Optional is an Optional where, essentially, the compiler adds a hidden "!" force-unwrap every time you try to reference that object.
That means that
limitLabel.text = //something
Is compiled as
limitLabel!.text = //something
and if limitLabel is nil, you crash.
If you call your leftLabels() function before your view has been loaded, or if that outlet is never connected, you will crash.
You can fix that by adding an optional unwrap to the statement:
limitLabel?.text = //something
(That construct is known as "optional chaining".)
Given that the crash message you're getting mentions "implicitly unwrapping an Optional value" it's likely that that is what is crashing in your case. However, you should fix the other issue as well.
The second way you can crash is in your array indexing.
limitLabel.text = limit[0].limitSum
When you fetch an object from an array by index, your app will crash if the array does not contain an item at that index. The expression limit[0] will crash if the limit array is empty.
The array type has a computed property first that will return an optional if the array is empty.
You should change that to limit.first?.limitSum.
Change the whole line to be:
limitLabel?.text = limit.first()?.limitSum
And it won't crash any more.

Swift 2d array empty after HealthKit function call - thread issues

I'm populating a twodimensional array with HealthKit data. It's working fine as long as the function loops through the 30 days. outside of that function the array is empty because HealthKit data is loaded in background and everything else executes before.
How can I wait until the data is loaded so I can access the data inside the arrays?
Here's what's happening inside my ViewController
print("function call starting") // 1
// Populate 2DArrays
self.hkManager.getTotalCalories(forDay: 30) {caloriesValue, date in
//check if caloriesValue is nil, only do something if it's not
guard let caloriesValue = caloriesValue else { return }
let dateValue = DateFormatter.localizedString(from: date!, dateStyle: .short, timeStyle: .none)
print("will append") // 7 (31x)
self.caloriesArray.append([(dateValue,caloriesValue)])
}
print("function call done") // 2
print(self.caloriesArray.count) // 3
hkManager.getWeightData(forDay: 30) {bodyMassValue, date in
// Check if bodyMassValue is nil, only do something, if it's not
guard let bodyMassValue = bodyMassValue else { return }
let dateValue = DateFormatter.localizedString(from: date!, dateStyle: .short, timeStyle: .none)
self.bodyMassArray.append([(dateValue,bodyMassValue)])
}
do {
self.age = try hkManager.getAge()
} catch let error {
print("Error calculating age: \(error)")
}
print(bodyMassArray) // 4
print(caloriesArray) // 5
print(age!) // 6
}
I've added numbers behind the print statements to make clear what get's executed when.
The functions I'm calling look like this:
func getTotalCalories(forDay days: Int, completion: #escaping ((_ calories: Int?, _ date: Date?) -> Void)) {
// Getting quantityType as .dietaryCaloriesConsumed
guard let calories = HKObjectType.quantityType(forIdentifier: .dietaryEnergyConsumed) else {
print("*** Unable to create a dietaryEnergyConsumed type ***")
return
}
let now = Date()
let startDate = Calendar.current.date(byAdding: DateComponents(day: -days), to: now)!
var interval = DateComponents()
interval.day = 1
var anchorComponents = Calendar.current.dateComponents([.day, .month, .year], from: now)
anchorComponents.hour = 0
let anchorDate = Calendar.current.date(from: anchorComponents)!
let query = HKStatisticsCollectionQuery(quantityType: calories,
quantitySamplePredicate: nil,
options: [.cumulativeSum],
anchorDate: anchorDate,
intervalComponents: interval)
query.initialResultsHandler = { _, results, error in
guard let results = results else {
print("ERROR")
return
}
results.enumerateStatistics(from: startDate, to: now) { statistics, _ in
DispatchQueue.main.async {
if let sum = statistics.sumQuantity() {
let calories = Int(sum.doubleValue(for: HKUnit.kilocalorie()).rounded())
completion(calories, statistics.startDate)
return
}
}
}
}
healthStore.execute(query)
}
Anyone any ideas what I need to do?
Thanks! :)

Get items from array by the nearest day

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!

Solve memory issue with migrating a big bunch of data

In my previous version of my app I stored all user data into .dat files (with NSKeyedArchiver), but in my new version I want to upgrade to a real(m) database.
I'm trying to import all of this data (and that can be a LOT) into Realm. But it's taking so much memory that the debugger eventually kill my app before the migration has finished. The 'strange' thing is that the data on hard disk is only 1.5 mb big, but it's taking memory for more than 1gb so I'm doing something wrong.
I also tried to work with multiple threads, but that didn't help. Well it speeded up the migration process (which is good), but it also took the same amount of memory.
Who can help me out? See my code below for more information..
FYI Async can be found here https://github.com/duemunk/Async
import Async
let startDate = NSDate(timeIntervalSince1970: 1388534400).startOfDay // Start from 2014 jan 1st
let endDate = NSDate().dateByAddingTimeInterval(172800).startOfDay // 2 days = 3600 * 24 * 2 = 172.800
var pathDate = startDate
let calendar = NSCalendar.currentCalendar()
let group = AsyncGroup()
var allPaths = [(Int, Int)]()
while calendar.compareDate(pathDate, toDate: endDate, toUnitGranularity: .Month) != .OrderedDescending {
// Components
let currentMonth = calendar.component(.Month, fromDate: pathDate)
let currentYear = calendar.component(.Year, fromDate: pathDate)
allPaths.append((currentYear, currentMonth))
// Advance by one month
pathDate = calendar.dateByAddingUnit(.Month, value: 1, toDate: pathDate, options: [])!
}
for path in allPaths {
group.background {
// Prepare path
let currentYear = path.0
let currentMonth = path.1
let path = (Path.Documents as NSString).stringByAppendingPathComponent("Stats_\(currentMonth)_\(currentYear).dat")
print(path)
if NSFileManager.defaultManager().fileExistsAtPath(path) {
NSKeyedUnarchiver.setClass(_OldStatisticsDataModel.self, forClassName: "StatisticsDataModel")
if let statistics = NSKeyedUnarchiver.unarchiveObjectWithFile(path) as? [_OldStatisticsDataModel] {
// Loop through days
for i in 1...31 {
let dateComponents = NSDateComponents()
dateComponents.year = currentYear
dateComponents.month = currentMonth
dateComponents.day = i
dateComponents.hour = 0
dateComponents.minute = 0
// Create date from components
let userCalendar = NSCalendar.currentCalendar() // user calendar
guard let date = userCalendar.dateFromComponents(dateComponents) else {
continue
}
// Search for order items
let filtered = statistics.filter {
if let date = $0.date {
let dateSince1970 = date.timeIntervalSince1970
return date.startOfDay.timeIntervalSince1970 <= dateSince1970 && date.endOfDay.timeIntervalSince1970 >= dateSince1970
}
return false
}
if filtered.isEmpty == false {
// Create order
let transaction = Transaction()
transaction.employee = Account.API().administratorEmployee()
let order = Order()
order.status = PayableStatus.Paid
order.createdDate = date.timeIntervalSince1970
order.paidDate = date.timeIntervalSince1970
// Loop through all found items
for item in filtered {
// Values
let price = (item.price?.doubleValue ?? 0.0) * 100.0
let tax = (item.tax?.doubleValue ?? 0.0) * 100.0
// Update transaction
transaction.amount += Int(price)
// Prepare order item
let orderItem = OrderItemm()
orderItem.amount = item.amount
orderItem.price = Int(price)
orderItem.taxPercentage = Int(tax)
orderItem.name = item.name ?? ""
orderItem.product = Product.API().productForName(orderItem.name, price: orderItem.price, tax: orderItem.taxPercentage)
// Add order item to order
order.orderItems.append(orderItem)
}
if order.orderItems.isEmpty == false {
print("\(date): \(order.orderItems.count) order items")
// Set transaction for order
order.transactions.append(transaction)
// Save the order
Order.API().saveOrders([order])
}
}
}
}
}
}
}
group.wait()
As in the comments of my question was suggested by #bdash, autoreleasepool did the trick.
I use AsyncSwift as syntactic sugar for grand central dispatch, but when using block groups the group keeps a reference to the block which caused that the memory wasn't released. I still make use of a group now but I leave the group after it's finished.
I provided my code example below, to make things clearer. Using multiple threads gave me incredibly (like 10 times faster) more performance while memory won't go above 150MB. Previously app was crashing at 1,3GB due to memory pressure.
let group = AsyncGroup()
var allPaths = [(Int, Int)]()
// Some logic to fill the paths -> not interesting
for path in allPaths {
group.enter() // The following block will be added to the group
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
autoreleasepool { // Creating an autoreleasepool to free up memory for the loaded statistics
// Stripped unnecessary stuff
if NSFileManager.defaultManager().fileExistsAtPath(path) {
// Load the statistics from .dat files
NSKeyedUnarchiver.setClass(_OldStatisticsDataModel.self, forClassName: "StatisticsDataModel")
if let statistics = NSKeyedUnarchiver.unarchiveObjectWithFile(path) as? [_OldStatisticsDataModel] {
// Loop through days
for i in 1...31 {
autoreleasepool {
// Do the heavy stuff here
}
}
}
}
}
group.leave() // Block has finished, now leave the group
}
}
group.wait()

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
}

Resources