I am trying to send data on my bluetooth device using app when it is on background mode. It is communicate to the device successfully but not on time. If I want the background process start after 2 minutes, but It starts after 10 minutes.
Here is my code. And I want to start the background process task after 2 minutes.
func sceneDidEnterBackground(_ scene: UIScene) {
let date = Date()
let calendar = Calendar.current
let hour = calendar.component(.hour, from: date)
let minutes = calendar.component(.minute, from: date)
print(minutes)
print("Scene enter background")
if MotorControl.motorWorkItem != nil{
scheduleAppRefresh()
}
}
func scheduleAppRefresh() {
let request = BGAppRefreshTaskRequest(identifier: "com.example.apple-samplecode.ColorFeed.refresh")
request.earliestBeginDate = Date(timeIntervalSinceNow: 2 * 60)
do {
try BGTaskScheduler.shared.submit(request)
} catch {
print("Could not schedule app refresh: \(error)")
}
}
SessionManager.runningSeriesIndex = 0
MotorControl.motorWorkItem = DispatchWorkItem {
print(SessionManager.runningSeriesIndex)
print(SessionManager.tempoSeriesData[SessionManager.runningSeriesIndex])
motor.setSpeedAndTimer(motorSpeedTimer: MotorSpeedTimer(speed: Int(SessionManager.tempoSeriesData[SessionManager.runningSeriesIndex]), timer: (2), timeNow: Int(Date().timeIntervalSince1970)))
if SessionManager.runningSeriesIndex < 8{
SessionManager.runningSeriesIndex += 1
DispatchQueue.main.async {
if UIApplication.shared.applicationState == .background {
self.scheduleAppRefresh()
}
else{
DispatchQueue.global(qos: .utility).asyncAfter(deadline: .now() + .seconds(60), execute: MotorControl.motorWorkItem!)
}
}
}
}
DispatchQueue.global(qos: .utility).async(execute: MotorControl.motorWorkItem!)
Related
Hello I am making an alarm app and I am trying to play audio when the user receives the local notification. I keep all my local notification functions and management in a swift file titled NotificationPublisher. It works when the user has the app in the foreground because the willPresent() function gets called, however I am trying to make it work in the background/phone is closed.
Is there a function that gets called when a local notification is presented in background and not just the foreground?
If not am I tackling this feature wrong.
Here is my sendNotification function where I take in my alarms and schedule the local notifications. (In my NotificationPublisher.swift)
func sendNotification(alarm : Alarm, badge: Int?) {
let notificationContent = UNMutableNotificationContent()
notificationContent.title = alarm.alarmName
notificationContent.subtitle = alarm.alarmTime + " " + alarm.alarmPeriod
notificationContent.body = "Click here to open the app and click the solve button!"
if let badge = badge {
var currentBadgeCount = UIApplication.shared.applicationIconBadgeNumber
currentBadgeCount += badge
notificationContent.badge = NSNumber(integerLiteral: currentBadgeCount)
}
notificationContent.sound = UNNotificationSound.default
UNUserNotificationCenter.current().delegate = self
var hour = ""
if(alarm.alarmTime.count == 4){
hour = String(alarm.alarmTime.prefix(1))
} else {
hour = String(alarm.alarmTime.prefix(2))
}
let minute = String(alarm.alarmTime.suffix(2))
var intHour = Int(hour)!
if(alarm.alarmPeriod == "PM" && intHour != 12){
intHour += 12
} else if(alarm.alarmPeriod == "AM" && intHour == 12) {
intHour = 0
}
var dateComponents = DateComponents()
if(alarm.alarmDays.filter{$0}.count == 0) {
dateComponents.hour = intHour
dateComponents.minute = Int(minute)!
dateComponents.timeZone = .current
let trigger = UNCalendarNotificationTrigger(dateMatching: dateComponents, repeats: true) // Repeating Alarm
let request = UNNotificationRequest(identifier: alarm.alarmKey, content: notificationContent, trigger: trigger) // replace trigger with delaytimetrigger and vice versa for exact time
UNUserNotificationCenter.current().add(request) { error in
if let error = error {
print(error.localizedDescription)
}
}
} else {
dateComponents.hour = intHour
dateComponents.minute = Int(minute)!
dateComponents.timeZone = .current
let days = ["sun", "mon", "tue", "wed", "thu", "fri", "sat"]
for i in alarm.alarmDays.enumerated() {
if(i.element){
dateComponents.weekday = i.offset + 1
let trigger = UNCalendarNotificationTrigger(dateMatching: dateComponents, repeats: true)
let request = UNNotificationRequest(identifier: alarm.alarmKey + days[i.offset], content: notificationContent, trigger: trigger)
UNUserNotificationCenter.current().add(request) { error in
if let error = error {
print(error.localizedDescription)
}
}
}
}
}
}
Here is the audio function that is in my ViewController.swift that I am calling from my willPresent() function.
func alarmSound() {
print("alarm sound was called")
if let player = player, player.isPlaying {
player.stop()
} else {
let urlString = Bundle.main.path(forResource: "buzzer", ofType: "mp3")
do {
try AVAudioSession.sharedInstance().setMode(.default)
try AVAudioSession.sharedInstance().setActive(true, options: .notifyOthersOnDeactivation)
guard let urlString = urlString else { return }
player = try AVAudioPlayer(contentsOf: URL(fileURLWithPath: urlString))
guard let player = player else { return }
player.numberOfLoops = -1
player.play()
} catch {
print(error)
}
}
}
I am not sure if this will work, but I know the setting sound property will change the sound for your notification. Please check the below link and share your feedback.
https://smashswift.com/create-custom-notification-sound/#:~:text=Scheduling%20local%20notification%20you%20need,of%20UNNotificationSound%20to%20sound%20field.&text=The%20filename%20has%20to%20match,the%20same%20with%20Push%20Notification.
I am trying to set up local notifications that are triggered under two circumstances:
- An x number of days after the first use of the app
- In case the user has gone an x number of days without using the app
Is this possible? All the information I have found online is about setting notifications to go off at a specific time, but how can I send depending on the app's first and last use?
First of all, you need a request to register the notifications, and after the notification is granted, you can call the functions that firing the notifications on the specific time.
In AppDelegate
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
let center = UNUserNotificationCenter.current()
let options: UNAuthorizationOptions = [.alert, .sound]
center.requestAuthorization(options: options, completionHandler: { granted, error in
if granted {
self.setNotificationForFirstTimeUseApp()
self.setNotificationForLastTimeUseApp()
print("Notification is granted")
} else {
print("Notification is not granted")
}
})
return true
}
Create the functions for firing the notification on a specific time:
func setNotificationForFirstTimeUseApp() {
// Check the app, if it is opened before
if let _ = UserDefaults.standard.value(forKey: "appOpened") as? Bool {
return
}
// Save a flag on preferences that it tells the app is opened for the first time
UserDefaults.standard.set(true, forKey: "appOpened")
let content = UNMutableNotificationContent()
content.title = "Using for the first time - Notification" // Add your own title
content.body = "Awesome thank you for using this application!" // Add your own description
content.sound = UNNotificationSound.default
var calendar = Calendar(identifier: .gregorian)
calendar.timeZone = NSTimeZone.local
// Specific the time you want to fire the notification (after some days, hours, minutes...)
if let date = Date.getDateByAdding(days: 0, hours: 0, minutes: 0, seconds: 5) {
let componentsForFireDate = calendar.dateComponents([.year, .month, .day, .hour, .minute, .second, .timeZone], from: date)
let trigger = UNCalendarNotificationTrigger(dateMatching: componentsForFireDate, repeats: false)
let identifier = "firstTimeUseApp"
let request = UNNotificationRequest(
identifier: identifier,
content: content,
trigger: trigger)
let center = UNUserNotificationCenter.current()
center.add(request, withCompletionHandler: { error in
if error != nil {
if let error = error {
print("Something went wrong: \(error)")
}
}
})
center.getPendingNotificationRequests(completionHandler: { requests in
print(String(format: "PendingNotificationRequests %i", requests.count))
for req in requests {
let trigger = req.trigger
if let trigger = trigger {
print("trigger \(trigger)")
}
}
})
}
}
func setNotificationForLastTimeUseApp () {
let content = UNMutableNotificationContent()
content.title = "Using for the last time - Notification" // Add your own title
content.body = "You have not used the application for a long time!" // Add your own description
content.sound = UNNotificationSound.default
var calendar = Calendar(identifier: .gregorian)
calendar.timeZone = NSTimeZone.local
// Specific the time you want to fire the notification (after some days, hours, minutes...)
if let date = Date.getDateByAdding(days: 0, hours: 0, minutes: 0, seconds: 10) {
let componentsForFireDate = calendar.dateComponents([.year, .month, .day, .hour, .minute, .second, .timeZone], from: date)
let trigger = UNCalendarNotificationTrigger(dateMatching: componentsForFireDate, repeats: false)
let identifier = "lastTimeUseApp"
let request = UNNotificationRequest(
identifier: identifier,
content: content,
trigger: trigger)
let center = UNUserNotificationCenter.current()
// Remove pending notifications
center.removePendingNotificationRequests(withIdentifiers: [identifier])
center.add(request, withCompletionHandler: { error in
if error != nil {
if let error = error {
print("Something went wrong: \(error)")
}
}
})
center.getPendingNotificationRequests(completionHandler: { requests in
print(String(format: "PendingNotificationRequests %i", requests.count))
for req in requests {
let trigger = req.trigger
if let trigger = trigger {
print("trigger \(trigger)")
}
}
})
}
}
And finally, add this function. It will calculate a future date by adding days, hours, minutes and seconds, from now.
extension Date {
static func getDateByAdding(days: Int, hours: Int, minutes: Int, seconds: Int) -> Date? {
let currentDate = Date()
var dateComponent = DateComponents()
dateComponent.day = days
dateComponent.hour = hours
dateComponent.minute = minutes
dateComponent.second = seconds
let futureDate = Calendar.current.date(byAdding: dateComponent, to: currentDate)
return futureDate
}
}
I hope this answer will help you with your issue!
I have implemented background tasks in Swift and everything works fine, the function is executed when I run the app in background on a real device. When I want to upload an image to firebase storage using putData in the background task of type BGAppRefreshTask, the completion handler of putData is only called when the app comes back to the foreground and is not executed in the background task.
How can I ensure that the upload task is executed during the background task?
Thank you all for your help!!
Here is where I register the task
//register for background tasks
BGTaskScheduler.shared.register(forTaskWithIdentifier: "com.foodnotifs.checknewfotos", using: nil) {(task) in
self.handleAppRefreshTask(task: task as! BGAppRefreshTask)
print("task registered")
//
}
Here is where I submit the task
let checkFotosTask = BGAppRefreshTaskRequest(identifier: "com.foodnotifs.checknewfotos")
checkFotosTask.earliestBeginDate = Date(timeIntervalSinceNow: 10*60)//every 10 min
do{
try BGTaskScheduler.shared.submit(checkFotosTask)
} catch{
print("unable to submit task: \(error.localizedDescription)")
}
Now here is the actual background task that gets executed:
func handleAppRefreshTask(task: BGAppRefreshTask)
{
let df = DateFormatter()
df.dateFormat = "yyyy-MM-dd hh:mm:ss"
let now:String = df.string(from: Date())
let user = Auth.auth().currentUser
if(user == nil){
task.setTaskCompleted(success: false)} //if no user for some reason, end task
let uid = user!.uid
let databaseRef = Database.database().reference()
databaseRef.child("users/" + uid + "/BACKGROUNDFETCHEDIMAGES/" + now).setValue(numberOfNewImages)
print("number of new images written to database")
let newAsset = fetchResults.object(at: 0)
let size = CGSize(width: newAsset.pixelWidth/3, height: newAsset.pixelHeight/3)
PHImageManager.default().requestImage(for: newAsset, targetSize: (size), contentMode: .aspectFill, options: nil, resultHandler: { image, _ in
print("image retrieved successfully from phmanager")
let storageRef = Storage.storage().reference(forURL: "gs://foodnotifs.appspot.com")
let storageProfileRef = storageRef.child(uid).child(now)
//create some self defined metadata
let metadata = StorageMetadata()
metadata.contentType = "image/jpg"
metadata.customMetadata = ["uid": uid, "date":now, "image_name":now]
//now upload the image to storage and update database entry
//let uploadTask = storageProfileRef.putData(image!.jpegData(compressionQuality: 0.4)!, metadata: metadata)
//CODE EXECUTES UP TO HERE, completion handler of putData is not executed
storageProfileRef.putData(image!.jpegData(compressionQuality: 0.4)!, metadata: metadata) { (uploadedMetadata, error) in
if error != nil{
print("getting error uploading to storage")
print(error!.localizedDescription)
return
}
else{
print("upload to storage successful")
var DLurl:String = ""
storageProfileRef.downloadURL { (url,error) in
guard let DLurl = url?.absoluteString else{
print("error getting download url for uploaded image")
print(error!.localizedDescription)
return
}
print("download url is: " + DLurl)
//successful upload, now update the database of this user with the image url
databaseRef.child("users").child(uid).child("IMAGE_URLs").child(now).setValue(DLurl){ (error, ref) in
if error == nil {
//on sucess, also save url to most recent
print("uploaded to database")
databaseRef.child("users").child(user!.uid).child("IMAGE_URLs").child("MOST_RECENT").setValue(DLurl)
}
else{
print("error in uploading to database")
print(error!.localizedDescription)
}
}//database updated succssfully
} //download url gotten successfully
} //succesful upload
}//storage profile ref putdata completion
})
}
else{
print("no camera roll found")
}
//CODE CONTINUES HERE AND REACHES THE END OF THE FUNCTION WITHOUT UPLOADING THE IMAGE
self.appWillBecomeInactive() //schedule again
print("background task finished")
task.setTaskCompleted(success: true) //task completed
}//end function
just in case anyone will have the same problem, I finally solved it using closures.
Here is the code that is working.
func uploadImage(completion:#escaping((String?) ->() )) {let df = DateFormatter()
df.dateFormat = "yyyy-MM-dd hh:mm:ss"
let now:String = df.string(from: Date())
let user = Auth.auth().currentUser
if(user == nil){
completion(nil)} //if no user for some reason, end task
let uid = user!.uid
let databaseRef = Database.database().reference()
databaseRef.child("users/" + uid + "/BACKGROUNDFETCHEDIMAGES/" + now).setValue(numberOfNewImages)
print("number of new images written to database")
let storageProfileRef = storageRef.child(uid).child(now)
//create some self defined metadata
let metadata = StorageMetadata()
//now upload the image to storage and update database entry
//let uploadTask = storageProfileRef.putData(image!.jpegData(compressionQuality: 0.4)!, metadata: metadata)
storageProfileRef.putData(image!.jpegData(compressionQuality: 0.4)!, metadata: metadata) { (uploadedMetadata, error) in
if error != nil{
print("getting error uploading to storage")
print(error!.localizedDescription)
return
}
else{
print("upload to storage successful")
var DLurl:String = ""
storageProfileRef.downloadURL { (url,error) in
guard let DLurl = url?.absoluteString else{
print("error getting download url for uploaded image")
print(error!.localizedDescription)
return
}
print("download url is: " + DLurl)
databaseRef.child("users").child(uid).child("IMAGE_URLs").child(now).setValue(DLurl){ (error, ref) in
if error == nil {
//on sucess, also save url to most recent
print("uploaded to database")
databaseRef.child("users").child(user!.uid).child("IMAGE_URLs").child("MOST_RECENT").setValue(DLurl)
completion(DLurl)
}
else{
print("error in uploading to database")
print(error!.localizedDescription)
}
}//database updated succssfully
} //download url gotten successfully
} //succesful upload
}//storage profile ref putdata completion
})
}
inside the handler handleAppRefreshTasks, include upload image:
//refresh task has a max run time of 30 seconds, process task has more but called less feequently
func handleAppRefreshTask(task: BGAppRefreshTask)
{
task.expirationHandler = {
print("expiration time for background foto update reached")
//do other handling when expiration time is reached
task.setTaskCompleted(success: false)
return
}
print("going into background task")
//first check if we have internet connection, if not stop the task
let monitor = NWPathMonitor()
monitor.pathUpdateHandler = { path in
if path.status == .satisfied {
print("We're connected!")
} else {
print("No connection.")
//we have no connection, finish the background task
task.setTaskCompleted(success: true)
return
}
}
print("background task entered")
uploadImage { (dlurl) in
if (dlurl != nil){
print("finished upload with download url:" + dlurl!)}
else{
print("dl url is nil, upload failed")
}
self.appWillBecomeInactive() //schedule again
print("background task finished")
task.setTaskCompleted(success: true) //task completed
}
}//end function
I'm trying to copy specific events from all calendars to the target calendar. Unfortunately my events are not saving in target calendar.
Simply my code in steps:
Check permissions (success)
Load calendars (success)
Load events (success)
Save events (failed)
I'm sure there are events to save from terminal which prints "Trying to save" couple of times.
And it looks like code pass through "try self.eventStore.save(event, span: .thisEvent)" and exits function there without calling "Saved" or entering catch clause.
There is a source code:
import UIKit
import EventKit
class ViewController: UIViewController{
#IBOutlet weak var status: UILabel!
var calendars: [EKCalendar]?
var targetCalendar: EKCalendar?
var targetCalendarEvents: [EKEvent]?
let eventStore = EKEventStore()
let targetCalendarName = "TargetCalendarName"
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func viewWillAppear(_ animated: Bool) {
DispatchQueue.main.async{
self.status.text = "Idle"
}
checkCalendarAuthorizationStatus()
}
func checkCalendarAuthorizationStatus() {
let status = EKEventStore.authorizationStatus(for: EKEntityType.event)
switch (status) {
case EKAuthorizationStatus.notDetermined:
// This happens on first-run
requestAccessToCalendar()
case EKAuthorizationStatus.authorized:
// Things are in line with being able to show the calendars in the table view
loadCalendars()
loadEvents()
case EKAuthorizationStatus.restricted, EKAuthorizationStatus.denied:
// We need to help them give us permission
print("Missing permissions [00]")
}
}
func requestAccessToCalendar() {
eventStore.requestAccess(to: EKEntityType.event, completion: {
(accessGranted: Bool, error: Error?) in
if accessGranted == true {
DispatchQueue.main.async(execute: {
self.loadCalendars()
self.loadEvents()
})
} else {
print("Missing permissions [01]")
}
})
}
func loadEvents(){
print("Loading..")
DispatchQueue.main.async{
self.status.text = "Loading"
}
let eventStore = EKEventStore()
var initialized = false
//Two months
let dateTo = NSDate(timeIntervalSinceNow: +30*24*3600 * 2)
for calendar in self.calendars! {
let predicate = eventStore.predicateForEvents(withStart: Date() as Date as Date, end: dateTo as Date, calendars: [calendar])
let events = eventStore.events(matching: predicate)
if calendar.title == targetCalendarName {
print("Initialized")
targetCalendar = calendar
targetCalendarEvents = events
initialized = true
}
}
if(!initialized){
print("Not Initialized")
} else {
for calendar in self.calendars! {
let predicate = eventStore.predicateForEvents(withStart: Date() as Date as Date, end: dateTo as Date, calendars: [calendar])
let events = eventStore.events(matching: predicate)
if calendar.title != targetCalendarName && calendar.title != "Contacts" {
//print("Loaded Calendar \(calendar.title)")
print("Loaded Calendar")
for event in events {
if(!(event.location?.isEmpty)!){
//print("Event \(event.location ?? "Missing Location") \(event.startDate) \(event.endDate)")
addEventToTarget(eventToAdd: event)
}
}
}
}
}
DispatchQueue.main.async {
self.status.text = "Done"
}
print("Done")
}
func loadCalendars() {
self.calendars = eventStore.calendars(for: EKEntityType.event)
}
func addEventToTarget(eventToAdd: EKEvent){
eventStore.requestAccess(to: .event) { (granted, error) in
for event in self.targetCalendarEvents! {
if(!(event.location?.isEmpty)!){
if(
eventToAdd.title == event.title &&
eventToAdd.startDate == event.startDate &&
eventToAdd.endDate == event.endDate
){
print("Possible duplicate - skipping")
return
}
}
}
if (granted) && (error == nil) {
let event:EKEvent = EKEvent(eventStore: self.eventStore)
event.title = eventToAdd.title
event.startDate = eventToAdd.startDate
event.endDate = eventToAdd.endDate
event.notes = ""
event.location = eventToAdd.location
event.calendar = self.targetCalendar
//print("Trying to save \(event.title) \(String(describing: event.location))")
print("Trying to save")
do {
try self.eventStore.save(event, span: .thisEvent)
print("Saved \(event.title) \(String(describing: event.location)) in \(event.calendar.title)")
} catch {
print("failed to save event with error : \(error as NSError)")
}
}
else{
print("failed to save event with error : \(String(describing: error)) or access not granted")
}
}
}
}
TargetCalendarName is correct calendar name
Sometimes there are events which saves successfully but only couple of them (2-5) for hundreds which should save. I don't have a clue why.
So 5 for 200 is not enough for me.
Thanks to #matt for proposing a solution.
Now I'm saving events ONCE (previously I was requesting access for every event which is very bad), so I request access to eventStore once and can save events succesfully.
func saveEvents(){
eventStore.requestAccess(to: .event) { (granted, error) in
if (granted) && (error == nil) {
for event in self.eventsToCopy {
print("Trying to save")
do {
try self.eventStore.save(event, span: .thisEvent)
print("Saved \(event.title) \(String(describing: event.location)) in \(event.calendar.title)")
} catch {
print("failed to save event with error : \(error as NSError)")
}
}
}
else{
print("failed to save event with error : \(String(describing: error)) or access not granted")
}
}
}
I'm working on an Alarm app. While setting up the alarm, user selects days which are getting stored in an array. But I'm not getting correct weekday when trying to place the value while making notification. In my code I'm getting weekdays in "daysValue" ([0,1,1,1,0,0,0], means alarm is set for monday, tuesday and wednesday) but I'm getting no clue how to pass these values in trigger date. Here's the code
var daysValue: [Int]
daysValue = AlarmController.shared.days
print(daysValue)
for _ in daysValue
{
for ind in 0...countNum
{
guard let fireDate = alarm.fireDate else { return }
print(fireDate)
let date = fireDate.addingTimeInterval((1.0*Double(ind)) * 60.0)
print(date)
let weekday = Calendar.current.component(.weekday, from: date)
print(weekday)
let triggerDate = Calendar.current.dateComponents([.weekday, .hour, .minute, .second], from: date)
DispatchQueue.main.async
{
print(triggerDate)
let trigger = UNCalendarNotificationTrigger(dateMatching: triggerDate, repeats: true)
let request = UNNotificationRequest(identifier: "\(alarm.uuid)\(ind)", content: notificationContent, trigger: trigger)
UNUserNotificationCenter.current().add(request)
{
(error) in
if let error = error
{
print("Unable to add notification request, \(error.localizedDescription)")
}
}
}
}
}