import AppKit import ShotdeckCore @MainActor enum Sharing { /// Presents the AirDrop picker for `fileURL`, anchored to `view`. /// Throws `ShotdeckError.airDropUnavailable` when the service cannot be created, /// `canPerform` is false, or `view` is not in a visible window (a detached view /// never produces an on-screen sheet). static func airDrop(fileURL: URL, from view: NSView) throws { guard let service = NSSharingService(named: .sendViaAirDrop), service.canPerform(withItems: [fileURL]) else { throw ShotdeckError.airDropUnavailable } // Presenting from a detached NSView (no window) yields a sheet that never appears. guard let window = view.window, window.isVisible else { throw ShotdeckError.airDropUnavailable } NSApp.activate() window.makeKeyAndOrderFront(nil) service.subject = fileURL.lastPathComponent let session = AirDropSession(service: service, window: window, view: view) AirDropSession.keepAlive(session) service.delegate = session service.perform(withItems: [fileURL]) } } /// Retains the sharing service for the life of the picker and supplies the real /// on-screen window as the sheet parent. `NSSharingService.delegate` is weak. @MainActor private final class AirDropSession: NSObject, NSSharingServiceDelegate { static var live: [AirDropSession] = [] let service: NSSharingService let window: NSWindow let view: NSView init(service: NSSharingService, window: NSWindow, view: NSView) { self.service = service self.window = window self.view = view } static func keepAlive(_ session: AirDropSession) { live.append(session) } private func drop() { Self.live.removeAll { $0 === self } } func sharingService( _ sharingService: NSSharingService, sourceWindowForShareItems items: [Any], sharingContentScope: UnsafeMutablePointer ) -> NSWindow? { sharingContentScope.pointee = .item return window } func sharingService( _ sharingService: NSSharingService, sourceFrameOnScreenForShareItem item: Any ) -> NSRect { let inWindow = view.convert(view.bounds, to: nil) return window.convertToScreen(inWindow) } func sharingService(_ sharingService: NSSharingService, didShareItems items: [Any]) { drop() } func sharingService( _ sharingService: NSSharingService, didFailToShareItems items: [Any], error: any Error ) { drop() } }