macOS Remote Push Notifications Not Showing Alert Banner - swift

I'm trying to enable Push Notifications for my macOS application. Everything seems to be working. I'm able to get the device token. Send the notification without errors. Except there is no alert that shows up on my Mac.
I added the following code to see if my application was receiving it or not.
func application(_ application: NSApplication, didReceiveRemoteNotification userInfo: [String : Any]) {
print(userInfo)
}
And after I send a notification I see the following in the console.
["aps": {
alert = "Alert - Hello World";
sound = "ping.aiff";
}]
So it looks like it's getting to the device ok, just not showing the alert.
I have tested the exact same setup on iOS and it's working fine and showing the alert there. So I must be missing something specifically on macOS.
I have tried the following things to fix this:
Tested with application closed and open (didn't work either time)
Ensured that notifications are enabled for application in System Preferences
If I create a local notification manually in code, it works perfectly, and the notification banner appears
I can not test it on older versions of macOS because the push notification API I'm using was just released in macOS Mojave
I have also tried to create another test project, and the same problem happened
I have ensured that Do Not Disturb is turned off, and have also checked in the notification center for the notification, but it does not display there either.
How can I get it to show the banner and play the sound on macOS?

In iOS the following code works fine:
UNUserNotificationCenter.current().requestAuthorization(options: options) { granted, _ in
guard granted else { return }
DispatchQueue.main.async {
application.registerForRemoteNotifications()
}
}
For macOS I changed that code to be:
UNUserNotificationCenter.current().requestAuthorization(options: options) { granted, _ in
guard granted else { return }
DispatchQueue.main.async {
NSApplication.shared.registerForRemoteNotifications()
}
}
Turns out the line NSApplication.shared.registerForRemoteNotifications() is incorrect. On macOS you have to pass in the same options you provided into this call.
Changing that line to the following worked.
NSApplication.shared.registerForRemoteNotifications(matching: [.alert, .sound, .badge])
What I find strange is that in Apple's documentation it says that method is deprecated, and that we should use registerForRemoteNotifications() instead. Which makes me think there is some type of bug with registerForRemoteNotifications() where the notifications are not appearing correctly.
One other thing to mention. It did take a little bit of time (couple minutes), and a few notifications sent, for them to actually appear after making that change. Not sure if it was just due to slow internet connection or what. But now they are appearing very quickly after being sent.
Edit
Apple has informed me that this is fixed in macOS 10.14.4. I have not been able to upgrade to the best and test it yet tho. So I can not confirm at this point. I will update this when I get a chance to test on macOS 10.14.4.

Clarifying, this is macOS, and NSApp is just NSApplication.shared
Actually I have to amend my answer because I did have some inconsistent results, so right now, I do have
if #available(OSX 10.14, *) {
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { [weak self] (success, error) in
if let error = error {
// LOG
} else {
NSApp.registerForRemoteNotifications(matching: [.alert, .badge, .sound])
UNUserNotificationCenter.current().setNotificationCategories(varWithMyCategories)
}
}
} else {
NSApp.registerForRemoteNotifications(matching: [.alert, .badge, .sound])
}

Related

Why doesn't print() work when sending a message via Apple Watch To iPhone?

I am learning how to create a companion Watch App for an iPhone app. (Everything is being done on the simulator)
I am sending a message when I tap a button on the Watch app.
session.sendMessage(["Message": "It Worked" ], replyHandler: nil) { (error) in
print(error)
}
Then I receive the message in ViewController.swift:
func session(_ session: WCSession, didReceiveMessage message: [String : Any]) {
print(message["Message"])
print("!!!!")
theLabel.text = message["Message"] as! String
}
Neither print function fire. However I know I am getting the message because my label is being updated. Break points also don't work in this function. If I put another function call in the didRecieveMessage function it will fire.
Is this a bug? It made me think my message wasn't being sent for the longest time because I was just trying to see if the function was firing via print() and breakpoints.
If not a bug, why doesn't print() work?
The reason neither is printing is because the debug console is hooked up to the WatchKit app, which you've only programmed to print in the case of an error.
And the reason the "!!!!" and message["Message"] are not printing is because the debug console is not hooked up to the iOS app.
If you hooked the console to the iOS app you would see the print work.

CloudKit CKShare userDidAcceptCloudKitShareWith Never Fires on Mac App

I am working on accepting a CKShare in a macOS app in Swift 4. I've already done all the following:
Create the CKShare and save it with its rootRecord to CloudKit
Add a participant (CKShare.Participant)
I've confirmed that the CKShare is on the CloudKit server and that the person I invited has access to it. Here's a screenshot: https://d.pr/i/0sMFQq
When I click the share link associated with the CKShare, it opens my app, but nothing happens and userDidAcceptCloudKitShareWith doesn't fire.
func application(_ application: NSApplication, userDidAcceptCloudKitShareWith metadata: CKShareMetadata) {
print("Made it!") //<-- This never gets logged :(
let shareOperation = CKAcceptSharesOperation(shareMetadatas: [metadata])
shareOperation.qualityOfService = .userInteractive
shareOperation.perShareCompletionBlock = {meta, share, error in
print("meta \(meta)\nshare \(share)\nerror \(error)")
}
shareOperation.acceptSharesCompletionBlock = { error in
if let error = error{
print("error in accept share completion \(error)")
}else{
//Send your user to where they need to go in your app
print("successful share:\n\(metadata)")
}
}
CKContainer.default().add(shareOperation)
}
Is there some kind of URL scheme I have to include in my info.plist? Or perhaps a protocol I need to conform to in my NSApplicationDelegate delegate? I can't, for the life of me, figure out what to do. Thanks in advance!
Update
I've tried a few more things on this. When I open the share link in a web browser, I see this:
Clicking OK makes the screen fade away to this:
Not particularly helpful. :) After doing this, the participant's status in CloudKit is still Invited, so the share still hasn't been accepted.
When I click on a share link within Messages, I am shown a popup like this:
After I click open, a new copy of my app shows up in the dock, then the app suddenly closes. The crash log states:
Terminating app due to uncaught exception 'CKException', reason: 'The application is missing required entitlement com.apple.developer.icloud-services'
I've tried turning iCloud off and on again in the Capabilities section of Xcode, but nothing changes. I know this exception can't be right because I can start my app normally and use CloudKit all day long. Only the CKShare causes this crash.
This is a mess. Save me, Obi-wan Kenobi, you're my only hope.
Yes,
You need to add this to your info.plist.
<key>CKSharingSupported</key>
<true/>
** EDITED ANSWER **
I use this code to share, I don't do it manually... not sure if this is an option under OS X I must confess. I am using iOS.
let share = CKShare(rootRecord: record2S!)
share[CKShareTitleKey] = "My Next Share" as CKRecordValue
share.publicPermission = .none
let sharingController = UICloudSharingController(preparationHandler: {(UICloudSharingController, handler:
#escaping (CKShare?, CKContainer?, Error?) -> Void) in
let modifyOp = CKModifyRecordsOperation(recordsToSave:
[record2S!, share], recordIDsToDelete: nil)
modifyOp.savePolicy = .allKeys
modifyOp.modifyRecordsCompletionBlock = { (record, recordID,
error) in
handler(share, CKContainer.default(), error)
}
CKContainer.default().privateCloudDatabase.add(modifyOp)
})
sharingController.availablePermissions = [.allowReadWrite,
.allowPrivate]
sharingController.delegate = self
sharingController.popoverPresentationController?.sourceView = self.view
DispatchQueue.main.async {
self.present(sharingController, animated:true, completion:nil)
}
This presents an activity controller in which you can choose say email and then send a link. You might also want to watch this video, focus on cloudKit JS right at the beginning.
Watch this WWDC video too https://developer.apple.com/videos/play/wwdc2015/710/
It talks about the cloudkit JSON API, using it you can query what has and what hasn't been shared in a terminal window/simple script perhaps. I did the same when using dropbox API a few years back. Hey you can even use the cloudkit JSON API within your code in place of the native calls.
I finally got it to work! I did all of the following:
Deleted my app from ~/Library/Developer/Excode/DerivedData
Made sure I had no other copies of my app archived anywhere on my machine.
Said a prayer.
Rebooted.
Sheesh, that was rough. :)
If your app is a Mac Catalyst app running on any version of macOS Catalina at least up to and including 10.15.4 Beta 1, a UIApplicationDelegate userDidAcceptCloudKitShareWith method will never be invoked.
After some significant debugging, we discovered that the MacCatalyst UIKit doesn’t even have an implementation for userDidAcceptCloudKitShareWithMetadata in its UIApplication delegate. It’s not broken, it’s just not there. So, at least temporarily, our workaround is the following, which seems to work, even if it’s very inelegant:
// Add CloudKit sharing acceptance handling to UINSApplicationDelegate, which is missing it.
#if targetEnvironment(macCatalyst)
extension NSObject {
#objc func application(_ application: NSObject, userDidAcceptCloudKitShareWithMetadata cloudKitShareMetadata: CKShare.Metadata) {
YourClass.acceptCloudKitShare(cloudKitShareMetadata: cloudKitShareMetadata)
}
}
#endif
If you are using a SceneDelegate, implement the delegate callback there, instead of on AppDelegate.
func windowScene(_ windowScene: UIWindowScene, userDidAcceptCloudKitShareWith cloudKitShareMetadata: CKShare.Metadata) {
// ...
}
You need to create the app delegate for your SwiftUI app using #NSApplicationDelegateAdaptor:
#main
struct Sharing_ServiceApp: App
{
#NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
var body: some Scene
{
WindowGroup
{
ContentView()
.environment(\.managedObjectContext, persistenceController.container.viewContext)
}
}
}
I put that line in and my code instantly started receiving the share requests.

Why dont my facebook interstitial ads show in my app?

Im in Swift and every-time I call them the delegate func didFailWithError gets called. Im using the same code I use for the facebook intersitial ads in all my apps and it works perfectly except for this new app I created. No ads pop up and I get a message in my console saying:
DiskCookieStorage changing policy from 2 to 0, cookie file:
file:///private/var/mobile/Containers/Data/Application/4E5FA239-208C-4B08-87C6-E4DB1CC3CC76/Library/Cookies/Cookies.binarycookies
This is how I setup the code in my GameViewController:
import UIKit
import SpriteKit
let interstitialFBAD: FBInterstitialAd = FBInterstitialAd(placementID: "2320559454634478_1321860725967683")
class GameViewController: UIViewController, FBInterstitialAdDelegate {
override func viewDidLoad() {
super.viewDidLoad()
if let scene = GameScene(fileNamed:"GameScene") {
loadFBInterstitialAd()
}
}
//fbAds--------------------------------------------------------------------------------------------------------
func loadFBInterstitialAd() {
interstitialFBAD.delegate = self
interstitialFBAD.load()
print("what")
}
func interstitialAdDidLoad(_ interstitialAd: FBInterstitialAd) {
interstitialFBAD.show(fromRootViewController: self)
print("popup")
}
func interstitialAd(_ interstitialAd: FBInterstitialAd, didFailWithError error: Error) {
print("failed")
}
//fbAds--------------------------------------------------------------------------------------------------------
So from
Im in Swift and every-time I call them the delegate func didFailWithError gets called. Im using the same code I use for the facebook intersitial ads in all my apps and it works perfectly except for this new app I created. No ads pop up and I get a message in my console saying:
This seems like my first solution is not the answer, but it's so obvious I need to point it out nonetheless: from the Facebook docs it seems like there's a couple steps to creating an app that can use the SDK properly (namely creating an app in Facebook and using the proper Info.plist keys, etc).
If that's not what is happening, though, which I imagine it's not, then this could still be due to the application not being properly set up but for a reason other than Facebook SDK.
Without seeing how you are initializing the SDK in your AppDelegate and without confirmation it's not Info.plist keys that are missing for the ads you are attempting to show, it's hard to say what the issue could be here. Have you contacted Facebook support? They'd definitely be able to help here as well. Somewhere off this page I'm sure you can find proper live support as a paying ads customer.

Listening on NSNotificationCenter messages in react-native app

I am trying to listen to some messages from NSNotificationCenter in my app and send out events to JS side when some messages are received. Mainly, I am interested in message NSSystemTimeZoneDidChangeNotification which can be used to detect if the user's device time zone has changed. I have added following code in AppDelegate.swift for this:
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject : AnyObject]?) -> Bool {
// Existing code
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(AppDelegate.timezoneChanged), name: NSSystemTimeZoneDidChangeNotification, object: nil)
}
func timezoneChanged() {
let appEventEmitter = self.bridge.moduleForClass(AppEventEmitter) as! AppEventEmitter
appEventEmitter.sendEventWithName("timezoneChanged", body:nil)
}
After this change, if I change the timezone on my phone and go to the app, the timezoneChanged function is invoked but I see an exception in code right away and the application crashes
fatal error: unexpectedly found nil while unwrapping an Optional value
I guess the problem is with appEventEmitter. I am using exact some code in other parts of the app to send an event to JS side but I don't know why does it not work here. If I change the timezoneChanged function implementation to just a print statement, then I am able to see the print statement and the app doesn't crash.
I don't know Swift language as such and not sure how to debug this problem. Please let me know if you have any pointers that can help in debugging this problem.

SLComposeViewController for FaceBook always returns SLComposeViewControllerResult.Done

Just recently my standard sharing-code was working as expected. When the user cancelled a Facebook share, the SLComposeViewController.completionHandler returned SLComposeViewControllerResult.Cancelled
Now however, after updates made by Apple or Facebook (the changes include a new design for the compose view), the completionHandler always gets the result Done.
I get the same behaviour on:
iPhone 6s plus running iOS 9.0.2
iPhone 6 plus running iOS 9.0.2
iPhone 5s running iOS 8.2
Anybody else have the same problem? Below is my code:
let fbComposeViewController = SLComposeViewController(forServiceType: SLServiceTypeFacebook)
fbComposeViewController.addURL( NSURL(string:"http://www.some.url.com/)"))
fbComposeViewController.completionHandler = { (result:SLComposeViewControllerResult) -> Void in
switch result {
case SLComposeViewControllerResult.Cancelled:
print("Cancelled") // Never gets called
break
case SLComposeViewControllerResult.Done:
print("Done")
break
}
}
self.presentViewController(fbComposeViewController, animated: true) {
}
Do you have the Facebook app installed when you are testing this? If not, that can explain this behaviour. See also this comment: https://developers.facebook.com/bugs/608854599254853/?comment_id=565037930316792