AVSystemController_SystemVolumeDidChangeNotification not giving callback for the first time - ios

I want to check if both the volume buttons are working fine. So I set the observer AVSystemController_SystemVolumeDidChangeNotification to check that.
NotificationCenter.default.addObserver(self, selector: #selector(volumeCallback(notification:)), name: NSNotification.Name("AVSystemController_SystemVolumeDidChangeNotification"), object: nil)
Given is volumeCallback method:
#objc private func volumeCallback(notification: NSNotification) {
// check if app is in forground
guard UIApplication.shared.applicationState == .active else {
return
}
//get volume level
if let userInfo = notification.userInfo {
if let volumeChangeType = userInfo["AVSystemController_AudioVolumeChangeReasonNotificationParameter"] as? String {
if volumeChangeType == "ExplicitVolumeChange" {
print("value changed")
let level = userInfo["AVSystemController_AudioVolumeNotificationParameter"] as? Float
guard let volLevel = level else {
return
}
// my work here
}
}
}
}
Now the problem is, I am not getting callback in volumeCallback for the first installation of the app. The weird thing is, this method is being called when the app is in background, but not being called in foreground.
I am using iPhone 5s (iOS 10.3.3).
I don't understand what is the problem in this code. Any help will be appreciated.

This can be easily done with key-value observer as AVAudioSession provides outputVolume property. Check here.
You can just add observer on this property and get callbacks.
Here's a simple way of doing this in Swift 5:
// Audio session object
private let session = AVAudioSession.sharedInstance()
// Observer
private var progressObserver: NSKeyValueObservation!
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
do {
try session.setActive(true, options: .notifyOthersOnDeactivation)
} catch {
print("cannot activate session")
}
progressObserver = session.observe(\.outputVolume) { [weak self] (session, value) in
print(session.outputVolume)
}
return true
}

Related

Handling dynamic link from Firebase in swift

I have made a dynamic link in Firebase, which op my iOS app, when I click it.
The problem is, that I can't print information about the link.
I use this function in the AppDelegate file to handle the dynamic link:
func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: #escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
print("Handeling dynamic link")
if let incomingURL = userActivity.webpageURL {
print("Incoming URL is \(incomingURL)")
let linkHandled = DynamicLinks.dynamicLinks().handleUniversalLink(incomingURL) { (dynamicLink, error) in
guard error == nil else {
print("Found an error! \(error!.localizedDescription)")
return
}
if let dynamicLink = dynamicLink {
self.handleIncomingDynamicLink(dynamicLink)
}
}
if linkHandled {
return true
} else {
// Maybe do other things with our incoming url
return false
}
}
return false
}
When I click on a dynamic link and open the app, none of the print statements is printed to the console.
It seems like this function is never tapped into.
The handleDynamicLink function is:
func handleIncomingDynamicLink(_ dynamicLink: DynamicLink) {
guard let url = dynamicLink.url else {
print("That is weird. My dynamic link object has no url")
return
}
print("Your incoming link parameter is \(url.absoluteString)")
}
I want to print information about the url so I can debug and use the information in the url to redirect to pages in the app.
When I'm testing this, I run the app on an iPhone connected to my mac.
I don't run it on the iOS simulator.
The code in your UIApplication delegate method looks good. The only difference I see with the way you have yours compared to mine is I'm returning true in the last line & you're returning false.
By the way, put breakpoints in that UIApplication delegate method & let me know if you're ever even getting to the first line in that delegate method. Are you able to hit any breakpoints in your handleIncomingDynamicLink() method?
Also, try adding this in applicationDidBecomeActive:
func applicationDidBecomeActive(_ application: UIApplication) {
guard let url = self.launchURL else { return }
self.launchURL = nil
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0, execute: { // wait to init. notifs.
DynamicLinks.dynamicLinks().handleUniversalLink(url) { (dynamiclink, error) in
if let dynamiclink = dynamiclink {
self.handleIncomingDynamicLink(dynamiclink)
}
}
})
}
And add this:
func application(_ app: UIApplication,
open url: URL,
options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
print("RECEIVED A URL THROUGH A CUSTOM SCHEME: \(url.absoluteString)")
if let dynamicLink = DynamicLinks.dynamicLinks().dynamicLink(fromCustomSchemeURL: url) {
self.handleIncomingDynamicLink(dynamicLink)
return true
} else { // Maybe handle Google or Facebook sign-in here
return false
}
}

Forwarding user's location to server when application is in the background (Swift/iOS)

I need to keep my server updated with user's location even when the app is in the background or terminated.
The location updating is working just fine and seems to wake the application as wanted.
My problem is regarding the forwarding of the user's location via a PUT request to the server.
I was able to go through the code with breakpoints and it goes well except that when I check with Charles if requests are going though, nothing appears.
Here is what I have so far:
API Client
final class BackgroundNetwork: NSObject, BackgroundNetworkInterface, URLSessionDelegate {
private let keychainStorage: Storage
private var backgroundURLSession: URLSession?
init(keychainStorage: Storage) {
self.keychainStorage = keychainStorage
super.init()
defer {
let sessionConfiguration = URLSessionConfiguration.background(withIdentifier: "backgroundURLSession")
sessionConfiguration.sessionSendsLaunchEvents = true
sessionConfiguration.allowsCellularAccess = true
backgroundURLSession = URLSession(configuration: sessionConfiguration,
delegate: self,
delegateQueue: nil)
}
}
func put<T: Encodable>(url: URL, headers: Headers, body: T) {
var urlRequest = URLRequest(url: url)
urlRequest.httpMethod = "PUT"
let authenticationToken: String? = try? keychainStorage.get(forKey: StorageKeys.authenticationToken)
if let authenticationToken = authenticationToken {
urlRequest.setValue(String(format: "Bearer %#", authenticationToken), forHTTPHeaderField: "Authorization")
}
headers.forEach { (key, value) in
if let value = value as? String {
urlRequest.setValue(value, forHTTPHeaderField: key)
}
}
do {
let jsonData = try JSONEncoder().encode(body)
urlRequest.httpBody = jsonData
} catch {
#if DEBUG
print("\(error.localizedDescription)")
#endif
}
backgroundURLSession?.dataTask(with: urlRequest)
}
}
AppDelegate
// ...
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
if launchOptions?[UIApplication.LaunchOptionsKey.location] != nil {
environment.locationInteractor.backgroundDelegate = self
_ = environment.locationInteractor.start()
}
return true
}
// ...
extension AppDelegate: LocationInteractorBackgroundDelegate {
func locationDidUpdate(location: CLLocation) {
taskId = UIApplication.shared.beginBackgroundTask {
UIApplication.shared.endBackgroundTask(self.taskId)
self.taskId = .invalid
}
environment.tourInteractor.updateLocationFromBackground(latitude: Float(location.coordinate.latitude),
longitude: Float(location.coordinate.longitude))
UIApplication.shared.endBackgroundTask(taskId)
taskId = .invalid
}
}
SceneDelegate (yes, the application is using SwiftUI and Combine and I target iOS 13 or later)
func sceneWillEnterForeground(_ scene: UIScene) {
if let environment = (UIApplication.shared.delegate as? AppDelegate)?.environment {
environment.locationInteractor.backgroundDelegate = nil
}
}
func sceneDidEnterBackground(_ scene: UIScene) {
if let appDelegate = (UIApplication.shared.delegate as? AppDelegate) {
appDelegate.environment.locationInteractor.backgroundDelegate = appDelegate
_ = appDelegate.environment.locationInteractor.start()
}
}
So basically, whenever my app goes in background, I set my delegate, restart the location updates and whenever an update comes, my interactor is called and a request is triggered.
According to breakpoints, eveything just works fine up to backgroundURLSession?.dataTask(with: urlRequest). But for some reason the request never gets fired.
I obviously checked Background Modes capabilities Location updates and Background fetch.
Any idea why ?
That’s correct, the line
backgroundURLSession?.dataTask(with: urlRequest)
does nothing. The way to do networking with a session task is to say resume, and you never say that. Your task is created and just thrown away. (I’m surprised the compiler doesn’t warn about this.)

Swift 5 store event until all of the app has initialised

Im working on an Ionic/Capacitor native plugin in Swift
I am trying to integrate OneSignal push notification service, my code below works just fine if the app is in the foreground and a user clicks the notification, however if the app is started from clicking a notification my events dont fire, I figure this is because the AppDelegate.swift loads before Capacitor/Ionic and when my NotificationCenter.default.post gets fired the data doesnt reach my plugin.
I guess I want to know if there is any way in Swift to store this post data until all parts of the app have loaded and initialised i.e Capacitor/Ionics webview
AppDelegate.swift
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
let notificationOpenedBlock: OSHandleNotificationActionBlock = { result in
// This block gets called when the user reacts to a notification received
let payload: OSNotificationPayload = result!.notification.payload
var fullMessage = payload.body
let nc = NotificationCenter.default
nc.post(name: Notification.Name("TestingEvents"), object: nil)
if payload.additionalData != nil {
if payload.title != nil {
let messageTitle = payload.title
print("Message Title = \(messageTitle!)")
}
let additionalData = payload.additionalData
if additionalData?["actionSelected"] != nil {
fullMessage = fullMessage! + "\nPressed ButtonID: \(additionalData!["actionSelected"])"
}
}
}
return true
}
MyPlugin.swift
public override func load() {
let nc = NotificationCenter.default
nc.addObserver(self, selector: #selector(handleSignal), name: Notification.Name("TestingEvents"), object: nil)
}
#objc func handleSignal() {
self.bridge.triggerWindowJSEvent(eventName: "myCustomEvent")
self.notifyListeners("myPluginEvent", data: [:])
}
and my app.component.ts
window.addEventListener('myCustomEvent', () => {
alert("myCustomEvent ")
});
Plugins.myPlugin.addListener("myPluginEvent", () => {
alert("myPluginEvent ")
});
this is a logical problem. what you can do here is save the notificaiton data (OSNotificationPayload) in an object/dictionary/array you receive in didFinishLaunchingWithOptions and add an observer "HandleNotificaionAfterPluginSetup" to appdelegate.
For this observer, you can trigger a post notification from your plugin once it is initialized and have everything ready to handle notificaitons. once you are all set in your plugin and have triggered the notificaion that code can execute in the notificaion handler you just setup which willl execute the code
if payload.title != nil {
let messageTitle = payload.title
print("Message Title = \(messageTitle!)")
}
let additionalData = payload.additionalData
if additionalData?["actionSelected"] != nil {
fullMessage = fullMessage! + "\nPressed ButtonID: \(additionalData!["actionSelected"])"
}
}```

Segue not performing on quick action when app is already open (Xcode 9)

The Problem
I wanted to create a quick action (3D Touch) that allows a user to go directly to the statistics section of a game. I based my solution on the code from this tutorial and got it working with this code. I added some extra code because I want to use the quick action to do something different than in the tutorial. This is the function that is responsible for performing a segue from the initial view to the statistics view. It's in AppDelegate.swift:
From AppDelegate.swift
func handleQuickAction(shortcutItem: UIApplicationShortcutItem) -> Bool {
var quickActionHandled = false
let type = shortcutItem.type.components(separatedBy: ".").last
if let shortcutType = Shortcut.init(rawValue: type!) {
switch shortcutType {
case .statistics:
quickActionHandled = true
// I use dispatchQueue to ensure that the segue occurs immediately
DispatchQueue.main.async {
self.window?.rootViewController?.performSegue(withIdentifier: "ARView_to_stats", sender: self.window?.rootViewController)
}
}
}
return quickActionHandled
}
This function is called from the following delegate function:
From AppDelegate.swift
func application(_ application: UIApplication, performActionFor shortcutItem: UIApplicationShortcutItem, completionHandler: #escaping (Bool) -> Void) {
completionHandler(handleQuickAction(shortcutItem: shortcutItem))
}
This setup works perfectly if the app has been closed (from the app switcher / multitasking view) and I tap on the quick action. However, if I go home without closing the application completely and try to do the same, it does not go to the statistics view but rather to the game view.
I've tried this many times over and it crashes once in a while with the following message:
com.apple.CoreMotion.MotionThread (11): EXC_BAD_ACCESS (code=1, address=0x48307beb8)
How could I change this to make it work as desired.
What I tried
Removing DispatchQueue.main.async
Try something like this
var isQuickLaunched = false
var quickLaunchOption: Shortcut!
func handleQuickAction(shortcutItem: UIApplicationShortcutItem) -> Bool {
var quickActionHandled = false
let type = shortcutItem.type.components(separatedBy: ".").last
if let shortcutType = Shortcut.init(rawValue: type!) {
isQuickLaunched = true
quickLaunchOption = shortcutType
quickActionHandled = true
}
return quickActionHandled
}
func application(_ application: UIApplication, performActionFor shortcutItem: UIApplicationShortcutItem, completionHandler: #escaping (Bool) -> Void) {
completionHandler(handleQuickAction(shortcutItem: shortcutItem))
}
func applicationDidBecomeActive(application: UIApplication) {
if (isQuickLaunched == true) {
isQuickLaunched = false
switch quickLaunchOption {
case .statistics:
DispatchQueue.main.async {
self.window?.rootViewController?.performSegue(withIdentifier: "ARView_to_stats", sender: self.window?.rootViewController)
}
}
}
}

Notifications with Swift 2 and Cloudkit

I am making a "texting app" you can call it and it uses cloudkit and I have been looking everywhere to add notifications that work with cloudkit... Would someone be able to tell me the code to add push notifications for cloudkit in detail because I am very lost... Also I wan't the notifications to go to different "texting rooms" (in cloudkit it would be record types...) For instance I have one record type called "text" and another one called "text 2" I don't want notifications from "text" to get to people who use "text2" and vise versa.
Using Swift 2.0 with El Captain & Xcode 7.2.1
Elia, You need to add this to your app delegate. Which will arrive in a userInfo packet of data, which you can then parse to see which database/app sent it.
UIApplicationDelegate to the class
application.registerForRemoteNotifications() to the
func application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
Than this method
func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject]) {
let notification = CKQueryNotification(fromRemoteNotificationDictionary: userInfo as! [String : NSObject])
let container = CKContainer(identifier: "iCloud.com")
let publicDB = container.publicCloudDatabase
if notification.notificationType == .Query {
let queryNotification = notification as! CKQueryNotification
if queryNotification.queryNotificationReason == .RecordUpdated {
print("queryNotification.recordID \(queryNotification.recordID)")
// Your notification
}
}
print("userInfo \(userInfo["ck"])")
NSNotificationCenter.defaultCenter().postNotificationName("NotificationIdentifier", object: self, userInfo:dataDict)
}
}
}
}
}
That'll get you started.
You can use this method to check your subscriptions programmatically, of course while your developing you can use the dashboard.
func fetchSubsInPlace() {
let container = CKContainer(identifier: "iCloud.com")
let publicDB = container.publicCloudDatabase
publicDB.fetchAllSubscriptionsWithCompletionHandler({subscriptions, error in
for subscriptionObject in subscriptions! {
let subscription: CKSubscription = subscriptionObject as CKSubscription
print("subscription \(subscription)")
}
})
}
And finally when you got it; you can this routine to ensure you capture any subscriptions you missed while your app was sleeping and make sure that subscriptions don't go to all your devices, once you treated them too.
func fetchNotificationChanges() {
let operation = CKFetchNotificationChangesOperation(previousServerChangeToken: nil)
var notificationIDsToMarkRead = [CKNotificationID]()
operation.notificationChangedBlock = { (notification: CKNotification) -> Void in
// Process each notification received
if notification.notificationType == .Query {
let queryNotification = notification as! CKQueryNotification
let reason = queryNotification.queryNotificationReason
let recordID = queryNotification.recordID
print("reason \(reason)")
print("recordID \(recordID)")
// Do your process here depending on the reason of the change
// Add the notification id to the array of processed notifications to mark them as read
notificationIDsToMarkRead.append(queryNotification.notificationID!)
}
}
operation.fetchNotificationChangesCompletionBlock = { (serverChangeToken: CKServerChangeToken?, operationError: NSError?) -> Void in
guard operationError == nil else {
// Handle the error here
return
}
// Mark the notifications as read to avoid processing them again
let markOperation = CKMarkNotificationsReadOperation(notificationIDsToMarkRead: notificationIDsToMarkRead)
markOperation.markNotificationsReadCompletionBlock = { (notificationIDsMarkedRead: [CKNotificationID]?, operationError: NSError?) -> Void in
guard operationError == nil else {
// Handle the error here
return
}
}
let operationQueue = NSOperationQueue()
operationQueue.addOperation(markOperation)
}
let operationQueue = NSOperationQueue()
operationQueue.addOperation(operation)
}
}

Resources