MPRemoteCommandCenter not available after changing audio session category - swift

My app has got the option to allow its sound to be mixed with other apps.
According to Apple, MPRemoteCommandCenter is only available when apps do not allow for mixing.
Inside my app, when the user taps the button to change the mixWithOthers setting, the audio session is set accordingly.
However, even when the user switches back to not allow mixing anymore MPRemoteCommandCenter will not show up in lock screen until the app has been removed from cache (swiped up) and started again.
Is there a way to achieve the desired behaviour without having to re-start the app?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
UIApplication.shared.beginReceivingRemoteControlEvents()
}
var isMixWithOthersAllowed: Bool
func startProcess() {
setAudioSession {
setupMediaControls()
}
}
func setAudioSession(completion: #escaping () -> Void) {
let audioSession = AVAudioSession.sharedInstance()
do {
if isMixWithOthersAllowed {
try audioSession.setCategory(.playback, options: [.mixWithOthers])
/* The remote command center will not be available when mixing with others,
as stated by Apple in the docs. */
} else {
try audioSession.setCategory(.playback)
/* The remote command center should be available when switching back to
this category, but it will only show up after the app has been killed
and started fresh again. I'd like it to be available without restarting
the application. */
}
try audioSession.setActive(true)
} catch let error as NSError {
configureAudioSessionError = error
}
assert(configureAudioSessionError == nil, "Create audio session failed")
completion()
}
func setupMediaControls() {
let commandCenter = MPRemoteCommandCenter.shared()
commandCenter.playCommand.isEnabled = true
commandCenter.pauseCommand.isEnabled = true
commandCenter.nextTrackCommand.isEnabled = true
commandCenter.previousTrackCommand.isEnabled = true
// Setup handlers for commands
// ...
setupNowPlaying()
}
func setupNowPlaying() {
// Configure audio track metadata and update UI elements
}

Related

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.

Audio playback lock screen control not displaying

I am trying display audio controls on the lock screen, but the problem is the audio control does not display anything in the lock screen. I already enabled background modes and the audio plays in the background.
In app delegate class, right when my app launches, I set up my audio session
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
setupAudioSession()
UIApplication.shared.beginReceivingRemoteControlEvents()
return true
}
func setupAudioSession(){
do {
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback, with: [])
self.becomeFirstResponder()
do {
try AVAudioSession.sharedInstance().setActive(true)
print("AVAudioSession is Active")
} catch let error as NSError {
print(error.localizedDescription)
}
} catch let error as NSError {
print(error.localizedDescription)
}
}
In my main controller, I call the setupLockScreen function after I play the audio.
func setupLockScreen(){
let commandCenter = MPRemoteCommandCenter.shared()
commandCenter.playCommand.isEnabled = true
commandCenter.playCommand.addTarget { (event) -> MPRemoteCommandHandlerStatus in
if self.player?.rate == 0.0 {
self.player?.play()
return .success
}
return .commandFailed
}
var nowPlayingInfo = [String : Any]()
nowPlayingInfo[MPMediaItemPropertyTitle] = "My Song"
nowPlayingInfo[MPNowPlayingInfoPropertyElapsedPlaybackTime] = audioplayerItem.duration.seconds
nowPlayingInfo[MPMediaItemPropertyPlaybackDuration] = audioplayerItem.asset.duration.seconds
nowPlayingInfo[MPNowPlayingInfoPropertyPlaybackRate] = player?.rate
MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo
}
I read many articles and I looked all the qustions in Stack Overflow but no luck.
Is the problem that you nowPlayingInfo is not displaying on the lock screen or is it that the controls you added are not responding?
To have your nowPlayingInfo appear on the lockscreen, you need to do a few things listed here in an answer on Handling External Player Events Notifications. One is to have a non mixable audio session (which you already have with AVAudioSessionCategoryPlayback) and the other is to be playing audio or only recently have stopped playing audio. Is your app actually playing audio?
Background audio, which you have is a sort of derived requirement, because lockscreen=>background=>background audio needed to play audio, so I should add that to the other answer.
If the problem is that the lockscreen controls aren't enabled/responsive, then try adding a pauseCommand as well. The playCommand/pauseCommand pair seem to be a minimum requirement for receiving lockscreen/external player commands.
p.s. your MPNowPlayingInfoPropertyElapsedPlaybackTime looks wrong. shouldn't it be
nowPlayingInfo[MPNowPlayingInfoPropertyElapsedPlaybackTime] = CMTimeGetSeconds(audioplayerItem.currentTime)
Do it like this (this is swift 3):
override func viewDidLoad() {
super.viewDidLoad()
UIApplication.shared.beginReceivingRemoteControlEvents()
let commandCenter = MPRemoteCommandCenter.shared()
commandCenter.pauseCommand.addTarget { (event) -> MPRemoteCommandHandlerStatus in
//Update your button here for the pause command
return .success
}
commandCenter.playCommand.addTarget { (event) -> MPRemoteCommandHandlerStatus in
//Update your button here for the play command
return .success
}
}
This answer found HERE

iBeacon Ranging in background

I'm trying to set my application as a way to just start ringing in the background and get a notification when the user hit the shoulder button or home button(not all the time) and I don't want using background mode. So I coded this in swift, it works when my app is working in foreground and background for 10 seconds and ranging won't be restarted when the user illuminates his phone's screen. First I started background task and then in app delegate I was trying to start ranging for the beacon in the immediate range. Can someone help me about this?
#Background Task
class BackgroundTask1 : NSObject {
private let application: UIApplication!
private var identifier = UIBackgroundTaskInvalid
init(application: UIApplication) {
self.application = application
}
class func run(application: UIApplication, handler: (BackgroundTask1) -> ()) {
let backgroundTask = BackgroundTask1(application: application)
backgroundTask.begin()
handler(backgroundTask)
}
func begin() {
print("begin")
self.identifier = application.beginBackgroundTask {
self.end()
}
}
func end() {
if (identifier != UIBackgroundTaskInvalid) {
application.endBackgroundTask(identifier)
}
identifier = UIBackgroundTaskInvalid
}}
#AppDelegate
func init_()
{
let uuidString = "43F2ACD1-5522-4E0D-9E3F-4A828EA12C24"
let beaconRegionIdentifier = "Hello"
let beaconUUID:UUID = UUID(uuidString:uuidString)!
beaconRegion = CLBeaconRegion(proximityUUID: beaconUUID, identifier: beaconRegionIdentifier)
beaconRegion.notifyEntryStateOnDisplay = true
locationManager = CLLocationManager()
if (CLLocationManager.authorizationStatus() != CLAuthorizationStatus.authorizedWhenInUse) {
locationManager!.requestWhenInUseAuthorization()
}
locationManager!.delegate = self
locationManager!.pausesLocationUpdatesAutomatically=false
}
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
init_()
locationManager?.startRangingBeacons(in: beaconRegion)
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.
}
var timer = Timer()
func applicationDidEnterBackground(_ application: UIApplication) {
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
BackgroundTask1.run(application:
application) { (BackgroundTask1_) in
DispatchQueue.global(qos: .default).async
{
DispatchQueue.main.async {
self.init_()
self.locationManager?.startRangingBeacons(in: self.beaconRegion)
}
}
}
}

Download in background in Swift

I'm trying to make my app download images in background. But when I press [Home] button, the app stop download. Is there any way to make it continue download even when I use another app? I have seen some apps can do like that but I don't know how.
This is what I've tried so far.
//
// AppDelegate.swift
// Swift-TableView-Example
//
// Created by Bilal ARSLAN on 11/10/14.
// Copyright (c) 2014 Bilal ARSLAN. All rights reserved.
//
import UIKit
import WebKit
protocol DownloadInBackgroundDelegate {
func downloadInBackgroundDidFinish(chapterid:Int, chaptername:String, storyid:Int, progressPercent:Float)
}
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
var shareCache = NSURLCache()
var downloadDelegate:DownloadInBackgroundDelegate? = nil
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// Override point for customization after application launch.
var navigationBarAppearace = UINavigationBar.appearance()
application.setStatusBarOrientation(UIInterfaceOrientation.PortraitUpsideDown, animated: false)
self.startDownload()
return true
}
func application(application: UIApplication,
didReceiveRemoteNotification userInfo: [NSObject : AnyObject],
fetchCompletionHandler completionHandler: (UIBackgroundFetchResult) -> Void) {
}
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.
}
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.
FBAppEvents.activateApp()
}
func applicationWillTerminate(application: UIApplication) {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
}
func application(application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: NSData) {
}
func application(application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: NSError) {
}
func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject]) {
}
func applicationDidReceiveMemoryWarning(application: UIApplication) {
NSURLCache.sharedURLCache().removeAllCachedResponses()
}
func application(application: UIApplication, willChangeStatusBarOrientation newStatusBarOrientation: UIInterfaceOrientation, duration: NSTimeInterval) {
application.windows
}
func startDownload(){
var filesPath = [String]()
filesPath.append("https://developer.apple.com/library/ios/documentation/iphone/conceptual/iphoneosprogrammingguide/iphoneappprogrammingguide.pdf")
filesPath.append("https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/MobileHIG/MobileHIG.pdf")
filesPath.append("https://developer.apple.com/library/ios/documentation/NetworkingInternetWeb/Conceptual/NetworkingOverview/NetworkingOverview.pdf")
filesPath.append("https://developer.apple.com/library/ios/documentation/AudioVideo/Conceptual/AVFoundationPG/AVFoundationPG.pdf")
filesPath.append("http://manuals.info.apple.com/MANUALS/1000/MA1565/en_US/iphone_user_guide.pdf")
downloadFiles(0, filesPath: filesPath)
}
func downloadFiles(index: Int, filesPath: [String]) -> Void {
var imgURL: NSURL = NSURL(string: filesPath[index].stringByTrimmingCharactersInSet(.whitespaceAndNewlineCharacterSet()).stringByAddingPercentEscapesUsingEncoding(NSUTF8StringEncoding)!)!
let request: NSURLRequest = NSURLRequest(URL: imgURL)
NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue.mainQueue(), completionHandler: {(response: NSURLResponse!,data: NSData!,error: NSError!) -> Void in
var fileCacheName = String(format: "%04d", index)
dispatch_async(dispatch_get_main_queue(), {
var fileExt = (data != nil && error == nil) ? Utility.checkImageType(data) : ""
let paths = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0] as! String
let imagePath = paths.stringByAppendingPathComponent("\(fileCacheName).png")
if data.writeToFile(imagePath, atomically: false)
{
println("saved")
}
if index < filesPath.count - 1
{
var nextIndex:Int = index + 1
self.downloadFiles(nextIndex, filesPath: filesPath)
}
})
})
}
}
Answer
Based on the comment below, I found this thread : objective c - Proper use of beginBackgroundTaskWithExpirationHandler . I can solve my problem with it.
For downloading and storing of the images, instead of writing the logic yourself, I suggest you use some well known libraries, like:
https://github.com/Haneke/Haneke
https://github.com/rs/SDWebImage
Reason behind that is those libraries are well tested, quite robust and mainly very easy to use for basic tasks.
Now for the background download, there is beginBackgroundTaskWithExpirationHandler: that is specifically designed to do that. When you use it, you will get few more minutes to execute whatever you need (after that limit, your application will get terminated no matter what).
You can write following methods:
func beginBackgroundTask() -> UIBackgroundTaskIdentifier {
return UIApplication.sharedApplication().beginBackgroundTaskWithExpirationHandler({})
}
func endBackgroundTask(taskID: UIBackgroundTaskIdentifier) {
UIApplication.sharedApplication().endBackgroundTask(taskID)
}
When you want to use it, you just simple begin / end the task when starting / finishing the download call:
// Start task
let task = self.beginBackgroundTask()
// Do whatever you need
self.someBackgroundTask()
// End task
self.endBackgroundTask(task)
Hope it helps!
Use beginBackgroundTaskWithExpirationHandler: from the UIApplication to start a background task when the app enters the background
See Apple's document on multitasking background execution for details. See download in background in iphone its a similar question.

Not able to set Interactive Push Notifications on iOS8

I was already able to set Interactive LOCAL notifications, but the Remote notifications aren't working. I'm using Parse.com to send the JSON
My AppDelegate.Swift looks like this:
//
// AppDelegate.swift
// SwifferApp
//
// Created by Training on 29/06/14.
// Copyright (c) 2014 Training. All rights reserved.
//
import UIKit
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: NSDictionary?) -> Bool {
UINavigationBar.appearance().barTintColor = UIColor.orangeColor()
UINavigationBar.appearance().tintColor = UIColor.whiteColor()
Parse.setApplicationId("eUEC7O4Jad0Kt3orqRouU0OJhkGuE20n4uSfrLYE", clientKey: "WypmaQ8XyqH26AeWIANttqwUjRJR4CIM55ioXvez")
let notificationTypes:UIUserNotificationType = UIUserNotificationType.Alert | UIUserNotificationType.Badge | UIUserNotificationType.Sound
let notificationSettings:UIUserNotificationSettings = UIUserNotificationSettings(forTypes: notificationTypes, categories: nil)
UIApplication.sharedApplication().registerUserNotificationSettings(notificationSettings)
return true
}
func application(application: UIApplication!, didRegisterUserNotificationSettings notificationSettings: UIUserNotificationSettings!) {
UIApplication.sharedApplication().registerForRemoteNotifications()
}
func application(application: UIApplication!, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: NSData!) {
let currentInstallation:PFInstallation = PFInstallation.currentInstallation()
currentInstallation.setDeviceTokenFromData(deviceToken)
currentInstallation.saveInBackground()
}
func application(application: UIApplication!, didFailToRegisterForRemoteNotificationsWithError error: NSError!) {
println(error.localizedDescription)
}
func application(application: UIApplication!, didReceiveRemoteNotification userInfo:NSDictionary!) {
var notification:NSDictionary = userInfo.objectForKey("aps") as NSDictionary
if notification.objectForKey("content-available"){
if notification.objectForKey("content-available").isEqualToNumber(1){
NSNotificationCenter.defaultCenter().postNotificationName("reloadTimeline", object: nil)
}
}else{
PFPush.handlePush(userInfo)
}
}
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.
}
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:.
}
}
on Parse, I'm setting the Push payload like this:
{
"alert": "Tune in for the World Series, tonight at 8pm EDT",
"badge": "Increment",
"sound": "chime",
"category": "FIRST_CATEGORY"
}
and I receive the push, but not with the custom buttons I've set.
I'm not sure if my problem is the same to yours (Make sure your problem is not due to Parse). Just post my solution here in case anyone else would encounter the same issue.
My problem is in the notification category.
Make sure you have set the category when registering the notification settings (I'm using Objective-C, no much difference):
UIMutableUserNotificationCategory *notificationCategory = [[UIMutableUserNotificationCategory alloc] init];
notificationCategory.identifier = #"CallNotificationCategory";
[notificationCategory setActions:#[declineAction, answerAction] forContext:UIUserNotificationActionContextDefault];
NSSet *categories = [[NSSet alloc] initWithObjects:notificationCategory, nil];
And, when you send remote notification, make sure you have "category" in the payload and the value is the same as you defined in the client. In my case it's something like:
{
"alert": "Tune in for the World Series, tonight at 8pm EDT",
"badge": "Increment",
"sound": "chime",
"category": "CallNotificationCategory"
}
This is in case if anyone comes across this problem while using Firebase Remote Notifications.
Just ask the backend developer to send:
"notification" : {
"title" : YOUR_TITLE,
"body" : YOUR_BODY,
"click_action" : YOUR_CATEGORY_NAME
}
You need to pass categories while registering for APNS.
Look at my sample :
var replyAction : UIMutableUserNotificationAction = UIMutableUserNotificationAction()
replyAction.identifier = "REPLY_ACTION"
replyAction.title = "Yes, I need!"
replyAction.activationMode = UIUserNotificationActivationMode.Background
replyAction.authenticationRequired = false
var replyCategory : UIMutableUserNotificationCategory = UIMutableUserNotificationCategory()
replyCategory.identifier = "REPLY_CATEGORY"
let replyActions:NSArray = [replyAction]
replyCategory.setActions(replyActions, forContext: UIUserNotificationActionContext.Default)
replyCategory.setActions(replyActions, forContext: UIUserNotificationActionContext.Minimal)
let categories = NSSet(object: replyCategory)
let settings : UIUserNotificationType = UIUserNotificationType.Sound | UIUserNotificationType.Alert | UIUserNotificationType.Badge
UIApplication.sharedApplication().registerUserNotificationSettings(UIUserNotificationSettings(forTypes: settings, categories: categories))
UIApplication.sharedApplication().registerForRemoteNotifications()
This worked for me to get interactive push notifications displaying and working in Swift with Parse. Note that you need to create a UIMutableNotificationAction for each interactive button you want to create. Source below goes into much more detail of configuring options for buttons.
In your app delegate file:
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
let notificationTypes:UIUserNotificationType = UIUserNotificationType.Alert | UIUserNotificationType.Badge | UIUserNotificationType.Sound
var notificationActionAccept :UIMutableUserNotificationAction = UIMutableUserNotificationAction()
notificationActionAccept.identifier = "ACCEPT_IDENTIFIER"
notificationActionAccept.title = "Accept"
notificationActionAccept.destructive = true
notificationActionAccept.authenticationRequired = false
notificationActionAccept.activationMode = UIUserNotificationActivationMode.Background
var notificationCategory:UIMutableUserNotificationCategory = UIMutableUserNotificationCategory()
notificationCategory.identifier = "CallNotificationCategory"
notificationCategory .setActions([notificationActionAccept], forContext: UIUserNotificationActionContext.Default)
let notificationSettings:UIUserNotificationSettings = UIUserNotificationSettings(forTypes: notificationTypes, categories: NSSet(array:[notificationCategory]))
UIApplication.sharedApplication().registerUserNotificationSettings(notificationSettings)
return true
}
And in Parse Cloud when you send the push, you would match the category to the UIMutableUserNotificationCategory like #Xialin mentioned above.
Looks like you weren't setting categories or at least not setting them until after you had registered the UINotificationSettings - you need to set them before or it won't work.
I got most of this info at the link below. It goes into more detail if needed. Hope this helps:
http://thecodeninja.tumblr.com/post/90742435155/notifications-in-ios-8-part-2-using-swift-what-is