Local Notification every X day - Swift - 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

Related

Set time from DatePicker for reminder

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)

How to schedule local notification from iOS14 widget?

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

Is it possible to have two DateCompontents for one Local Notification?

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

How do I get this the currentStandHour value in Apple Watch iOS?

I want to retrieve the value that indicates whether or not the user has stood this hour. I also want to be able to retrieve the StandHours count for the day.
Here are the Apple links that I've been trying to understand in order get the value from HealthKit. I provide these links to help provide understanding for what I'm looking for and also to help you answer my question.
appleStandHour type property: https://developer.apple.com/documentation/healthkit/hkcategorytypeidentifier/1615539-applestandhour
HealthKit category type identifier: https://developer.apple.com/documentation/healthkit/hkcategorytypeidentifier
HealthKit constants: https://developer.apple.com/documentation/healthkit/healthkit_constants
Bruno's answer is only half of the answer. For example, his standUnit variable is how he pulls the # of hours that the user has stood today. I tested it. Also, I made the assumption that it had to be pulled from within the scope of the summaries variable.
I have found another question on StackOverflow that might provide some clues. I think they managed to pull a value via a HKCategoryTypeIdentifier: Watch os 2.0 beta: access heart beat rate
Here's my attempted code as far as I have been able to get:
import UIKit
import HealthKit
import HealthKitUI
class ViewController: UIViewController {
let hkStoreOnVC : HKHealthStore = HKHealthStore()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
authorizeHealthKit()
hkTest()
hkTest2()
}
func authorizeHealthKit() { //-> Bool {
print("health kit authorize?")
let healthStore = HKHealthStore()
let objectTypes: Set<HKObjectType> = [
HKObjectType.activitySummaryType()
]
healthStore.requestAuthorization(toShare: nil, read: objectTypes) { (success, error) in
// Authorization request finished, hopefully the user allowed access!
print("health kit authorized")
}
}
func hkTest() {
print("health kit test.")
let calendar = Calendar.autoupdatingCurrent
var dateComponents = calendar.dateComponents(
[ .year, .month, .day ],
from: Date()
)
// This line is required to make the whole thing work
dateComponents.calendar = calendar
let predicate = HKQuery.predicateForActivitySummary(with: dateComponents)
//----------------------
let query = HKActivitySummaryQuery(predicate: predicate) { (query, summaries, error) in
print("query")
guard let summaries = summaries, summaries.count > 0
else {
print("no summaries")
return
}
// Handle data
for thisSummary in summaries {
// print("for each summary")
let standUnit = HKUnit.count()
let standHours = thisSummary.appleStandHours.doubleValue(for: standUnit)
print("stand hours \(standHours)")
}//end for
} //end query
}
func hkTest2() {
var isEnabled = true
print ("authorize health kit" )
if HKHealthStore.isHealthDataAvailable() {
let stepsCount = NSSet(objects: HKQuantityType.quantityType(forIdentifier: HKQuantityTypeIdentifier.stepCount ) )
for thisValue in stepsCount {
// thisValue.
print("thisValue: \(thisValue)")
}
print(" authorize HK - steps count \(stepsCount) ")
}
// Create the date components for the predicate
guard let calendar = NSCalendar(calendarIdentifier: NSCalendar.Identifier.gregorian) else {
fatalError("*** This should never fail. ***")
}
let endDate = NSDate()
guard let startDate = calendar.date(byAdding: .day, value: -7, to: endDate as Date, options: []) else {
fatalError("*** unable to calculate the start date ***")
}
let units: NSCalendar.Unit = [.day, .month, .year, .era]
var startDateComponents = calendar.components(units, from: startDate)
startDateComponents.calendar = calendar as Calendar
var endDateComponents = calendar.components(units, from: endDate as Date)
endDateComponents.calendar = calendar as Calendar
// Create the predicate for the query
let summariesWithinRange = HKQuery.predicate(forActivitySummariesBetweenStart: startDateComponents, end: endDateComponents)
// Build the query
let query = HKActivitySummaryQuery(predicate: summariesWithinRange) { (query, summaries, error) -> Void in
guard let activitySummaries = summaries else {
guard let queryError = error else {
fatalError("*** Did not return a valid error object. ***")
}
// Handle the error here...
return
}
for thisSummary in activitySummaries {
// print("for each summary")
let standUnit = HKUnit.count()
let standHours = thisSummary.appleStandHours.doubleValue(for: standUnit)
// let stoodThisHourMaybe = thisSummary.appleStandHours.categ //doubleValue(for: standUnit)
//\(thisSummary.description) //stand unit _\(standUnit)_
print("day#\(thisSummary.dateComponents(for: calendar as Calendar).day) stand hours \(standHours) ")
}//end for
// Do something with the summaries here...
for thisItem in activitySummaries {
//thisItem.appleStandHours
print("abc \( thisItem.appleStandHours ) " )
}//end for
}
// Run the query
let hkStore : HKHealthStore = HKHealthStore()
hkStore.execute(query)
//***
let aStandHour = HKCategoryType.categoryType(forIdentifier: .appleStandHour)
// This is the type you want updates on. It can be any health kit type, including heart rate.
// let distanceType = HKObjectType.quantityType(forIdentifier: HKQuantityTypeIdentifier.distanceWalkingRunning)
// Match samples with a start date after the workout start
// let predicate = .predicat //( , endDate: nil, options: .None)
// let theDate : Date =
let thepredicate = HKQuery.predicateForCategorySamples(with: .greaterThanOrEqualTo, value: 0) //.predicateForSamplesWithStartDate(startDate , endDate: nil, options: .None)
// predicate
// let predicate = . //(theDate , endDate: nil, options: .None)
let hka : HKQueryAnchor = HKQueryAnchor(fromValue: 0)
let sHourQuery = HKAnchoredObjectQuery(type: aStandHour!, predicate: thepredicate, anchor: hka, limit: 0, resultsHandler: { ( query, samples, deletedObjects, anchor, error) -> Void in
// Handle when the query first returns results
// TODO: do whatever you want with samples (note you are not on the main thread)
print("getting here A?")
// for thisSample in samples! {
// print("A smpLType \(thisSample.sampleType) thisSample \(thisSample)")
// }
})
// This is called each time a new value is entered into HealthKit (samples may be batched together for efficiency)
sHourQuery.updateHandler = { (query, samples, deletedObjects, anchor, error) -> Void in
// Handle update notifications after the query has initially run
// TODO: do whatever you want with samples (note you are not on the main thread)
print("getting here B?")
for thisSample in samples! {
print("B smpLType \(thisSample.sampleType) thisSample \(thisSample)")
}
}
// Start the query
self.hkStoreOnVC.execute(sHourQuery)
//***
}//end func
func myCompletionHandler(bTest: Bool ) {
print("my completion handler")
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}//end viewController Class
Here's the code output - the log never prints to "getting here b?":
health kit authorize?
health kit test.
authorize health kit
health kit authorized
thisValue: HKQuantityTypeIdentifierStepCount
authorize HK - steps count {(
HKQuantityTypeIdentifierStepCount
)}
2017-11-04 19:18:30.100562-0500 watchapptest[25048:4695625] refreshPreferences: HangTracerEnabled: 0
2017-11-04 19:18:30.100600-0500 watchapptest[25048:4695625] refreshPreferences: HangTracerDuration: 500
2017-11-04 19:18:30.100615-0500 watchapptest[25048:4695625] refreshPreferences: ActivationLoggingEnabled: 0 ActivationLoggingTaskedOffByDA:0
getting here A?
day#Optional(28) stand hours 14.0
day#Optional(29) stand hours 14.0
day#Optional(30) stand hours 14.0
day#Optional(31) stand hours 14.0
day#Optional(1) stand hours 16.0
day#Optional(2) stand hours 13.0
day#Optional(3) stand hours 15.0
day#Optional(4) stand hours 13.0
abc 14 count
abc 14 count
abc 14 count
abc 14 count
abc 16 count
abc 13 count
abc 15 count
abc 13 count
I am new to HealthKit, so there probably is a nicer way to do this. But this seems to work for me. I check the actually standing minutes and call the completion handler with minutes > 0.
private let store = HKHealthStore()
func askPermission() {
let standType = HKQuantityType.quantityType(forIdentifier: .appleStandTime)!
store.requestAuthorization(toShare: [], read: [standType], completion: { (success, error) in
self.didStandThisHour { (didStand) in
print("Did stand this hour: \(didStand)")
}
})
}
func didStandThisHour(_ didStand: #escaping (Bool) -> ()) {
let store = HKHealthStore()
let calendar = Calendar.autoupdatingCurrent
let dateComponents = calendar.dateComponents([.year, .month, .day, .hour], from: Date())
let endDate = Date()
let startDate = calendar.date(from: dateComponents)!
let standTime = HKQuantityType.quantityType(forIdentifier: HKQuantityTypeIdentifier.appleStandTime)!
var interval = DateComponents()
interval.hour = 1
let predicate = HKQuery.predicateForSamples(withStart: startDate, end: endDate, options: .strictStartDate)
let query = HKStatisticsCollectionQuery(quantityType: standTime, quantitySamplePredicate: predicate, options: [.cumulativeSum], anchorDate: startDate, intervalComponents:interval)
query.initialResultsHandler = { query, results, error in
guard error == nil, let myResults = results else {
fatalError("Something is wrong with HealthKit link")
}
myResults.enumerateStatistics(from: startDate, to: endDate, with: { (statistics, stop) in
guard let quantity = statistics.sumQuantity() else {
didStand(false)
return
}
let minutes = quantity.doubleValue(for: .minute())
didStand(minutes > 0)
})
}
store.execute(query)
}
Ok, if you want to retrieve today's activity ring info (including stand hours) you first request user authorization for the object type you want to retrieve:
let healthStore = HKHealthStore()
let objectTypes: Set<HKObjectType> = [
HKObjectType.activitySummaryType()
]
healthStore.requestAuthorization(toShare: nil, read: objectTypes) { (success, error) in
// Authorization request finished, hopefully the user allowed access!
}
Then you can use this predicate to retrieve today's date:
let calendar = Calendar.autoupdatingCurrent
var dateComponents = calendar.dateComponents(
[ .year, .month, .day ],
from: Date()
)
// This line is required to make the whole thing work
dateComponents.calendar = calendar
let predicate = HKQuery.predicateForActivitySummary(with: dateComponents)
Create a query...
let query = HKActivitySummaryQuery(predicate: predicate) { (query, summaries, error) in
guard let summaries = summaries, summaries.count > 0
else {
return
}
// Handle data
}
The data you'll receive is of type HKActivitySummary and you can retrieve, for example:
let sandUnit = HKUnit.count()
let standHours = summary.appleStandHours.doubleValue(for: standUnit)

Error:XPC connection interrupted

I am scheduling notification by running the loop in an array.Notification is working fine.But my application is crashing with this error.I know that the OS stops working and os gets rebooted but if i dont call this scheduleNotification function then everything works fine so i know that the error is generation because of this function when i pops to other screen and comes back again my app gets crashg but i dont know why?? please help me out.
func scheduleNotification() {
if dictInfo.object(forKey: "name") !as AnyObject as ? String == "zzz" {
} else {
let calendar = Calendar.current
var calendarComponents = DateComponents()
let strDay = (dictInfo["dd"
as NSString] !as AnyObject).doubleValue
let strMonth = (dictInfo["mm"
as NSString]) !
let dayy = strDay
let monthh = strMonth
calendarComponents.hour = 11
calendarComponents.minute = 04
calendarComponents.day = Int(dayy!) - 2
print(calendarComponents.day)
let df = DateFormatter()
df.dateFormat = "MM" // if you need 3 letter month just use "LLL"
let datee1 = df.date(from: String(describing: monthh))
print(datee1)
let monthh11 = (Calendar.current as NSCalendar).component(.month, from: datee1!)
print(monthh11) // 5
//calendarComponents.year = Int(yearr!)
calendarComponents.month = Int(monthh11)
let newComponents = DateComponents(calendar: calendar, timeZone: .current, month: calendarComponents.month, day: calendarComponents.day, hour: calendarComponents.hour, minute: calendarComponents.minute)
if# available(iOS 10.0, * ) {
let strName: String = String(describing: dictInfo["name"] !)
let str2: String! = "Today is \(strName)\'s Birthday!"
let trigger = UNCalendarNotificationTrigger(dateMatching: newComponents, repeats: false)
let content = UNMutableNotificationContent()
content.title = "Alert Me!"
content.body = str2
content.sound = UNNotificationSound.default()
content.categoryIdentifier = "myCategory"
content.userInfo = ((dictInfo as Any) as ? [AnyHashable: Any]) !
print(content.userInfo.count)
let request = UNNotificationRequest(identifier: strName, content: content, trigger: trigger)
// UNUserNotificationCenter.current().removeAllPendingNotificationRequests()
UNUserNotificationCenter.current().add(request) {
(error) in
if let error = error {
print("Uh oh! We had an error: \(error)")
} else {
}
}
}
}
MBProgressHUD.hide(
for: self.view, animated: true)
tableVieww.isHidden = false
//self.navigationItem.leftBarButtonItem?.isEnabled = true
}