Change push notifications programmatically in Swift - ios

I have an app where I have implemented push notifications. I check with the user to allow remote notifications with:
let settings = UIUserNotificationSettings(forTypes: [.Alert, .Badge, .Sound], categories: nil)
UIApplication.sharedApplication().registerUserNotificationSettings(settings)
UIApplication.sharedApplication().registerForRemoteNotifications()
I also store the response in a DB with the device token.
I have also implemented a UISwitch in a settings page to enable/disable push notifications however I have only been able to enable/disable this in the DB column.
My problem is that if a user selected Don't Allow in the initial request, I cannot enable push notifications in the phone settings so even if I set the value to enable in the DB, the notification will never reach the phone as the phone settings are still set to disable.
Is there a way in Swift 2 to change the push notification in the phone setting from within the app instead of the user having to go in to the settings to change? Or is it completely redundant to have a UISwitch allow the user to toggle on/off push notifications?

There isn't any way you can change push notifications permission status from program. Also, prompt asking user to allow push notifications can not be shown again and again. You can refer this https://developer.apple.com/library/ios/technotes/tn2265/_index.html.
The first time a push-enabled app registers for push notifications, iOS asks the user if they wish to receive notifications for that app. Once the user has responded to this alert it is not presented again unless the device is restored or the app has been uninstalled for at least a day.
So using UISwitch to toggle permission status doesn't make any sense unless you use switch status to turn on/off remote notifications from your server.

Updated with swift 4 :
func switchChanged(sender: UISwitch!) {
print("Switch value is \(sender.isOn)")
if(sender.isOn){
print("on")
UIApplication.shared.registerForRemoteNotifications()
}
else{
print("Off")
UIApplication.shared.unregisterForRemoteNotifications()
}
}

if you are using FireBase to send push notifications to your devices , you can use topic subscription to enable push notification in subscribed devices and unsubscribe users from the topic when you don't want the user to receive push notification in those devices that have been unsubscribed.
to subscribe a user to a topic simply import Firebase, then use this method:
Messaging.messaging().subscribe(toTopic: "topicName")
and to unsubscribe a user use:
Messaging.messaging().unsubscribe(fromTopic: "topicName")

if settings.authorizationStatus != .authorized{
// Either denied or notDetermined
// Ask the user to enable it in settings.
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge]) {
(granted, error) in
// add your own
UNUserNotificationCenter.current().delegate = self
let alertController = UIAlertController(title: appName, message: "Please enable notifications in settings and try again.", preferredStyle: .alert)
let settingsAction = UIAlertAction(title: "Settings", style: .default) { (_) -> Void in
guard let settingsUrl = URL(string: UIApplication.openSettingsURLString) else {
return
}
if UIApplication.shared.canOpenURL(settingsUrl) {
UIApplication.shared.open(settingsUrl, completionHandler: { (success) in
})
}
}
let cancelAction = UIAlertAction(title: "Cancel", style: .default, handler: nil)
alertController.addAction(cancelAction)
alertController.addAction(settingsAction)
DispatchQueue.main.async {
(UIApplication.shared.delegate as? AppDelegate)?.window?.rootViewController?.present(alertController, animated: true, completion: nil)
}
}
}

Related

Local Notification not working in iOS 10.3.1

I have an app which will send a local notification for specific location change when the app is in background or not running. Im using region monitoring to get location changes and create notification request if needed. My problem is the notification is not working in iOS 10 where as it is working fine in iOS 11 & 12. Below is the code to create a notification request.
func getRequest() {
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound]) { (granted, error) in
if granted {
DispatchQueue.main.async {
self.scheduleNotification()
}
}
}
}
func scheduleNotification() {
let timeTrigger = UNTimeIntervalNotificationTrigger.init(timeInterval: 15.0, repeats: false)
let center = UNUserNotificationCenter.current()
let content = UNMutableNotificationContent.init()
content.title = "Notification works!"
content.sound = UNNotificationSound.default
let request = UNNotificationRequest.init(identifier: "LocalNotification", content: content, trigger: timeTrigger)
center.add(request) { (error) in
print(error?.localizedDescription ?? "No Error")
}
}
Is there anything I miss here that must be included for iOS 10? Why is it not working in iOS 10 alone?
After spending some hours on stack over flow, thanks to this answer in another post. We should also mention the content.body. This should not be empty in iOS 10. For some reason the notification works without body in iOS 11 & 12.

UNMutableNotificationContent with custom sound

I'm trying to create a simple demo where I click a button, and a custom notification appears, accompanied with a custom sound.
The behaviour that happens:
The notification appears properly on both emulator and connected device (iPhone SE)
The standard sound plays on emulator, but not on the device.
The custom sound does not play at all. On the emulator I get the default notification sound, while on the device I don't get any sound at all.
The sound file 'alert.caf' was added to the Assets.xcassets.
This is my class (access rights have been handled in the app launch callback):
import UIKit
import UserNotifications
class HomeViewController: UIViewController, UNUserNotificationCenterDelegate {
#IBOutlet weak var btnNotification: UIButton!
#IBAction func triggerNotification(_ sender: Any) {
/* let alert = UIAlertController(title: "Alert", message: "Message", preferredStyle: UIAlertController.Style.alert)
alert.addAction(UIAlertAction(title: "Click", style: UIAlertAction.Style.default, handler: nil))
self.present(alert, animated: true, completion: nil) */
execNotification()
}
override func viewDidLoad() {
super.viewDidLoad()
UNUserNotificationCenter.current().delegate = self
}
func userNotificationCenter(
_ center: UNUserNotificationCenter,
willPresent notification: UNNotification,
withCompletionHandler completionHandler:
#escaping (UNNotificationPresentationOptions) -> Void) {
completionHandler([.alert,.sound])
}
private func execNotification() {
let notificationContent = UNMutableNotificationContent()
notificationContent.title = "UNUserNotification Sample"
notificationContent.body = "Sample test msg"
notificationContent.badge = 0
notificationContent.sound = UNNotificationSound(named: UNNotificationSoundName(rawValue: "alert.caf"))
notificationContent.categoryIdentifier = "GENERAL"
let notificationTrigger = UNTimeIntervalNotificationTrigger(timeInterval: 0.2, repeats: false)
let notificationRequest = UNNotificationRequest(identifier: "general", content: notificationContent, trigger: notificationTrigger)
// Add Request to User Notification Center
UNUserNotificationCenter.current().add(notificationRequest) { (error) in
if let error = error {
print("Unable to Add Notification Request (\(error), \(error.localizedDescription))")
}
}
}
}
Note: I checked all settings that may have cause the alert not to be played, but there really is no reason. The app settings are correct, there is no "Do not disturb" mode on.
I use Xcode 10.1
I also could not setup custom sound with only difference that i was developing app for macOS.
My setup:
macOS Big Sur 11.4
Xcode 12.5
Solution:
Custom sound was played only by adding 'wav' file to project ('caf' did not work) and then restarting mac.
Considering similarities between iOS and mac platforms i would suggest to add 'wav' file to the project + restart iPhone.
A few tips to note:
Make sure the sound file is located in the current executable’s main bundle or in the Library/Sounds directory of the current app container directory (source).
Make sure the sound file is less than 30 seconds. If the sound file is longer than 30 seconds, the system plays the default sound instead (source).
Make sure the sound file is added in your target's "Copy Bundle Resources" in Build Phases.
Select the sound file and make sure in its "Target Membership" your app's target is properly selected.

TabItem with BadgeValue only shows up when I launch the app from the notification trigger

I have an app with push notifications using PHP EasyAPNS notification working fine on Swift 3, iOS 10. But one thing I can't understand is why the badge on TabItem is works fine when I launch the app from the notification alert but not when I open the app direct from the app icon (with the red Badge)
So here is the code that I use on AppDelegate:
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [String:Any])
{
print("Message details \(userInfo)")
if let aps = userInfo["aps"] as? NSDictionary
{
if let alertMessage = aps["alert"] as? String {
let rootViewController = self.window?.rootViewController as! UITabBarController!
let tabArray = rootViewController?.tabBar.items as NSArray!
let tabItem = tabArray?.object(at: 3) as! UITabBarItem
tabItem.badgeValue = "1"
let myAlert = UIAlertController(title: "Message", message: alertMessage, preferredStyle: UIAlertControllerStyle.alert)
let okAction = UIAlertAction(title: "Ok", style: UIAlertActionStyle.default, handler: nil)
myAlert.addAction(okAction)
self.window?.rootViewController?.present(myAlert, animated: true, completion: nil)
}
}
}
So, when I click in the alert to open my app, the badge is fine like this:
But when I open the app using the icon itself, the badge is not showing up:
Anyone have any idea what I'm doing wrong?
Please let me know if I can improve the question!
You should be using the application(_:didReceiveRemoteNotification:fetchCompletionHandler:) method to handle notifications. As mentioned in the docs (found here), this method is called whether the app is in the foreground or background.
Also worth noting from the docs for application(_:didReceiveRemoteNotification:)
If the app is not running when a remote notification arrives, the
method launches the app and provides the appropriate information in
the launch options dictionary. The app does not call this method to
handle that remote notification.
Note, if the app was not running and the user taps on the icon, the app will call application(_:didFinishLaunchingWithOptions:). There will be appropriate launchOption key-value pairs if the app had a remote notification which needs handling.

Apple watch local notification not working when the watch isn't worn

We are using the UNUserNotification framework provided by WatchOS 3.0 to create local notification to notify user at a predefined moment. However, the notification is not shown when the watch is not being worn on the wrist. It does work well when someone is wearing it.
We cannot find this description on any documentation. Is that normal? If yes, how to help the user to avoiding missing some notifs ?
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge]) { (granted, error) in
// Enable or disable features based on authorization.
if granted {
let content = UNMutableNotificationContent()
content.title = title
content.body = body
content.sound = UNNotificationSound.default()
content.userInfo = userInfo
let trigger = UNTimeIntervalNotificationTrigger.init(
timeInterval: interval,
repeats: false)
let identifier = stringWithUUID()
let request = UNNotificationRequest.init(
identifier: identifier,
content: content,
trigger: trigger
)
UNUserNotificationCenter.current().add(request, withCompletionHandler: nil)
completion?(true, nil)
}
else {
completion?(false, error)
}
}
This is normal, Apple Watch automatically locks when you take it off your wrist and notifications go to your iPhone instead.

iOS permission alert issue

I have a view that:
Creates an observer for UIApplicationDidBecomeActiveNotification with invokes a selector
Sequentially asks the user for permissions to: use the camera, location & receiving push notifications.
The view has three UIButtons with state depending on each permission state, which navigate the user to settings if permissions for anything were rejected
Tapping a button which represents a permission with rejected state navigates the user to settings
Once each alert hides, using the observer action, next alert is triggered and all button states are updated to reflect any changes
Once all permissions are granted it pushes next view with the rest of the signup/in flow.
The problem is: on some devices, when running the app from a clean state (app removed and reinstalled), permissions for location & notifications are set to rejected by default, as if the user was presented an alert that was rejected.
I couldn't pinpoint any rational issue behind this, except for leftover settings from some outdated build that don't get deleted when installing a new one. This view seems to be the only place that can possibly trigger these alerts.
Did anyone have a similar issue and can suggest anything?
I would suggest you to try to check for states of location services and notification services before asking user to use it. Since if user is going to disable these the moment you ask him for permission, he will need to go to the settings and enable it there. You should try to detect if user has disabled location/notification/camera.
For camera use:
func accessToCamera(granted: #escaping (() -> Void)) {
if UIImagePickerController.isSourceTypeAvailable(.camera) {
let status = AVCaptureDevice.authorizationStatus(forMediaType: AVMediaTypeAudio)
if status == .authorized {
granted()
} else if status == .denied {
self.cameraPermissionAlert()
} else if status == .notDetermined {
AVCaptureDevice.requestAccess(forMediaType: AVMediaTypeVideo, completionHandler: { (accessAllowed) in
if accessAllowed {
granted()
} else {
self.cameraPermissionAlert()
}
})
} else if status == .restricted {
self.cameraPermissionAlert()
}
} else {
print("Camera not available on this device")
}
}
func cameraPermissionAlert() {
let alert = UIAlertController(title: "Access to camera not available", message: "Please enable access to camera in order to use this feature", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Settings", style: .default, handler: { (action) in
if let url = URL(string: UIApplicationOpenSettingsURLString) {
if UIApplication.shared.canOpenURL(url) {
UIApplication.shared.open(url, options: [:], completionHandler: nil)
}
}
}))
alert.addAction(UIAlertAction(title: "Cancel", style: .default, handler: nil))
if let top = UIApplication.topViewController() { // This is extension to UIApplication that finds top view controller and displays it
top.present(alert, animated: true, completion: nil)
}
}
For remote notifications you can use something like this:
Determine on iPhone if user has enabled push notifications
And for location services:
Check if location services are enabled
In both of these cases you can detect if this is disabled or not by user and present user with alert controller that has open settings functionality.

Resources