I'm facing the following issue. I have an app that request location in "always" mode that for some reason goes to sleep when the screen is off. It also asks for accelerometer data. All these are running inside a long running Task.
I thought that "location always" would keep the app to receive location data and thus maintains the app running.
The second problem is that the app should play a sound under certain circumstances while the screen is off, to notify the user to unlock it because he must interact with the app.
My info.plist contains the following:
<key>UIBackgroundModes</key>
<array>
<string>bluetooth-central</string>
<string>remote-notification</string>
<string>processing</string>
<string>location</string>
</array>
But AFAIK, it does not help achieve what I want.
Thanks for your help.
Below is an example of LocationService usage with background mode.
public class LocationManager
{
protected CLLocationManager locMgr;
public event EventHandler<LocationUpdatedEventArgs> LocationUpdated = delegate { };
public LocationManager()
{
this.locMgr = new CLLocationManager
{
// This mode is resistant to applications being killed in the background
PausesLocationUpdatesAutomatically = false
};
if (UIDevice.CurrentDevice.CheckSystemVersion(8, 0))
{
locMgr.RequestAlwaysAuthorization();
}
if (UIDevice.CurrentDevice.CheckSystemVersion(9, 0))
{
locMgr.AllowsBackgroundLocationUpdates = true;
}
}
public CLLocationManager LocMgr
{
get { return this.locMgr; }
}
public void StartLocationUpdates()
{
if (CLLocationManager.LocationServicesEnabled)
{
LocMgr.DesiredAccuracy = 1;
LocMgr.LocationsUpdated += (object sender, CLLocationsUpdatedEventArgs e) =>
{
LocationUpdated(this, new LocationUpdatedEventArgs(e.Locations[e.Locations.Length - 1]));
};
LocMgr.StartUpdatingLocation();
}
}
}
When an application monitors the location service in background mode, you can try local notification to post with a sound
// Trigger by position
#IBAction func locationInterval(_ sender: Any) {
let content = UNMutableNotificationContent()
content.title = "xx"
content.body = "xx"
let coordinate = CLLocationCoordinate2D(latitude: 31.29065118, longitude: 118.3623587)
let region = CLCircularRegion(center: coordinate, radius: 500, identifier: "center")
region.notifyOnEntry = true
region.notifyOnExit = false
let trigger = UNLocationNotificationTrigger(region: region, repeats: true)
let requestIdentifier = "com.abc.testUserNotifications"
let request = UNNotificationRequest(identifier: requestIdentifier, content: content, trigger: trigger)
UNUserNotificationCenter.current().add(request, withCompletionHandler: nil)
}
Hope it helps.
Related
New to Xcode and Swift. My app has a timer that counts down. I'd like for the countdown to be visible from the lock screen as a notification, but I can't figure out how to (of if it's even possible to) update the content of an existing local notification.
The only solution I've found so far is to cancel the current notification and show a new one every second, which is not ideal.
Code:
struct TimerApp: View {
private let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
#State private var isActive: Bool = true // whether not timer is active
#State private var timeRemaining: Int = 600 // 60 seconds * 10 mins = 10 min-countdown timer
var body: some View {
// body stuff
// toggle isActive if user stops/starts timer
}.onReceive(timer, perform: { _ in
guard isActive else { return }
if timeRemaining > 0 {
// would like to update current notification here
// *******
// instead, removing and adding a new one right now
UNUserNotificationCenter.current().removeAllDeliveredNotifications()
UNUserNotificationCenter.current().removeAllPendingNotificationRequests()
addNotification()
timeRemaining -= 1
} else {
isActive = false
timeRemaining = 0
}
}
func addNotification() {
let center = UNUserNotificationCenter.current()
let addRequest = {
let content = UNMutableNotificationContent()
content.title = "App Title"
content.body = "Time: \(timeFormatted())"
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 0.0001, repeats: false)
let request = UNNotificationRequest(identifier: "onlyNotification", content: content, trigger: trigger)
center.add(request)
}
center.getNotificationSettings { settings in
if settings.authorizationStatus == .authorized {
addRequest()
} else {
center.requestAuthorization(options: [.alert, .badge]) { success, error in
if success {
addRequest()
} else if let error = error {
print("error :( \(error.localizedDescription)")
}
}
}
}
}
func timeFormatted() -> String {
// converts timeRemaining to 00:00 format and returns string
}
}
And here is what the hilariously bad solution looks like right now.
Currently it's not possible to update a pending or delivered local notification request.
Just like you guessed, in order to deliver a notification with a different content instead, you need to remove the pending request using UNNotificationCenter's methods removePendingNotificationRequests(withIdentifiers:) or removeAllPendingNotificationRequests() and add a new request with the updated content.
I'm creating a flutter plugin to make WebRTC calls using the Twilio API. On the iOS side I use CXProvider and CallKit to make/receive calls. My problem is, the native call screen UI is always launched in the background and my Flutter app stay on the front.
Here a demo video :
I really don't understand this behavior.
This is how I display the incoming notification
func reportIncomingCall(from: String, uuid: UUID) {
let callHandle = CXHandle(type: .generic, value: from)
let callUpdate = CXCallUpdate()
callUpdate.remoteHandle = callHandle
callUpdate.localizedCallerName = from
callUpdate.supportsDTMF = true
callUpdate.supportsHolding = true
callUpdate.supportsGrouping = false
callUpdate.supportsUngrouping = false
callUpdate.hasVideo = false
// this display the callInvite UI
self.callKitProvider.reportNewIncomingCall(with: uuid, update: callUpdate) { error in
if let error = error {
print("error", error as Any)
}
}
}
This is how I answer a call from the native side
func performAnswerVoiceCall(uuid: UUID, completionHandler: #escaping (Bool) -> Swift.Void) {
if let ci = self.twilioVoiceDelegate!.callInvite {
let acceptOptions: AcceptOptions = AcceptOptions(callInvite: ci) { (builder) in
builder.uuid = ci.uuid
}
let theCall = ci.accept(options: acceptOptions, delegate: self.twilioVoiceDelegate!)
self.twilioVoiceDelegate!.call = theCall
self.twilioVoiceDelegate!.callCompletionCallback = completionHandler
self.twilioVoiceDelegate!.callInvite = nil
}
}
If anyone has a suggestion, It will be a pleasure
That's is how CallKIt works. Try to receive a call using WhatsApp on iOS. You get the same behavior
in my application I have one requirement. I am running my application and playing one video using AVPlayerViewcontroller. In middle I received call and I had answer the call and my video will pause.After 5 seconds I will get new url from server to play in AVPlayerViewcontroller. That time new url is playing in background can able to hear the sound along with phone call. In this scenario I want to send phone app to background and want to see the video which is playing in avplayer.
Please let me know is there any way to achieve this.
SWIFT:
Observe for Interruption Notifications
func setupNotifications() {
let notificationCenter = NotificationCenter.default
notificationCenter.addObserver(self,
selector: #selector(handleInterruption),
name: .AVAudioSessionInterruption,
object: nil)
}
Respond to Interruption Notifications
#objc func handleInterruption(notification: Notification) {
guard let userInfo = notification.userInfo,
let typeValue = userInfo[AVAudioSessionInterruptionTypeKey] as? UInt,
let type = AVAudioSessionInterruptionType(rawValue: typeValue) else {
return
}
if type == .began {
// Interruption began, take appropriate actions
}
else if type == .ended {
if let optionsValue = userInfo[AVAudioSessionInterruptionOptionKey] as? UInt {
let options = AVAudioSessionInterruptionOptions(rawValue: optionsValue)
if options.contains(.shouldResume) {
// Interruption Ended - playback should resume
} else {
// Interruption Ended - playback should NOT resume
}
}
}
}
Apple documentation
I'm making an app that used circular regions for geofences. When the phone is active or the app is open, the geofence notifications are working fine in both simulator and device (iPhone 6 running 10.3.1).
In the simulator it works fine; When the user enters a region, it wakes up, makes a sound and shows an alert on the lock screen.
On the phone, the "didEnterRegion" delegate calls are made when entering the region (I log some messages) but the phone is not making an alert and waking up. When I push the home button once, I can see the alert on the lock screen, but I want it to wake up and show the alert instantly - like when I get a message. It works in the simulator, so I wonder what could be wrong? It has worked for me a few times, where the alert was shown on both the phone and my watch, but 95% of the time it's not working - the notifications are generated but only visible if I manually wake up the phone.
How to fix this?
Here's the code I use for creating the local notification:
// https://blog.codecentric.de/en/2016/11/setup-ios-10-local-notification/
let location = CLLocation(latitude: item.coordinate.latitude, longitude: item.coordinate.longitude)
GeoTools.decodePosition(location: location) {
(address, city) in
let content = UNMutableNotificationContent()
content.title = "Camera nearby!"
content.subtitle = item.id
content.body = "\(address), \(city)"
content.categoryIdentifier = Constants.notificationCategoryId
content.sound = UNNotificationSound.default()
content.threadIdentifier = item.id
// FIXME make action for clicking notification
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 0.001, repeats: false) // FIXME HACK
let request = UNNotificationRequest(identifier: "camNotification", content: content, trigger: trigger)
let unc = UNUserNotificationCenter.current()
unc.removeAllPendingNotificationRequests()
unc.add(request, withCompletionHandler: { (error) in
if let error = error {
print(error)
}
else {
print("completed")
}
})
}
Here is some code that I just verified wakes the device when notification is presented:
let message = "CLRegion event"
// Show an alert if application is active:
if UIApplication.shared.applicationState == .active {
if let viewController = UIApplication.shared.keyWindow?.rootViewController {
showSimpleAlertWithTitle(nil, message: message, viewController: viewController)
}
}
else {
// Otherwise app is in background, present a local notification:
let content = UNMutableNotificationContent()
content.body = message
content.sound = UNNotificationSound.default()
content.categoryIdentifier = "message"
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 1.0, repeats: false)
let request = UNNotificationRequest(identifier: "com.foobar", content: content, trigger: trigger)
UNUserNotificationCenter.current().add(request, withCompletionHandler: nil)
}
Really the only diff is that I don't call removeAllPendingNotifications() so if you must remove notifications I wonder if removePendingNotificationRequests(withIdentifiers identifiers: [String]) might be more precise?
I'm working on simple To-Do app which uses IOS 8 feature of CLRegion support in UILocalNotification.
Here is the code I'm using to schedule local notification:
var schedule = false
let notification = UILocalNotification()
/// Schedlue with date
if let reminder = self.dateReminderInfo {
schedule = true
notification.fireDate = reminder.fireDate
notification.repeatInterval = reminder.repeatInterval
notification.alertBody = self.title
}
/// Schedule with location
if let reminder = self.locationReminderInfo {
schedule = true
let region = CLCircularRegion(circularRegionWithCenter: reminder.place.coordinate, radius: CLLocationDistance(reminder.distance), identifier: taskObjectID)
region.notifyOnEntry = reminder.onArrive
region.notifyOnExit = reminder.onArrive
notification.region = region
notification.regionTriggersOnce = false
}
/// Schedule
if schedule {
notification.userInfo = ["objectID": taskObjectID]
UIApplication.sharedApplication().scheduleLocalNotification(notification)
}
Scheduling with date works. I schedule notification, exit the app and at the proper time notification is displayed on the screen, great. The problem is when I'm scheduling notification with the location. I pass coordinates and radius in meters (e.g. 100 meters). The app isn't displaying nothing. I was testing it out of the home placing the point in distance of 1km and getting there. No notification displayed. I was playing also with simulator and changing location from available there to custom location near my place and back. No notification displayed. Where is the problem?
In the AppDelegate I'm registering for notifications:
application.registerUserNotificationSettings(UIUserNotificationSettings(forTypes: UIUserNotificationType.Sound | UIUserNotificationType.Alert | UIUserNotificationType.Badge, categories: nil))
Here is the code of location services.
func startLocationService() {
self.locationManager = CLLocationManager()
self.locationManager.delegate = self
self.locationManager.desiredAccuracy = kCLLocationAccuracyBest
let status = CLLocationManager.authorizationStatus()
if status == CLAuthorizationStatus.AuthorizedWhenInUse || status == CLAuthorizationStatus.Denied {
let title = (status == CLAuthorizationStatus.Denied) ? "Location services are off" : "Background location is not enabled"
let message = "To use background location you must turn on 'Always' in the Location Services Settings"
let alertController = UIAlertController(title: title, message: message, preferredStyle: UIAlertControllerStyle.Alert)
/// Settings
alertController.addAction(UIAlertAction.normalAction("Settings", handler: { _ in
UIApplication.sharedApplication().openURL(NSURL(string: UIApplicationOpenSettingsURLString)!)
return
}))
/// Cancel
alertController.addAction(UIAlertAction.cancelAction(String.cancelString(), handler: nil))
self.presentViewController(alertController, animated: true, completion: nil)
} else if status == CLAuthorizationStatus.NotDetermined {
/// nothing
}
self.locationManager.requestAlwaysAuthorization()
self.locationManager.requestWhenInUseAuthorization()
self.locationManager.startUpdatingLocation()
}
CLLocationManager is working because I've got delegate method which is called very often.
/// Mark: CLLocationManagerDelegate
func locationManager(manager: CLLocationManager!, didUpdateLocations locations: [AnyObject]!) {
if let location = locations.first as? CLLocation {
self.navigationItem.title = "\(location.coordinate.latitude)" + " " + "\(location.coordinate.longitude)"
println(self.navigationItem.title)
}
}
I'm trying second day to solve the problem and can't find good solution how to display local notification when user appear in the place or leave the place. I don't know if using UILocalNotification region property is good for this solution or maybe I should create mechanism which will search through the places I've got saved in the app and check every time location change. Any comments to this topic are appreciated too ;)
Thank you in advance.
I see that you are not registering for the notifications
let settings = UIUserNotificationSettings(forTypes: notificationType, categories: categories)
application.registerUserNotificationSettings(settings)
You can find more information here iOS 8 Notifications in Swift and here CoreLocation and region Monitoring
I am having the same issue, however I've found a way to trigger the notification.
It looks like it is not responding to the radius parameter. The way I trigger the notification is by simulating the location being very far away from my region.
So if I set the region for a small city in Denmark, and move my location 1km, and back again, it does not trigger.
However, if I set the region for the same small city in Denmark, and move my location to London and back again, then it will trigger.
So for me it looks like the radius parameter is somehow disgarded? I tried with radius on 250.0, 100.0, 10.0, 0.0001, and it had no impact at all.
Can this maybe inspire someone else to what the problem can be and how to solve it?