citelao
citelao

Reputation: 6046

How do I handle dragging onto the dock icon in SwiftUI?

I have set up a SwiftUI app that seems to accept images dropped onto the dock icon, but I cannot figure out where to handle the dropped image in my app code.

How can I handle dropping an image (or any particular file) onto the dock icon of a SwiftUI app?

Background

With older-style Swift code that used NSApplication, handling file drops on an application's dock icon could be done in two steps:

  1. Register the types you want to accept in CFBundleDocumentTypes in your Info.plist.
  2. Implement application:openFile: (and probably application:openFiles:) on your NSApplicationDelegate.

This is documented succinctly in a separate question.

Creating an app delegate in Swift UI (doesn't work)

SwiftUI apps do not provide an app delegate by default, though. To implement these functions, you must do some additional work:

  1. Create a class that implements NSObject and NSApplicationDelegate (or UIApplicationDelegate):

    // or NSApplicationDelegate
    class AppDelegate: NSObject, UIApplicationDelegate {
       // ...
    }
    
  2. In your @main App implementation, setup the delegate:

    ... : App {
        // or @NSApplicationDelegateAdaptor
        @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
    

You can now implement app delegate methods! For example, this will print when your app launches:

func applicationWillFinishLaunching(_ notification: Notification) {
    print("App Delegate loaded!")
}

But implementing the openFile functions doesn't work:

func application(_ sender: NSApplication, openFile filename: String) -> Bool {
    print("test", filename)
    return false
 }
    
func application(_ sender: NSApplication, openFiles filenames: [String]) {
     print("another test", filenames)
}

Neither of these print out when dragging a file onto the app.

Scene delegates?

This seems to be a result of some work to separate AppDelegate functionality into SceneDelegates:

For those who might bump their heads in this, the equivalent functionality gets called in the scenedelegate now due to the separation of appdelegate’s functionality. The equivalent-ish function is scene(_ scene: openURLContexts:). I haven’t researched if it’s possible to ‘opt out’ of this, but for my purposes there’s no reason not to adopt the new behavior

— m_bedwell on application(open: options:) not being called (emphasis added)

But there is no obvious way to access a SceneDelegate for our code (which may not even be applicable on macOS?). There is a promising similar question.

Is there a better way?

Upvotes: 4

Views: 1038

Answers (1)

citelao
citelao

Reputation: 6046

There's a very easy way to handle drops onto the Dock icon in SwiftUI!

  1. Update your Info.plist to indicate your app supports the file types you want (as you would in an older app):

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
    <plist version="1.0">
    <dict>
        <!-- ... -->
        <key>CFBundleTypeName</key>
        <string>Image files</string>
        <key>CFBundleTypeRole</key>
        <string>Viewer</string>
        <key>LSHandlerRank</key>
        <string>Default</string>
        <key>LSItemContentTypes</key>
        <array>
            <string>public.image</string>
        </array>
    </dict>
    </plist>
    
  2. In your body in your App, use onOpenURL:

    var body: some Scene {
        WindowGroup {
            ContentView()
                .onOpenURL { (url) in
                    // Handle url here
                }
        }
    }
    

— ianivs (How to handle URL callbacks with new SwiftUI @main startup?)

Upvotes: 5

Related Questions