swift Ekevents to 2 dimensional array sorted by Date - swift

I have an array with events which are sorted by date. I want a separate section for each day [monday], [tuesday], ....
How do I create a 2-dimensional array that looks like this?
And how do i get each event in the appropriate section?
[
[monday]
[tuesday]
[wednesday]
. .and so on ]
func getEvents(){
let calendars = eventStore.calendars(for: .event)
for calendar in calendars {
// if calendar.title == "Arbeit" {
let anfang = NSDate()
let ende = Calendar.current.date(byAdding: .day, value: 6, to: anfang as Date)!
let predicate = eventStore.predicateForEvents(withStart: anfang as Date, end: ende as Date, calendars: [calendar])
let events = eventStore.events(matching: predicate)
var found:Bool = false
for event in events {
if (allEvents.count == 0)
{
allEvents.append(event)
}
if (allEvents.count > 0)
{
found = false
if ( event.title == allEvents[allEvents.count-1].title && event.startDate == allEvents[allEvents.count-1].startDate)
{
found = true
}
}
if(!found)
{
allEvents.append(event)
}
}

The following solution first groups the events based start date by weekday in a dictionary and then transforms the dictionary to a 2D array sorted with todays day first
First set up a Calendar and DateFormatter to use
let locale = Locale(identifier: "en_US_POSIX")
var calendar = Calendar.current
calendar.locale = locale
let formatter = DateFormatter()
formatter.locale = locale
formatter.dateFormat = "EEEE"
Group the events
let byWeekday = Dictionary(grouping: events, by: { formatter.string(from: $0.startDate) })
Get the weekdays in the right order
let weekdays = calendar.weekdaySymbols
let todayIndex = weekdays.firstIndex(of: formatter.string(from: Date())) ?? calendar.weekdaySymbols.startIndex
let sortedWeekdays = weekdays[todayIndex..<weekdays.endIndex] + weekdays[weekdays.startIndex..<todayIndex]
and then create and fill the array
var eventsByDay = [[EKEvent]]()
for day in sortedWeekdays {
eventsByDay.append(byWeekday[day] ?? [])
}

Related

Compare current system date with another date(which is coming as string format 2019-11-22) in swift

I'm getting date as a sting format from API (2019-11-22), I want to compare this date with current date.
I tried converting current date as string format this is success but this is not satisfying requiremet. I have to convert to String(2019-11-22) to Date and then I can compare two dates.
How can I convert string (2019-11-22) to Date to compare with system date pls help I'm lead knowledge in dates. Thanks in advance.
extension Date {
static func getCurrentDate() -> String {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd"
return dateFormatter.string(from: Date())
}
}
if Date() < apiEach["ExpiryDate"]! as! Date{
//apiEach["ExpiryDate"]! is 2019-11-22
pritn("You can proceed it's not outdated")
}
apiEach["ExpiryDate"] is a string (apiEach["ExpiryDate"] as! Date will crash) so you have two options:
Convert the current date to string
if Date.getCurrentDate() < apiEach["ExpiryDate"] as! String { ...
Convert the API string to Date and compare that
let dateFormatter = DateFormatter()
dateFormatter.locale = Locale(identifier: "en_US_POSIX")
dateFormatter.dateFormat = "yyyy-MM-dd"
if let apiDate = dateFormatter.date(from: apiEach["ExpiryDate"] as! String),
Date() < apiDate { ...
func minimumDate(result:String) -> Bool {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd"
let myDate = dateFormatter.date(from: "\(result)")
dateFormatter.dateFormat = "yyyy-MM-dd"
let now = Date()
let startDateComparisionResult:ComparisonResult = now.compare(myDate!)
if startDateComparisionResult == ComparisonResult.orderedAscending {
print("Current date is smaller than end date.")
let somedateString = dateFormatter.string(from: myDate!)
print(somedateString)
return true
}
else if startDateComparisionResult == ComparisonResult.orderedDescending {
// Current date is greater than end date.
print("Current date is greater than end date.")
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd"
let date1String = dateFormatter.string(from: myDate!)
let date2String = dateFormatter.string(from: now)
if date1String == date2String {
print("Equal date")
return true
}
return false
}
else if startDateComparisionResult == ComparisonResult.orderedSame {
// Current date and end date are same
print("Current date and end date are same")
return true
}
return true
}
Since the date format "yyyy-MM-dd" can be properly sorted/compared you can either convert current date to a string and compare it with your API value
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd"
let now = dateFormatter.string(from: Date())
switch now.compare(input) { //input is from API
case .orderedSame:
print("Same")
case .orderedAscending:
print("Before")
case .orderedDescending:
print("After")
}
If you want to compare dates it is important that Date() will also include the current time while a date converted using a date formatter with a date only format will have its time zeroed out (midnight) so the comparison might not be correct if the date part is the same. To handle this it is better to use DateComponents
let calendar = Calendar.current
guard let date = dateFormatter.date(from: input) else { return //or error }
let components = calendar.dateComponents([.year, .month, .day], from: now, to: date)
if let year = components.year, let month = components.month, let day = components.day {
switch (year + month + day) {
case 0: // all values are zero
print("Same")
case ..<0 : // values will be zero or negative so sum is negative
print("After")
default: // values will be zero or positive so sum is positive
print("Before")
}
}

Fetch All Photos from Library based on creationDate in Swift [Faster Way?]

I have a UICollectionView displaying library photos based on latest "creationDate". For that I am using below code:
struct AssetsData {
var creationDate: Date, assetResult: PHFetchResult<PHAsset>
}
func fetchPhotos() -> [AssetsData] {
//Date Formatter
let formatter = DateFormatter()
formatter.dateStyle = DateFormatter.Style.medium
formatter.timeStyle = DateFormatter.Style.none
//Photos fetch
let fetchOptions = PHFetchOptions()
let sortOrder = [NSSortDescriptor(key: "creationDate", ascending: false)]
fetchOptions.sortDescriptors = sortOrder
let assetsFetchResult = PHAsset.fetchAssets(with: .image, options: fetchOptions)
var arrCreationDate = [Date]()
var arrDates = [String]()
//Getting All dates
for index in 0..<assetsFetchResult.count {
if let creationDate = assetsFetchResult[index].creationDate {
let formattedDate = formatter.string(from: creationDate)
if !arrDates.contains(formattedDate) {
arrDates.append(formattedDate)
arrCreationDate.append(creationDate)
}
}
}
//Fetching Assets based on Dates
var arrPhotoAssetsData = [AssetsData]()
for createdDate in arrCreationDate {
if let startDate = getDate(forDay: createdDate.day, forMonth: createdDate.month, forYear: createdDate.year, forHour: 0, forMinute: 0, forSecond: 0), let endDate = getDate(forDay: createdDate.day, forMonth: createdDate.month, forYear: createdDate.year, forHour: 23, forMinute: 59, forSecond: 59) {
fetchOptions.predicate = NSPredicate(format: "creationDate > %# AND creationDate < %#", startDate as NSDate, endDate as NSDate)
let assetsPhotoFetchResult = PHAsset.fetchAssets(with: PHAssetMediaType.image, options: fetchOptions)
arrPhotoAssetsData.append(AssetsData(creationDate: createdDate, assetResult: assetsPhotoFetchResult))
}
}
return arrPhotoAssetsData
}
func getDate(forDay day: Int, forMonth month: Int, forYear year: Int, forHour hour: Int, forMinute minute: Int, forSecond second: Int) -> Date? {
var dateComponents = DateComponents()
dateComponents.day = day
dateComponents.month = month
dateComponents.year = year
dateComponents.hour = hour
dateComponents.minute = minute
dateComponents.second = second
var gregorian = Calendar(identifier: Calendar.Identifier.gregorian)
gregorian.timeZone = NSTimeZone.system
return gregorian.date(from: dateComponents)
}
The code works nicely! But the problem is it takes almost 7 - 9 seconds to load 10k+ photos. Till 6k photos there is no problem, but I really need some efficient way so that I can load some of the asset in UICollectionView and rest of them I can add later. I need that no matter the photos count, it should not take more than 2 - 3 seconds. Can anybody please help?
Let's say you have 8k photos. So you iterate through two 'for' loops in order to get the arrCreationDate and arrPhotoAssets data(which is double the work needed)
Instead, you can try doing it through a single loop. Here's a rough way:-
let assetsFetchResult = PHAsset.fetchAssets(with: .image, options: fetchOptions)
let fetchOptions = PHFetchOptions()
var arrCreationDate = [Date]()
var arrPhotoAssetsData = [AssetsData]()
var arrDates = [String]()
for index in 0..<assetsFetchResult.count {
if let creationDate = assetsFetchResult[index].creationDate {
let formattedDate = formatter.string(from: creationDate)
if !arrDates.contains(formattedDate) {
//You can convert the formattedDate to actual date here and do a check similar to this, do what you do in the other loop here too
if(actualDate < actualDateOfTheFirstElementAtArray){
arrCreationDate.insert(actualDate, at: 0)
fetchOptions.predicate = NSPredicate(format: "creationDate > %# AND creationDate < %#", startDate as NSDate, endDate as NSDate)
let assetsPhotoFetchResult = PHAsset.fetchAssets(with: PHAssetMediaType.image, options: fetchOptions)
arrPhotoAssetsData.insert(AssetsData(creationDate: createdDate, assetResult: assetsPhotoFetchResult), at: 0)
}
}
}
}
This is just for you to get a rough idea of what I'm talking about, as this will reduce half the burden(just a single loop)
Also try using prefetchDataSource for your collection view to preload it with some data
EDIT:-
I assume that you have tried the following already:-
func fetchPhotos() -> [AssetsData] {
//Date Formatter
let formatter = DateFormatter()
formatter.dateStyle = DateFormatter.Style.medium
formatter.timeStyle = DateFormatter.Style.none
//Photos fetch
let fetchOptions = PHFetchOptions()
let sortOrder = [NSSortDescriptor(key: "creationDate", ascending: false)]
fetchOptions.sortDescriptors = sortOrder
let assetsFetchResult = PHAsset.fetchAssets(with: .image, options: fetchOptions)
var arrCreationDate = [Date]()
var arrDates = [String]()
var arrPhotoAssetsData = [AssetsData]()
//Getting All dates
for index in 0..<assetsFetchResult.count {
if let creationDate = assetsFetchResult[index].creationDate {
let formattedDate = formatter.string(from: creationDate)
if !arrDates.contains(formattedDate) {
arrDates.append(formattedDate)
arrCreationDate.append(creationDate)
convertToAssetsDataAndAppend(date: creationDate, fetchOptions: fetchOptions, toArray: &arrPhotoAssetsData)
}
}
}
return arrPhotoAssetsData
}
func convertToAssetsDataAndAppend(date: Date, fetchOptions: PHFetchOptions, toArray: inout [AssetsData]){
if let startDate = getDate(forDay: date.day, forMonth: date.month, forYear: date.year, forHour: 0, forMinute: 0, forSecond: 0), let endDate = getDate(forDay: date.day, forMonth: date.month, forYear: date.year, forHour: 23, forMinute: 59, forSecond: 59) {
fetchOptions.predicate = NSPredicate(format: "creationDate > %# AND creationDate < %#", startDate as NSDate, endDate as NSDate)
let assetsPhotoFetchResult = PHAsset.fetchAssets(with: PHAssetMediaType.image, options: fetchOptions)
toArray.append(AssetsData(creationDate: date, assetResult: assetsPhotoFetchResult))
}
}
func getDate(forDay day: Int, forMonth month: Int, forYear year: Int, forHour hour: Int, forMinute minute: Int, forSecond second: Int) -> Date? {
var dateComponents = DateComponents()
dateComponents.day = day
dateComponents.month = month
dateComponents.year = year
dateComponents.hour = hour
dateComponents.minute = minute
dateComponents.second = second
var gregorian = Calendar(identifier: Calendar.Identifier.gregorian)
gregorian.timeZone = NSTimeZone.system
return gregorian.date(from: dateComponents)
}
If this doesn't help, how about reloading the collection view with some kind of callback after every loop iteration? (with the above approach)
This way, you won't make the user wait until the whole thing gets loaded
Idk, these might look petty but I'm just trying to help :)

Calculate age from birth date using textfield and NSDateComponents in Swift4

I am trying calculate the age from birthday Date in Swift with this function: (want to write in a textField and pass this data from VC in a Label)
{
var a = self.dob.text
var c = a!.components(separatedBy: "-")
var y1 = c[2]
let cal = NSCalendar? = NSCalendar(calendarIdentifier: .gregorian)
let now = Date()
let year = Calendar.components(.year, from: dob!, to: now, options: [])
let age = (year!) - Int(y1)!
self.myage.text = String(age)
}
But I get an error cannot assign NSCalendar?.Type, but I don't know why get this error (its my first time coding)
You have a few problems in your code. First there is a type as already mentioned by Qi Hao, second you are passing dob is a text field you and Calendar components method expects two dates, so you should first parse the text field date then you can get the year component difference from input date and now:
Playground Testing
let dob = UITextField()
dob.text = "03-27-2002"
let dateFormatter = DateFormatter()
dateFormatter.locale = Locale(identifier: "en_US_POSIX")
dateFormatter.dateFormat = "MM-dd-yyyy"
if let date = dateFormatter.date(from: dob.text!) {
let age = Calendar.current.dateComponents([.year], from: date, to: Date()).year!
print(age) // 16
}
func age(on baseDate: DateComponents) -> Int {
if baseDate.month > month {
return baseDate.year - year
}
if baseDate.month == month && baseDate.day >= day {
return baseDate.year - year
}
return baseDate.year - year - 1
}
try this:
func calcAge(birthday: String) -> Int {
let dateFormater = DateFormatter()
dateFormater.dateFormat = "MM/dd/yyyy"
let birthdayDate = dateFormater.date(from: birthday)
let calendar: NSCalendar! = NSCalendar(calendarIdentifier: .gregorian)
let now = Date()
let calcAge = calendar.components(.year, from: birthdayDate!, to: now, options: [])
let age = calcAge.year
return age!
}

how to make daily local notification depending on specific time in CVcalendar?

I am using cvcalendar and there is in everyday a different times for fajer, dohor , aser , maghreb , ishaa . for example i have selected the Adan for Fajer, so i want to get the adan in everyday and everyday has a different time. so when i get a notification in DidreceivedLocalNotification i want go to next day in calendar and get the time of the next day, knowing that am getting the times from CoreData .
in viewWillappear
let date = NSDate()
let dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "dd-MM-yyyy"
let calendar = NSCalendar.currentCalendar()
let calendarForDate = NSCalendar.currentCalendar()
let componentsForDate = calendar.components([.Day , .Month , .Year], fromDate: date)
let year = componentsForDate.year
let month = componentsForDate.month
let day = componentsForDate.day
//////////
//// Conditions after selecting the user (vibration, beep, Adan ) these conditions are testing the selected choice to send a notification to the user on his choice
//
if prayerCommingFromAdan.id == 0 && prayerCommingFromAdan.ringToneId != 0{
notificationId.id = 0
let hours = prayer0.time[0...1]
let minutes = prayer0.time[3...4]
let fajerTime = "\(month)-\(day)-\(year) \(hours):\(minutes)"
var dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "MM-dd-yyyy hh:mm"
dateFormatter.timeZone = NSTimeZone.localTimeZone()
// convert string into date
let dateValue = dateFormatter.dateFromString(fajerTime) as NSDate!
var dateComparisionResult:NSComparisonResult = NSDate().compare(dateValue)
if dateComparisionResult == NSComparisonResult.OrderedAscending
{
addNotificationAlarm(year, month: month, day: day, hour: prayer0.time[0...1], minutes: prayer0.time[3...4], soundId: prayerCommingFromAdan.ringToneId, notificationBody: "It is al fajr adan")
}
What should i do in DidreceivedLocalNotification in AppDelegate?
You can use this code for scheduling your daily notifications according to time.
func ScheduleMorning() {
let calendar: NSCalendar = NSCalendar(calendarIdentifier: NSCalendarIdentifierGregorian)!
var dateFire=NSDate()
var fireComponents = calendar.components([.Hour, .Minute, .Second], fromDate: dateFire)
if (fireComponents.hour >= 7) {
dateFire=dateFire.dateByAddingTimeInterval(86400) // Use tomorrow's date
fireComponents = calendar.components([.Hour, .Minute, .Second], fromDate: dateFire)
}
fireComponents.hour = 7
fireComponents.minute = 0
fireComponents.second = 0
// Here is the fire time you can change as per your requirement
dateFire = calendar.dateFromComponents(fireComponents)!
let localNotification = UILocalNotification()
localNotification.fireDate = dateFire // Pass your Date here
localNotification.alertBody = "Your Message here."
localNotification.userInfo = ["CustomField1": "w00t"]
localNotification.repeatInterval = NSCalendarUnit.Day
UIApplication.sharedApplication().scheduleLocalNotification(localNotification) }
for receiving
func application(application: UIApplication, didReceiveLocalNotification notification: UILocalNotification) {
// Do something serious in a real app.
print("Received Local Notification:")
print(notification.userInfo)
}

NSDateFormatter. Setting time elapsed since date in swift

I'm looking to call a function that checks time elapsed since date. This will determine how the timeLable displays in my messages view controller, similar to IMessage.
The code I'm using below only shows HH:MM
let date = dateFormatter().dateFromString((recent["date"] as? String)!)
timeLabel.text = NSDateFormatter.localizedStringFromDate(date!, dateStyle: NSDateFormatterStyle.NoStyle, timeStyle: NSDateFormatterStyle.NoStyle)
I'm looking to change it to something along the lines of:
If date is today, date = "HH:MM"
If date is Yesterday, date = "Yesterday"
If date is the day before yesterday and so on, date = "Monday, Tuesday, Wednesday..."
If date is over 1 week, date = MM/DD/YY
Or try this. Note that we have to use components:fromDate: and then use components:fromDateComponents:toDateComponents:options: because if we don't 23:59 last night returns 23:59 instead of Yesterday.
extension NSDateFormatter {
static func friendlyStringForDate(date:NSDate) -> String {
// Fetch the default calendar
let calendar = NSCalendar.currentCalendar()
// Compute components from target date
let from = calendar.components([.Day, .Month, .Year], fromDate: date)
// Compute components from current date
let to = calendar.components([.Day, .Month, .Year], fromDate: NSDate())
// Compute days difference between the two
let delta = calendar.components(.Day, fromDateComponents: from, toDateComponents: to, options: [])
switch delta.day {
case 0:
let formatter = NSDateFormatter()
formatter.timeZone = NSTimeZone.defaultTimeZone()
formatter.dateFormat = "HH:mm"
return formatter.stringFromDate(date)
case 1:
return "Yesterday"
case 2..<7:
let formatter = NSDateFormatter()
formatter.timeStyle = .NoStyle
formatter.dateFormat = "EEEE"
return formatter.stringFromDate(date)
default:
let formatter = NSDateFormatter()
formatter.timeStyle = .NoStyle
formatter.dateFormat = "MM/dd/YY"
return formatter.stringFromDate(date)
}
}
}
Now then, to use it just:
timeLabel.text = NSDateFormatter.friendlyStringForDate(date!)
SWIFT 3:
extension DateFormatter {
static func friendlyStringForDate(date: Date) -> String {
// Fetch the default calendar
let calendar = Calendar.current
let unitFlags: NSCalendar.Unit = [.day]
// Compute days difference between the two
let delta = (calendar as NSCalendar).components(unitFlags, from: date, to: Date(), options: [])
if let day = delta.day {
switch day {
case 0:
let formatter = DateFormatter()
formatter.timeZone = NSTimeZone.default
formatter.dateFormat = "hh:mm a"
return formatter.string(from: date)
case 1:
return "Yesterday"
case 2..<7:
let formatter = DateFormatter()
formatter.timeStyle = .none
formatter.dateFormat = "EEEE"
return formatter.string(from: date)
default:
let formatter = DateFormatter()
formatter.timeStyle = .none
formatter.dateFormat = "MM/dd/YY"
return formatter.string(from: date)
}
}
return ""
}
}