didRegisterForRemoteNotificationsWithDeviceToken does not get triggered - Push Notifications not working - swift

I've spent hours now trying to figure out why didRegisterForRemoteNotificationsWithDeviceToken is not being called.
It worked before. I didn't touch it in weeks. Now stopped working.
Here's my setup:
Got didRegisterForRemoteNotificationsWithDeviceToken sitting in SceneDelegate
Have declared SceneDelegate to be the UNUserNotificationCenterDelegate
I'm setting UNUserNotificationCenter.current().delegate = self in func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions)
I'm calling UNUserNotificationCenter.current().requestAuthorization { granted, error in... from one of the UIViewControllers in my App, specifically in viewDidLoad of that Controller - I get the Authorization Pop-up and when accepting I get a true back for granted
Within UNUserNotificationCenter.current().getNotificationSettings { settings in ... a check for settings.authorizationStatus == .authorized returns true
Push Notifications are added as a capability for the App in "Signing & Capabilities" and the entitlement file has been created. I've even already deleted and re-created the entitlement file - just to be sure...
I've made sure to run the app on a real device (both via Xcode as well as via TestFlight) - so the delegate not being called is not related to the App running in the Simulator.
Have checked if didFailToRegisterForRemoteNotificationsWithError gets called instead at least - but it doesn't.
I have tried with a second physical device which I have never used for testing before.
I've even checked the status of the APNS Sandbox here: https://developer.apple.com/system-status/
The didReceive delegate gets called though if I'm sending a test notification to simulator via terminal command xcrun simctl push... and I'm getting the notification.
Provisioning profile is managed by Xcode and the specified AppID is configured for Push Notifications. Certificates are set up in apple developer account.
Here's how I'm requesting Authorization from the user within viewDidLoad of one of my Apps UIViewControlles
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { granted, error in
DispatchQueue.main.async {
if (granted) {
//self.processInitialAPNSRegistration()
UIApplication.shared.registerForRemoteNotifications()
UserDefaults.standard.set(true, forKey: "pushNotificationsEnabled")
}
print("permission granted?: \(granted)")
}
}
And here's the didRegisterForRemoteNotificationsWithDeviceToken delegate method sitting inside SceneDelegate. Note: I'm also setting UNUserNotificationCenter.current().delegate = self in scene willConnectTo and declaredSceneDelegateto implement theUNUserNotificationCenterDelegate` protocol.
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
let token = deviceToken.map { String(format:"%02.2hhx", $0) }.joined()
print("didRegisterForRemoteNotificationsWithDeviceToken GOT CALLED - APNS TOKEN IS: \(token)")
self.apiService.setAPNSToken(apnsToken: token, completion: {result in
switch result {
case .success(let resultString):
DispatchQueue.main.async {
UserDefaults.standard.set(token, forKey: "apnsToken")
print(resultString, " TOKEN IS: \(token)")
}
case .failure(let error):
print("AN ERROR OCCURED: \(error.localizedDescription)")
}
})
}
Whatever I do, didRegisterForRemoteNotificationsWithDeviceToken is not getting triggered. I'm running out of ideas on what is going wrong.
Where is the error? How can I get didRegisterForRemoteNotificationsWithDeviceToken to execute again?

I think you have gone wrong in the second step didRegisterForRemoteNotificationsWithDeviceToken, didFailToRegisterForRemoteNotificationsWithError delegates are available in UIApplicationDelegate not in UNUserNotificationCenterDelegate
Add UIApplicationDelegate to your SceneDelegate class and in willConnectTo function set the delegate as:
UIApplication.shared.delegate = self
I hope this works... let me know if you still see an issue.

Have you made sure that you use the right provisioning profile which is using a push notification enabled AppId? This one is missing in your steps.
As I am not eligible to put comments yet, posting this in answer.

Related

Firebase Cloud Messaging on iOS: 'application:didRegisterForRemoteNotificationsWithDeviceToken' must be present, but is never called

I'm adding Firebase Cloud Messaging in an iOS app (iOS 15.2.1 on a 7th Gen iPad), and I am able to get it to work, but have taken a step that the Firebase docs don't specify, and I don't understand why it's working and why it doesn't work when I don't do this.
First, I'm following these Firebase docs. These docs make no mention of needing to have an application:didRegisterForRemoteNotificationsWithDeviceToken method in the App Delegate. However, if I don't have this method present, I consistently get this error spewed to the console whenever I try to access the token with Messaging.messaging().token:
2022-01-31 22:00:57.448319-0800 authNavFirebaseUISample[4755:1363959] 8.10.0 - [Firebase/Messaging][I-FCM002022] APNS device token not set before retrieving FCM Token for Sender ID 'XXXXXXXXXXXX'. Notifications to this FCM Token will not be delivered over APNS.Be sure to re-retrieve the FCM token once the APNS device token is set.
When I do add in this method, I no longer get that error when accessing the token via Messaging.messaging().token. Furthermore, when this method is present, I can successfully send test notifications via the Notification Composer.
What's particularly strange about all of this is that the actual application:didRegisterForRemoteNotificationsWithDeviceToken is never actually invoked. However, its presence is required and without it, I get that error spew and Notification Composer doesn't work.
I've added relevant code snippets below. Any ideas? Thanks!
App Delegate
class AppDelegate: NSObject, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
FirebaseApp.configure()
NotificationHelpers.setupRemoteNotifications(application)
return true
}
// NOTE: Why is this needed?!?!
// If this method isn't present, each time we try to retrieve the FCM (Firebase Cloud
// Messaging) token, we get:
// APNS device token not set before retrieving FCM Token for Sender ID 'XXXXXXXXXXXX'. Notifications to
// this FCM Token will not be delivered over APNS.Be sure to re-retrieve the FCM token once the APNS
// device token is set.
// Adding this method prevents that. However, the method never actually gets executed. (Try putting a
// print statement in there, it won't print.) But if you remove the method, then we get the above error.
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
}
func application(
_ application: UIApplication,
configurationForConnecting connectingSceneSession: UISceneSession,
options: UIScene.ConnectionOptions
) -> UISceneConfiguration {
let sceneConfig = UISceneConfiguration(name: nil, sessionRole: connectingSceneSession.role)
sceneConfig.delegateClass = MySceneDelegate.self
return sceneConfig
}
}
NotificationHelpers
class NotificationHelpers {
static func setupRemoteNotifications(_ application: UIApplication) -> Void {
let delegate = NotificationsDelegate()
// For iOS 10 and above display notification (sent via APNS)
UNUserNotificationCenter.current().delegate = delegate
let authOptions: UNAuthorizationOptions = [.alert, .badge, .sound]
UNUserNotificationCenter.current().requestAuthorization(
options: authOptions,
completionHandler: { _, _ in }
)
application.registerForRemoteNotifications()
Messaging.messaging().delegate = delegate
}
}
Token accessing code (invoked from button press in app)
Button("retrieve FCM") {
// Get token here, but really can be from anywhere
Messaging.messaging().token { token, error in
if let error = error {
print("Error fetching FCM registration token: \(error)")
} else if let token = token {
print("FCM registration token: \(token)")
}
}
}

how to test in ios device about bgtaskscheduler function not using debug function

i have no problem when using debug function in my ios device not simulator.
(ex, e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateLaunchForTaskWithIdentifier:#"TASK_IDENTIFIER"] )
but when do not using debug function, follow my code, it will be play the music after 60 seconds going to background. however nothing to happen in the device.
how do i test the device not using debug function?
import UIKit
import BackgroundTasks
import os.log
import AVFoundation
private let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "AppDelegate")
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
let bgTaskIdentifier = "com.hakjun.bgTest.playMusic"
var alarmTime : Int = 0
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
BGTaskScheduler.shared.register(forTaskWithIdentifier: bgTaskIdentifier, using: nil) { task in
self.handleAppRefresh(task: task as! BGAppRefreshTask)
print("test bg")
}
return true
}
func scheduleAppRefresh(time : Double) {
let request = BGAppRefreshTaskRequest(identifier: bgTaskIdentifier)
request.earliestBeginDate = Date(timeIntervalSinceNow: time)
do {
try BGTaskScheduler.shared.submit(request)
print("schedule app refresh")
} catch {
print("Could not schedule app refresh task \(error.localizedDescription)")
}
}
func handleAppRefresh(task : BGAppRefreshTask){
scheduleAppRefresh(time: 60)
let queue = OperationQueue()
queue.maxConcurrentOperationCount = 1
let appRefreshOperation = BlockOperation {
Singleton.sharedInstance.play()
}
// queue.addOperation(appRefreshOperation)
task.expirationHandler = {
print("expire background")
queue.cancelAllOperations()
}
let lastOperation = queue.operations.last
lastOperation?.completionBlock = {
task.setTaskCompleted(success: !(lastOperation?.isCancelled ?? false))
}
print("background handle")
queue.addOperation(appRefreshOperation)
}
// MARK: UISceneSession Lifecycle
func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
// Called when a new scene session is being created.
// Use this method to select a configuration to create the new scene with.
return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
}
func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) {
// Called when the user discards a scene session.
// If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
// Use this method to release any resources that were specific to the discarded scenes, as they will not return.
}
func applicationDidEnterBackground(_ application: UIApplication) {
print("test bg os log2")
logger.log("App did enter background")
scheduleAppRefresh(time: 60)
}
}
class Singleton {
static let sharedInstance = Singleton()
private var player: AVAudioPlayer?
func play() {
let audioSession = AVAudioSession.sharedInstance()
guard let url = Bundle.main.url(forResource: "alarm2", withExtension: "mp3") else { return }
do {
try audioSession.setCategory(.playback, mode: .default, options: [])
} catch let error as NSError {
print("audioSession 설정 오류 : \(error.localizedDescription)")
}
do {
try AVAudioSession.sharedInstance().setCategory(AVAudioSession.Category.playback)
try AVAudioSession.sharedInstance().setActive(true)
player = try AVAudioPlayer(contentsOf: url, fileTypeHint: AVFileType.mp3.rawValue)
guard let player = player else { return }
player.play()
} catch let error {
print(error.localizedDescription)
}
}
func stop() {
player?.stop()
}
}
FYI, BGAppResfreshTask is for “updating your app with small bits of information”, i.e., for performing a small network request to refresh your app so that, when the user next launches the app, you have more current information ready and waiting for them. But this app refresh is performed at a time chosen at the discretion of the OS, based upon many factors, but not earlier than earliestBeginDate.
Thus is not appropriate for an alarm clock because (a) you are not doing a network request to refresh your app; and (b) it is not guaranteed to run at the designated “earliest” date, only some time thereafter.
You might consider scheduling a user notification, instead.
You asked:
how do i test the device not using debug function?
You add logging statements. But rather than using print or NSLog, one would add Logger statements as discussed in WWDC 2020 Explore logging in Swift. (Or, if supporting iOS versions prior to iOS 14, use os_log; this was described in WWDC 2016 video Unified Logging and Activity Tracing, but that video is no longer available.) These Logger/os_log logging statements issued from an iOS app can be monitored from the macOS Console app.
So, once you have added your logging messages in your code in the relevant spots, using Logger (or os_log), you can then
install app on your device,
connect device to your computer,
launch the app directly from your device and
you can watch the log statements issued by your app in your macOS Console.
See points 3 and 4 in Swift: print() vs println() vs NSLog().
But note, you do not want to run the app from Xcode. You can install it by running it from Xcode, but then stop execution and re-launch the app directly on the device, not using Xcode. Unfortunately, being attached to the Xcode debugger keeps the app artificially running in the background when it would really be otherwise suspended when running independently on the device. So, when testing background execution on a physical device, do not debug it from Xcode directly, but rather add logging statements, launch the app directly from the device, and watch the logging statements in the macOS console.
Alternatively, sometimes background processes happen hours later, so I also will occasionally write log statements to a text file in the Application Support directory, and revisit that file later (by downloading the container back to my Mac later). In the case of background fetch and background tasks (which can happen hours or days later), this can be useful. In the case of an alarm app, though, the macOS Console approach outlined above is easiest.

ATTrackingManager stopped working in iOS 15

ATTrackingManager.requestTrackingAuthorization stopped working on ios 15. Application rejected from Apple.
According to the discussion in Apple Developer Forum, you need to add delay for about one second when calling requestTrackingAuthorization.
https://developer.apple.com/forums/thread/690607
Example:
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0, execute: {
ATTrackingManager.requestTrackingAuthorization(completionHandler: { status in
// Tracking authorization completed. Start loading ads here.
// loadAd()
})
})
P.S.
Also if you have requesting push notification permission, firstly you need request push notification then request tracking authorization with a delay =>
private func requestPushNotificationPermission() {
let center = UNUserNotificationCenter.current()
UNUserNotificationCenter.current().delegate = self
center.requestAuthorization(options: [.sound, .alert, .badge], completionHandler: { (granted, error) in
if #available(iOS 14.0, *) {
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0, execute: {
ATTrackingManager.requestTrackingAuthorization(completionHandler: { status in
// Tracking authorization completed. Start loading ads here.
// loadAd()
})
})
}})
UIApplication.shared.registerForRemoteNotifications()
}
The problem has been solved, just call it in applicationDidBecomeActive:
https://developer.apple.com/forums/thread/690762
Follow by apple doc:
Calls to the API only prompt when the application state is UIApplicationStateActive.
So, we need to call ATTrackingManager.requestTrackingAuthorization on
applicationDidBecomeActive of AppDelegate.
But If you're using scenes (see Scenes), UIKit will not call this method. Use sceneDidBecomeActive(_:) instead to restart any tasks or refresh your app’s user interface. UIKit posts a didBecomeActiveNotification regardless of whether your app uses scenes.
So, my approach is to register on addObserver on didFinishLaunchingWithOptions such as:
NotificationCenter.default.addObserver(self, selector: #selector(handleRequestEvent), name: UIApplication.didBecomeActiveNotification, object: nil)
on handleRequestEvent:
requestPermission() // func call ATTrackingManager.requestTrackingAuthorization NotificationCenter.default.removeObserver(self, name: UIApplication.didBecomeActiveNotification, object: nil)
Hope this helps. It's work for me.
Make sure your iPhone's Settings -> Privacy -> Tracking is enabled. Otherwise, it won't prompt for request Aurthorization.

snapkit login kit not working Swift Xcode 11.3.1

I am using Xcode 11.3.1 and try to login with snapchat with loginkit I add the add information in info.plist and my code is
SCSDKLoginClient.login(from: self, completion: { success, error in
if let error = error {
print(error.localizedDescription)
return
}
if success {
self.fetchSnapUserInfo() //example code
}
})
this code show me the login ui of snapchat and I am login into snapchat with my account.
but I am stuck on this ui
when I am click on continue nothing is happing . SCSDKLoginClient completion block not called.
Please ensure your URL scheme is configured correctly.
URL Schemes
hello everyOne soo finally i found the solution
i am using 11.3.1 and when i create new project the add
AppDelegate and SceneDelegate default class.
so according to snapchat logkit documentation i add
func application(_ app: UIApplication,
open url: URL,
options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
return SCSDKLoginClient.application(app, open: url, options: options)
}
method in my Appdelegate class. but this method never get called in xocode 11.3.1
so the solution of my problem is this
func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
guard let url = URLContexts.first?.url else {
return
}
SCSDKLoginClient.application( UIApplication.shared, open: url, options: nil)
}
you need to add this method into your sceneDelegate file. then everything work fine.
synpchat need to update there doc for new xcode 11.3.1. i hope this answer help you guy's because i also wast my 3 day's on this issue.
happy Coding :)

Thread1: signal SIGABRT

I am getting Thread 1: signal SIGABRT error on appDelegate, I think it is because of facebook login button which I have been trying to connect to my xcode project through facebook sdk. I dont know if the way i am connecting facebook login button through outlet is correct or not. because at first it was giving me an error of optional unwrapping, when I avoided it by adding ? in the code below
fbloginbtnview?.delegate = self
fbloginbtnview?.permissions = ["email"]
now I get signal SIGABRT error.
I have been watching all the tutorials and reading all the questions on stackoverflow, but can not find anything helpful to connect facebook login button, because al the helps available are either for older versions of swift and xcode or I dont get axactly what i want.
my swift version is 5, and xcode 9.3
can anyone please give me a right peice of code to connect a facebook login button?
This is appdelegate
import UIKit
import Firebase
import CoreData
import FirebaseAuth
import FacebookCore
import FBSDKCoreKit
import FBSDKLoginKit
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
//below for fb sdk
//....
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
ApplicationDelegate.shared.application(application, didFinishLaunchingWithOptions: launchOptions)
return true
}
//....
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any]) -> Bool {
guard let urlScheme = url.scheme else { return false }
if urlScheme.hasPrefix("fb") {
return ApplicationDelegate.shared.application(app, open: url, options: options)
}
return true
}
// above for fb sdk nothing
//
func applicationWillResignActive(_ application: UIApplication) {
// 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, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
}
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.
}
func applicationWillEnterForeground(_ application: UIApplication) {
// Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
}
func applicationDidBecomeActive(_ application: UIApplication) {
// 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 applicationWillTerminate(_ application: UIApplication) {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
// Saves changes in the application's managed object context before the application terminates.
self.saveContext()
}
// MARK: - Core Data stack
lazy var persistentContainer: NSPersistentContainer = {
/*
The persistent container for the application. This implementation
creates and returns a container, having loaded the store for the
application to it. This property is optional since there are legitimate
error conditions that could cause the creation of the store to fail.
*/
let container = NSPersistentContainer(name: "LetsGoTogether")
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
// Replace this implementation with code to handle the error appropriately.
// fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
/*
Typical reasons for an error here include:
* The parent directory does not exist, cannot be created, or disallows writing.
* The persistent store is not accessible, due to permissions or data protection when the device is locked.
* The device is out of space.
* The store could not be migrated to the current model version.
Check the error message to determine what the actual problem was.
*/
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
return container
}()
// MARK: - Core Data Saving support
func saveContext () {
let context = persistentContainer.viewContext
if context.hasChanges {
do {
try context.save()
} catch {
// Replace this implementation with code to handle the error appropriately.
// fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
let nserror = error as NSError
fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
}
}
}
}
This below is my View controller code
import UIKit
import Firebase
import FBSDKLoginKit
import FacebookCore
class ViewController: UIViewController, UINavigationControllerDelegate, UIImagePickerControllerDelegate, LoginButtonDelegate {
#IBOutlet var fbloginbtnview: FBLoginButton!
override func viewDidLoad() {
super.viewDidLoad()
// fb
fbloginbtnview?.delegate = self
fbloginbtnview?.permissions = ["email"]
}
func loginButton(_ loginButton: FBLoginButton, didCompleteWith result: LoginManagerLoginResult?, error: Error?) {
if let error = error {
print("error took place\(error.localizedDescription)")
return
}
print("Success")
}
func loginButtonDidLogOut(_ loginButton: FBLoginButton) {
print("user signed out")
}
}
You have declared fbloginbtnview as an implicit optional, which is normal but means it is assumed to be valid. References to it will fail if you haven't connected the outlet to an actual button (or to a button of the right type) in interface builder.