I am working on a local notification for an app that should only fire on a specific day and repeat on that day until the user decided to remove it. An example notification is on that fires every Wednesday at 11:00am.
The function - scheduleLocalNotification is triggered when the user presses done on the Time picker.
var sundayNotification : UILocalNotification!
var mondayNotification : UILocalNotification!
var tuesdayNotification : UILocalNotification!
var wednesdayNotification : UILocalNotification!
var thursdayNotification : UILocalNotification!
var fridayNotification : UILocalNotification!
var saturdayNotification : UILocalNotification!
func scheduleLocalNotification() {
//Check the Dayname and match fireDate
if dayName == "Sunday" {
sundayNotification = UILocalNotification()
//timeSelected comes from the timePicker i.e. timeSelected = timePicker.date
sundayNotification.fireDate = fixNotificationDate(timeSelected)
sundayNotification.alertTitle = "Sunday's Workout"
sundayNotification.alertBody = "This is the message from Sunday's workout"
sundayNotification.alertAction = "Snooze"
sundayNotification.category = "workoutReminderID"
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
appDelegate.saveContext()
UIApplication.sharedApplication().scheduleLocalNotification(sundayNotification)
} else if dayName == "Monday" {
mondayNotification = UILocalNotification()
mondayNotification.fireDate = fixNotificationDate(timeSelected)
mondayNotification.alertTitle = "Monday's Workout"
mondayNotification.alertBody = "This is the message from Monday's workout"
mondayNotification.alertAction = "Snooze"
mondayNotification.category = "workoutReminderID"
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
appDelegate.saveContext()
UIApplication.sharedApplication().scheduleLocalNotification(mondayNotification)
} else if ..... }
}
func fixNotificationDate(dateToFix: NSDate) -> NSDate {
let dateComponets: NSDateComponents = NSCalendar.currentCalendar().components([NSCalendarUnit.Hour, NSCalendarUnit.Minute], fromDate: dateToFix)
dateComponets.second = 0
if dayName == "Sunday" {
dateComponets.weekday = 1 //n = 7
} else if dayName == "Monday" {
dateComponets.weekday = 2
} else if dayName == "Tuesday" {
dateComponets.weekday = 3
} else if dayName == "Wednesday" {
dateComponets.weekday = 4
} else if dayName == "Thursday" {
dateComponets.weekday = 5
} else if dayName == "Friday" {
dateComponets.weekday = 6
} else if dayName == "Saturday" {
dateComponets.weekday = 7
}
let fixedDate: NSDate! = NSCalendar.currentCalendar().dateFromComponents(dateComponets)
return fixedDate
}
After doing a check of the day to assign the notification the right UILocalNotification, this still strangely does not work e.g. setting different times on a different days (Sunday at 10:10am and Monday at 10:15am) for the notification does not change anything. The notification still fires on a day I did not select, i.e. Today at both those times instead of those days.
Any clues on what I could be doing wrong or missing?
Thanks in advance. :)
After some work, I came up with this. We start by changing the date to the first fire date the user selects.
func setNotificationDay(today: NSDate, selectedDay: Int) -> NSDate {
let daysToAdd: Int!
let calendar = NSCalendar.currentCalendar()
let weekday = calendar.component(.Weekday, fromDate: today)
if weekday > selectedDay {
daysToAdd = (7 - weekday) + selectedDay
} else {
daysToAdd = (selectedDay - weekday)
}
let newDate = calendar.dateByAddingUnit(.Weekday, value: daysToAdd, toDate: today, options: [])
return newDate! //if you don't have a date it'll crash
}
You can just use NSDate() instead of requiring it as a parameter. I'll leave that up to you.
Now, we need to set the notification. I didn't do anything with your fixNotificationDate(), but you can add it in easily. The key point here is to set notification.repeatInterval = .Weekday or it won't repeat. I also added a dictionary to set the day name. It's better than repeating the code as much as you did.
It does create a second call to NSCalendar, but you can probably find a way to avoid doing that in your code.
func scheduleLocalNotification(date: NSDate) {
let dayDic = [1:"Sunday",
2:"Monday",
3:"Tuesday",
4:"Wednesday",
5:"Thursday",
6:"Friday",
7:"Saturday"]
let calendar = NSCalendar.currentCalendar()
let weekday = calendar.component(.Weekday, fromDate: date)
let notification = UILocalNotification()
notification.fireDate = date
notification.repeatInterval = .Weekday
notification.alertTitle = String(format: "%#'s Workout", dayDic[weekday]!)
notification.alertBody = String(format: "This is the message from %#'s workout", dayDic[weekday]!)
notification.alertAction = "Snooze"
notification.category = "workoutReminderID"
notification.soundName = UILocalNotificationDefaultSoundName
UIApplication.sharedApplication().scheduleLocalNotification(notification)
}
And that should solve your problem. I didn't test it because it would take at least a week! ;)
Hope that helps!
Related
I'm making a day streak counter using UserDefaults and Core Data.
The idea is that a number will be added unto by 1 every separate day an action is performed-- this will be the streak number.
If this action wasn't performed for 24 hours, the number would reset to zero.
I have a function to set the end of the streak:
// set date time to the end of the day so the user has 24hrs to add to the streak
func changeDateTime(userDate: NSDate) -> NSDate {
let dateComponents = NSDateComponents()
let currentCalendar = NSCalendar.current
let year = Int(currentCalendar.component(NSCalendar.Unit.Year, fromDate:
userDate))
let month = Int(currentCalendar.component(NSCalendar.Unit.Month, fromDate:
userDate))
let day = Int(currentCalendar.component(NSCalendar.Unit.Day, fromDate: userDate))
dateComponents.year = year
dateComponents.month = month
dateComponents.day = day
dateComponents.hour = 23
dateComponents.minute = 59
dateComponents.second = 59
guard let returnDate = currentCalendar.dateFromComponents(dateComponents) else {
return userDate
}
return returnDate
}
It is returning the following Errors:
'NSDate' is not implicitly convertible to 'Date'; did you mean to use
'as' to explicitly convert?
Cannot convert value of type 'NSCalendar.Unit' to expected argument
type 'Calendar.Component'
When using the suggested corrections I only get more errors with no suggested corrections. I'm having trouble figuring out the proper way to express this
The full Code is:
let userDefaults = UserDefaults.standard
var moc: NSManagedObjectContext!
var lastStreakEndDate: NSDate!
var streakTotal: Int!
override func viewDidLoad() {
super.viewDidLoad()
// checks for object if nil creates one (used for first run)
if userDefaults.object(forKey: "lastStreakEndDate") == nil {
userDefaults.set(NSDate(), forKey: "lastStreakEndDate")
}
lastStreakEndDate = (userDefaults.object(forKey: "lastStreakEndDate") as! NSDate)
streakTotal = calculateStreak(lastDate: lastStreakEndDate)
}
// fetches dates since last streak
func fetchLatestDates(moc: NSManagedObjectContext, lastDate: NSDate) -> [NSDate] {
var dates = [NSDate]()
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "streakCount")
let datePredicate = NSPredicate(format: "date < %#", lastDate)
fetchRequest.predicate = datePredicate
do {
let result = try moc.fetch(fetchRequest)
let allDates = result as! [NSDate]
if allDates.count > 0 {
for date in allDates {
dates.append(date)
}
}
} catch {
fatalError()
}
return dates
}
// set date time to the end of the day so the user has 24hrs to add to the streak
func changeDateTime(userDate: NSDate) -> NSDate {
let dateComponents = NSDateComponents()
let currentCalendar = NSCalendar.current
let year = Int(currentCalendar.component(NSCalendar.Unit.Year, fromDate: userDate))
let month = Int(currentCalendar.component(NSCalendar.Unit.Month, fromDate: userDate))
let day = Int(currentCalendar.component(NSCalendar.Unit.Day, fromDate: userDate))
dateComponents.year = year
dateComponents.month = month
dateComponents.day = day
dateComponents.hour = 23
dateComponents.minute = 59
dateComponents.second = 59
guard let returnDate = currentCalendar.dateFromComponents(dateComponents) else {
return userDate
}
return returnDate
}
// adds a day to the date
func addDay(today: NSDate) -> NSDate {
let tomorrow = NSCalendar.currentCalendar.dateByAddingUnit(.Day, value: 1, toDate: today, options: NSCalendar.Options(rawValue: 0))
return tomorrow!
}
// this method returns the total of the streak and sets the ending date of the last streak
func calculateStreak(lastDate: NSDate) -> Int {
let dateList = fetchLatestDates(moc: moc, lastDate: lastDate)
let compareDate = changeDateTime(userDate: lastDate)
var streakDateList = [NSDate]()
var tomorrow = addDay(today: compareDate)
for date in dateList {
changeDateTime(userDate: date)
if date == tomorrow {
streakDateList.append(date)
}
tomorrow = addDay(today: tomorrow)
}
userDefaults.set(streakDateList.last, forKey: "lastStreakEndDate")
return streakDateList.count
}
Any Help is Appreciated
You need
// set date time to the end of the day so the user has 24hrs to add to the streak
func changeDateTime(userDate: Date) -> Date {
var dateComponents = DateComponents()
let currentCalendar = Calendar.current
let year = Int(currentCalendar.component(.year, from:
userDate))
let month = Int(currentCalendar.component(.month, from:
userDate))
let day = Int(currentCalendar.component(.day, from: userDate))
dateComponents.year = year
dateComponents.month = month
dateComponents.day = day
dateComponents.hour = 23
dateComponents.minute = 59
dateComponents.second = 59
guard let returnDate = currentCalendar.date(from:dateComponents) else {
return userDate
}
return returnDate
}
OR shortly
// set date time to the end of the day so the user has 24hrs to add to the streak
func changeDateTime(userDate: Date) -> Date {
var dateComponents = DateComponents()
let currentCalendar = Calendar.current
let res = currentCalendar.dateComponents([.year,.month,.day],from:userDate)
dateComponents.year = res.year
dateComponents.month = res.month
dateComponents.day = res.day
dateComponents.hour = 23
dateComponents.minute = 59
dateComponents.second = 59
guard let returnDate = currentCalendar.date(from:dateComponents) else {
return userDate
}
return returnDate
}
what i am to do with the button is once it is pressed it will disable for the rest of the day. now my code does disable it once it is pressed but if the user leaves the application and comes back to it, the button will be enabled again. is there a way to use NSUserDefaults?
let save = UserDefaults.standard
let calendar = Calendar.current
let now = Date()
this is in the viewDidLoad:
let seven_today = calendar.date(
bySettingHour: 7,
minute: 0,
second: 0,
of: now)!
let two_thirty_today = calendar.date(
bySettingHour: 14,
minute: 30,
second: 0,
of: now)!
if now >= seven_today && now <= two_thirty_today
{
getPointsOutlet.isEnabled = true
}
else
{
getPointsOutlet.isEnabled = false
}
this is the function of pressing the button:
Total_Points += 12
pointsLabel.text = "Total Points: \(Total_Points)"
getPointsOutlet.isEnabled = false
Try this:
if now >= seven_today && now <= two_thirty_today
{
let savedDayNum = defaults.integer(forKey: "dayClickNum")
let date = Date()
let calendar = Calendar.current
let nowDayNum = calendar.component(.day, from: date)
if(savedDayNum == nowDayNum)
{
getPointsOutlet.isEnabled = false
}
else
{
getPointsOutlet.isEnabled = true
}
}
else
{
getPointsOutlet.isEnabled = false
}
// in function click do this
Total_Points += 12
pointsLabel.text = "Total Points: \(Total_Points)"
getPointsOutlet.isEnabled = false
let date = Date()
let calendar = Calendar.current
let dayOfClickDate = calendar.component(.day, from: date)
defaults.set(dayOfClickDate, forKey: "dayClickNum")
Also for a perfect solution you may take care of month , as app may not be launched again by user for a month that may disable the button if the stored num day is coincidence d with open day of another month
I did a lot of searching through Stackoverflow but I havent found answer for my problem.
I am developing an app and I get JSON data for some events. What I get is the start time of the event and the duration of the event. All data in recived as String.
In one screen of the app I would like to show only the event that are currently going on.
for example:
Class Event {
var startTime: String?
var duration: String?
}
let event1 = Event()
event1.starTime = "12-12-2016, 10:50 AM"
event1.duration = "50min"
let event2 = Event()
event2.starTime = "12-12-2016, 09:50 AM"
event2.duration = "40min"
let event3 = Event()
event3.starTime = "12-12-2016, 10:10 AM"
event3.duration = "90min"
let allEvents = [event1, event2, event3]
and let say the the current date and time is 12-12-2016, 11:00AM. How can I filter/find events in allEvents that are still going on if we compare them to the current date?
Thank you in advance.
EDIT: My solution
I created method for converting dateString and durationString to startDate: Date and endDate: Date
static func convertDateStringAndDurationStringToStartAndEndDate(date: String, duration: String) -> (start: Date, end: Date)? {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
guard let startDate = dateFormatter.date(from: date) else { return nil }
guard let duration = Int(duration) else { return nil }
// recived interval is in minutes, time interval must be calculated in seconds
let timeInterval = TimeInterval(Int(duration) * 60 )
let endDate = Date(timeInterval: timeInterval, since: startDate)
return (startDate, endDate)
}
For filtering I have created separated method. In my case I am using Realm database, but you will get the point.
static func filterResultsForNowPlaying(results: Results<Show>?) -> Results<Show>? {
let currentDate = NSDate()
let datePredicate = NSPredicate(format: "startDate <= %# AND %# <= endDate", currentDate, currentDate)
let filteredShows = results?.filter(datePredicate)
return filteredShows
}
You will need to convert them into dates, using DateFormatter, and then use a .filter over the array and have it match on if the current date is in range.
If you have the ability to change the Event class, you can greatly simplify your code if you replace your Event class with the DateInterval class, which does the same thing:
let minutes = 60;
let formatter = DateFormatter()
formatter.dateFormat = "MM-dd-yyyy"
let event1 = DateInterval(
start: formatter.date(from: "12-12-2016")!,
duration: TimeInterval(20 * minutes)
)
let now = Date()
if (event1.contains(now)) {
print("Event 1 is still active")
}
I'm trying have my app pop viewControllers when it reaches 9:00 UTC time every day. I don't want it to use local time as that can change in different regions, and can be altered. I thought of using a server time, but I'm having issues getting that solution to work. I got an epoch timestamp and converted it to a Date.
let ref = FIRDatabase.database().reference()
let timestamp = FIRServerValue.timestamp()
ref.setValue(timestamp)
ref.observe(.value, with: {
snap in
if let t = snap.value as? TimeInterval {
// Cast the value to an NSTimeInterval
// and divide by 1000 to get seconds.
print("this is the time in seconds \(t)")
let date = Date(timeIntervalSince1970: t)
print("this is the time \(Date(timeIntervalSince1970: t/1000))")
It prints out this is the time 2016-10-10 18:40:21 +0000.
The problem is figuring out how to get only the hour minutes and seconds out of this so I can compare the time dates.
One of options might be:
To request a current time from the server on the start of the app.
Calculate a difference between 9:00 UTC and the current time.
Set a timer (NSTimer) which fires when the difference has passed.
And finally, handle the callback in any way you like: pop screens, show a popup, etc.
You could use something like this:
let calendar = Calendar.current
let components = NSDateComponents()
components.hour = 9
let nineOClock = calendar.nextDate(after: Date(), matching: components as DateComponents, matchingPolicy: .strict)
let timer = Timer(fireAt: nineOClock!, interval: 0, target: self, selector: #selector(doSomething), userInfo: nil, repeats: true)
RunLoop.main.add(timer, forMode: RunLoopMode.commonModes)
func doSomething(){
print("Doing something")
}
A method which calculates time (in seconds) until the next 9 am based on the current time (seconds from 1970).
func untilNineAmSeconds(now: Int) -> Int {
let todaySeconds = now % 86400
let hourSeconds = 3600
let nineAmSeconds = 9 * hourSeconds
let daySeconds = 24 * hourSeconds
if todaySeconds < nineAmSeconds {
return nineAmSeconds - todaySeconds
} else {
return (daySeconds - todaySeconds) + nineAmSeconds
}
}
Now you (1) request a current time from Firebase, (2) get a time interval before 9 am, and (3) schedule a timer.
let ref = FIRDatabase.database().reference()
let timestamp = FIRServerValue.timestamp()
ref.setValue(timestamp)
ref.observe(.value, with: { snap in
guard let ts = snap.value as? TimeInterval else {
return
}
let seconds = untilNineAmSeconds(now: Int(ts))
let timer = Timer.scheduledTimer(withTimeInterval: TimeInterval(seconds), repeats: false) { _ in
// TODO: pop view controllers
}
// ...
}
Thanks to Artem and his help, I was directed on the right path. After a lot of time spent researching date conversion, I figured this out. The final goal is to convert the two values into Integers so I can compare them in seconds and use them in a timer to figure out how much time is remaining.
ref.observe(.value, with: {
snap in
if let t = snap.value as? TimeInterval {
let FinalTimeInterval = self.convertStringToDateToIntStartTime() - self.convertEpochTimeStampToDateToStringToDateToIntEndTime(timeInterval: t)
if FinalTimeInterval < 0 {
print("less than zero")
} else {
let timer = Timer.scheduledTimer(withTimeInterval: TimeInterval(FinalTimeInterval), repeats: false) { _ in
// TODO: pop view controllers
print("i'm so happy this is working")
}
RunLoop.current.add(timer, forMode: RunLoopMode.commonModes)
}
}
})
func convertEpochTimeStampToDateToStringToDateToIntEndTime(timeInterval: TimeInterval) -> Int {
let date = Date(timeIntervalSince1970: timeInterval/1000)
var dateFormater = DateFormatter()
dateFormater.timeZone = TimeZone(identifier: "UTC")
dateFormater.dateFormat = "HH:mm:ss"
let dateString = dateFormater.string(from: date)
let endTime = dateFormater.date(from: dateString)
dateFormater.dateFormat = "HH"
let endHours = Int(dateFormater.string(from: endTime!))
dateFormater.dateFormat = "mm"
let endMinutes = Int(dateFormater.string(from: endTime!))
dateFormater.dateFormat = "ss"
let endSeconds = Int(dateFormater.string(from: endTime!))
return endSeconds! + endMinutes! * 60 + endHours! * 3600
}
func convertStringToDateToIntStartTime() -> Int {
var dateFormater = DateFormatter()
dateFormater.timeZone = TimeZone(identifier: "UTC")
dateFormater.dateFormat = "HH:mm:ss"
let startTime = dateFormater.date(from: "09:00:00")
dateFormater.dateFormat = "HH"
let startHours = Int(dateFormater.string(from: startTime!))
dateFormater.dateFormat = "mm"
let startMinutes = Int(dateFormater.string(from: startTime!))
dateFormater.dateFormat = "ss"
let startSeconds = Int(dateFormater.string(from: startTime!))
return startSeconds! + startMinutes! * 60 + startHours! * 3600
}
I am trying to schedule the UILocalNotifications.
The functionality i want to implement is that to repeat the notification on a perticular day, like (every monday) which will be selected by the user.
Is there any way to schedule the UILocalNotification so the notification will be trigger on that day only.
let notification = UILocalNotification()
let dict:NSDictionary = ["ID" : "your ID goes here"]
notification.userInfo = dict as! [String : String]
notification.alertBody = "\(title)"
notification.alertAction = "Open"
notification.fireDate = dateToFire
notification.repeatInterval = .Day // Can be used to repeat the notification
notification.soundName = UILocalNotificationDefaultSoundName
UIApplication.sharedApplication().scheduleLocalNotification(notification)
I don't sure this will help you, but I have worked with this before. I create 7 buttons to select 7 days in a week. This is my old code:
class RemindNotification {
var timerNotification = NSTimer()
func notification(story: Story) {
let calendar = NSCalendar(calendarIdentifier: NSCalendarIdentifierGregorian)
let date = NSDate()
let dateComponents = calendar!.components([NSCalendarUnit.Day, NSCalendarUnit.WeekOfMonth, NSCalendarUnit.Month,NSCalendarUnit.Year,NSCalendarUnit.Hour,NSCalendarUnit.Minute], fromDate:date)
dateComponents.hour = story.remindeAtHour
dateComponents.minute = story.remindeAtMinute
for index in 0..<story.remindeAtDaysOfWeek.count {
if story.remindeAtDaysOfWeek[index] == true {
if index != 6{
dateComponents.weekday = index + 2
fireNotification(calendar!, dateComponents: dateComponents)
} else {
dateComponents.weekday = 1
fireNotification(calendar!, dateComponents: dateComponents)
}
} else {
dateComponents.weekday = 0
}
}
}
func fireNotification(calendar: NSCalendar, dateComponents: NSDateComponents) {
let notification = UILocalNotification()
notification.alertAction = "Title"
notification.alertBody = "It's time to take a photo"
notification.repeatInterval = NSCalendarUnit.WeekOfYear
notification.fireDate = calendar.dateFromComponents(dateComponents)
UIApplication.sharedApplication().scheduleLocalNotification(notification)
}
}
This is for my App, I can choose the day, and then it can fire LocalNotification at the specific time that I set in each day.
You can refer to it. Hope this help.
Make it a weekly by doing:
notification.repeatInterval = .Weekday // Or maybe .WeekOfYear
Whatever day of the week dateToFire is, the notification will be repeated once a week on that day of the week.