Creating UILocalNotifications with CoreData attribute - swift

In my iOS app I am trying to create a localNotification to notify the user 15 minutes prior to the event beginning. However I am stuck. I am using CoreData to store data. I have an Appointment object which can be created. A date attribute is associated with a Appointment object. I am really stuck with it. I do not know how to set up the timeInterval and the rest of the notification process.
I do not know how to set up the timeInterval from the time the Appointment is created to 15 minutes prior to when it begins.
Here is some of my code:
func scheduleNotifications() {
let content = UNMutableNotificationContent()
guard let client = client, let name = client.name, let formula = formula, let date = formula.date else { return }
content.title = "BookMe"
content.subtitle = ""
content.body = "Your appointment with \(name) will begin soon."
content.badge = 1
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: ??, repeats: false)
Edited: This is what I have but nothing is firing.
let date = formula.date
let fireDate = Calendar.current.date(byAdding: DateComponents(minute: -15), to: date as Date)
guard let timeInterval = fireDate?.timeIntervalSince(Date()) else { return }
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: timeInterval, repeats: false)
let request = UNNotificationRequest(identifier: self.timerUserNotificationIdentifier, content: content, trigger: trigger)
UNUserNotificationCenter.current().add(request, withCompletionHandler: nil)

As I understand it you are looking to find a time interval between right now and 15 minutes before some date so that you can fire a notification 15 minutes before that date.
Here's a quick example I knocked up in a playground
// Create a date in the future - this is what you get from your own data object and I'm creating it here just so I have a date.
let scheduledDate = Calendar.current.date(from: DateComponents(year: 2017, month: 09, day: 22, hour: 22))!
// Create a date 15 minutes earlier than your shcheduled date, this is when you want your notification to fire
let fireDate = Calendar.current.date(byAdding: DateComponents(minute: -15), to: scheduledDate)!
// Then just work out the time interval between the fire date and now to get the time interval
let timeInterval = fireDate.timeIntervalSince(Date())
Excuse the force unwrapping of the created dates, these are because it's an example, you should instead not use exclamation marks and handle errors gracefully.
edited to add
UNTimeIntervalNotificationTrigger, which you are trying to use requires a TimeInterval between now and the time you want to fire the notification. A TimeInterval is a Double that represents a number of seconds. In some cases, such as this one, it represents a delay, the number of seconds between now and the time you want to fire the the notification. In other cases it represents a date by the number of seconds from a fixed date. This fixed date is either timeIntervalSince1970 - "The interval between the date object and 00:00:00 UTC on 1 January 1970." Which is what you use for UNIX timestamps or timeIntervalSinceReferenceDate - "The interval between the date object and 00:00:00 UTC on 1 January 2001."
Whatever you do, resist the temptation to modify dates by adding or removing numbers of seconds directly, use DateComponents instead.

Related

Repeating local notification after a given date

I need to create different types of notifications. I managed to create a notification at a specific date, but now I need to create one that repeats daily, after a start date. It's basically a reminder to take a daily medication, but the user will only start taking that medication at a specific day.
Here is my func:
func addNotification(text: String, date: Date, id: String) {
let content = UNMutableNotificationContent()
content.title = "My App"
content.body = text
content.sound = .default
content.badge = NSNumber(integerLiteral: UIApplication.shared.applicationIconBadgeNumber + 1)
var dateComponents = DateComponents()
dateComponents.hour = Calendar.current.component(.hour, from: date)
dateComponents.minute = Calendar.current.component(.minute, from: date)
var trigger = UNCalendarNotificationTrigger(dateMatching: dateComponents, repeats: true)
let request = UNNotificationRequest(identifier: id, content: content, trigger: trigger)
UNUserNotificationCenter.current().add(request) { error in
if let error {
print(error.localizedDescription)
}
}
}
As it is, it will notify daily, but from the moment the func is fired. I need to receive a start date and only begin firing after that.
One thing that comes to mind is: if I also specify the year, month and day on "dayComponents" like this:
dateComponents.hour = Calendar.current.component(.hour, from: date)
dateComponents.minute = Calendar.current.component(.minute, from: date)
dateComponents.day = Calendar.current.component(.day, from: date)
dateComponents.month = Calendar.current.component(.month, from: date)
dateComponents.year = Calendar.current.component(.year, from: date)
and set the trigger to repeat, would it consider all the parameters (hour, minute, day, month, year) and never repeat (as this day will never happen again), or will it only consider hour and minute to repeat? I wanted to try that, but then I'd need to wait for a whole day, so maybe someone here knows the answer!

Repeat UserNotification every specific day of week for iOS 10

I'm working on local notification scheduling module for iOS 10 which repeats local notification for example every Sunday or every Monday..etc. Lets say i scheduled a notification for this date which is 2016-12-27 10:53:22 +0000 and using UNCalendarNotificationTrigger with repeat value equals true, the notification get triggered for ones in that date, and it doesn't repeat next week at the same time.
What could be the reason for that? and how is it possible to repeat every week for specific day in iOS 10.
Here is the code for creating local notification:
let content = UNMutableNotificationContent()
content.title = object.title
content.body = object.body
content.sound = UNNotificationSound.default()
let date = object.fireDate
let triggerDate = Calendar.current.dateComponents([.year,.month,.day,.hour,.minute,.second,], from: date as Date)
let trigger = UNCalendarNotificationTrigger(dateMatching: triggerDate,
repeats: true)
// Swift
let identifier = object.id
let request = UNNotificationRequest(identifier: identifier,
content: content, trigger: trigger)
localNotification.add(request, withCompletionHandler: { (error) in
if error != nil {
// Something went wrong
print(error!)
}else {
print("Reminder \(object.id) has been added successfully at \(object.fireDate)")
}
})
Update:
I have also discovered after the notification get fired at that date and to check that there is no more pending notification exist or to check if it has been rescheduled again or not. actually with repeat equals true, it has not been scheduled again for next week.
UNUserNotificationCenter.current().getPendingNotificationRequests(completionHandler: { (notficiations) in
for localNotification in notficiations {
print(localNotification)
}
})
And the result was:
<UNNotificationRequest: 0x174223ca0; identifier: A1, content: <UNNotificationContent: 0x1742e2980; title: My Title, subtitle: (null), body: My Body, categoryIdentifier: , launchImageName: , peopleIdentifiers: (
), threadIdentifier: , attachments: (
), badge: (null), sound: <UNNotificationSound: 0x1740b1820>, hasDefaultAction: YES, shouldAddToNotificationsList: YES, shouldAlwaysAlertWhileAppIsForeground: NO, shouldLockDevice: NO, shouldPauseMedia: NO, isSnoozeable: NO, fromSnooze: NO, darwinNotificationName: (null), darwinSnoozedNotificationName: (null), trigger: <UNCalendarNotificationTrigger: 0x174223cc0; dateComponents: <NSDateComponents: 0x17415e140>
Calendar Year: 2016
Month: 12
Day: 27
Hour: 14
Minute: 46
Second: 15, repeats: YES>>
I don't know if its actually a bug in iOS or not.
Triggering date format is not proper to repeat notification in a day of a week.
Your current trigger date components includes year,month,day, etc so this notification repeat in each year in that particular month and day.Change trigger date like mentioned below to repeat notification in a day of a week.
let triggerDate = Calendar.current.dateComponents([.weekday,.hour,.minute], from: date as Date)
Here is approach which should work:
func addNotificationForAlarm(alarm: MyAlarm) {
let myAlarmNotifContent = UNMutableNotificationContent()
myAlarmNotifContent.title = "Reminder"
myAlarmNotifContent.body = alarm.activity
myAlarmNotifContent.sound = UNNotificationSound.default()
if alarm.repeatDays.count == 1 {
} else {
for index in 1...alarm.repeatDays.count {
createNotif(date: alarm.date, weekDay: index, content: myAlarmNotifContent)
}
}
}
private func createNotif(date: Date, weekDay: Int, content: UNNotificationContent) {
var dateComponent = DateComponents()
dateComponent.weekday = weekDay
dateComponent.hour = Calendar.current.dateComponents([.hour], from: date).hashValue
dateComponent.minute = Calendar.current.dateComponents([.minute], from: date).hashValue
let myAlarmTrigger = UNCalendarNotificationTrigger(dateMatching: dateComponent, repeats: true)
setupNotificationSettings()
let identifier = "Your-Notification"
let request = UNNotificationRequest(identifier: identifier, content: content, trigger: myAlarmTrigger)
let center = UNUserNotificationCenter.current()
center.add(request, withCompletionHandler: { (error) in
if error != nil {
//TODO: Handle the error
}
})
}
Basically what I have found is that you can set a separate notification for each day you want the alarm to trigger. For example, you want every Monday and every Tuesday, so create a date component for each day and add it to the trigger. It is not perfect but is a solution that I think is better then calculating time intervals.

UNNotificationTrigger about fireDate

In iOS 10 I use new frameWork UNNotification .When setting UNNotificationTrigger I want to set a fire Date on UNNotification. But I can't find set a fire Date in UNNotificationTrigger's properties. So How can i set a fire Date in UNNotificationTrigger. I want set a fire Date in UNNotificationTrigger, how can i set
There are two types of triggers in User Notifications framework:
UNTimeIntervalNotificationTrigger - Used for creating a notification with a set interval time:
Fire in 30 minutes (60 seconds times 30) ,
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: (30*60), repeats: false)
UNCalendarNotificationTrigger- Used for creating a notification at a certain date as in your case and for repeating with specific criteria:
let date = DateComponents()
date.hour = 8
date.minute = 30
let trigger = UNCalendarNotificationTrigger(dateMatching: date, repeats: true)
The solution (Swift 3) for you would be to convert your date to a dateComponent and this can be done by:
import UserNotifications
var newComponents = Calendar.current.dateComponents([.year,.month,.day,.hour,.minute,.second,], from: date)
let trigger = UNCalendarNotificationTrigger(dateMatching: newComponents, repeats: false)

Constructing a date for getRequestedUpdateDateWithHandler:

I need to update my watchOS complication at midnight every day.
startOfDay is the beginning of the day (i.e., 12 AM today).
Should I add a day to the start of today like this?
func getNextRequestedUpdateDateWithHandler(handler: (NSDate?) -> Void) {
// Call the handler with the date when you would next like to be given the opportunity to update your complication content
let startOfDay = NSDate().startOfDay
let components = NSDateComponents()
components.day = 1
let startOfNextDay = NSCalendar.currentCalendar().dateByAddingComponents(components, toDate: startOfDay, options: NSCalendarOptions())
handler(startOfNextDay)
}
Or should I not add a day to the code, and just do something like this:
func getNextRequestedUpdateDateWithHandler(handler: (NSDate?) -> Void) {
// Call the handler with the date when you would next like to be given the opportunity to update your complication content
let startOfDay = NSDate().startOfDay
handler(startOfDay)
}
You'd want to advance the date one day, since you want your next requested update to occur at tomorrow's midnight. The first method would do what you want, but you can simplify it as follows:
let calendar = NSCalendar.currentCalendar()
let startOfDay = calendar.startOfDayForDate(NSDate())
let startOfNextDay = calendar.dateByAddingUnit(.Day, value: 1, toDate: startOfDay, options: NSCalendarOptions())!
The second code would return today's 12 AM, which would already be in the past.

Find difference in seconds between NSDates as integer using Swift

I'm writing a piece of code where I want to time how long a button was held down. To do that I recorded an NSDate() when the button was pressed, and tried using the timeIntervalSinceDate function when the button was released. That seems to work but I can't find any way to print the result or switch it to an integer.
var timeAtPress = NSDate()
#IBAction func pressed(sender: AnyObject) {
println("pressed")
timeAtPress = NSDate()
}
#IBAction func released(sender: AnyObject) {
println("released")
var elapsedTime = NSDate.timeIntervalSinceDate(timeAtPress)
duration = ???
}
I've seen a few similar questions, but I don't know C so I had a hard time understanding the answers given. If there is a more efficient way to find out how long the button was held down I'm open to suggestions.
Your attempt to calculate elapsedTime is incorrect. In Swift 3, it would be:
let elapsed = Date().timeIntervalSince(timeAtPress)
Note the () after the Date reference. The Date() instantiates a new date object, and then timeIntervalSince returns the time difference between that and timeAtPress. That will return a floating point value (technically, a TimeInterval).
If you want that as truncated to a Int value, you can just use:
let duration = Int(elapsed)
And, BTW, your definition of the timeAtPress variable doesn't need to instantiate a Date object. I presume you intended:
var timeAtPress: Date!
That defines the variable as a Date variable (an implicitly unwrapped one), but you'd presumably defer the actual instantiation of that variable until pressed is called.
Alternatively, I often use CFAbsoluteTimeGetCurrent(), e.g.,
var start: CFAbsoluteTime!
And when I want to set startTime, I do the following:
start = CFAbsoluteTimeGetCurrent()
And when I want to calculate the number of seconds elapsed, I do the following:
let elapsed = CFAbsoluteTimeGetCurrent() - start
It's worth noting that the CFAbsoluteTimeGetCurrent documentation warns us:
Repeated calls to this function do not guarantee monotonically increasing results. The system time may decrease due to synchronization with external time references or due to an explicit user change of the clock.
This means that if you're unfortunate enough to measure elapsed time when one of these adjustments take place, you can end up with incorrect elapsed time calculation. This is true for NSDate/Date calculations too. It's safest to use a mach_absolute_time based calculation (most easily done with CACurrentMediaTime):
let start = CACurrentMediaTime()
and
let elapsed = CACurrentMediaTime() - start
This uses mach_absolute_time, but avoids some of its complexities outlined in Technical Q&A QA1398.
Remember, though, that CACurrentMediaTime/mach_absolute_time will be reset when the device is rebooted. So, bottom line, if you need accurate elapsed time calculations while an app is running, use CACurrentMediaTime. But if you're going to save this start time in persistent storage which you might recall when the app is restarted at some future date, then you have to use Date or CFAbsoluteTimeGetCurrent, and just live with any inaccuracies that may entail.
Swift 5
let differenceInSeconds = Int(endDate.timeIntervalSince(startDate))
NSDate() and NSCalendar() sound like a good choice. Use calendar calculation and leave the actual math part to the framework. Here is a quick example of getting the seconds between two NSDate()
let startDate = NSDate()
let endDate = NSDate()
let calendar = NSCalendar.currentCalendar()
let dateComponents = calendar.components(NSCalendarUnit.CalendarUnitSecond, fromDate: startDate, toDate: endDate, options: nil)
let seconds = dateComponents.second
println("Seconds: \(seconds)")
According with the Freddy's answer, this is the function in swift 3:
let start = Date()
let end = Date(timeIntervalSince1970: 100)
let calendar = Calendar.current
let unitFlags = Set<Calendar.Component>([ .second])
let datecomponents = calendar.dateComponents(unitFlags, from: start, to: end)
let seconds = datecomponents.second
print(String(describing: seconds))
This is how you can get the difference in latest version of Swift 3
let calendar = NSCalendar.current
var compos:Set<Calendar.Component> = Set<Calendar.Component>()
compos.insert(.second)
compos.insert(.minute)
let difference = calendar.dateComponents(compos, from: fromDate, to: toDate)
print("diff in minute=\(difference.minute!)") // difference in minute
print("diff in seconds=\(difference.second!)") // difference in seconds
Reference: Getting the difference between two NSDates in (months/days/hours/minutes/seconds)
Swift 5
let startDate = Date()
let endDate = Date()
let calendar = Calendar.current
let dateComponents = calendar.compare(startDate, to: endDate, toGranularity: .second)
let seconds = dateComponents.rawValue
print("Seconds: \(seconds)")
For Swift 3
Seconds between 2 time in "hh:mm"
func secondsIn(_ str: String)->Int{
var strArr = str.characters.split{$0 == ":"}.map(String.init)
var sec = Int(strArr[0])! * 3600
var sec1 = Int(strArr[1])! * 36
print("sec")
print(sec+sec1)
return sec+sec1
}
Usage
var sec1 = secondsIn(shuttleTime)
var sec2 = secondsIn(dateToString(Date()))
print(sec1-sec2)