27

I want to build URI (or URL scheme) support in my app.

I do a LSSetDefaultHandlerForURLScheme() in my + (void)initialize and I setted the specific URL schemes also in my info.plist. So I have URL schemes without Apple Script or Apple Events.

When I call myScheme: in my favorite browser the system activates my app.

The problem is, how to handle the schemes when they are called. Or better said: How can I define what my app should do, when myScheme: is called.

Is there a special method that I have to implement or do I have to register one somewhere?

Leo Dabus
  • 229,809
  • 59
  • 489
  • 571
papr
  • 4,717
  • 5
  • 30
  • 38

5 Answers5

79

As you are mentioning AppleScript, I suppose you are working on Mac OS X.

A simple way to register and use a custom URL scheme is to define the scheme in your .plist:

<key>CFBundleURLTypes</key>
<array>
    <dict>
        <key>CFBundleURLName</key>
        <string>URLHandlerTestApp</string>
        <key>CFBundleURLSchemes</key>
        <array>
            <string>urlHandlerTestApp</string>
        </array>
    </dict>
</array>

To register the scheme, put this in your AppDelegate's initialization:

[[NSAppleEventManager sharedAppleEventManager]
    setEventHandler:self
        andSelector:@selector(handleURLEvent:withReplyEvent:)
      forEventClass:kInternetEventClass
         andEventID:kAEGetURL];

Whenever your application gets activated via URL scheme, the defined selector gets called.

A stub for the event-handling method, that shows how to get the URL string:

- (void)handleURLEvent:(NSAppleEventDescriptor*)event
        withReplyEvent:(NSAppleEventDescriptor*)replyEvent
{
    NSString* url = [[event paramDescriptorForKeyword:keyDirectObject]
                        stringValue];
    NSLog(@"%@", url);
}

Apple's documentation: Installing a Get URL Handler

Update I just noticed a problem for sandboxed apps that install the event handler in applicationDidFinishLaunching:. With enabled sandboxing, the handler method doesn't get called when the app is launched by clicking a URL that uses the custom scheme. By installing the handler a bit earlier, in applicationWillFinishLaunching:, the method gets called as expected:

- (void)applicationWillFinishLaunching:(NSNotification *)aNotification
{
    [[NSAppleEventManager sharedAppleEventManager]
        setEventHandler:self
            andSelector:@selector(handleURLEvent:withReplyEvent:)
          forEventClass:kInternetEventClass
             andEventID:kAEGetURL];
}

- (void)handleURLEvent:(NSAppleEventDescriptor*)event
        withReplyEvent:(NSAppleEventDescriptor*)replyEvent
{
    NSString* url = [[event paramDescriptorForKeyword:keyDirectObject]
                        stringValue];
    NSLog(@"%@", url);
}

On the iPhone, the easiest way to handle URL-scheme activation is, to implement UIApplicationDelegate's application:handleOpenURL: - Documentation

kch
  • 77,385
  • 46
  • 136
  • 148
Thomas Zoechling
  • 34,177
  • 3
  • 81
  • 112
  • Stupid question, but where exactly is the AppDelegate's intialization? Which method? – tschoffelen Jul 27 '12 at 17:58
  • 1
    What is keyDirectObject and where is it defined? I don't understand what the value of keyDirectObject is supposed to be. Thanks. – Joel Nov 19 '14 at 22:53
  • For those that are wondering like me, "How does keyDirectObject compile without syntax error for a missing symbol?", it's defined in AE/AppleEvents.h as an enum case. Somehow AE/AppleEvents.h gets included in your app even if you don't directly include it. – Joel Nov 19 '14 at 23:05
  • 1
    If you're targeting macOS 10.13 and up you can use the new NSApplicationDelegate method `func application(_ application: NSApplication, open urls: [URL])` -- no need to register with NSAppleEventManager, just implement the method in your app delegate. – Adam Preble Jul 18 '18 at 14:38
  • How to register my application to be used for my custom URLs? LSSetDefaultHandlerForURLScheme returns -54 which I translate into "Sandbox says no" (https://www.osstatus.com/search/results?platform=all&framework=all&search=-54)". – Wizard of Kneup Mar 24 '19 at 12:43
  • 1
    I can confirm that your solution works even today in macOS Monterey 12.6.1. – Martijn Nov 30 '22 at 10:45
9

All credits should go to weichsel and kch

I'm just adding swift(2.2/3.0) code for your convenience

func applicationWillFinishLaunching(_ notification: Notification) {
    NSAppleEventManager.shared().setEventHandler(self, andSelector: #selector(self.handleGetURL(event:reply:)), forEventClass: UInt32(kInternetEventClass), andEventID: UInt32(kAEGetURL) )
}

@objc func handleGetURL(event: NSAppleEventDescriptor, reply:NSAppleEventDescriptor) {
    if let urlString = event.paramDescriptor(forKeyword: keyDirectObject)?.stringValue {
        print("got urlString \(urlString)")
    }
}
imike
  • 5,515
  • 2
  • 37
  • 44
Laimonas
  • 5,781
  • 2
  • 19
  • 20
  • 2
    Do you know if there is a way to avoid the application to be activated and sent to the foreground when receiving an URL? – gabry Apr 22 '21 at 12:23
6

The problem is, how to handle the schemes when they are called.

That's where the Apple Events come in. When Launch Services wants your app to open a URL, it sends your app a kInternetEventClass/kAEGetURL event.

The Cocoa Scripting Guide uses this very task as an example of installing an event handler.

Peter Hosey
  • 95,783
  • 15
  • 211
  • 370
4

I'm just adding slightly different Swift 4/5 version of the code:

func applicationWillFinishLaunching(_ notification: Notification) {
    NSAppleEventManager
        .shared()
        .setEventHandler(
            self,
            andSelector: #selector(handleURL(event:reply:)),
            forEventClass: AEEventClass(kInternetEventClass),
            andEventID: AEEventID(kAEGetURL)
        )

}

@objc func handleURL(event: NSAppleEventDescriptor, reply: NSAppleEventDescriptor) {
    if let path = event.paramDescriptor(forKeyword: keyDirectObject)?.stringValue?.removingPercentEncoding {
        NSLog("Opened URL: \(path)")
    }
}
Martin Pilch
  • 3,245
  • 3
  • 38
  • 61
  • Do you know if there is a way to avoid the application to be activated and sent to the foreground when receiving an URL? – gabry Apr 22 '21 at 12:23
0

You can define the “get URL” command in a scripting terminology SDEF and implement the corresponding method. For example, Terminal’s SDEF contains the following command definition for handling URLs

<command name="get URL" code="GURLGURL" description="Open a command an ssh, telnet, or x-man-page URL." hidden="yes">
    <direct-parameter type="text" description="The URL to open." />
</command>

and declares that the application responds to it:

<class name="application" code="capp" description="The application's top-level scripting object.">
    <cocoa class="TTApplication"/>
    <responds-to command="get URL">
        <cocoa method="handleGetURLScriptCommand:" />
    </responds-to>
</class>

The TTApplication class (a subclass of NSApplication) defines the method:

- (void)handleGetURLScriptCommand:(NSScriptCommand *)command { … }
Chris Page
  • 18,263
  • 4
  • 39
  • 47