How do I do showInFinder in Mac Catalyst

Using UIApplication openURL will open the file directly, but the file location corresponding to finder cannot be opened. There are many resource files in the folder, so it will be difficult to locate specific files if only the folder is opened
Answered by benspratling4 in 665537022
Apparently, it can only be done using NSWorkspace. Which is part of AppKit. Which can't be imported into the code compiled directly into your frameworks.
However...
I heard others say code importing AppKit can be included in a "plug-in", which can be added for only the Mac-os version of your app and can be loaded at run time.

So here's my code for this:
I create a plug-in called "AppKitCompatibility".
The plug-in bundle is listed in the General pane of the project editor of my maccatalyst app, with Platforms set to "macOS" and Embed set to "Embed & Sign".
It contains 2 obj-c files.:
WorkspaceCompatibility.h
Code Block
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface WorkspaceCompatibility : NSObject
+ (void)showInFinder:(NSArray<NSURL *> *)urls;
@end
NS_ASSUME_NONNULL_END


WorkspaceCompatibility.m
Code Block
#import "WorkspaceCompatibility.h"
#import <AppKit/AppKit.h>
@implementation WorkspaceCompatibility
+ (void)showInFinder:(NSArray<NSURL *> *)urls {
[NSWorkspace.sharedWorkspace activateFileViewerSelectingURLs:urls];
}
@end

The plug-in's info.plist lists WorkspaceCompatibility as the principal class. I don't think that's strictly necessary, as the swift code below loads it by name.

Then in the swift UIKit portion of the app :

Code Block import Foundation
class MacCompatibility {
static let shared:MacCompatibility = MacCompatibility()
private init() {
}
#if targetEnvironment(macCatalyst)
//the classes loaded only live as long as the bundle instance, so the bundle must be retained for the duration of the app
private lazy var bundle:Bundle? = newBundle()
private func newBundle()->Bundle? {
let bun = Bundle(path: Bundle.main.builtInPlugInsPath?.appending("/AppKitCompatibility.bundle") ?? "")
bun?.load()
return bun
}
#endif
func revealUrlsInFinder(_ urls:[URL]) {
#if targetEnvironment(macCatalyst)
let files = urls as [NSURL]
let fileArray = files as NSArray
guard let macClass:AnyClass = bundle?.classNamed("WorkspaceCompatibility")
  ,let macCompatibility = macClass as AnyObject as? NSObjectProtocol //in Obj-c, classes are objects, too
else { return }
_ = macCompatibility.perform(NSSelectorFromString("showInFinder:"), with:fileArray) //so we can call a class method on the class
#endif
}
}


I normally hate singletons, but in this case the MacCompatibility is held as a singleton for 3 reasons: 1) the bundle must remain in memory as long as the code will be run, 2) it can be accessed from anywhere just as the workspace was, and 3) it's so little code, surely it's not a problem.


I have tested this, and it works. But I have not shipped it. I have heard other people say using plug-ins is supported by the MacAppStore.
Accepted Answer
Apparently, it can only be done using NSWorkspace. Which is part of AppKit. Which can't be imported into the code compiled directly into your frameworks.
However...
I heard others say code importing AppKit can be included in a "plug-in", which can be added for only the Mac-os version of your app and can be loaded at run time.

So here's my code for this:
I create a plug-in called "AppKitCompatibility".
The plug-in bundle is listed in the General pane of the project editor of my maccatalyst app, with Platforms set to "macOS" and Embed set to "Embed & Sign".
It contains 2 obj-c files.:
WorkspaceCompatibility.h
Code Block
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface WorkspaceCompatibility : NSObject
+ (void)showInFinder:(NSArray<NSURL *> *)urls;
@end
NS_ASSUME_NONNULL_END


WorkspaceCompatibility.m
Code Block
#import "WorkspaceCompatibility.h"
#import <AppKit/AppKit.h>
@implementation WorkspaceCompatibility
+ (void)showInFinder:(NSArray<NSURL *> *)urls {
[NSWorkspace.sharedWorkspace activateFileViewerSelectingURLs:urls];
}
@end

The plug-in's info.plist lists WorkspaceCompatibility as the principal class. I don't think that's strictly necessary, as the swift code below loads it by name.

Then in the swift UIKit portion of the app :

Code Block import Foundation
class MacCompatibility {
static let shared:MacCompatibility = MacCompatibility()
private init() {
}
#if targetEnvironment(macCatalyst)
//the classes loaded only live as long as the bundle instance, so the bundle must be retained for the duration of the app
private lazy var bundle:Bundle? = newBundle()
private func newBundle()->Bundle? {
let bun = Bundle(path: Bundle.main.builtInPlugInsPath?.appending("/AppKitCompatibility.bundle") ?? "")
bun?.load()
return bun
}
#endif
func revealUrlsInFinder(_ urls:[URL]) {
#if targetEnvironment(macCatalyst)
let files = urls as [NSURL]
let fileArray = files as NSArray
guard let macClass:AnyClass = bundle?.classNamed("WorkspaceCompatibility")
  ,let macCompatibility = macClass as AnyObject as? NSObjectProtocol //in Obj-c, classes are objects, too
else { return }
_ = macCompatibility.perform(NSSelectorFromString("showInFinder:"), with:fileArray) //so we can call a class method on the class
#endif
}
}


I normally hate singletons, but in this case the MacCompatibility is held as a singleton for 3 reasons: 1) the bundle must remain in memory as long as the code will be run, 2) it can be accessed from anywhere just as the workspace was, and 3) it's so little code, surely it's not a problem.


I have tested this, and it works. But I have not shipped it. I have heard other people say using plug-ins is supported by the MacAppStore.
How do I do showInFinder in Mac Catalyst
 
 
Q