How do you send notifications based on user input? SWIFT - swift

How do you set the notification time to match the input of the user?
If a notification reminds the user of new content (i.e the word of the day), is that considered a local notification or a push notification?
The userSelectedTime variable is created when they press done, but I don't know how to pass it on to my sendNotification function which uses DateComponents() to set the time.
#objc func donePressed() {
// formatter
let formatter = DateFormatter()
formatter.dateStyle = .none
formatter.timeStyle = .short
var userSelectedTime = timeTextField.text
userSelectedTime = formatter.string(from: timePicker.date)
print(userSelectedTime)
self.view.endEditing(true)
}
func sendNotification() {
// Default time 10:30, but base it on userSelectedTime if not void
var dateComponents = DateComponents()
dateComponents.hour = 10
dateComponents.minute = 30
let trigger = UNCalendarNotificationTrigger(dateMatching: dateComponents, repeats: true)
let request = UNNotificationRequest(identifier: UUID().uuidString, content: createNotificationContent(for: myData), trigger: trigger)
center.add(request)
}

First I will answer your question 2. - This mostly depends on where that information is coming from. If the "word of the day" is something you have stored internally inside the app, you can schedule a local notification with that information. However, if you want to change the word of the day externally every day, you will have to create a push notification to achieve this. (At least the only way to make it work properly). Push notifications would require some sort of service to push those to the app so the easiest for you is to go with the local notifications.
To summarize question 2:
Local notification: Something that is created within the app.
Push notification: Information 'pushed' from an external source.
Question 1:
You can do this in a number of ways. What I would recommend from looking at what you already have is to create the date components with the selected time and pass those components to your 'sendNotification()' function.
As an example:
// Capture the time and turn them into components
func donePressed() {
let calendar = Calendar.current
let components = calendar.dateComponents([.hour, .minute, .month, .year, .day], from: timePicker.date)
sendNotification(components: components)
}
// Schedule the notificaiton based on components
func sendNotification(components: DateComponents) {
let trigger = UNCalendarNotificationTrigger(dateMatching: components, repeats: true)
let request = UNNotificationRequest(identifier: UUID().uuidString, content: createNotificationContent(for: myData), trigger: trigger)
center.add(request)
}
Now... I can't see all your code, so I assume that what else you got going is working properly. But I hope you get the concept. I would however recommend creating a separate class like "NotificationScheduler" to handle these things, that way you will be able to separate things a bit more.

Related

How to add UNCalendarNotificationTrigger alarm daily from next day to everyday on LocalNotificationCenter in Swift

I have a question on Local Notification in Swift.
I want to add daily alarm from next day to everyday but can't write it.
let component: DateComponents = {
return Calendar.current.dateComponents([.hour, .minute], from: dateComponent)
}()
let trigger = UNCalendarNotificationTrigger(dateMatching: component, repeats: true)

ios - waiting 24hrs to send a notification

Our application sends a notification every morning at 8am to greet the user and remind him to complete a task. Here is the script we are using:
let center = UNUserNotificationCenter.current()
center.removePendingNotificationRequests(withIdentifiers: ["dailyReminder"])
let content = UNMutableNotificationContent()
content.title = "Good morning"
content.body = notificationMessages[randomIndex]
content.sound = .default
var dateComponents = DateComponents()
dateComponents.hour = 8
let trigger = UNCalendarNotificationTrigger(dateMatching: dateComponents, repeats: true)
let request = UNNotificationRequest(identifier: "dailyReminder", content: content, trigger: trigger)
center.add(request)
This is called after the user finishes the day's task so that they are prompted the next day. However, some people finish the task before 8am. In those cases we'd like for the notification to be sent at 8am of the next day.
How could I calculate if the script runs before or after 8 and add 24 hrs accordingly?
Since this is run locally you could just save the last time the function was sent by holding it in a UserDefault. For example, add the following to the code to save when the user has last done a task:
UserDefaults.standard.set("/(youWillWriteAFunctionForDate)", forKey: "checkTaskOfDate")
Then you can simply check if it was sent today, and plan accordingly:
defaults.string(forKey: "checkTaskOfDate") ?? "Never Done"

Having collectionViewCell.label.text Change Every Second with Timer

I am currently learning to code swift apps and am trying to do some projects on my own. My current personal challenge is to do a countdown timer app. The concept is simple: the user enters a date, from a UIDatePicker, and the app shows the time remaining until his list of various events (uses user default values to keep the events in memory). All the said events are shown in a collection view (see below for screen shots).
I ran into something too complicated for my actual skillset and I thought you guys probably would have the answer, or at least a few suggestions! Here is my issue: I'd like for the time remaining between today and the event to decrease every second, and to be shown through a label inside a collectionViewCell. I found a very simple way to do so, with a timer, but implementing the said solution with a collectionViewCell is giving me quite the headache.
Here are the code excerpts I'd like to share, and hopefully it's enough:
#objc func UpdateTimeLabel(index: Int) -> String {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss Z"
dateFormatter.timeZone = userCalendar.timeZone
let date = dateFormatter.date(from: UserDefaults.standard.array(forKey: "dates")![index] as! String)!
let timeLeft = userCalendar.dateComponents([.day, .hour, .minute, .second], from: currentDate, to: date)
return "\(timeLeft.day!) days \(timeLeft.hour!) hours \(timeLeft.minute!) minutes \(timeLeft.second!) seconds"
}
That's my function I'd like to fire every second. It is currently made in such a way that its property, called index, is the indexPath.row of the collectionViewCell. It references to an array of event dates stored as UserDefaults. There's as many cells in the collectionView as there is events in the array.
See below the way I implemented it inside the collectionView forItemAt function:
func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
// Configure the state of the cell based on the propertires of the card if reprensents
let cardCell = cell as? EventCollectionViewCell
// UI Configuration
cardCell?.eventTitleLabel.text = ((UserDefaults.standard.array(forKey: "events")![indexPath.row]) as! String)
cardCell?.eventDateLabel.text = ("Event on: " + "\((UserDefaults.standard.array(forKey: "dates")![indexPath.row]) as! String)")
cardCell?.countdownLabel.text = UpdateTimeLabel(index: indexPath.row)
cardCell?.eventView.layer.cornerRadius = 10
}
The actual result is that every cell shows the time remaining between today and the event. That's already more than I thought I could do by myself!!!
Results of actual code
Where I think one of you can probably step-in is by helping me answering this question: Where should a timer, looking something like the code below, be placed in order for the cardCell.countdownLabel.text to be updated every second?
timer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(UpdateTimeLabel), userInfo: nil, repeats: true)
I tried every ways I can think of and am now at a road block. I still have many more things to fix in my project, so I'm not completely stopped yet. If you need more code lines and/or precisions, let me know and I'll provide you with anything you deem helpful.
Thank you so much for taking the time to review my question,
Louis G
Add this method UpdateTimeLabel method into the EventCollectionViewCell and modify method like this
#objc func UpdateTimeLabel() {
let userCalendar = Calendar(identifier: .indian)
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss Z"
dateFormatter.timeZone = userCalendar.timeZone
let date = dateFormatter.date(from: UserDefaults.standard.array(forKey: "dates")![index] as! String)!
let timeLeft = userCalendar.dateComponents([.day, .hour, .minute, .second], from: Date(), to: date)
countdownLabel.text = "\(timeLeft.day!) days \(timeLeft.hour!) hours \(timeLeft.minute!) minutes \(timeLeft.second!) seconds"
}
After doing this add a property index in EventCollectionViewCell and on didSet of index fire timer in EventCollectionViewCell eg:
var index: Int {
didSet {
let timer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(UpdateTimeLabel), userInfo: nil, repeats: true)
timer.fire()
}
}
Replace
cardCell?.countdownLabel.text = UpdateTimeLabel(index: indexPath.row)
with
cardCell?.index = indexPath.row
I found why the solution #Satyen suggested didn't work! Ok I know, it's been almost a month since his last answer, but I have to say I got pretty discouraged when I saw it didn't fix my app and decided to work on other projects, hoping someone else would jump in and offer another solution in the meanwhile. Anyways, I decided this morning to find the issue through a few sessions, and I'm proud to report I did!
Ok so here's what went wrong:
I was using a variable called "current date" as today's. This very variable was declared outside the UpdateTimeLabel function, and transformed in dateComponents right of the bath. Result: the current date wasn't refreshed every second, so every time the event's date was compared to currentDate, the result stayed the same.
Here is the working code:
#objc func UpdateTimeLabel() {
let userCalendar = Calendar.current
let todayDate = Date()
let components = userCalendar.dateComponents([.year, .month, .day, .hour, .minute, .second], from: todayDate)
let currentDate = userCalendar.date(from: components)!
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss Z"
dateFormatter.timeZone = userCalendar.timeZone
let date = dateFormatter.date(from: UserDefaults.standard.array(forKey: "dates")![index] as! String)!
let timeLeft = userCalendar.dateComponents([.day, .hour, .minute, .second], from: currentDate, to: date)
countdownLabel.text = "\(timeLeft.day!) days \(timeLeft.hour!) hours \(timeLeft.minute!) minutes \(timeLeft.second!) seconds"
if currentDate >= date {
countdownLabel.text = "Cheers! This event has occured already."
print("An event expired.")
}
}
Fired by this var index, as suggested by #Satyen:
var index: Int = 0 {
didSet {
timer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(UpdateTimeLabel), userInfo: nil, repeats: true)
timer.fire()
print("timer fired")
}
}
Everything is now working like expected! I still have a few efficiency tweaks to make my code simpler, but overall it is now displaying the remaining time and decreasing it every second! So excited!!
Thank you to #Satyen once again for his precious input!

Is it possible to mutate the content of a local notification in swift?

In the next version of my app, I want to integrate the possibility that the user can enable weekly reports like the screentime feature from apple.
I know how to setup Local Notifications (The code is already working for other types of notifications like reminders for events), but for this idea, I need the notification text to be updated shortly before firing because I don`t know the exact content of the notification when I add it to the NotificationCenter.
Is there a function which is called shortly before firing or how would I do this?
This is my working code for scheduling notifications:
fileprivate func generateNotification(title: String, body: String, triggerDate: DateComponents) -> String{
//Trigger
let trigger = UNCalendarNotificationTrigger(dateMatching: triggerDate, repeats: false)
//Content
let content = UNMutableNotificationContent()
content.title = title
content.body = body
content.sound = UNNotificationSound.default
//Request
let identifier = UUID().uuidString
let request = UNNotificationRequest(identifier: identifier, content: content, trigger: trigger)
let notificationCenter = UNUserNotificationCenter.current()
notificationCenter.add(request) { (error) in
if let error = error {
print("Error \(error.localizedDescription)")
}
}
return identifier
}

Change Repeating Reminder's Content Daily

In my app, I ask the user to set up a repeating reminder. In this reminder, I would like the body to say something different each day. For example, I have over 500 quotes in my Firebase db and I want my reminder to show a new quote each day. How can I programmatically change the body of the reminder each day without user interaction?
#IBAction func saveButtonPressed(_ sender: Any) {
let content = UNMutableNotificationContent()
let identifier = "myApp"
content.title = "myApp"
content.body = "I want to change this programatically each day"
let trigger = UNCalendarNotificationTrigger(dateMatching: Calendar.current.dateComponents([.year, .month, .day, .hour, .minute], from: reminderTime.date), repeats: true)
let request = UNNotificationRequest(identifier: identifier, content: content, trigger: trigger)
UNUserNotificationCenter.current().add(request){
(error) in
if error != nil
{
print("here error in setting up notification")
print(error!)
}
else
{
print("notification scheduled")
}
}
}
Unfortunately, you can not handle the LocalNotification showing moment. You just tell the system when to show your notification and the system show it without calling your application.
But you can add multiple notification requests at once (up to 64). For example, all notifications for the next month and update your notifications on app launch. Just make sure that your notification requests has different identifiers.
For example, you create 30 notifications and user receives them for 5 days. Then he opens your app and you add 5 notifications more.
If you'll need to remove some of the pending notifications, you can make it using the following method.
let center = UNUserNotificationCenter.current()
center.removePendingNotificationRequests(withIdentifiers: identifiers)