I hosted Ejabberd chat server on Amazon and added two user there, when I tried to connect Ejabberd server through Adium, it asks for certificate then only it gets connected, now I'm developing chat application in Swift using Ejabberd server and XMPP, I configured all code , passed host name and port no: 5222, but its not connecting to server.
Should I need to write program to fetch server certificate and pass my computer certificate (.p12) file to server?
NOTE: I configured Ejabberd server in localhost and through iOS Swift Code, it's working perfectly when I send message from iOS app then it shows in Adium and when Adium user send message then I go to Ejabberd Web Admin Panel and can check offline Messages.
Here is the code in Swift for connecting Ejabberd hosted on Amazon:
//
// AppDelegate.swift
// Thanks to Process One for this.
import UIKit
import XMPPFramework
protocol ChatDelegate {
func buddyWentOnline(_ name: String)
func buddyWentOffline(_ name: String)
func didDisconnect()
}
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate, XMPPRosterDelegate, XMPPStreamDelegate {
var window: UIWindow?
var delegate:ChatDelegate! = nil
let xmppStream = XMPPStream()
let xmppRosterStorage = XMPPRosterCoreDataStorage()
var xmppRoster: XMPPRoster
override init() {
xmppRoster = XMPPRoster(rosterStorage: xmppRosterStorage)
}
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
DDLog.add(DDTTYLogger.sharedInstance())
setupStream()
return true
}
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 throttle down OpenGL ES frame rates. Games should use this method to pause the game.
disconnect()
}
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.
connect()
}
func applicationWillTerminate(_ application: UIApplication) {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
}
//MARK: Private Methods
func setupStream() {//fileprivate
//xmppRoster = XMPPRoster(rosterStorage: xmppRosterStorage)
xmppRoster.activate(xmppStream)
xmppStream?.addDelegate(self, delegateQueue: DispatchQueue.main)
xmppRoster.addDelegate(self, delegateQueue: DispatchQueue.main)
}
func goOnline() { //fileprivate
let presence = XMPPPresence()
let domain = xmppStream?.myJID.domain
if domain == "gmail.com" || domain == "gtalk.com" || domain == "talk.google.com" {
let priority = DDXMLElement.element(withName: "priority", stringValue: "24") as! DDXMLElement
presence?.addChild(priority)
}
xmppStream?.send(presence)
}
func goOffline() { //fileprivate
let presence = XMPPPresence(type: "unavailable")
xmppStream?.send(presence)
}
func connect() -> Bool {
xmppStream.hostName = "amazon hosted ejabber ip address"
xmppStream.hostPort=5222
if !(xmppStream?.isConnected())! {
let jabberID = UserDefaults.standard.string(forKey: "userID")
let myPassword = UserDefaults.standard.string(forKey: "userPassword")
if !(xmppStream?.isDisconnected())! {
return true
}
if jabberID == nil && myPassword == nil {
return false
}
xmppStream?.myJID = XMPPJID.init(string: jabberID)
do {
try xmppStream?.connect(withTimeout: XMPPStreamTimeoutNone)
print("Connection success")
return true
} catch {
print("Something went wrong!")
return false
}
} else {
return true
}
}
func disconnect() {
goOffline()
xmppStream?.disconnect()
}
//MARK: XMPP Delegates
func xmppStreamDidConnect(_ sender: XMPPStream!) {
do {
try xmppStream?.authenticate(withPassword: UserDefaults.standard.string(forKey: "userPassword"))
} catch {
print("Could not authenticate")
}
}
func xmppStreamDidAuthenticate(_ sender: XMPPStream!) {
goOnline()
}
func xmppStream(_ sender: XMPPStream!, didReceive iq: XMPPIQ!) -> Bool {
print("Did receive IQ")
return false
}
func xmppStream(_ sender: XMPPStream!, didReceive message: XMPPMessage!) {
print("Did receive message \(message)")
}
func xmppStream(_ sender: XMPPStream!, didSend message: XMPPMessage!) {
print("Did send message \(message)")
}
func xmppStream(_ sender: XMPPStream!, didReceive presence: XMPPPresence!) {
let presenceType = presence.type()
let myUsername = sender.myJID.user
let presenceFromUser = presence.from().user
if presenceFromUser != myUsername {
print("Did receive presence from \(presenceFromUser)")
if presenceType == "available" {
delegate.buddyWentOnline("\(presenceFromUser)#gmail.com")
} else if presenceType == "unavailable" {
delegate.buddyWentOffline("\(presenceFromUser)#gmail.com")
}
}
}
func xmppRoster(_ sender: XMPPRoster!, didReceiveRosterItem item: DDXMLElement!) {
print("Did receive Roster item")
}
}
Finally i managed to solve this issue and i am really amaze to implement chat application using Ejabberd server hosted on Amazon.
Issue :
1. When i was running my application , it was not connecting ?
Answer : Certificate issue (Server - client side certificate handshake resolves issue)
2. Unable to fetch online buddy ?
Answer : i used Adium to make all user online and then run my application and it works like a charm.
Great learning for me for this application.
If any1 having any issue with chat ,feel free to comment i am looking forward to help. Thanks
Related
I'm trying to use applicationWillResignActive() in order to sync some data to my Firestore database before the application enters the background.
func applicationWillResignActive(_ application: UIApplication) {
self.uploadWantToPlay()
}
When I call my upload function from applicationWillResignActive() it runs but no data is added to Firestore before the next time the application becomes active.
When I for testing purposes instead run the same function from one of my ViewControllers the data is added instantly to Firestore.
I've also tried calling the function from applicationDidEnterBackground(), I've tried running it in it's own DispatchQueue. But it's had the same result.
How can I run this function as the user is about to leave the app and have it perform the database sync properly?
The functions handling the database sync;
func uploadWantToPlay() {
print ("Inside uploadWantToPlay")
if let wantToPlay = User.active.wantToPlayList {
if let listEntries = wantToPlay.list_entries {
let cleanedEntries = listEntries.compactMap({ (entry: ListEntry) -> ListEntry? in
if entry.game?.first_release_date != nil {
return entry
} else {
return nil
}
})
let gamesToUpload = cleanedEntries.filter {
$0.game!.first_release_date! > Int64(NSDate().timeIntervalSince1970 * 1000)
}
DatabaseConnection().writeWantToPlayToDatabase(user: User.active,wantToPlay: gamesToUpload)
}
}
}
func writeWantToPlayToDatabase(user: User, wantToPlay: [ListEntry]) {
firebaseSignIn()
let deviceId = ["\(user.deviceId)": "Device ID"]
for entry in wantToPlay {
let wantToPlayGameRef = fireStore.collection(WANTTOPLAY).document("\(entry.game!.id!)")
wantToPlayGameRef.updateData(deviceId) {(err) in
if err != nil {
wantToPlayGameRef.setData(deviceId) {(err) in
if let err = err {
Events().logException(withError: err, withMsg: "DatabaseConnection-writeWantToPlayToDatabase(user, [ListEntry]) Failed to write to database")
} else {
print("Document successfully written to WantToPlayGames")
}
}
} else {
print("Document successfully updated in WantToPlayGames")
}
}
}
}
According to the Apple documentation
Apps moving to the background are expected to put themselves into a
quiescent state as quickly as possible so that they can be suspended
by the system. If your app is in the middle of a task and needs a
little extra time to complete that task, it can call the
beginBackgroundTaskWithName:expirationHandler: or
beginBackgroundTaskWithExpirationHandler: method of the UIApplication
object to request some additional execution time. Calling either of
these methods delays the suspension of your app temporarily, giving it
a little extra time to finish its work. Upon completion of that work,
your app must call the endBackgroundTask: method to let the system
know that it is finished and can be suspended.
So, what you need to do here is to perform a finite length task while your app is being suspended. This will buy your app enough time to sync your records to the server.
An example snippet:
import UIKit
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
var backgroundTask: UIBackgroundTaskIdentifier!
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
return true
}
func applicationWillResignActive(_ application: UIApplication) {
self.registerBackgroundTask()
// Do your background work here
print("Do your background work here")
// end the task when work is completed
self.endBackgroundTask()
}
func registerBackgroundTask() {
self.backgroundTask = UIApplication.shared.beginBackgroundTask { [weak self] in
self?.endBackgroundTask()
}
assert(self.backgroundTask != UIBackgroundTaskInvalid)
}
func endBackgroundTask() {
print("Background task ended.")
UIApplication.shared.endBackgroundTask(backgroundTask)
backgroundTask = UIBackgroundTaskInvalid
}
}
For further information refer to this article.
I am currently developing an iOS app to login to my Spotify account and play songs in there.
This is my code:
import UIKit
import AVKit
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate,
SPTAudioStreamingDelegate {
var window: UIWindow?
let kClientId = "hidden------my client ID"
let kRedirectUrl = URL(string: "spotify-study2-login://return-after-login")
var session: SPTSession?
var player: SPTAudioStreamingController?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey : Any]? = nil) -> Bool {
// set up Spotofy
SPTAuth.defaultInstance().clientID = kClientId
SPTAuth.defaultInstance().redirectURL = kRedirectUrl
SPTAuth.defaultInstance().requestedScopes = [SPTAuthStreamingScope] as [AnyObject]
let loginUrl = SPTAuth.defaultInstance().spotifyAppAuthenticationURL()
application.open(loginUrl!)
return true
}
// handle auth
func application(_ application: UIApplication, open url: URL, sourceApplication: String?, annotation: Any) -> Bool {
if SPTAuth.defaultInstance().canHandle(url) {
SPTAuth.defaultInstance().handleAuthCallback(withTriggeredAuthURL: url, callback: { error, session in
if error != nil {
print("*** Auth error: \(String(describing: error))")
}
// Call the -loginUsingSession: method to login SDK
self.loginUsingSession(session: session!)
})
return true
}
return false
}
func loginUsingSession(session: SPTSession) {
// Get the player Instance
player = SPTAudioStreamingController.sharedInstance()
if let player = player {
player.delegate = self
// start the player (will start a thread)
try! player.start(withClientId: kClientId)
// Login SDK before we can start playback
player.login(withAccessToken: session.accessToken)
let urlStr = "spotify:track:3yMPqvbPNaL5DUDOmwEr6l" // a song I choose. I already confirmed this song really exsits.
self.player?.playSpotifyURI(urlStr, startingWith: 0, startingWithPosition: 0, callback: { error in
if error != nil {
print("*** failed to play: \(String(describing: error))")
return
} else {
print("play")
}
})
}
}
// MARK: SPTAudioStreamingDelegate.
func audioStreamingDidLogin(audioStreaming: SPTAudioStreamingController!) {
let urlStr = "spotify:track:3yMPqvbPNaL5DUDOmwEr6l" // a song I choose. I already confirmed this song really exsits.
player!.playSpotifyURI(urlStr, startingWith: 0, startingWithPosition: 0, callback: { error in
if error != nil {
print("*** failed to play: \(String(describing: error))")
return
} else {
print("play")
}
})
}
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.
//log
print("func applicationWillResignActive has been called")
}
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.
//log
print("func applicationDidEnterBackground has been called")
}
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.
//log
print("func applicationWillEnterForeground has been called")
}
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.
//log
print("applicationDidBecomeActive")
}
func applicationWillTerminate(_ application: UIApplication) {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
//log
print("func applicationWillTerminate has been called")
}
}
When I run the debugger, I found an error message saying:
Error Domain=com.spotify.ios-sdk.playback Code=1 "The operation failed
due to an unspecified issue." UserInfo={NSLocalizedDescription=The
operation failed due to an unspecified issue.}
This error message came from the part below:
// MARK: SPTAudioStreamingDelegate.
func audioStreamingDidLogin(audioStreaming:
SPTAudioStreamingController!) {
let urlStr = "spotify:track:3yMPqvbPNaL5DUDOmwEr6l" // a song I choose. I already confirmed this song really exsits.
player!.playSpotifyURI(urlStr, startingWith: 0, startingWithPosition: 0, callback: { error in
if error != nil {
print("*** failed to play: \(String(describing: error))")
return
} else {
print("play")
}
})
}
In addition, I also found
2017-11-19 19:31:04.050872+0900 SpotifyStudy2[756:110616] Caching
allowed 1
from the part below:
// Login SDK before we can start playback
player.login(withAccessToken: session.accessToken)
Googling about this problem cost me lots of time but still I haven't found a nice answer to solve this.
If you have any idea what this error specifically means, your answer will help me so much...! Thank you in advance.
I was facing the same issue, and I have resolved using belove code
do {
try SPTAudioStreamingController.sharedInstance()?.start(withClientId: SPTAuth.defaultInstance().clientID, audioController: nil, allowCaching: true)
SPTAudioStreamingController.sharedInstance().delegate = self
SPTAudioStreamingController.sharedInstance().playbackDelegate = self
SPTAudioStreamingController.sharedInstance().diskCache = SPTDiskCache() /* capacity: 1024 * 1024 * 64 */
SPTAudioStreamingController.sharedInstance().login(withAccessToken: "Token here")
} catch _ {
print("catch")
}
I'm trying to change button state depending on call state.
I used code from here to detect call state: How to get a call event using CTCallCenter:setCallEventHandler: that occurred while the app was suspended?
And it works fine when app is in foreground. But it doesn't work in background at all. In documentation for CTCallCenter.callEventHandler:
When your application resumes the active state, it receives a single
call event for each call that changed state—no matter how many state
changes the call experienced while your application was suspended. The
single call event sent to your handler, upon your application
returning to the active state, describes the call’s state at that
time.
But I don't get any call events when app resumes active. All I get is last saved call state when app was in foreground. How can I detect call state in background?
Here is my code:
AppDelegate.swift
let callСenter = CTCallCenter()
func block (call:CTCall!)
{
callState = String(call.callState)
print(call.callState)
}
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool
{
//check for call state
callСenter.callEventHandler = block
...
return true
}
ViewController.swift
override func viewDidLoad()
{
super.viewDidLoad()
NotificationCenter.default.addObserver(
self,
selector: #selector(cameBackFromSleep),
name: NSNotification.Name.UIApplicationDidBecomeActive,
object: nil
)
...
}
func cameBackFromSleep()
{
self.viewWillAppear(true)
}
override func viewWillAppear(_ animated: Bool)
{
switch callState
{
case "CTCallStateConnected":
print("callState: ", callState)
self.textLabel.isHidden = true
startBtnAnimation()
case "CTCallStateDisconnected":
print("callState: ", callState)
self.textLabel.center.y += self.view.bounds.height
self.textLabel.isHidden = false
stopBtnAnimation()
default: break
}
}
Finally, I solved it! I used code from this answer: Find if user is in a call or not?
I removed everything from AppDelegate, all job is done in ViewController:
override func viewDidLoad()
{
super.viewDidLoad()
NotificationCenter.default.addObserver(
self,
selector: #selector(cameBackFromSleep),
name: NSNotification.Name.UIApplicationDidBecomeActive,
object: nil
)
...
}
private func isOnPhoneCall() -> Bool
{
let callCntr = CTCallCenter()
if let calls = callCntr.currentCalls
{
for call in calls
{
if call.callState == CTCallStateConnected || call.callState == CTCallStateDialing || call.callState == CTCallStateIncoming
{
print("In call")
return true
}
}
}
print("No calls")
return false
}
func cameBackFromSleep()
{
self.viewWillAppear(true)
}
override func viewWillAppear(_ animated: Bool)
{
print("is on call", isOnPhoneCall())
switch isOnPhoneCall()
{
case true:
print("startBtnAnimation")
startBtnAnimation()
recordBtnIsPressed = true
case false:
print("stopBtnAnimation")
stopBtnAnimation()
recordBtnIsPressed = false
default: break
}
}
Now it works fine. Not sure why CTCallCenter works so weird in AppDelegate.
My problem is I want to show a loading screen for the initial Push Notification Prompt "The app wants to send you push notifications."
So if the user hits yes I can proceed and start the app in the then invoked delegate methods:
- (void)application:(UIApplication*)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData*)deviceToken
{
[self hideLoadingScreen];
}
- (void)application:(UIApplication*)application didFailToRegisterForRemoteNotificationsWithError:(NSError*)error
{
[self hideLoadingScreen];
}
However if the user hits no, none of these methods get called, which makes sense. My question is, is there a different delegate method that gets fired if he declines?
My problem is if no is selected, the loading screens never disappear. So I somehow need to know when the user is done with the selection.
In iOS 7, when the system's push notification prompt appears, the app becomes inactive and UIApplicationWillResignActiveNotification fires. Similarly when the user responds to the prompt (pressing either Yes or No), the app becomes active again and UIApplicationDidBecomeActiveNotification fires.
So you can listen for this notification, and then hide your loading screen.
Note: While the prompt is displayed, the Home button, Notification Center, and Control Center are disabled so they cannot trigger a false-positive UIApplicationDidBecomeActiveNotification. However if the user presses Lock button it will trigger UIApplicationDidBecomeActiveNotification.
You can always get current allowed notification types from:
UIRemoteNotificationType notificationTypes = [[UIApplication sharedApplication] enabledRemoteNotificationTypes];
Keep in mind user can also disable notification in phone settings.
If you check that on didRegisterForRemoteNotificationsWithDeviceToken you should see if types you asked for are enabled.
Some of the answers here are not relevant anymore, or are more complicated than it should be, since UserNotifications framework and iOS 10 you can easily get this data like so:
let center = UNUserNotificationCenter.current()
// Request permission to display alerts and play sounds.
center.requestAuthorization(options: [.alert, .sound])
{ (granted, error) in
// Enable or disable features based on authorization.
}
Couldn't you just do the following:
- (void)application:(UIApplication *)application didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings {
BOOL pushEnabled = notificationSettings.types & UIUserNotificationTypeAlert;
}
This method should be the callback to that push notifications prompt, and from there, you can check the bitmask to see if push notifications were enabled or not.
Here's how I did it in Swift 3. They key here is to keep track of the application's lifecycle state internally. When the push prompt is presented, the application resigns active, but does not enter the background. This is all in my AppDelegate.swift.
This is a really big hack and is not recommended in production. Apple could change the way these alerts are presented and this could break at any time. This was tested using various iPhones and iPads running iOS 9 and 10.
/// An internal value used to track application lifecycle state
enum ApplicationLifecycleState {
case willResignActive
case didEnterBackground
case willEnterForeground
case didBecomeActive
case unknown
}
/// This is used purely for tracking the application lifecycle for handling the system push notification alert
var internalLifecycleState: ApplicationLifecycleState = .unknown {
didSet {
// If we're not in the middle of asking for push permissions, none of the below applies, just bail out here
if !isAskingForPushPermissions { return }
// WARNING: Application lifecycle trickery ahead
// The normal application lifecycle calls for backgrounding are as follows:
// applicationWillResignActive -> applicationDidEnterBackground -> applicationWillEnterForeground -> applicationDidBecomeActive
// However, when the system push notification alert is presented, the application resigns active, but does not enter the background:
// applicationWillResignActive -> [user taps on alert] -> applicationDidBecomeActive
// We can use this discrepancy to our advantage to detect if the user did not allow push permissions
// If applicationDidBecomeActive
// AND the previous state was applicationWillResignActive
// AND the notification types bitmask is 0, we know that the user did not allow push permissions
// User denied permissions
if internalLifecycleState == .didBecomeActive
&& oldValue == .willResignActive
&& UIApplication.shared.currentUserNotificationSettings?.types.rawValue == 0 {
// We're done
firePushCompletionBlockAndCleanup(registered: false)
} else {
// The state below can only be entered on iOS 10 devices.
// If the user backgrounds the app while the system alert is being shown,
// when the app is foregrounded the alert will dismiss itself without user interaction.
// This is the equivalent of the user denying push permissions.
// On iOS versions below 10, the user cannot background the app while a system alert is being shown.
if #available(iOS 10, *), internalLifecycleState == .didBecomeActive {
firePushCompletionBlockAndCleanup(registered: false)
}
}
}
}
/// Used internally to track if the system push notification alert is currently being presented
var isAskingForPushPermissions = false
typealias PushNotificationRegistrationCompletionBlock = ((_ registered: Bool) -> Void)
// ...
func applicationWillResignActive(_ application: UIApplication) {
internalLifecycleState = .willResignActive
}
func applicationDidEnterBackground(_ application: UIApplication) {
internalLifecycleState = .didEnterBackground
}
func applicationWillEnterForeground(_ application: UIApplication) {
internalLifecycleState = .willEnterForeground
}
func applicationDidBecomeActive(_ application: UIApplication) {
internalLifecycleState = .didBecomeActive
}
// ...
func setupPushNotifications(_ application: UIApplication = UIApplication.shared, completion: #escaping PushNotificationRegistrationCompletionBlock) {
isAskingForPushPermissions = true
pushCompletionBlock = completion
let settings = UIUserNotificationSettings(types: [.alert, .sound, .badge], categories: nil)
application.registerUserNotificationSettings(settings)
application.registerForRemoteNotifications()
}
fileprivate func firePushCompletionBlockAndCleanup(registered: Bool) {
pushCompletionBlock?(registered)
pushCompletionBlock = nil
isAskingForPushPermissions = false
}
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
// application:didRegisterForRemoteNotificationsWithDeviceToken may be called more than once (once for each notification type)
// By checking that the notification types bitmask is greater than 0, we can find the final time this is called (after the user actually tapped "allow")
// If the user denied push permissions, this function is never called with a positive notification type bitmask value
if UIApplication.shared.currentUserNotificationSettings?.types.rawValue ?? 0 > 0 {
firePushCompletionBlockAndCleanup(registered: true)
}
}
func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
print("Failed to register for notifications with error: " + error.localizedDescription)
firePushCompletionBlockAndCleanup(registered: false)
}
Usage:
appDelegate.setupPushNotifications(completion: { [weak self] (registered) in
// If registered is false, the user denied permissions
})
For Swift 3 and Swift 4.0
Using NotificationCenter and the AppDelegate method didRegister notificationSettings. NotificationSettings show whether the users opted for badges, sounds, etc. and will be an empty array if they declined push notifications. It is fired specifically when users respond to the push notifications prompt and seems to be what most devs use, since it's more specific than checking didBecomeActive. But Apple might change this. Who knows?
Unfortunately, NotificationCenter does not have a preset notification name so you either have to setup and extension (see end) or use the raw value in (SO has more on this).
In AppDelegate:
func application(_ application: UIApplication, didRegister notificationSettings: UIUserNotificationSettings) {
// if not registered users will have an empty set of settings
let accepted: Bool = !notificationSettings.types.isEmpty
NotificationCenter.default.post(name: Notification.Name(rawValue: "didRespondToPrompt"), object: self, userInfo: ["didAccept" : accepted])
}
Then observe wherever you need to, for example in a view controller:
class MyViewController: UIViewController {
//MARK: - Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(MyViewController.didRespondToPushPrompt(_:)), name: NSNotification.Name(rawValue: "didRespondToPrompt"), object: nil)
}
#objc func didRespondToPushPrompt(_ notification: Notification) {
if let userInfo: [AnyHashable : Any] = notification.userInfo, let didAccept: Bool = userInfo[NSNotificationKeyNames.didAccept] as? Bool, !didAccept {
//if user doesn't accept, do this...
} else {
//all other situations code goes here
}
}
}
Couple of things: First, for Swift 4.0, I'm using "#objc" in front of one method, but it's not necessary for Swift 3.
Also, for using NotificationCenter, in practice I did not use "rawValue". Instead I made an extension like so:
import Foundation
extension NSNotification.Name {
static let DidRegisterForPushNotifications = NSNotification.Name("DidRegisterForPushNotifications")
}
Which I could then use like so:
NotificationCenter.default.post(name: Notification.Name.DidRegisterForPushNotifications, object: self, userInfo: ["didAccept" : myBool])
etc., etc.
2nd May 2019
This is the implementation to check if notifications are authorized any time in your app, Simple call this function.
private func checkNotificationsAuthorizationStatus() {
let userNotificationCenter = UNUserNotificationCenter.current()
userNotificationCenter.getNotificationSettings { (notificationSettings) in
switch notificationSettings.authorizationStatus {
case .authorized:
print("The app is authorized to schedule or receive notifications.")
case .denied:
print("The app isn't authorized to schedule or receive notifications.")
case .notDetermined:
print("The user hasn't yet made a choice about whether the app is allowed to schedule notifications.")
case .provisional:
print("The application is provisionally authorized to post noninterruptive user notifications.")
}
}
}
I guess you can have a BOOL variable to check it in your AppDelegate because there seems to be no way other than using external APIs. See this.
AppDelegate.m
// declare a BOOL
BOOL allow = NO;
- (void)application:(UIApplication*)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData*)deviceToken
{
allow = YES;
[self hideLoadingScreen];
}
- (void)application:(UIApplication*)application didFailToRegisterForRemoteNotificationsWithError:(NSError*)error
{
allow = YES;
[self hiedLoadingScreen];
}
Now I guess you can access this BOOL variable to differentiate when Don't allow is pressed or not.
Here is a SWIFT 2 code example for you guys ... It's complicated little bit ,but I hope my comments will help you understand it.
Define variables
var appDidBecomeActiveCount = 0
var userDefaults:NSUserDefaults!
AppDelegate - didFinishLaunchingWithOptions
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
userDefaults = NSUserDefaults.standardUserDefaults()
if userDefaults.valueForKey("FirstLaunche") == nil {
userDefaults.setBool(true, forKey: "FirstLaunche")
userDefaults.synchronize()
}
// Register for notification
//iOS 8+
let settings:UIUserNotificationSettings = UIUserNotificationSettings(forTypes: [UIUserNotificationType.Alert , UIUserNotificationType.Badge ,UIUserNotificationType.Sound], categories: nil)
UIApplication.sharedApplication().registerUserNotificationSettings(settings)
UIApplication.sharedApplication().registerForRemoteNotifications()
}
AppDelegate - applicationDidBecomeActive
func applicationDidBecomeActive(application: UIApplication) {
//Delay until alert get dismissed and notification type setted in app
delay(0.5, closure: { () -> () in
self.checkTheDilemma()
})
}
//I love this short method <3_<3
func delay(delay:Double, closure:()->()) {
dispatch_after(
dispatch_time(
DISPATCH_TIME_NOW,
Int64(delay * Double(NSEC_PER_SEC))
),
dispatch_get_main_queue(), closure)
}
Check action
func checkTheDilemma (){
//Checking if this user turned off push notifications or didn't allow it at all
let notificationType = UIApplication.sharedApplication().currentUserNotificationSettings()?.types
if userDefaults.valueForKey("FirstLaunche") as! Bool == true {
//User now is asked for notification permission because it's app's first launche
// if appDidBecomeActiveCount == 0 --> Pop up message will appeare
// if appDidBecomeActiveCount == 1 --> Pop up message dismissed
// if notificationType?.rawValue == 0 --> Notifications off
// if notificationType?.rawValue > 0 --> Notifications on
if notificationType?.rawValue == 0
&& appDidBecomeActiveCount == 1 { //If user disabled notifications from pop up alert
// ** User just tapped "Don't allow" btn :\
// Do what ever you are here for
//Now set FirstLaunche = false
userDefaults.setBool(false, forKey: "FirstLaunche")
userDefaults.synchronize()
}
} else {
if notificationType?.rawValue == 0
&& appDidBecomeActiveCount == 0 { // This guy is not registered for push notification
// ** User disabled notifications in past (because this is not his first launch)
}
}
appDidBecomeActiveCount++
}
You can detect if user has cancelled the notification prompt in didRegisterUserNotificationSettings method that fires after calling registerForRemoteNotificationTypes by checking the notificationSettings.types.
If you have requested a number of settings but notificationSettings.types == UIUserNotificationTypeNone means, that user has cancelled the prompt.
But don't forget that registerForRemoteNotificationTypes method is now deprecated!
A great way using C# Xamarin as of iOS 13
I put it in a timer on the page where I prompt and check the authorization status
It may be possible to get the actual callback but this way works for me
using System.Timers;
Timer notificationsPermissionTimer = new Timer();
public override void ViewDidLoad()
{
SetupNotificationsPermissionTimer();
base.ViewDidLoad();
}
public override void ViewWillDisappear(bool animated)
{
this.notificationsPermissionTimer.Elapsed -= NotificationsPermissionTimer_Elapsed;
base.ViewWillDisappear(animated);
}
private void SetUpNotificationsPermissionTimer()
{
this.notificationsPermissionTimer = new Timer();
this.notificationsPermissionTimer.Interval = 500;
this.notificationsPermissionTimer.Start();
this.notificationsPermissionTimer.Elapsed += NotificationsPermissionTimer_Elapsed;
}
private void NotificationsPermissionTimer_Elapsed(object sender, ElapsedEventArgs e)
{
Task.Run(CheckNotificationsAuthorizationStatus);
}
private async Task CheckNotificationsAuthorizationStatus()
{
var userNotificationCenter = await UNUserNotificationCenter.Current.GetNotificationSettingsAsync();
switch(userNotificationCenter.AuthorizationStatus)
{
case UNAuthorizationStatus.Authorized:
// Do Something
break;
case UNAuthorizationStatus.Denied:
// Do Something
break;
case UNAuthorizationStatus.NotDetermined:
// Do Nothing
break;
case UNAuthorizationStatus.Provisional:
break;
}
}
My problem is I want to show a loading screen for the initial Push Notification Prompt "The app wants to send you push notifications."
So if the user hits yes I can proceed and start the app in the then invoked delegate methods:
- (void)application:(UIApplication*)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData*)deviceToken
{
[self hideLoadingScreen];
}
- (void)application:(UIApplication*)application didFailToRegisterForRemoteNotificationsWithError:(NSError*)error
{
[self hideLoadingScreen];
}
However if the user hits no, none of these methods get called, which makes sense. My question is, is there a different delegate method that gets fired if he declines?
My problem is if no is selected, the loading screens never disappear. So I somehow need to know when the user is done with the selection.
In iOS 7, when the system's push notification prompt appears, the app becomes inactive and UIApplicationWillResignActiveNotification fires. Similarly when the user responds to the prompt (pressing either Yes or No), the app becomes active again and UIApplicationDidBecomeActiveNotification fires.
So you can listen for this notification, and then hide your loading screen.
Note: While the prompt is displayed, the Home button, Notification Center, and Control Center are disabled so they cannot trigger a false-positive UIApplicationDidBecomeActiveNotification. However if the user presses Lock button it will trigger UIApplicationDidBecomeActiveNotification.
You can always get current allowed notification types from:
UIRemoteNotificationType notificationTypes = [[UIApplication sharedApplication] enabledRemoteNotificationTypes];
Keep in mind user can also disable notification in phone settings.
If you check that on didRegisterForRemoteNotificationsWithDeviceToken you should see if types you asked for are enabled.
Some of the answers here are not relevant anymore, or are more complicated than it should be, since UserNotifications framework and iOS 10 you can easily get this data like so:
let center = UNUserNotificationCenter.current()
// Request permission to display alerts and play sounds.
center.requestAuthorization(options: [.alert, .sound])
{ (granted, error) in
// Enable or disable features based on authorization.
}
Couldn't you just do the following:
- (void)application:(UIApplication *)application didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings {
BOOL pushEnabled = notificationSettings.types & UIUserNotificationTypeAlert;
}
This method should be the callback to that push notifications prompt, and from there, you can check the bitmask to see if push notifications were enabled or not.
Here's how I did it in Swift 3. They key here is to keep track of the application's lifecycle state internally. When the push prompt is presented, the application resigns active, but does not enter the background. This is all in my AppDelegate.swift.
This is a really big hack and is not recommended in production. Apple could change the way these alerts are presented and this could break at any time. This was tested using various iPhones and iPads running iOS 9 and 10.
/// An internal value used to track application lifecycle state
enum ApplicationLifecycleState {
case willResignActive
case didEnterBackground
case willEnterForeground
case didBecomeActive
case unknown
}
/// This is used purely for tracking the application lifecycle for handling the system push notification alert
var internalLifecycleState: ApplicationLifecycleState = .unknown {
didSet {
// If we're not in the middle of asking for push permissions, none of the below applies, just bail out here
if !isAskingForPushPermissions { return }
// WARNING: Application lifecycle trickery ahead
// The normal application lifecycle calls for backgrounding are as follows:
// applicationWillResignActive -> applicationDidEnterBackground -> applicationWillEnterForeground -> applicationDidBecomeActive
// However, when the system push notification alert is presented, the application resigns active, but does not enter the background:
// applicationWillResignActive -> [user taps on alert] -> applicationDidBecomeActive
// We can use this discrepancy to our advantage to detect if the user did not allow push permissions
// If applicationDidBecomeActive
// AND the previous state was applicationWillResignActive
// AND the notification types bitmask is 0, we know that the user did not allow push permissions
// User denied permissions
if internalLifecycleState == .didBecomeActive
&& oldValue == .willResignActive
&& UIApplication.shared.currentUserNotificationSettings?.types.rawValue == 0 {
// We're done
firePushCompletionBlockAndCleanup(registered: false)
} else {
// The state below can only be entered on iOS 10 devices.
// If the user backgrounds the app while the system alert is being shown,
// when the app is foregrounded the alert will dismiss itself without user interaction.
// This is the equivalent of the user denying push permissions.
// On iOS versions below 10, the user cannot background the app while a system alert is being shown.
if #available(iOS 10, *), internalLifecycleState == .didBecomeActive {
firePushCompletionBlockAndCleanup(registered: false)
}
}
}
}
/// Used internally to track if the system push notification alert is currently being presented
var isAskingForPushPermissions = false
typealias PushNotificationRegistrationCompletionBlock = ((_ registered: Bool) -> Void)
// ...
func applicationWillResignActive(_ application: UIApplication) {
internalLifecycleState = .willResignActive
}
func applicationDidEnterBackground(_ application: UIApplication) {
internalLifecycleState = .didEnterBackground
}
func applicationWillEnterForeground(_ application: UIApplication) {
internalLifecycleState = .willEnterForeground
}
func applicationDidBecomeActive(_ application: UIApplication) {
internalLifecycleState = .didBecomeActive
}
// ...
func setupPushNotifications(_ application: UIApplication = UIApplication.shared, completion: #escaping PushNotificationRegistrationCompletionBlock) {
isAskingForPushPermissions = true
pushCompletionBlock = completion
let settings = UIUserNotificationSettings(types: [.alert, .sound, .badge], categories: nil)
application.registerUserNotificationSettings(settings)
application.registerForRemoteNotifications()
}
fileprivate func firePushCompletionBlockAndCleanup(registered: Bool) {
pushCompletionBlock?(registered)
pushCompletionBlock = nil
isAskingForPushPermissions = false
}
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
// application:didRegisterForRemoteNotificationsWithDeviceToken may be called more than once (once for each notification type)
// By checking that the notification types bitmask is greater than 0, we can find the final time this is called (after the user actually tapped "allow")
// If the user denied push permissions, this function is never called with a positive notification type bitmask value
if UIApplication.shared.currentUserNotificationSettings?.types.rawValue ?? 0 > 0 {
firePushCompletionBlockAndCleanup(registered: true)
}
}
func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
print("Failed to register for notifications with error: " + error.localizedDescription)
firePushCompletionBlockAndCleanup(registered: false)
}
Usage:
appDelegate.setupPushNotifications(completion: { [weak self] (registered) in
// If registered is false, the user denied permissions
})
For Swift 3 and Swift 4.0
Using NotificationCenter and the AppDelegate method didRegister notificationSettings. NotificationSettings show whether the users opted for badges, sounds, etc. and will be an empty array if they declined push notifications. It is fired specifically when users respond to the push notifications prompt and seems to be what most devs use, since it's more specific than checking didBecomeActive. But Apple might change this. Who knows?
Unfortunately, NotificationCenter does not have a preset notification name so you either have to setup and extension (see end) or use the raw value in (SO has more on this).
In AppDelegate:
func application(_ application: UIApplication, didRegister notificationSettings: UIUserNotificationSettings) {
// if not registered users will have an empty set of settings
let accepted: Bool = !notificationSettings.types.isEmpty
NotificationCenter.default.post(name: Notification.Name(rawValue: "didRespondToPrompt"), object: self, userInfo: ["didAccept" : accepted])
}
Then observe wherever you need to, for example in a view controller:
class MyViewController: UIViewController {
//MARK: - Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(MyViewController.didRespondToPushPrompt(_:)), name: NSNotification.Name(rawValue: "didRespondToPrompt"), object: nil)
}
#objc func didRespondToPushPrompt(_ notification: Notification) {
if let userInfo: [AnyHashable : Any] = notification.userInfo, let didAccept: Bool = userInfo[NSNotificationKeyNames.didAccept] as? Bool, !didAccept {
//if user doesn't accept, do this...
} else {
//all other situations code goes here
}
}
}
Couple of things: First, for Swift 4.0, I'm using "#objc" in front of one method, but it's not necessary for Swift 3.
Also, for using NotificationCenter, in practice I did not use "rawValue". Instead I made an extension like so:
import Foundation
extension NSNotification.Name {
static let DidRegisterForPushNotifications = NSNotification.Name("DidRegisterForPushNotifications")
}
Which I could then use like so:
NotificationCenter.default.post(name: Notification.Name.DidRegisterForPushNotifications, object: self, userInfo: ["didAccept" : myBool])
etc., etc.
2nd May 2019
This is the implementation to check if notifications are authorized any time in your app, Simple call this function.
private func checkNotificationsAuthorizationStatus() {
let userNotificationCenter = UNUserNotificationCenter.current()
userNotificationCenter.getNotificationSettings { (notificationSettings) in
switch notificationSettings.authorizationStatus {
case .authorized:
print("The app is authorized to schedule or receive notifications.")
case .denied:
print("The app isn't authorized to schedule or receive notifications.")
case .notDetermined:
print("The user hasn't yet made a choice about whether the app is allowed to schedule notifications.")
case .provisional:
print("The application is provisionally authorized to post noninterruptive user notifications.")
}
}
}
I guess you can have a BOOL variable to check it in your AppDelegate because there seems to be no way other than using external APIs. See this.
AppDelegate.m
// declare a BOOL
BOOL allow = NO;
- (void)application:(UIApplication*)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData*)deviceToken
{
allow = YES;
[self hideLoadingScreen];
}
- (void)application:(UIApplication*)application didFailToRegisterForRemoteNotificationsWithError:(NSError*)error
{
allow = YES;
[self hiedLoadingScreen];
}
Now I guess you can access this BOOL variable to differentiate when Don't allow is pressed or not.
Here is a SWIFT 2 code example for you guys ... It's complicated little bit ,but I hope my comments will help you understand it.
Define variables
var appDidBecomeActiveCount = 0
var userDefaults:NSUserDefaults!
AppDelegate - didFinishLaunchingWithOptions
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
userDefaults = NSUserDefaults.standardUserDefaults()
if userDefaults.valueForKey("FirstLaunche") == nil {
userDefaults.setBool(true, forKey: "FirstLaunche")
userDefaults.synchronize()
}
// Register for notification
//iOS 8+
let settings:UIUserNotificationSettings = UIUserNotificationSettings(forTypes: [UIUserNotificationType.Alert , UIUserNotificationType.Badge ,UIUserNotificationType.Sound], categories: nil)
UIApplication.sharedApplication().registerUserNotificationSettings(settings)
UIApplication.sharedApplication().registerForRemoteNotifications()
}
AppDelegate - applicationDidBecomeActive
func applicationDidBecomeActive(application: UIApplication) {
//Delay until alert get dismissed and notification type setted in app
delay(0.5, closure: { () -> () in
self.checkTheDilemma()
})
}
//I love this short method <3_<3
func delay(delay:Double, closure:()->()) {
dispatch_after(
dispatch_time(
DISPATCH_TIME_NOW,
Int64(delay * Double(NSEC_PER_SEC))
),
dispatch_get_main_queue(), closure)
}
Check action
func checkTheDilemma (){
//Checking if this user turned off push notifications or didn't allow it at all
let notificationType = UIApplication.sharedApplication().currentUserNotificationSettings()?.types
if userDefaults.valueForKey("FirstLaunche") as! Bool == true {
//User now is asked for notification permission because it's app's first launche
// if appDidBecomeActiveCount == 0 --> Pop up message will appeare
// if appDidBecomeActiveCount == 1 --> Pop up message dismissed
// if notificationType?.rawValue == 0 --> Notifications off
// if notificationType?.rawValue > 0 --> Notifications on
if notificationType?.rawValue == 0
&& appDidBecomeActiveCount == 1 { //If user disabled notifications from pop up alert
// ** User just tapped "Don't allow" btn :\
// Do what ever you are here for
//Now set FirstLaunche = false
userDefaults.setBool(false, forKey: "FirstLaunche")
userDefaults.synchronize()
}
} else {
if notificationType?.rawValue == 0
&& appDidBecomeActiveCount == 0 { // This guy is not registered for push notification
// ** User disabled notifications in past (because this is not his first launch)
}
}
appDidBecomeActiveCount++
}
You can detect if user has cancelled the notification prompt in didRegisterUserNotificationSettings method that fires after calling registerForRemoteNotificationTypes by checking the notificationSettings.types.
If you have requested a number of settings but notificationSettings.types == UIUserNotificationTypeNone means, that user has cancelled the prompt.
But don't forget that registerForRemoteNotificationTypes method is now deprecated!
A great way using C# Xamarin as of iOS 13
I put it in a timer on the page where I prompt and check the authorization status
It may be possible to get the actual callback but this way works for me
using System.Timers;
Timer notificationsPermissionTimer = new Timer();
public override void ViewDidLoad()
{
SetupNotificationsPermissionTimer();
base.ViewDidLoad();
}
public override void ViewWillDisappear(bool animated)
{
this.notificationsPermissionTimer.Elapsed -= NotificationsPermissionTimer_Elapsed;
base.ViewWillDisappear(animated);
}
private void SetUpNotificationsPermissionTimer()
{
this.notificationsPermissionTimer = new Timer();
this.notificationsPermissionTimer.Interval = 500;
this.notificationsPermissionTimer.Start();
this.notificationsPermissionTimer.Elapsed += NotificationsPermissionTimer_Elapsed;
}
private void NotificationsPermissionTimer_Elapsed(object sender, ElapsedEventArgs e)
{
Task.Run(CheckNotificationsAuthorizationStatus);
}
private async Task CheckNotificationsAuthorizationStatus()
{
var userNotificationCenter = await UNUserNotificationCenter.Current.GetNotificationSettingsAsync();
switch(userNotificationCenter.AuthorizationStatus)
{
case UNAuthorizationStatus.Authorized:
// Do Something
break;
case UNAuthorizationStatus.Denied:
// Do Something
break;
case UNAuthorizationStatus.NotDetermined:
// Do Nothing
break;
case UNAuthorizationStatus.Provisional:
break;
}
}