narner
narner

Reputation: 3241

Implementing "Open file with" in Swift Cocoa App

I'm working on a macOS cocoa-app in Swift where I import several different file types into the app for the user to interact with.

I'm currently trying to determine if it's possible to implement the "Open file with" feature, so that the user could open those files in a different program if they wanted to:

enter image description here

I've found a few different SO questions that seem tangentially related to what I'm trying to do:

Swift: How to open file with associated application?

Launch OSX Finder window with specific files selected

...but so far nothing to indicate if it's possible to implement right-click Finder/file (?) access in the way I had in mind.

Apologies if this is too vague of a question; any help / guidance appreciated!

Upvotes: 4

Views: 1785

Answers (3)

Philip Pegden
Philip Pegden

Reputation: 2164

LSCopyApplicationURLsForURL is deprecated. You can use this alternative:

func getListOfExternalApps(forURL url: URL) -> [(URL, Image)] {
    let listOfExternalApps = NSWorkspace.shared.urlsForApplications(toOpen: url)
            
    let icons = listOfExternalApps.map {
        let nsimage = NSWorkspace.shared.icon(forFile: $0.path())
        nsimage.size = CGSize(width: .s16, height: .s16)
        return Image(nsImage: nsimage)
    }

    return Array(zip(listOfExternalApps, icons))
}

Upvotes: 3

Andrew
Andrew

Reputation: 11364

2022, Swift 5

Get app list associated with local file:

func getAppsAssociatedWith(_ url: URL?) {
    guard let url = localFileURL,
          let retainedArr = LSCopyApplicationURLsForURL( url as CFURL, .all)?.takeRetainedValue(),
          let listOfRelatedApps = retainedArr as? Array<URL>
    else {
        return []
    }
        
    return listOfRelatedApps
}

Getting thumbnail for app:

let singleAppIcon = NSWorkspace.shared
                       .icon(forFile: appUrl.path)
                       .scaledCopy(sizeOfLargerSide: 17)

Open url with app:

@available(macOS 10.15, iOS 9.0, *)
public class func openUrlWithApp(_ urls: [URL], appUrl: URL) {
    NSWorkspace.shared.open(urls, withApplicationAt: appUrl, configuration: NSWorkspace.OpenConfiguration())
}

In my app I'm cashing all apps icons in dictionary.

[someFile localURL : app icon]

If I have already got icon earlier - no need to get it once more

var relatedAppsThumbnails: [URL: Image] = [:]

func updateRelatedApps() {
        guard let url = currImgUrl, // file url to get icons from related apps
              let retainedArr = LSCopyApplicationURLsForURL( url as CFURL, .all)?.takeRetainedValue(),
              let listOfRelatedApps = retainedArr as? Array<URL>
        else {
            relatedApps = []
            return
        }
        
        self.relatedApps = listOfRelatedApps
        
        // add app icon in case of it wasn't added yet
        for appUrl in listOfRelatedApps {
            if relatedAppsThumbnails[appUrl] == nil {
                let nsImg = NSWorkspace.shared.icon(forFile: appUrl.path)
                    .scaledCopy(sizeOfLargerSide: 17)
                
                relatedAppsThumbnails[appUrl] = Image(nsImage: nsImg)
            }
        }
    }

Upvotes: 2

James Bucanek
James Bucanek

Reputation: 3439

Without going into details, it's pretty straight forward:

  1. Get the list of all known applications that can open a specific file type (see LSCopyApplicationURLsForURL, a Core Foundation C function).
  2. Build the menu. You can use NSWorkspace (and probably URL) to get the application icons.
  3. Use NSWorkspace.openFile(_:withApplication:) to tell the application to open the given document.

Upvotes: 3

Related Questions