CloudKit CKShare userDidAcceptCloudKitShareWith Never Fires on Mac App - cloudkit

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.

Related

Disable copy-past premission in iOS 16.1

as you may know, iOS 16 introduced a bug that when your app use UIPasteboardit keeps asking for permission every time. This annoying alert is gone after 16.1 as I heard, but still see it. User must go to the app setting in the iOS setting and change Paste from other apps to allow.
Is there anyway to do it on app that user doesn't need to do those crazy steps? Otherwise we should inform every user to do that to get rid of that alert.
Would be so grateful if you can give me a good hint to fix it. Maybe some privacy key on info.plist?
here is my simple code to read from clipboard
private func getURLFromClipboard() {
let pasteboardString: String? = UIPasteboard.general.string
if let string = pasteboardString {
if validateUrl(urlString: string as NSString) {
presentShareURL(url: string)
}
}
}

SwiftUI Universal Links not working for NFC

Our app uses universal links and the AASA file seems to be working fine because hyperlinks in emails, as well as QR coded URLs both launch the app. They both also cause the SwiftUI .onOpenURL(perform:) function to be called.
Things are a bit more confusing with NFC. The URL is recognised and the app launches, suggesting the issue is not related to AASA. However, the onOpenURL function is not called. Can anyone tell me how to handle universal links from NFC? Ideally I'd like to keep it pure SwiftUI but if I need to use the AppDelegate so be it.
The code in the main SwiftUI file is:
import SwiftUI
#main
struct MyApp: App {
#UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
var body: some Scene {
WindowGroup {
ContentView()
.onOpenURL(perform: { url in
print("url opened:\(url)")
// prints when opened from URL in email,
// Notes or QR code, but not when opened
// from URL embedded in NFC NDEF
})
}
}
}
I have also tried defining the application(_:continue:restorationHandler:) function in the AppDelegate, but that doesn't get called either.
Thanks for any help.
The solution is to add a continue user activity handler on a suitable view:
.onContinueUserActivity(NSUserActivityTypeBrowsingWeb) { userActivity in
print("Continue activity \(userActivity)")
guard let url = userActivity.webpageURL else {
return
}
print("User wants to open URL: \(url)")
// TODO same handling as done in onOpenURL()
}
It's not obvious why the same Universal Link received through a click on a link in Safari should be handled differently than the same link read from a tag, but apparently it's not the same thing.
In case it helps anyone with the same issue, I solved the issue by registering a URL schema for the app and then using .onOpenURL(perform:).
Seems a strange way to have to do it but it works as required, so happy days!

macOS Remote Push Notifications Not Showing Alert Banner

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])
}

Cannot quit the app if I cancel the save operation in a NSDocument

I'm subclassing the NSDocument class in order have a specific behavior during the save. I save the project in a folder and I create specific sub-folders with audio files used for this project.
I override the following function save(to:ofType:for:delegate:didSave:contextInfo) but I noticed a weird behaviour. Let's say I've the following implementation:
override func save(to url: URL,
ofType typeName: String,
for saveOperation: NSDocument.SaveOperationType,
delegate: Any?,
didSave didSaveSelector: Selector?,
contextInfo: UnsafeMutableRawPointer?) {
guard let customURL = createCustomUrlFrom(url: url) else { return }
super.save(to: customURL,
ofType: typeName,
for: saveOperation,
delegate: delegate,
didSave: didSaveSelector,
contextInfo: contextInfo)
}
I try to create a custom URL and if I don't manage to do it I cancel the save operation.
Now if I quit the app before saving, the app prompts me to save. If I cannot create the custom URL and I return (before the super.save… call), the quit button or "cmd+q" doesn't work! I've to force quit to close the app.
Anyone see what I did wrong here? Does something is running in the background that prevents me to close the app?
UPDATE
Maybe it comes from the sheet prompted when we quit an edited document. We have the window saying Do you want to save the changes made to the document “Untitled”? with 3 buttons Dont's save, cancel save...
If I click on save, then the project already exists I show a window to let the user replace the project or cancel the save (it's done in the createTargetUrlFrom(url:) func. If the user choose to cancel the app cannot be quit.
So I think about the first window running in background maybe…
I found the solution to this issue!
In fact the application was waiting a reply to the NSTerminateLater. I needed to use the reply(toApplicationShouldTerminate:) function. So I just added NSApp.reply(toApplicationShouldTerminate: false) before returning from the guard statement.

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.