macOS Quick Look Preview Extension not calling -viewDidDisappear - swift

I'm implementing a Quick Look Preview extension on macOS that will preview some unsupported audio types.
To be able to stop the audio playback when the user stops previewing, I'm pausing my audio engine in -viewDidDisappear.
However, the problem is that -viewDidDisappear is only called when exiting out of Quick Look in Finder altogether, not when I'm navigating to the next/previous file with the arrow keys.
class PreviewViewController: NSViewController, QLPreviewingController {
func preparePreviewOfFile(at url: URL, completionHandler handler: #escaping (Error?) -> Void) {
player.load(url)
player.play()
}
override func viewDidDisappear() {
// Not called when switching to the next or previous preview in Finder!
player.stop()
}
}
I've tried going through all the usual suspects on both NSViewController (-removeFromParent, -viewWillDisappear, -viewDidDisappear) and it's NSView (-viewDidHide, -viewDidMoveToWindow, -viewDidMoveToSuperview), but without success.
Neither of them are called when I switch to a different file in the Quick Look preview in Finder with the arrow keys (resulting in the audio just continuing to play while I'm Quick Looking some other file).
Anyone knows how to know if my preview view controller has been hidden?

Related

How to debug an app activated from dock menu

I'm trying to understand how to debug a dock open document event.
I'd like to understand the environment so that normal document restoration can be disabled on startup when the app is stated to open some document, especially when the doc itself is a candidate for restoration.
But, since the app is seemingly started, "no debug", breaks in several app delegate methods are never called; i.e.,
func applicationShouldHandleReopen(_ sender: NSApplication, hasVisibleWindows flag: Bool) -> Bool
func applicationDockMenu(sender: NSApplication) -> NSMenu?
As each document open event will instantiate a document window, normally I'd like to avoid that but having so is useful, so I'd like the ability to debug it while in this sort of start up, so long as I'm attached to the debugger.
If you want to debug the app launch process while the app is not connected to a debugger, you may need to just rely on unified logging. For example, I often use this pattern, with a private OSLog for each source file, which I can then observe these log statements in the Console:
// ViewController.swift
import Cocoa
import os.log
private let log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "ViewController")
class ViewController: NSViewController {
var string = "..."
override func viewDidLoad() {
super.viewDidLoad()
os_log(.debug, log: log, "viewDidLoad: string = %{public}#", string)
}
...
}
Then, if the Console is configured to show debugging statements (e.g. “Action” » “Include Debug Messages”), I can filter for these (e.g. type subsystem: in the search bar, followed by your subsystem, the bundle identifier in this case). Then, completely independent of Xcode, I can watch these log statements from the Console app (and filter for my subsystem and/or category) as my app runs, not attached to the debugger:
This technique works for both macOS and iOS apps.
For more information, see the Unified Logging and Activity Tracing video.
Obviously, once your debug build is running, but having been launched independently of the debugger, you can manually attach Xcode’s debugger to it via the “Debug” » “Attach to process” menu option.
But attaching a debugger to an app that is already running is not practical when you’re trying to debug the startup sequence when an app launches. But the aforementioned unified logging is often sufficient for confirming what methods were called when, through the judicious use of os_log statements.

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.

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.

How to reload an application from a 'play again?' button

Using Xcode 7.3, writing Swift program; searched for this topic but could not find an answer. Apologies, in advance. Last program language was COBOL.
Application background:
It is a single view application, User keys in data and at some point the User 'wins' - at that point, two buttons are available: 'Quit?' and 'Play Again?'
For the 'Quit?' button I have:
// MARK: C.This IBAction used to quit the game
#IBAction func quitIt(sender: AnyObject)
{
exit(0)
}
Hopefully, the above is the most efficient way.
What I need help on is the 'Play Again?' button:
// TODO: B. This IBAction used to play another game
#IBAction func Playagain(sender: AnyObject)
{
}
What code do I put in?
Move the startup code for your game to it's own func(tion).
Call that function both from [wherever you moved it from] and also in PlayAgain().
I hope this is for OS X rather than iOS - Apple will probably reject your iOS app if it exits. They consider that "crashing".
https://developer.apple.com/library/ios/qa/qa1561/_index.html

How to get filenames when you drag'n'drop files from Finder on your app

I'm trying to let my NSView accept files which you can drag and drop from Finder.
I've already checked the documentation (https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/DragandDrop/Tasks/acceptingdrags.html#//apple_ref/doc/uid/20000993-BABHHIHC), but the only thing that works is accepting a drop of a String.
As the docs say, I have registered the types which can be dropped
func commonInit(){
let allowedDropTypes = [NSFilenamesPboardType]
registerForDraggedTypes( allowedDropTypes )
Swift.print( registeredDraggedTypes )
}
If I print out the registeredDraggedTypes, I get NSFilenamesPboardType.
I've also implemented draggingEntered(sender)
override func draggingEntered(sender: NSDraggingInfo) -> NSDragOperation {
Swift.print("Dragging entered")
return sender.draggingSourceOperationMask()
}
And prepareForDragOperation(sender) and performDragOperation(sender)
override func prepareForDragOperation(sender: NSDraggingInfo) -> Bool {
Swift.print("Prepare for drag operation")
return true
}
override func performDragOperation(sender: NSDraggingInfo) -> Bool {
Swift.print("Perform for drag operation")
return true
}
If I run my app, I can't drop any files from Finder on my app.
When I add NSStringPboardType to the array of allowed types, I can drag a piece of text on the app. If I then try to add NSPDFPboardType to the array and drag a pdf on the app, it also doesn't work.
I've also tried working with the UTI public.file-url, but to no avail.
Also, app sandboxing is turned off.
I hope somebody can help me out :-)
I found out what the problem was.
My NSView had NSImageViews as subviews and these were blocking the drag operation.
I've added an invisible NSView subview at the top and registered the drag operation on that view. Now everything works fine.
Can you try to allow/add the filetypes you want in the Info plist of your project?
example info plist file
I think you need to define the type of files you allow your application to open/handle, although not 100% sure if this is related to drag/drop. Worth a try at least.