iBeacon background ranging - swift

I'm trying to set my application as a way to just start ranging in the background and get a notification when the user hit the shoulder button or home button(not all the time) and I don't want using background mode.
So I coded this in swift, it works when my app is working in background just for 10 seconds and ranging won't be restarted when the user re-enter or exit from region but I get callback for about 180 seconds which means my ranging will work for 3 minutes but it only send notification in its first 10 seconds.
I started a task in the background and then call my run function which will wake up init function. I'll be happy if someone shares its experience.
func applicationDidEnterBackground(_ application: UIApplication) {
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
BackgroundTask1.run(application:
application) { (BackgroundTask1_) in
}
run and init functions:
class func run(application: UIApplication, handler: (BackgroundTask1) -> ()) {
let backgroundTask = BackgroundTask1(application: application)
backgroundTask.begin()
handler(backgroundTask)
}
func init_()
{
let uuidString = "43F2ACD1-5522-4E0D-9E3F-4A828EA12C24"
let beaconRegionIdentifier = "Hello"
let beaconUUID:UUID = UUID(uuidString:uuidString)!
beaconRegion = CLBeaconRegion(proximityUUID: beaconUUID, identifier: beaconRegionIdentifier)
beaconRegion.notifyEntryStateOnDisplay = true
locationManager_ = CLLocationManager()
if (CLLocationManager.authorizationStatus() != CLAuthorizationStatus.authorizedWhenInUse) {
locationManager_!.requestWhenInUseAuthorization()
}
locationManager_?.allowsBackgroundLocationUpdates = true
locationManager_!.delegate = self
locationManager_!.pausesLocationUpdatesAutomatically=false
locationManager_?.startRangingBeacons(in: beaconRegion)
}
This is notificaion's code:
func locationManager(_ manager: CLLocationManager, didRangeBeacons beacons: [CLBeacon], in region: CLBeaconRegion) {
print("sdljflkj")
if beacons.count == 0 {
return
}
let currentbeacon = beacons[0]
lastproximity = currentbeacon.proximity
if currentbeacon.proximity == CLProximity.immediate{
DispatchQueue.global(qos: .userInitiated).async {
let content = UNMutableNotificationContent()
content.title = "Forget Me Not"
content.body = "Are you forgetting something?"
content.sound = .default()
let request = UNNotificationRequest(identifier: "ForgetMeNot", content: content, trigger: nil)
UNUserNotificationCenter.current().add(request, withCompletionHandler: nil)
print("here1")
NotificationCenter.default.post(name: Notification.Name(rawValue: "iBeaconFoundReceivedNotification"), object: nil, userInfo: ["Major":currentbeacon.major, "minor": currentbeacon.minor])
}
}
}

Related

How to do Apple Watch Background App Refresh?

I've consulted many variations of background app refresh for Apple Watch so that I can update the complications for my app. However, the process seems very much hit or miss and most of the time it doesn't run at all after some time.
Here is the code I currently have:
BackgroundService.swift
Responsibility: Schedule background refresh and handle download processing and update complications.
import Foundation
import WatchKit
final class BackgroundService: NSObject, URLSessionDownloadDelegate {
var isStarted = false
private let requestFactory: RequestFactory
private let logManager: LogManager
private let complicationService: ComplicationService
private let notificationService: NotificationService
private var pendingBackgroundTask: WKURLSessionRefreshBackgroundTask?
private var backgroundSession: URLSession?
init(requestFactory: RequestFactory,
logManager: LogManager,
complicationService: ComplicationService,
notificationService: NotificationService
) {
self.requestFactory = requestFactory
self.logManager = logManager
self.complicationService = complicationService
self.notificationService = notificationService
super.init()
NotificationCenter.default.addObserver(self,
selector: #selector(handleInitialSchedule(_:)),
name: Notification.Name("ScheduleBackgroundTasks"),
object: nil
)
}
func updateContent() {
self.logManager.debugMessage("In BackgroundService updateContent")
let complicationsUpdateRequest = self.requestFactory.makeComplicationsUpdateRequest()
let config = URLSessionConfiguration.background(withIdentifier: "app.wakawatch.background-refresh")
config.isDiscretionary = false
config.sessionSendsLaunchEvents = true
self.backgroundSession = URLSession(configuration: config,
delegate: self,
delegateQueue: nil)
let backgroundTask = self.backgroundSession?.downloadTask(with: complicationsUpdateRequest)
backgroundTask?.resume()
self.isStarted = true
self.logManager.debugMessage("backgroundTask started")
}
func handleDownload(_ backgroundTask: WKURLSessionRefreshBackgroundTask) {
self.logManager.debugMessage("Handling finished download")
self.pendingBackgroundTask = backgroundTask
}
func urlSession(_ session: URLSession,
downloadTask: URLSessionDownloadTask,
didFinishDownloadingTo location: URL) {
processFile(file: location)
self.logManager.debugMessage("Marking pending background tasks as completed.")
if self.pendingBackgroundTask != nil {
self.pendingBackgroundTask?.setTaskCompletedWithSnapshot(false)
self.backgroundSession?.invalidateAndCancel()
self.pendingBackgroundTask = nil
self.backgroundSession = nil
self.logManager.debugMessage("Pending background task cleared")
}
self.schedule()
}
func processFile(file: URL) {
guard let data = try? Data(contentsOf: file) else {
self.logManager.errorMessage("file could not be read as data")
return
}
guard let backgroundUpdateResponse = try? JSONDecoder().decode(BackgroundUpdateResponse.self, from: data) else {
self.logManager.errorMessage("Unable to decode response to Swift object")
return
}
let defaults = UserDefaults.standard
defaults.set(backgroundUpdateResponse.totalTimeCodedInSeconds,
forKey: DefaultsKeys.complicationCurrentTimeCoded)
self.complicationService.updateTimelines()
self.notificationService.isPermissionGranted(onGrantedHandler: {
self.notificationService.notifyGoalsAchieved(newGoals: backgroundUpdateResponse.goals)
})
self.logManager.debugMessage("Complication updated")
}
func schedule() {
let time = self.isStarted ? 15 * 60 : 60
let nextInterval = TimeInterval(time)
let preferredDate = Date.now.addingTimeInterval(nextInterval)
WKExtension.shared().scheduleBackgroundRefresh(withPreferredDate: preferredDate,
userInfo: nil) { error in
if error != nil {
self.logManager.reportError(error!)
return
}
self.logManager.debugMessage("Scheduled for \(preferredDate)")
}
}
#objc func handleInitialSchedule(_ notification: NSNotification) {
if !self.isStarted {
self.schedule()
}
}
deinit {
NotificationCenter.default.removeObserver(self)
}
}
The flow for the above file's usage is that it will be used by the ExtensionDelegate to schedule background refresh. The first time, it'll schedule a refresh for 1 minute out and then every 15 minutes after that.
Here is the ExtensionDelegate:
import Foundation
import WatchKit
class ExtensionDelegate: NSObject, WKExtensionDelegate {
private var backgroundService: BackgroundService?
private var logManager: LogManager?
override init() {
super.init()
self.backgroundService = DependencyInjection.shared.container.resolve(BackgroundService.self)!
self.logManager = DependencyInjection.shared.container.resolve(LogManager.self)!
}
func isAuthorized() -> Bool {
let defaults = UserDefaults.standard
return defaults.bool(forKey: DefaultsKeys.authorized)
}
func applicationDidFinishLaunching() {
self.logManager?.debugMessage("In applicationDidFinishLaunching")
if isAuthorized() && !(self.backgroundService?.isStarted ?? false) {
self.backgroundService?.schedule()
}
}
func handle(_ backgroundTasks: Set<WKRefreshBackgroundTask>) {
self.logManager?.debugMessage("In handle backgroundTasks")
if !isAuthorized() {
return
}
for task in backgroundTasks {
self.logManager?.debugMessage("Processing task: \(task.debugDescription)")
switch task {
case let backgroundTask as WKApplicationRefreshBackgroundTask:
self.backgroundService?.updateContent()
backgroundTask.setTaskCompletedWithSnapshot(false)
case let urlSessionTask as WKURLSessionRefreshBackgroundTask:
self.backgroundService?.handleDownload(urlSessionTask)
default:
task.setTaskCompletedWithSnapshot(false)
}
}
}
}
When the app is launched from background, it'll try to schedule for the first time in applicationDidFinishLaunching.
From my understanding, WKExtension.shared().scheduleBackgroundRefresh will get called in schedule, then after the preferred time WatchOS will call handle with WKApplicationRefreshBackgroundTask task. Then I will use that to schedule a background URL session task and immediately start it as seen in the updateContent method of BackgroundService. After some time, WatchOS will then call ExtensionDelegate's handle method with WKURLSessionRefreshBackgroundTask and I'll handle that using the handleDownload task. In there, I process the response, update the complications, clear the task, and finally schedule a new background refresh.
I've found it works great if I'm actively working on the app or interacting with it in general. But let's say I go to sleep then the next day the complication will not have updated at all.
Ideally, I'd like for it to function as well as the Weather app complication WatchOS has. I don't interact with the complication, but it reliably updates.
Is the above process correct or are there any samples of correct implementations?
Some of the posts I've consulted:
https://wjwickham.com/posts/refreshing-data-in-the-background-on-watchOS/
https://developer.apple.com/documentation/watchkit/background_execution/using_background_tasks
https://spin.atomicobject.com/2021/01/26/complications-basic-functionality/

Launch Location Updates at specific time in background - Swift (watchOS)

I've developing an indipendent WatchOS app whose aim is identifying when an user leaves a specific area, sending consequentially a notification. In order to do that, the application heavily relies on background location updates.
So far, app is working fine. It fetches position based on distanceFilter property of CLLocationManager. The problem is battery. The approach I followed keep background location updates in execution, even though they're fetched only when a specific distance is "covered".
My idea then was to disable location update when the area is left by the user, and also disable this service during night hours. However, I'm facing serious problem with this type of approach.
My main problem is that disabling location update while in background does not allow me to resume it. I tried doing this with:
A Timer.
scheduleBackgroundRefresh(withPreferredDate:userInfo:scheduledCompletion:) method, calling startLocationUpdates() in the delegate
Nothing seems to work. My question is:
There is a way for resume background location updates if it was previously disabled?
Thank you in advance.
UPDATE n.2: I've tried to execute location updates with WKApplicationRefreshBackgroundTask but it just ignore requestLocation() function (suggested by #RomuloBM)
//In extension delegate handle() function
case let backgroundTask as WKApplicationRefreshBackgroundTask:
// Be sure to complete the background ta
LocMng = LocationManager() // I even tried to create a new element!
LocMng.LocMng.requestLocation()// it is just ignored
backgroundTask.setTaskCompletedWithSnapshot(false)
I call a background task with this function in my LocationManager:
//In didUpdateLocation
if background {
WKExtension.shared().scheduleBackgroundRefresh(withPreferredDate: Date(timeIntervalSinceNow: 30), userInfo: nil){ _ in
print("Done")
self.background = false
self.LocMng.stopUpdatingLocation()
}
}
For reference, here is my LocationManager class:
enum ScanningMode {
case Precise
case Normal
}
class LocationManager : NSObject, CLLocationManagerDelegate, UNUserNotificationCenterDelegate, ObservableObject {
let LocMng = CLLocationManager()
let NotMng = UNUserNotificationCenter.current()
var modeOfScanning: ScanningMode!
var region: CLCircularRegion!
var previousLocation: CLLocation!
// variables for position...
override init() {
super.init()
// stuff for my app...
modeOfScanning = .Precise
setupManager()
setupNotification()
startLocalization()
}
private func startLocalization(){
switch modeOfScanning!{
case ScanningMode.Precise:
LocMng.desiredAccuracy = kCLLocationAccuracyBest
LocMng.distanceFilter = 15
case ScanningMode.Normal:
LocMng.desiredAccuracy = kCLLocationAccuracyHundredMeters
LocMng.distanceFilter = 80
}
LocMng.startUpdatingLocation()
}
private func setupManager(){
LocMng.requestAlwaysAuthorization()
LocMng.delegate = self
LocMng.desiredAccuracy = kCLLocationAccuracyBest
}
private func setupNotification(){
NotMng.delegate = self
NotMng.requestAuthorization(options: [.alert, .badge, .sound]) { (granted, error) in
if granted {
print("NotificationCenter Authorization Granted!")
}
}
}
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
if status == CLAuthorizationStatus.authorizedAlways{
}
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
LocMng.allowsBackgroundLocationUpdates = true
// For the sake of clarity, I will cut out this chunk of code and
// just showing how I execute my action based on the result of location
// This is just an example
actualLocation = locations[length-1]
//find if in a forget
if previousLocation != nil{
if !region.contains(actualLocation!.coordinate) && region.contains(previousLocation!.coordinate){
//Schedule notification
LocMng.stopUpdatingLocation() // <- this does not allow me to resume
}
}
previousLocation = actualLocation
}
}

WatchOS Complication not updating automatically

I'm trying to get my watchOS complication to automatically update every few minutes. In the ComplicationCOntroller.swift, I setup the complication in the getCurrentTimelineEntry() function, and it works the first time, but only updates the value displayed in the complication if I launch the WatchOS app. Can I set my complication to automatically update in the background? Even if it's a set time interval, e.g. "update in 3 minutes". Here's my ExtensionDelegate:
import WatchKit
import WatchConnectivity
class ExtensionDelegate: NSObject, WKExtensionDelegate, WCSessionDelegate {
let session = WCSession.default
let defaults = UserDefaults.standard
let server = CLKComplicationServer.sharedInstance()
override init() {
super.init()
WKInterfaceDevice.current().isBatteryMonitoringEnabled = true
session.delegate = self
session.activate()
}
func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {
print("Session started on \(WKInterfaceDevice.current().name)")
defaults.set("32", forKey: "PhoneBatteryLevel")
}
func session(_ session: WCSession, didReceiveApplicationContext applicationContext: [String : Any]) {
print("Received application context: \(applicationContext["PhoneBatteryLevel"]!)")
defaults.set(applicationContext["PhoneBatteryLevel"], forKey: "PhoneBatteryLevel")
for complication in server.activeComplications! {
server.reloadTimeline(for: complication)
print("Reloading complications...")
}
}
func applicationDidFinishLaunching() {
// Perform any final initialization of your application.
}
func applicationDidBecomeActive() {
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
}
func applicationWillResignActive() {
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, etc.
}
func handle(_ backgroundTasks: Set<WKRefreshBackgroundTask>) {
// Sent when the system needs to launch the application in the background to process tasks. Tasks arrive in a set, so loop through and process each one.
for task in backgroundTasks {
// Use a switch statement to check the task type
switch task {
case let backgroundTask as WKApplicationRefreshBackgroundTask:
// Be sure to complete the background task once you’re done.
backgroundTask.setTaskCompletedWithSnapshot(false)
case let snapshotTask as WKSnapshotRefreshBackgroundTask:
// Snapshot tasks have a unique completion call, make sure to set your expiration date
snapshotTask.setTaskCompleted(restoredDefaultState: true, estimatedSnapshotExpiration: Date.distantFuture, userInfo: nil)
case let connectivityTask as WKWatchConnectivityRefreshBackgroundTask:
// Be sure to complete the connectivity task once you’re done.
connectivityTask.setTaskCompletedWithSnapshot(false)
case let urlSessionTask as WKURLSessionRefreshBackgroundTask:
// Be sure to complete the URL session task once you’re done.
urlSessionTask.setTaskCompletedWithSnapshot(false)
case let relevantShortcutTask as WKRelevantShortcutRefreshBackgroundTask:
// Be sure to complete the relevant-shortcut task once you're done.
relevantShortcutTask.setTaskCompletedWithSnapshot(false)
case let intentDidRunTask as WKIntentDidRunRefreshBackgroundTask:
// Be sure to complete the intent-did-run task once you're done.
intentDidRunTask.setTaskCompletedWithSnapshot(false)
default:
// make sure to complete unhandled task types
task.setTaskCompletedWithSnapshot(false)
}
}
}
}
And the relevant ComplicationController code:
func getCurrentTimelineEntry(for complication: CLKComplication, withHandler handler: #escaping (CLKComplicationTimelineEntry?) -> Void) {
switch complication.family {
case .modularSmall:
let template = CLKComplicationTemplateModularSmallSimpleText()
template.textProvider = CLKSimpleTextProvider(text: "\(defaults.integer(forKey: "PhoneBatteryLevel"))")
template.tintColor = UIColor.yellow
let entry = CLKComplicationTimelineEntry(date: Date(), complicationTemplate: template)
handler(entry)
case .modularLarge:
let template = CLKComplicationTemplateModularLargeStandardBody()
template.headerTextProvider = CLKSimpleTextProvider(text: "Battery Levels")
template.headerTextProvider.tintColor = UIColor.green
template.body1TextProvider = CLKSimpleTextProvider(text: "iPhone: \(defaults.integer(forKey: "PhoneBatteryLevel"))%")
template.body2TextProvider = CLKSimpleTextProvider(text: "Watch: \(getWatchBatteryLevel())%")
let entry = CLKComplicationTimelineEntry(date: Date(), complicationTemplate: template)
handler(entry)
default:
return
}
}

How to show region notification for iOS at most once a month?

My app's deployment target is 10.0, and I used UNUserNotificationCenter to show region notification even when the app is closed/killed. But new mission is to show it at most once a month, though the user may enter the region more than once a month.
What I tried until now (which worked great) is...
let content = UNMutableNotificationContent()
content.title = "... Reminder"
content.body = "Welcome to \(element.name). Please let us know how we can serve you and your loved ones, and we hope ... will simplify your visit here."
content.sound = UNNotificationSound.default
content.categoryIdentifier = "LOCATION_CAT"
let centerCoordinate2D = element.location.coordinate
let identifierName = element.name.replacingOccurrences(of: " ", with: "_")
let region = CLCircularRegion(center: centerCoordinate2D, radius: 300, identifier: identifierName)
region.notifyOnExit = false
region.notifyOnEntry = true
let trigger = UNLocationNotificationTrigger(region: region, repeats: true)
// request = content + trigger
let request = UNNotificationRequest(identifier: "REGION \(element.name)", content: content, trigger: trigger)
// add (or "schedule") request to center
let center = UNUserNotificationCenter.current()
center.add(request, withCompletionHandler: { (error: Error?) in
if let theError = error {
print(theError.localizedDescription)
}
})
But then, to let it happen at most once a month, I did the following:
let centerCoordinate2D = element.location.coordinate
let identifierName = element.name.replacingOccurrences(of: " ", with: "_")
let region = CLCircularRegion(center: centerCoordinate2D, radius: 300, identifier: identifierName)
region.notifyOnExit = true
region.notifyOnEntry = true
R.shared.appleLocationManager.startMonitoring(for: region)
Also in AppDelegate.swift,
extension AppDelegate: CLLocationManagerDelegate {
// called when user Enters a monitored region
func locationManager(_ manager: CLLocationManager, didEnterRegion region: CLRegion) {
print("AppDelegate.locationManager( didEnterRegion ): called with region identifier: \(region.identifier)")
if region is CLCircularRegion {
// Do what you want if this information
self.handleEvent(forRegion: region)
}
}
func handleEvent(forRegion region: CLRegion) {
// we save 'date' with "NOTIFICATION DATE request_identifier" as its key.
let key = "NOTIFICATION DATE \(region.identifier)"
let defaultValue = defaults.double(forKey: key)
if defaultValue == 0 {
print("AppDelegate.handleEvent(): need to show notification: no key")
// set value first.
defaults.set(Date().timeIntervalSince1970, forKey: key)
showNotification(forRegion: region)
} else {
let diff = Date().timeIntervalSince(Date(timeIntervalSince1970: defaultValue))
if diff > 60 * 60 * 24 * 30 {
print("AppDelegate.handleEvent(): need to show notification: diff > 30 days")
// set value first.
defaults.set(Date().timeIntervalSince1970, forKey: key)
showNotification(forRegion: region)
} else {
// just pass.
print("AppDelegate.handleEvent(): need NOT to show notification: diff: \(dot2(diff / 24 / 60)) mins")
}
}
}
func showNotification(forRegion region: CLRegion, message: String = "") {
// customize your notification content
let content = UNMutableNotificationContent()
content.title = "... Reminder"
let hospitalName = region.identifier.replacingOccurrences(of: "_", with: " ")
content.body = "Welcome to \(hospitalName). \(message) Please let us know how we can serve you and your loved ones, and we hope ... will simplify your visit here."
content.sound = UNNotificationSound.default
// the actual trigger object
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 0,
repeats: false)
// notification unique identifier, for this example, same as the region to avoid duplicate notifications
let identifier = "REGION \(hospitalName)"
// the notification request object
let request = UNNotificationRequest(identifier: identifier,
content: content,
trigger: trigger)
// trying to add the notification request to notification center
UNUserNotificationCenter.current().add(request, withCompletionHandler: { (error) in
if let theError = error {
print(theError.localizedDescription)
}
})
}
with the following, still for the AppDelegate class:
let appleLocationManager = CLLocationManager()
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
self.appleLocationManager.delegate = self
...
}
I think there is error in the code, but it is not clear to me if the locationManager( didExitRegion: ) is to be called even when the app is closed/killed - in which case appleLocationManager is not alive?
If I can't use locationManager( didExitRegion: ) for this problem, what can I do to make the region notification happen at most once a month? I also know that there is a different type of trigger, UNTimeIntervalNotificationTrigger or UNLocationNotificationTrigger, and I wanted to use them somehow to solve this problem, but would there be any way to make it run some of my code even when the app is not running at all? If this is impossible to solve, isn't it enough to say that the region notification is too much restricted?
Described as Apple's document, region monitoring with CLLocationManager wakes up your app if needed.
c.f. https://developer.apple.com/documentation/corelocation/monitoring_the_user_s_proximity_to_geographic_regions
Whenever the user crosses the boundary of one of your app's registered regions, the system notifies your app. If an iOS app is not running when the boundary crossing occurs, the system tries to launch it. An iOS app that supports region monitoring must enable the Location updates background mode so that it can be launched in the background.
You can also detect if the app is launched by regional notification with UIApplication.LaunchOptionsKey.
Boundary crossing notifications are delivered to your location manager's delegate object. Specifically, the location manager calls the locationManager(:didEnterRegion:) or locationManager(:didExitRegion:) methods of its delegate. If your app was launched, you must configure a CLLocationManager object and delegate object right away so that you can receive these notifications. To determine whether your app was launched for a location event, look for the UIApplication.LaunchOptionsKey in the launch options dictionary.

swift, detect ibeacons on the background and send notifications when in range

Hello I'm new at IBeacons and beginner on swift and I'm trying to make a small app that detects Ibeacon on the background of the app and send a notification when the Ibeacon is in range I manage to do so but only when I walk while the app is open I could not make it work and search for Ibeacons on the background even though I gave the app access to take the location on the background by using
if (CLLocationManager.authorizationStatus() != CLAuthorizationStatus.authorizedAlways) {
locationManager.requestAlwaysAuthorization();
}
this is my main problem, I have a side problem too that the app does not save the person name if the app is closed and opened again the app will forget the name. Here is my code I'd really appreciate your help so much also please if you have any references to learn more about IBeacons applications I'd appreciate it
import UIKit
import CoreLocation
import UserNotifications
class ViewController: UIViewController, CLLocationManagerDelegate {
#IBOutlet weak var field: UITextField!
#IBOutlet weak var textLbl : UILabel!
var inRoom = false
var name = ""
var sendYet = false ;
func sendHiNoti()
{
name = field.text! ;
let content = UNMutableNotificationContent()
content.title = "Heloo "+name
content.subtitle = "Welcome to your Home"
content.body = "this messaage to welcome you in Home"
content.badge = 1
content.sound = UNNotificationSound(named: UNNotificationSoundName(rawValue: "quiteimpressed.mp3"))
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 2, repeats: false)
let request = UNNotificationRequest(identifier: "azanSoon", content: content, trigger: trigger)
UNUserNotificationCenter.current().removeAllPendingNotificationRequests()
UNUserNotificationCenter.current().add(request) {(error) in
if let error = error {
print("error: \(error)")
}
}
}
func sendByeNoti()
{
name = field.text! ;
let content = UNMutableNotificationContent()
content.title = "OH"+name
content.subtitle = "You are going out already ??"
content.body = "Take care of your self"
content.badge = 1
content.sound = UNNotificationSound(named: UNNotificationSoundName(rawValue: "quiteimpressed.mp3"))
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 2, repeats: false)
let request = UNNotificationRequest(identifier: "azanSoon", content: content, trigger: trigger)
UNUserNotificationCenter.current().removeAllPendingNotificationRequests()
UNUserNotificationCenter.current().add(request) {(error) in
if let error = error {
print("error: \(error)")
}
}
}
#IBAction func getText(){
name = field.text!
let alert = UIAlertController(title: "Your Name is", message: name, preferredStyle: UIAlertController.Style.alert)
alert.addAction(UIAlertAction(title: "Ok", style: UIAlertAction.Style.default, handler: nil))
self.present(alert, animated: true, completion: nil)
}
var locationManager = CLLocationManager()
override func viewDidLoad() {
super.viewDidLoad()
locationManager.delegate = self
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge], completionHandler: {didAllow, error in})
let uuid = UUID(uuidString: "E2C56DB5-DFFB-48D2-B060-D0F5A71096E0")!
let beaconRegion = CLBeaconRegion(proximityUUID: uuid, major: 444, minor: 333, identifier: "abcdefac005b")
if (CLLocationManager.authorizationStatus() != CLAuthorizationStatus.authorizedAlways) {
locationManager.requestAlwaysAuthorization();
}
locationManager.startRangingBeacons(in: beaconRegion)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func locationManager(_ manager: CLLocationManager, didRangeBeacons beacons: [CLBeacon], in region: CLBeaconRegion) {
print(beacons)
if(beacons.count > 0){
if(!sendYet){
if beacons[0].proximity.rawValue < 2 {
textLbl.text = "In the room"
sendHiNoti()
sendYet = true ;
}
}
else if beacons[0].proximity.rawValue >= 3 {
textLbl.text = "Outside the room"
sendByeNoti()
sendYet = false ;
}
}
}
}
The code shown uses beacon ranging locationManager.startRangingBeacons(in: beaconRegion) which is generally not supported in the background for more than 10 seconds after transition between foreground and background.
The locationManager.requestAlwaysAuthorization() will only unlock the ability to use beacon monitoring in the background. Beacon monitoring gives you a single call when beacons either first appear (didEnter(region: region)) or all disappear(didExit(region: region)).
This is the only beacon API which works in the background under normal circumstances.
It is possible to do beacon ranging in the background for longer than 10 seconds using two techniques:
You can get 180 seconds of background ranging after the app transitions to the background by starting a background task as described in my blog post here.
You can also tell iOS that you are a location app to unlock unlimited background beacon ranging. You must first implement the solution in part 1. Then, in your Info.plist, declare:
<key>UIBackgroundModes</key>
<array>
<string>location</string>
</array>
Finally, in your code run locationManager.startUpdatingLocation(). This will cause your app to receive regular updates of its latitude/longitude, but as a side effect allows you background task from step 1 to run forever, allowing ranging to continue forever in the background.
If you choose to use option 2, beware that it will be more difficult to get your app approved for sale in the AppStore. You must convince Apple reviewers that your app is a legitimate location app (like Waze or Apple Maps) and the user is aware that your app is always running in the background. If you do not convince them of this, they will reject your app.
Separately, it is simple to save off values to persistent storage so they are retained across app restarts. Just use NSUserDefaults like this:
// save off name when user fills it in
UserDefaults.standard.set(name, forKey: "name")
// load back name on app restart
name = UserDefaults.standard.string(forKey: "name")