Setting Timer Countdown From When Object was Created - swift

I'm querying the createdAt date from several objects in Parse. I want to create a 24 hour (or 48/72, etc.) countDown timer that counts down from when the object was created to 24 hours later. (I'm also then formatting it, and displaying it on a cell UILabel.)
Example: If an object was created at 19:34:33, I want it to expire 24 hours after that (or how ever many hours i specify after it was created). Ultimately showing on the UILabel the hours left until the object expires.
Currently, I'm retrieving when it was created making it repetitively count down from when it was created.
However, I want to make the logic so that it takes when the object was created and then shows how many hours are left until the 24 hours or 48 hours, 72 hours, etc are up.
EDIT
Thanks to #pulsar I added a few more lines of code to the description below. The problem now is that I can only retrieve and correctly countDown the createdAt date and time for 1 object, because only the first object is queried, making all the other objects have the same expiration countDown timer in their respective indexPath.row as the first object in Parse.
I can't figure out how to add all the objects so that they all have their own respective countDown expiration times that is triggered by the expiresAt function.
Here's how i'm querying it and formatting it (in the viewDidLoad):
This is the question I asked that help me format the dates: Swift countDown timer Logic
Please see the comments in the code!
var createdAt = object.createdAt
if createdAt != nil {
//assuming this is where i have to set expiration logic?
let calendar = NSCalendar.currentCalendar()
let comps = calendar.components([.Hour, .Minute, .Second], fromDate: createdAt as NSDate!)
let hours = comps.hour * 3600
let minutes = comps.minute * 60
let seconds = comps.second
//I'm adding these two lines below but not sure what to do with them considering I need to add all the objects to an array that will then display it on indexPath.row(s)
let twentyFourHours = NSTimeInterval(60 * 60 * 24)
self.expiresAt = NSDate(timeInterval: twentyFourHours, sinceDate: object.createdAt!!)
self.timerInt.append(hours + minutes + seconds)
//What do i append to the timerInt array? How can i append the objects while triggering the the expiresAt function?
}
Here's my countDown function:
func countDown() {
//timerInt is an array where I'm storing the Int values.
for i in 0 ..< timerInt.count {
let hours = timerInt[i] / 3600
//have to somehow add the expiresAt method while looping through each value [i]...?
let minsec = timerInt[i] % 3600
let minutes = minsec / 60
let seconds = minsec % 60
print(String(format: "%02d:%02d:%02d", hours, minutes, seconds))
timerInt[i]--
//Im assuming best practice would be to loop through the values in order to change the values/set the expiration time to each object (the expiresAt method). Any ideas of how and where I can add this in this loop so that it reflects the countDown I want to set?
}
self.tableView.reloadData()
}
Lastly, for my indexPath.row, I am formatting it and displaying it like this:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let myCell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! cell
//I'm formatting the hours, minutes, seconds. However I'm missing the expiresAt function and I have no clue as to where and how to include it... Should it be here or in the countDown() loop?
let hours = timerInt[indexPath.row] / 3600
let minsec = timerInt[indexPath.row] % 3600
let minutes = minsec / 60
let seconds = minsec % 60
myCell.secondLabel.text = String(format: "%02d:%02d:%02d", hours, minutes, seconds)
return myCell
}
Ideas on how to set it to countdown 24/48/72 hours later from when it was created?
Any and all help is much appreciated!

Sounds like what you need is to set the expiry date and then get the date components between the current date and the expiry date. Then you can use an NSTimer to refresh the display. (Don't forget to call NSTimer.invalidate() when you're done).
An example:
class YourViewController: UIViewController {
var expiresAt: NSDate?
func viewDidLoad() {
// your parse logic here
let twentyFourHours = NSTimeInterval(60 * 60 * 24)
expiresAt = NSDate(timeInterval: twentyFourHours, sinceDate: createdAt)
scheduleTimer()
}
func scheduleTimer() {
NSTimer.scheduledTimerWithTimeInterval(1.0 / 30.0, target: self, selector: "tick:", userInfo: nil, repeats: true)
}
#objc
func tick(timer: NSTimer) {
guard let expiresAt = expiresAt else {
return
}
let calendar = NSCalendar(calendarIdentifier: NSCalendarIdentifierGregorian)
if let components = calendar?.components([.Hour, .Minute, .Second], fromDate: NSDate(), toDate: expiresAt, options: []) {
print(formatDateComponents(components))
}
}
func formatDateComponents(components: NSDateComponents) -> String {
let hours = components.hour
let minutes = components.minute
let seconds = components.second
return "\(hours):\(minutes):\(seconds)"
}
}
You could also make your life much easier by using a structure to store the date components rather than doing that complicated parsing of your timerInt strings.
struct Time {
let hours: String
let minutes: String
let seconds: String
}

//: Playground - noun: a place where people can play
import XCPlayground
XCPlaygroundPage.currentPage.needsIndefiniteExecution = true
import UIKit
class MyView: UIView {
weak var l1: UILabel?
weak var l2: UILabel?
weak var l3: UILabel?
let validFor: NSTimeInterval
var validTo: NSDate = NSDate()
lazy var timer: NSTimer = NSTimer(timeInterval: self.validFor, target: self, selector: "done", userInfo: nil, repeats: false)
init(validFor: NSTimeInterval) {
self.validFor = validFor
super.init(frame: CGRect(x: 0, y: 0, width: 500, height: 100))
validTo = timer.fireDate
let ll1 = UILabel(frame: CGRect(x: 1, y: 1, width: 498, height: 30))
ll1.text = "created at: \(NSDate())"
self.addSubview(ll1)
l1 = ll1
let ll2 = UILabel(frame: CGRect(x: 1, y: 33, width: 498, height: 30))
ll2.text = "valid to: \(validTo)"
self.addSubview(ll2)
l2 = ll2
let ll3 = UILabel(frame: CGRect(x: 1, y: 66, width: 498, height: 30))
ll3.text = "valid for next: \(validTo.timeIntervalSinceNow) second"
self.addSubview(ll3)
l3 = ll3
NSRunLoop.currentRunLoop().addTimer(timer, forMode: NSRunLoopCommonModes)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// when MyView expires it trigers this function
// and give me a chance to update UI
func done() {
// update UI
dispatch_async(dispatch_get_main_queue(), { [unowned self] () -> Void in
self.l2?.text = " EXPIRED"
self.l3?.text = ""
if let text1 = self.l1?.text,
let text2 = self.l2?.text,
let text3 = self.l3?.text
{
print("")
print(text1, text2, text3)
}
})
}
func updateState() {
// periodically updating UI on request
if timer.valid {
let v = validTo.timeIntervalSinceNow
// update UI
dispatch_async(dispatch_get_main_queue(), { [unowned self] () -> Void in
self.l3?.text = "valid for next: \(v) second"
if let text1 = self.l1?.text,
let text2 = self.l2?.text,
let text3 = self.l3?.text
{
print(text1, text2, text3)
}
})
}
}
}
let c = MyView(validFor: 10.0) // in seconds
let queue = dispatch_queue_create("update", DISPATCH_QUEUE_SERIAL)
// periodic action tu update UI
// in regular intervals
// this is just for demonstration, avoid using use sleep
// in real application
dispatch_async(queue) { () -> Void in
repeat {
c.updateState()
sleep(3)
} while true
}
dispatch_async(dispatch_get_main_queue(), { () -> Void in
print("the app is running and responding on user actions")
print("MyView labels are updating 'automatically'\n")
})
print("playground continue ....\n")

Related

reset date to next hour after top of hour is reached

I Want My swift code to count down to the nearest top of the hour. So if the time is 146 the user code should count down 14 minutes. Right now My code below counts down to a spefic day and time. I just want it to count down to the nearest hour when the app is running.
import UIKit
class ViewController: UIViewController {
#IBOutlet var timerLabel: UILabel!
var timer: Timer!
override func viewDidLoad() {
super.viewDidLoad()
timer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(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], from: date)
let currentDate = userCalendar.date(from: components)!
// Set Event Date
var eventDateComponents = DateComponents()
eventDateComponents.year = 2021
eventDateComponents.month = 01
eventDateComponents.day = 01
eventDateComponents.hour = 01
eventDateComponents.minute = 00
eventDateComponents.timeZone = TimeZone(abbreviation: "GMT")
let eventDate = userCalendar.date(from: eventDateComponents)!
let timeLeft = userCalendar.dateComponents([.day, .hour, .minute, ], from: currentDate, to: eventDate)
timerLabel.text = "\(timeLeft.day!)d \(timeLeft.hour!)h \(timeLeft.minute!)m "
endEvent(currentdate: currentDate, eventdate: eventDate)
}
func endEvent(currentdate: Date, eventdate: Date) {
if currentdate >= eventdate {
timerLabel.text = "Happy New Year!"
// Stop Timer
timer.invalidate()
}
}
}
edit/update:
My goal in my swift code is when the top of the hour is reached. After trying to implement #Leo's answer it prints "Top of Hour" and it does the problem is that It only does it one time. As long as the app is open I want it to print "Top of Hour" at every hour. So I need to reset the end date which is what I tried to do at
let date = Date()
end = date.nextHour
That does not let the code compile. So I have to reset the end var to the next hour.
No need to update the user interface 10 times per second. As it is it will drain the device's battery much faster than needed while it should only run once a minute. You can change your timer timeInterval to 1 second and schedule it to fire at the next even second. To get the next even hour and the next even minute you can use Calendar method
func nextDate(after date: Date, matching components: DateComponents, matchingPolicy: Calendar.MatchingPolicy, repeatedTimePolicy: Calendar.RepeatedTimePolicy = .first, direction: Calendar.SearchDirection = .forward) -> Date?
Just create two computed properties extending Date and pass zero for minute or nanosecond components:
extension Date {
var nextHour: Date {
Calendar.current.nextDate(after: self, matching: DateComponents(minute: 0), matchingPolicy: .strict)!
}
var nextSecond: Date {
Calendar.current.nextDate(after: self, matching: DateComponents(nanosecond: 0), matchingPolicy: .strict)!
}
var minute: Int {
Calendar.current.component(.minute, from: self)
}
}
Now add a property to your view controller to keep a reference of the end date. Note that there is no need to declare your timer as optional:
var end: Date?
var timer = Timer()
And create a DateComponentsFormatter to create a localized description of the remaining time:
extension Formatter {
static let minutesRemaining: DateComponentsFormatter = {
let formatter = DateComponentsFormatter()
formatter.formattingContext = .standalone
formatter.unitsStyle = .short
formatter.allowedUnits = [.minute, .second]
formatter.includesTimeRemainingPhrase = true
return formatter
}()
}
Now you just setup the end date and to schedule your timer to fire at the next even minute:
override func viewDidLoad() {
super.viewDidLoad()
// get the current date
let date = Date()
// set the end date
end = date.nextHour
// schedule the timer to fire at the next even second and set its interval to 1 second
timer = .init(fireAt: date.nextSecond, interval: 1, target: self, selector: #selector(updateUI), userInfo: nil, repeats: true)
RunLoop.main.add(timer, forMode: .common)
updateUI()
}
#objc func updateUI() {
if Date().minute == 0 || Date() > end {
end = Date().nextHour
timerLabel.text = "beginning of hour"
print("beginning of hour")
} else {
// update the remaining time (for a perfect sync we need to subtract a second from the current time)
let text = Formatter.minutesRemaining.string(from: Date().addingTimeInterval(-1), to: end) ?? ""
timerLabel.text = text
print(text)
}
}

swift how to convert date component in a double to get upload speed

I try to check the speed of upload by sending an image to a server via FTP, I know it is not very sharp, but I have no alternative. Issue number one is to test time, this code is always giving me 0 seconds maybe it is right maybe not, but the main issue id that I cannot even divide the size in mb of image by time in seconds, since time elapsed is expressed in dateComponent, how to do it?
using this code
func pushfileUpload() {
print("uploading...")
let startDate = NSDate()
//*****************************************************************
//find file in app bundle
let imageChecked = findFileInBundle(nameWithNoEx: "image", extension: "jpg")
//convert to Data
let imageData = imageChecked.jpegData(compressionQuality: 1)
//instanziate the class
let ftpUploader = FTPUpload.init(baseUrl: Constants.kHostname, userName: Constants.kUsername, password: Constants.kPassword, directoryPath: Constants.kFolder)
ftpUploader.send(data: imageData!, with: "image") { (result) in
if result {
print("result is \(result)")
} else {
print("no result")
}
}
//*****************************************************************
print("...uploaded")
let endDate = NSDate()
let difference = timeDifference(date1: startDate as Date, date2: endDate as Date)
// print("Time difference is : \(difference)")
//1 converto to string
let differenceString = String(difference)
//2 pick first 3 ints
let array = differenceString.compactMap{Int(String($0))}
//3 create new int
let newInt = array[0...3]
var newString = ""
for i in newInt {
newString.append(i.description)
}
var fromIntToString = Int(newString)
fromIntToString = fromIntToString! * 1000
let speed = 1500 / fromIntToString!
print("speed: \(speed)")
}
func timeDifference(date1: Date, date2: Date) -> Int {
let calendar = NSCalendar.current
var compos:Set<Calendar.Component> = Set<Calendar.Component>()
// compos.insert(.second)
compos.insert(.nanosecond)
let difference = calendar.dateComponents(compos, from: date1, to: date2)
// print("diff in seconds= \(difference.second!)") // difference in seconds
print("diff in nanoseconds = \(difference.nanosecond!)") // difference in nanoseconds
let newValue = difference.nanosecond!
return newValue
}
//UPADTED code
func pushfileUpload() {
print("uploading...")
let startDate = Date()
//*****************************************************************
//find file in app bundle
let imageChecked = findFileInBundle(nameWithNoEx: "image", extension: "jpg")
//convert to Data
let imageData = imageChecked.jpegData(compressionQuality: 1)
//instanziate the class
let ftpUploader = FTPUpload.init(baseUrl: Constants.kHostname, userName: Constants.kUsername, password: Constants.kPassword, directoryPath: Constants.kFolder)
ftpUploader.send(data: imageData!, with: "image") { (result) in
if result {
print("result is \(result)")
//-----------------------------------------------------
//Your code to calculate elapsed time belongs here
let endDate = Date()
let elapsed = endDate.timeIntervalSinceReferenceDate -
startDate.timeIntervalSinceReferenceDate
print("The download took \(elapsed) seconds.")
print("speed is \(1500 / elapsed)")
//-----------------------------------------------------
} else {
print("no result")
}
}}
prints on console
The download took 1.281269907951355 seconds.
speed is 1170.7135168720042
As others have said, you need to move your code that calculates total time inside your closure:
func pushfileUpload() {
print("uploading...")
let startDate = Date()
//*****************************************************************
//find file in app bundle
let imageChecked = findFileInBundle(nameWithNoEx: "image", extension: "jpg")
//convert to Data
let imageData = imageChecked.jpegData(compressionQuality: 1)
//instanziate the class
let ftpUploader = FTPUpload.init(baseUrl: Constants.kHostname, userName: Constants.kUsername, password: Constants.kPassword, directoryPath: Constants.kFolder)
ftpUploader.send(data: imageData!, with: "image") { (result) in
if result {
print("result is \(result)")
//-----------------------------------------------------
//Your code to calculate elapsed time belongs here
let endDate = Date()
let elapsed = endDate.timeIntervalSinceReferenceDate -
startDate.timeIntervalSinceReferenceDate
print("The download took \(elasped) seconds."
//-----------------------------------------------------
} else {
print("no result")
}
}
...
As others have mentioned, there's no reason to deal with date components. The method timeIntervalSinceReferenceDate gives you a double precision count of seconds for a date, so it's easy to do math on dates to figure out the difference between them. You can evaluate the difference to as many decimal places as you want.

Local Notification every X day - Swift

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

Charts' line chart doesn't render line chart properly

I'm using the Charts framework and I'm experiencing some very weird behavior in my line chart.
When I segue to the ChartViewContoller and the default selection has data, the chart renders normally:
but if I segue to this view controller when the default selection doesn't have any data and then select an item that has data, it looks like this:
1) segue to this:
2) then select an item that has data:
Of course viewDidLoad is called when I segue to the view controller and as long as the default selection has data when I segue to it, I can select another item that has data or doesn't and the chart will continue to render properly. So the difference appears to be in viewDidLoad but I've tried everything I can think of but nothing fixes the problem. Here's my viewDidLoad:
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor(hexString: "232B35")
self.title = "1RM"
chartView.delegate = self
chartView.chartDescription?.enabled = false
let leftAxis = chartView.leftAxis
leftAxis.axisMinimum = 190
leftAxis.labelTextColor = NSUIColor.white
let xAxis = chartView.xAxis
xAxis.labelPosition = .bottom
xAxis.axisMinimum = 0
xAxis.granularity = 1
xAxis.axisLineWidth = 5
xAxis.valueFormatter = self
xAxis.labelTextColor = NSUIColor.white
chartView.configureDefaults()
chartView.rightAxis.enabled = false // this fixed the extra xAxis grid lines
chartView.backgroundColor = NSUIColor(red: 35/255.0, green: 43/255.0, blue: 53/255.0, alpha: 1.0)
fetchData()
chartView.setVisibleXRangeMaximum(7)
chartView.animate(yAxisDuration: 1.0)
}
here's what's happening in fetchData():
func fetchData() {
chartView.data = nil
let liftName = UserDefaults.selectedLiftForChart()
let liftEvents = dataManager.fetchLiftsEventsOfTypeByName(liftName)
guard liftEvents.count > 0 else {
chartView.noDataText = "There's no \(liftName) data to display"
shouldHideData = true
return }
// put them into a Dictionary grouped by each unique day
let groupedEvents = Dictionary(grouping: liftEvents, by: { floor($0.date.timeIntervalSince1970 / 86400) })
// grab the maximum 1RM from each day
let dailyMaximums = groupedEvents.map { $1.max(by: { $0.oneRepMax < $1.oneRepMax }) }
// MARK: - TODO: Fix the silly unwrapping
sortedLiftEvents = dailyMaximums.sorted(by: { $0?.date.compare(($1?.date)!) == .orderedAscending }) as! [LiftEvent]
let intervalBetweenDates: TimeInterval = 3600 * 24 // 3600 = 1 hour
let startDate = (sortedLiftEvents.first?.date)! - intervalBetweenDates
let lastDate = sortedLiftEvents.last?.date
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "MMM d"
let dates:[Date] = intervalDates(from: startDate, to: lastDate!, with: intervalBetweenDates)
days = dates.map {dateFormatter.string(from: $0)}
generateLineData()
}
and finally, this is the generateLineData method:
func fetchData() {
chartView.data = nil
let liftName = UserDefaults.selectedLiftForChart()
let liftEvents = dataManager.fetchLiftsEventsOfTypeByName(liftName)
guard liftEvents.count > 0 else {
chartView.noDataText = "There's no \(liftName) data to display"
shouldHideData = true
return }
// put them into a Dictionary grouped by each unique day
let groupedEvents = Dictionary(grouping: liftEvents, by: { floor($0.date.timeIntervalSince1970 / 86400) })
// grab the maximum 1RM from each day
let dailyMaximums = groupedEvents.map { $1.max(by: { $0.oneRepMax < $1.oneRepMax }) }
// MARK: - TODO: Fix the silly unwrapping
sortedLiftEvents = dailyMaximums.sorted(by: { $0?.date.compare(($1?.date)!) == .orderedAscending }) as! [LiftEvent]
let intervalBetweenDates: TimeInterval = 3600 * 24 // 3600 = 1 hour
let startDate = (sortedLiftEvents.first?.date)! - intervalBetweenDates
let lastDate = sortedLiftEvents.last?.date
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "MMM d"
let dates:[Date] = intervalDates(from: startDate, to: lastDate!, with: intervalBetweenDates)
days = dates.map {dateFormatter.string(from: $0)}
generateLineData()
}
I've tried putting chartView.setVisibleXRangeMaximum(7) in the method that sets the chart data and verified that chartView.visibleXRange is 7 each time the chart is rendered but it doesn't make a difference. I've also made sure that the max XRange is being set after the data is set for the chart.
Is there anything else I can try or is this perhaps a bug that hasn't been fixed yet?
Thanks
Well I finally figured it out. I knew from reading the documentation that some properties must be set after the chart data is handed to the chart. It was not entirely clear which properties but through lots of debugging and process of elimination I determined it was the xAxis properties that needed to be reset whenever the data changed.
Now, when the data is changed I call my new function:
func resetxAxis() {
let xAxis = chartView.xAxis
xAxis.labelPosition = .bottom
xAxis.axisMinimum = 0
xAxis.granularity = 1
xAxis.axisLineWidth = 5
xAxis.valueFormatter = self
}
This had been in my viewDidLoad method so I made the above method out of it and can call it any time it's needed.

Swift: Run func every minutes

I'm working in swift and want to run a function every minute. I want to update a label with a count down timer with how many minutes left till the next update.
I have a basic version working
if let date = newDate {
let formatter : NSDateFormatter = NSDateFormatter()
formatter.dateFormat = "HH:mm"
formatter.timeZone = NSTimeZone.defaultTimeZone()
let string : NSString = formatter.stringFromDate(date)
let calendar = NSCalendar.currentCalendar()
let comp = calendar.components([.Minute], fromDate: date)
let minute = comp.minute
let remaining : Int = 60 - minute
var mins = "s"
if remaining == 1 {
mins = ""
}
self.refreshInLabel.text = "Refreshes at \(string) - \n \(remaining) minute\(mins) remaining "
}
which is updating when i view the page on the app, but i want it to auto update every minutes.
I've looked at NSTimer, i believe it can be done with this (as shown here: How to make a countdown with NSTimer on Swift) but i can't work out how to make it fire on the minute, only after a certain time display
Edit:
I have the following so far
override func viewDidLoad() {
super.viewDidLoad()
let calendar = NSCalendar.currentCalendar()
let comp = calendar.components([.Minute], fromDate: NSDate())
minute = comp.minute
_ = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: #selector(AdoptionCentreVC.updateTimer), userInfo: nil, repeats: true)
}
func updateTimer() {
let calendar = NSCalendar.currentCalendar()
let comp = calendar.components([.Minute], fromDate: NSDate())
let curMin = comp.minute
if(curMin > minute) {
NSLog("Changed")
self.minute = curMin
}
}
I'd like to know if theres a better way
What I suggest you is to fire a NSNotification when you want to start your func every minute.
When you receive your NSNotification call a function like this :
var yourTimer = NSTimer()
func callWhenNotificationReceived(){
yourFuncToFire()
yourTimer = NSTimer.scheduledTimerWithTimeInterval(60, target: self, selector: #selector(YourViewController.yourFuncToFire) , userInfo: nil, repeats: true)
}
var TotalTime:Int = 0
var timer: NSTimer?
triggerCountDownTimerFor(time:Int)
func triggerCountDownTimerFor(time:Int)
{
totalTime = time
timer = NSTimer.scheduledTimerWithTimeInterval(1, target:self, selector: #selector(LoginViewController.updateTimer), userInfo: nil, repeats: true)
}
func updateTimer()
{
let date = NSDate()
let formatter : NSDateFormatter = NSDateFormatter()
formatter.dateFormat = "HH:mm"
formatter.timeZone = NSTimeZone.defaultTimeZone()
let string : NSString = formatter.stringFromDate(date)
let calendar = NSCalendar.currentCalendar()
let comp = calendar.components([.Minute], fromDate: date)
let minute = comp.minute
let remaining : Int = TotalTime - minute
var mins = "s"
if remaining == 1 {
mins = ""
}
print("Refreshes at \(string) - \n \(remaining) minute\(mins) remaining ")
}