Example (I try to create something like this.
Now u can see, here you may choose time and interval)
I created reminder, where a user can choose how often to receive notifications (every 2 days, every 3 days etc.), but I also want the user to be able to choose the time. I know how to create reminder which will send notifications at the selected time, but I have to use UNCalendarNotificationTrigger, and problem is that I use UNTimeIntervalNotificationTrigger. Thank you
#IBOutlet weak var picker: UIPickerView!
#IBOutlet weak var timePicker: UIDatePicker!
#IBAction func setReminderBtnTapped(_ sender: Any) {
let content = UNMutableNotificationContent()
content.title = "Test"
content.body = "Blablabla"
content.sound = .default
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: TimeInterval((picker.selectedRow(inComponent: 1)+1)*(24*3600)), repeats: true)
let request = UNNotificationRequest(identifier: "blabla.reminder", content: content, trigger: trigger)
UNUserNotificationCenter.current().add(request) { (error) in
if let error = error {
print("error")
}
}
print("Notification added")
}
try this code:
private func setLocalNotification(hour: Int, minute: Int, completionHandler: #escaping (Bool) -> Void) {
let center = UNUserNotificationCenter.current()
center.requestAuthorization(options: [.alert, .badge, .sound]) { [weak self] (granted, error) in
guard let self = self else {
return
}
let center = UNUserNotificationCenter.current()
center.removeAllPendingNotificationRequests()
guard error == nil else {
completionHandler(false)
return
}
if granted {
let content = UNMutableNotificationContent()
content.title = "title"
content.sound = UNNotificationSound.default
content.badge = 1
var dateComponents = DateComponents()
dateComponents.hour = hour
dateComponents.minute = minute
let trigger = UNCalendarNotificationTrigger(dateMatching: dateComponents, repeats: true)
let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: trigger)
center.add(request)
completionHandler(true)
return
}
completionHandler(false)
}
}
and you can call by get date from datePicker like this:
let date = self.timepPicker.date
let calendar = Calendar.current
let hour = calendar.component(.hour, from: date) // get hour
let minutes = calendar.component(.minute, from: date) // get minute
self.setLocalNotification(hour: hour, minute: minutes) { [weak self] isSet in
guard let self = self else {
return
}
if !isSet {
let center = UNUserNotificationCenter.current()
center.removeAllPendingNotificationRequests() // remove previus notification
}
}
update answer for
repeat X time in every n day:
create extension from date for create simple date getDateComponents:
extension Date {
public func getDateComponents() -> DateComponents {
let dateComponents = Calendar.current.dateComponents([.hour, .minute, .second, .day], from: self)
return dateComponents
}
}
and update setLocalNotification function:
private func setLocalNotification(repeatCount: Int, jumpNextDay: Int, hour: Int, minute: Int) {
let center = UNUserNotificationCenter.current()
center.requestAuthorization(options: [.alert, .badge, .sound]) { (granted, error) in
let center = UNUserNotificationCenter.current()
center.removeAllPendingNotificationRequests()
guard error == nil else {
return
}
if granted {
let content = UNMutableNotificationContent()
content.title = "title"
content.sound = UNNotificationSound.default
content.badge = 1
// create date from hour and minute based on current
let firstDate = Calendar.current.date(bySettingHour: hour, minute: minute, second: 0, of: Date())!
let firstTimeInterval = firstDate.timeIntervalSince1970 // get timeInterval
// example run for
// repeat count 5 and jump 2
// 2 4 6 8 10
for i in stride(from: jumpNextDay, to: jumpNextDay * (repeatCount + 1), by: jumpNextDay) {
// create next day
// 86400 next time interval added to first time interval
// create time interval from first date
// 2 next day = firstTimeInterval + 2 * 86400
let timeIntervalForTrigger = firstTimeInterval + Double((i * 86400))
// create date from time interval
let date = Date.init(timeIntervalSince1970: timeIntervalForTrigger)
// create datecomponnet
let dateComponents = date.getDateComponents()
let trigger = UNCalendarNotificationTrigger(dateMatching: dateComponents, repeats: false)
let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: trigger)
center.add(request)
}
}
}
}
and call:
let date = self.timepPicker.date
let calendar = Calendar.current
let hour = calendar.component(.hour, from: date) // get hour
let minutes = calendar.component(.minute, from: date) // get minute
self.setLocalNotification(repeatCount: 2, jumpNextDay: 2, hour: hour, minute: minutes)
Related
How can I change up my code to allow my notification to trigger every 12 hours since the last API call. In the code, you can see where I commented out the variable containing the last API call (lastRefreshDate)
// let lastRefreshDate = userDefaults.object(forKey: Constants.Defaults.LATEST_REQUEST_TIME) as? Date
var dateComponents = DateComponents(calendar: Calendar.current, hour: 12, minute: 0)
if let date = UserDefaults.getDate() {
dateComponents = Calendar.current.dateComponents([.hour, .minute], from: date)
}
let trigger = UNCalendarNotificationTrigger(dateMatching: dateComponents, repeats: true)
let request = UNNotificationRequest(identifier: "reminder", content: content, trigger: trigger)
center.add(request) { (error) in
if error != nil {
print(error!)
}
}
I would like to schedule a local notification from iOS14 widget.
How can I do it?
I tried implementing the following:
let center = UNUserNotificationCenter.current()
center.delegate = self
let content = UNMutableNotificationContent()
content.title = "Some Title"
content.body = "Some Body"
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 5, repeats: true)
let request = UNNotificationRequest(identifier: UUID().uuidString,
content: content, trigger: trigger)
center.add(request) { (error) in
if error != nil {
// Handle any errors.
}
I did with this.
func createDate(day: Int, month : Int, hour: Int, minute: Int, year: Int)->Date{
var components = DateComponents()
components.hour = hour
components.minute = minute
components.year = year
components.day = day
components.month = month
components.timeZone = .current
let calendar = Calendar(identifier: .gregorian)
return calendar.date(from: components)!
}
// 📢///CreateNitification
func scheduleNotification(at date: Date, identifierUnic : String, body: String, titles:String) {
let triggerWeekly = Calendar.current.dateComponents([.day, .month, .hour,.minute, .year], from: date)
let trigger = UNCalendarNotificationTrigger(dateMatching: triggerWeekly, repeats: true)
let content = UNMutableNotificationContent()
content.title = titles
content.body = body
content.sound = UNNotificationSound.default
content.categoryIdentifier = "todoList2"
let request = UNNotificationRequest(identifier: identifierUnic, content: content, trigger: trigger)
// UNUserNotificationCenter.current().delegate = self
/// UNUserNotificationCenter.current().removeDeliveredNotifications(withIdentifiers: ["textNotification2"])
/// UNUserNotificationCenter.current().removeAllPendingNotificationRequests()
UNUserNotificationCenter.current().add(request) {(error) in
if let error = error {
print(" We had an error: \(error)")
}}
}
// Call
func callNotifications(){
UNUserNotificationCenter.current().removeAllPendingNotificationRequests()
let adte = createDate(day: 3, month: 6, hour: 16, minute: 51, year: 2021)
scheduleNotification(at: adte, identifierUnic: UUID().uuidString, body: "Widgets on Work", titles: "check 123")
}
func getTimeline(in context: Context, completion: #escaping (Timeline<Entry>) -> ()) {
callNotifications()
var entries: [SimpleEntry] = []
// Generate a timeline consisting of five entries an hour apart, starting from the current date.
let currentDate = Date()
for hourOffset in 0 ..< 5 {
let entryDate = Calendar.current.date(byAdding: .hour, value: hourOffset, to: currentDate)!
let entry = SimpleEntry(date: entryDate)
entries.append(entry)
}
let timeline = Timeline(entries: entries, policy: .atEnd)
completion(timeline)
}
Notification is working fine
but one thing is pending, how can I call UNUserNotificationCenter.current().delegate = self?
Thanks
I am trying to set two different times for Local Notifications - the default one being 10 am every day (if the user does not touch the date picker to pick their own notification time AKA it is nil), and one based on the user's input. The user input one does work and sends notifications every day, but if no time is chosen the default time does not work.
This is my code, can someone tell me what I am doing wrong? I basically want to check that if the user input is NIL it should revert to the default time set.
In settings:
IBOutlet var userDatePicker: UIDatePicker!
IBAction func pickerValueChanged(_ sender: UIDatePicker) {
var selectedTime = Date()
selectedTime = sender.date
// convert to data type DateComponents
let convertedSelectedTime = calendar.dateComponents([.hour, .minute,], from: selectedTime)
let delegate = UIApplication.shared.delegate as? AppDelegate
delegate?.sendNotification(with: convertedSelectedTime)
UserDefaults.standard.set(selectedTime, forKey: "SavedTime")
}
In AppDelegate:
func sendNotification(with userSelectedTime: DateComponents?) {
let center = UNUserNotificationCenter.current()
let content = UNMutableNotificationContent()
content.title = "Testing"
content.body = "testing"
var defaultTime = DateComponents()
defaultTime.hour = 10
defaultTime.minute = 00`// trying to set default time to 10 am if user never picks a time`
let components = userSelectedTime ?? defaultTime//option between the two
let trigger = UNCalendarNotificationTrigger(dateMatching: components, repeats: true)
let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: trigger)
center.add(request)
}
Try this. All of its fields are optional. So you can use those you need.
DateComponents(calendar:timeZone:era:year:month:day:hour:minute:second:nanosecond:weekday:weekdayOrdinal:quarter:weekOfMonth:weekOfYear:yearForWeekOfYear:)
let triggerDaily = DateComponents(calendar: Calendar.current,
timeZone: Calendar.current.timeZone,
hour: 10, // Use 22 if PM
minute: 00,
second: 00,
nanosecond: 00)
let trigger = UNCalendarNotificationTrigger(dateMatching: triggerDaily, repeats: false)
More info
DateComponents - Apple
Update
In pickerValueChanged, you are schedulling notification every time DatePicker value is changed. That is bad. There you should only store date.
func pickerValueChanged(_ sender: UIDatePicker) {
UserDefaults.standard.set(sender.date, forKey: "SavedTime")
}
sendNotification should only send notification.
func sendNotification(with dateComponents: DateComponents) {
let center = UNUserNotificationCenter.current()
let content = UNMutableNotificationContent()
content.title = "Testing"
content.body = "testing"
let trigger = UNCalendarNotificationTrigger(dateMatching: dateComponents, repeats: true)
let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: trigger)
center.add(request)
}
UIApplication has alife method that is called when an App goes to background. You can schedule notifications there.
func applicationDidEnterBackground(_ application: UIApplication) {
let date = UserDefaults.standard.value(forKey: "SavedTime") as? Date
if let date = date {
let convertedSelectedTime = Calendar.current.dateComponents([.hour, .minute,], from: date)
sendNotification(with: convertedSelectedTime)
} else {
let dateComponent = DateComponents(calendar: Calendar.current,
timeZone: Calendar.current.timeZone,
hour: 10, // Use 22 if PM
minute: 00,
second: 00,
nanosecond: 00)
sendNotification(with: dateComponent)
}
}
Use SavedTime if it exists in UserDefaaults. Else use 10:00.
If you use SceneDelegate, use sceneDidEnterBackground instead of applicationDidEnterBackground
func sceneDidEnterBackground(_ scene: UIScene) {
}
They both are life-cycle methods.
More info.
Managing Your App's Life Cycle
UIApplication
I am trying to make countdown (NSTimer).
for example:- count down timer 2d:21h:50m:20s
I used some code:-
override func viewDidLoad() {
super.viewDidLoad()
self.timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(self.UpdateTime), userInfo: nil, repeats: true)
}
#objc func UpdateTime() {
let userCalendar = Calendar.current
// Set Current Date
let date = Date()
let components = userCalendar.dateComponents([.hour, .minute, .month, .year, .day, .second], from: date)
let currentDate = userCalendar.date(from: components)!
// Set Event Date
var eventDateComponents = DateComponents()
eventDateComponents.year = 2020
eventDateComponents.month = 07
eventDateComponents.day = 2
eventDateComponents.hour = 0
eventDateComponents.minute = 0
eventDateComponents.second = 0
eventDateComponents.timeZone = TimeZone(abbreviation: "GMT")
// Convert eventDateComponents to the user's calendar
let eventDate = userCalendar.date(from: eventDateComponents)!
// Change the seconds to days, hours, minutes and seconds
let timeLeft = userCalendar.dateComponents([.day, .hour, .minute, .second], from: currentDate, to: eventDate)
// Display Countdown
timerLabel.text = "\(timeLeft.day!)d \(timeLeft.hour!)h \(timeLeft.minute!)m \ (timeLeft.second!)s"
// Show diffrent text when the event has passed
}
My API Response:-
func callForTimerApi(){
let url = "\(ApiLink.HOST_URL)/wp/v2/getDeadline"
Alamofire.request(url, method: .get, parameters: nil, encoding: URLEncoding.default, headers: nil).responseJSON { response in
switch response.result {
case .success:
if let value = response.result.value {
print(value)
let dict = value as! [String:Any]
let userData = dict.validatedValue("data", expected:[[String:Any]]() as AnyObject) as! [[String:Any]]
for item in userData {
let length = item.validatedValue("length", expected: String() as AnyObject) as! String
print(length)
self.counters = Int(length)!
}
}
case .failure(let error):
print(error)
presentAlert("", msgStr: "Could not connect to the server.", controller: self)
}
}
}
I got it 48 length in hours from API,and my coutdown timer starts 2d:24h:45m:20s and my starts timer is over than show countdown timer is 0d:0h:0m:0s
Before I begin please don't burn me as I know this has been asked hundreds of times on here with no reliable answer but I believe there's a solution using background refresh. https://medisafe.com/ app seems to have solved it!
The goal :
To trigger a local notification at a specified time every x days
My solution
step 1: get timer interval from start date and odd occurrence (this case 2) days from (edited)
step 2: set interval timer on this difference with a repeat
step 3: activate background refresh ( if the app is even terminated it will load the app in the background and give me a small window to perform some tasks)
step 4. set background refresh to trigger once a day
step 5: perform get items api which will refresh all timers and notifications
step 6 sit back and smile with amazement at my solution
but this fails.
so a timer interval
let newTrigger = UNTimeIntervalNotificationTrigger(timeInterval: 172800,repeats: true)
but this will just reset the timer every day when the background fetch is performed and it will trigger 2 days from NOW and not from the start date.
So there must be a way of comparing dates hours and minutes (start date, x date, and current date to work out the timer interval value.
currently im using calendar components. to trigger everyday im doing the following
var triggerType : DateComponents? {
var triggerT : DateComponents?
var cal = Calendar(identifier: .gregorian)
cal.firstWeekday = 2
if let notificationModel = self.notificationModel {
switch notificationModel.reminderType {
case .daily?, .weekly?:
if let date = notificationModel.date {
triggerT = cal.dateComponents([.weekday, .hour, .minute], from:date)
if let weekday = notificationModel.weekday {
triggerT?.weekday = weekday
}
}
case .alternateDays?:
if let date = notificationModel.date {
triggerT = cal.dateComponents([ .hour, .minute], from:date)
// THIS IS WHERE I NEED HELP
}
case .monthly?:
if let date = notificationModel.date {
triggerT = cal.dateComponents([.day,.hour,.minute], from: date)
}
case .yearly?:
triggerT = Calendar.current.dateComponents([.month,.day,.hour,.minute], from: (notificationModel.date)!)
case .oneOff?:
triggerT = Calendar.current.dateComponents([.year,.month,.day,.hour,.minute], from: (notificationModel.date)!)
case .none:
DispatchQueue.main.async {
if let category = self.notificationModel?.category, let title = self.notificationModel?.title {
Toast.down("An error was discovered in \(category). Please change the occurance value for the following \(title)")
}
}
}
} else {
print("NOTIFICATION MODEL IS CORRUPT")
}
return triggerT
}
func add(notification: NotificationModel){
let content = UNMutableNotificationContent()
if let title = notification.title,
let body = notification.body,
let identifier = notification.identifier {
content.title = title
content.body = body
content.sound = UNNotificationSound.default()
content.categoryIdentifier = (notification.category?.rawValue)!
content.setValue("YES", forKeyPath: "shouldAlwaysAlertWhileAppIsForeground")
var trigger : UNCalendarNotificationTrigger?
if let triggerType = self.triggerType {
if let occurance = notification.occurance {
if occurance > 0 {
}
}
trigger = UNCalendarNotificationTrigger(dateMatching: triggerType, repeats: true)
} else {
return
}
let interval = Date().timeIntervalSince1970
let identifierString = "2\(interval)"
var request : UNNotificationRequest!
if notification.reminderType == .alternateDays {
print("ADDING TIMER NOTIFICATION")
print("REMINDER TIME = \(notification.date)")
// 172800 = two days
let newTrigger = UNTimeIntervalNotificationTrigger(timeInterval: 172800,
repeats: true)
request = UNNotificationRequest(identifier: identifierString,
content: content, trigger: newTrigger)
} else {
request = UNNotificationRequest(identifier: identifierString,
content: content, trigger: trigger)
}
center.add(request, withCompletionHandler: { (error) in
if let error = error {
// Something went wrong
print(error.localizedDescription)
} else
{
print("ADDING NOTIDCIATION \(content.title)")
}
})
//SNOOZE OR DELETE NOTIFICATIONS
let snoozeAction = UNNotificationAction(identifier: "Snooze", title: "Snooze", options: [])
let deleteAction = UNNotificationAction(identifier: "UYLDeleteAction",title: "Delete", options: [.destructive])
//Create a category with the actions: This requires another unique identifier (you probably want to define these magic strings in an enum):
let category = UNNotificationCategory(identifier: notification.category!.rawValue,
actions: [snoozeAction,deleteAction],
intentIdentifiers: [], options: [])
//Register the category with the notification center. It is recommended to do this early in the app lifecycle.
center.setNotificationCategories([category])
//To include this action in our notifications we need to set the category in the notification content:
} else {
print("Failed to add notification")
}
}
however, I want every other day and dont want to use the 64 notification limit.
thanks for your time
Thomas
Lets say you want to trigger notification 2, 4 and 6 days from now, here is how you can do it:
For my example I added extension to Date
extension Date {
func adding(days: Int) -> Date? {
var dateComponents = DateComponents()
dateComponents.day = days
return NSCalendar.current.date(byAdding: dateComponents, to: self)
}
}
Then you could just create new notifications for dates specified, in this example 2, 4, 6 days from now
let date = Date()
for i in [2, 4, 6] {
if let date = date.adding(days: i) {
scheduleNotification(withDate: date)
}
}
func scheduleNotification(withDate date: Date) {
let notificationContent = UNMutableNotificationContent()
notificationContent.title = "Title"
notificationContent.subtitle = "Subtitle"
notificationContent.body = "Body"
let identifier = "Make up identifiers here"
let dateComponents = Calendar.autoupdatingCurrent.dateComponents([.day, .month, .year, .hour, .minute, .second], from: date)
let trigger = UNCalendarNotificationTrigger(dateMatching: dateComponents, repeats: false)
let notificationReques = UNNotificationRequest(identifier: identifier, content: notificationContent, trigger: trigger)
UNUserNotificationCenter.current().add(notificationReques) { error in
if let e = error {
print("Error \(e.localizedDescription)")
}
}
}
This should schedule 3 notifications - 2, 4, 6 days from now...
So thanks for the directions on here this is the final solution i came up with. Ensure you turn on background modes in app capabilities so the current week is updated. i did mine to every day.
Then the code with comments.
//: Playground - noun: a place where people can play
import UIKit
import UserNotifications
Lets create some helper clases to make it easier to work with dates
// HELPERS
extension Date {
public var weekday: Int {
return Calendar.current.component(.weekday, from: self)
}
public var hour: Int {
get {
return Calendar.current.component(.hour, from: self)
}
set {
let allowedRange = Calendar.current.range(of: .hour, in: .day, for: self)!
guard allowedRange.contains(newValue) else { return }
let currentHour = Calendar.current.component(.hour, from: self)
let hoursToAdd = newValue - currentHour
if let date = Calendar.current.date(byAdding: .hour, value: hoursToAdd, to: self) {
self = date
}
}
}
public var minute: Int {
get {
return Calendar.current.component(.minute, from: self)
}
set {
let allowedRange = Calendar.current.range(of: .minute, in: .hour, for: self)!
guard allowedRange.contains(newValue) else { return }
let currentMinutes = Calendar.current.component(.minute, from: self)
let minutesToAdd = newValue - currentMinutes
if let date = Calendar.current.date(byAdding: .minute, value: minutesToAdd, to: self) {
self = date
}
}
}
}
Then we create our custom notification struct
struct CustomNotification {
static func everyOtherDay(wtihStartDate startDate: Date) -> [Int]? {
//
let currentDate = Date()
// get initial week day from start date to compare dates
let weekDay = startDate.weekday
// Then we need to get week of years for both dates
let cal = Calendar.current
guard let weekA = cal.dateComponents([.weekOfYear], from: startDate).weekOfYear else { return nil}
guard let weekB = cal.dateComponents([.weekOfYear], from: currentDate).weekOfYear else {return nil}
// create two arrays for week days
let weekOne = [1,3,5,7]
let weekTwo = [2,4,6]
// then we create a module to check if we are in week one or week two
let currentWeek = (weekA - weekB) % 2
if currentWeek == 0 {
//week 1
return weekOne.contains(weekDay) ? weekOne : weekTwo
} else {
// week 2
return weekOne.contains(weekDay) ? weekTwo : weekOne
}
}
}
finally in our class where we create the notification. I personally use a notification manager. but to shwo you quickly
class AClass : NSObject {
func setupNotifications() {
let startDate = Date()
let weekDays = CustomNotification.everyOtherDay(wtihStartDate: startDate)
let cal = Calendar.current
let center = UNUserNotificationCenter.current()
if let weekDays = weekDays {
for day in weekDays {
let identifier = "Some Random ID"
let content = UNMutableNotificationContent()
content.title = "title"
content.body = "body"
content.sound = UNNotificationSound.default()
content.categoryIdentifier = "SOME CATEGORY"
content.setValue("YES", forKeyPath: "shouldAlwaysAlertWhileAppIsForeground")
var components = cal.dateComponents([.hour, .minute], from:startDate)
components.weekday = day
let trigger = UNCalendarNotificationTrigger(dateMatching: components, repeats: true)
let request = UNNotificationRequest(identifier: identifier,
content: content, trigger: trigger)
center.add(request, withCompletionHandler: { (error) in
if let error = error {
// Something went wrong
print("ERROR ADDING NOTIFICATION TO CENTER \(error.localizedDescription)")
} else
{
print("ADDING NOTIFCIATION \(content.categoryIdentifier)")
}
})
}
}
}
}
Then we need to setup background fetch in our app and app delegate
// OVER IN APP DELEGATE
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// setup background refresh ensuring you turn it on in app capabilities
// trigger back ground refrsh once a day
UIApplication.shared.setMinimumBackgroundFetchInterval(86400)
return true
}
func application(_ application: UIApplication, performFetchWithCompletionHandler completionHandler: #escaping (UIBackgroundFetchResult) -> Void) {
// FETCH DATA and REFRESH NOTIFICATIONS
// We need to do this to ensure the current week value is updated to either 1 or 0
// You will need to delete all notifications with same same category first else your going to be getting both weeks notifications
let aClass = AClass()
aClass.setupNotifications()
}
Hope this helps somebody :D Thomas