How to get the page URL shared via the Share button on macOS?

I have a macOS Share Extension invoked when users tap the Share button in Safari. I'm trying to get the page's URL from the -[NSExtensionItem attachments] attribute, but it comes as an NSSecureCoding object, and I'm unable to read the URL from it.

In the loadView method, I'm filtering and loading the attachments of type public.url:

override func loadView() {
    super.loadView()

    guard let inputItem = extensionContext?.inputItems.first as? NSExtensionItem else {
        print("Didn't received input item from action.")
        return
    }

    var itemProvider: NSItemProvider?
    itemProvider = inputItem.attachments?.filter({ $0.registeredTypeIdentifiers.contains("public.url") }).first ?? inputItem.attachments?.filter({ $0.registeredTypeIdentifiers.contains("public.plain-text") }).first

    guard let itemProvider = itemProvider else {
        print("Didn't received attachments from input item.")
        return
    }

    if itemProvider.canLoadObject(ofClass: URL.self) {
        itemProvider.loadItem(forTypeIdentifier: "public.url", completionHandler: onLoadVideoURL)
    } else if itemProvider.canLoadObject(ofClass: String.self) {
        itemProvider.loadItem(forTypeIdentifier: "public.plain-text", completionHandler: onLoadVideoURL)
    } else {
        print("This action only supports URL and String.")
    }
}

The itemProvider.loadItem method runs for the type identifier public.url, calling the completion handler bellow:

@objc private func onLoadVideoURL(dict: NSSecureCoding?, error: Error?) {
    print("URL: \(dict.debugDescription)")
    // ...
}

But the content that it prints to the console is:

URL: Optional(<68747470 733a2f2f 73746163 6b6f7665 72666c6f 772e636f 6d2f7175 65737469 6f6e732f 35323231 39373030 2f686f77 2d746f2d 63617374 2d6e7373 65637572 65636f64 696e672d 746f2d6d 6b6d6170 6974656d 2d696e2d 61637469 6f6e2d65 7874656e 73696f6e>)

The same code works as expected on iOS, printing the shared URL to the console.

Do I have to somehow convert this NSSecureCoding to URL or another object? Or should I do this in a completely different way on macOS? The goal is to access the page's URL from the Share Extension activated when the user selects it in the Share Menu.

Accepted Reply

Turns out that the NSSecureCoding content was the URL, but in hexadecimal. Here's how I'm converting it to String:

let urlHex = dict.debugDescription
    .replacingOccurrences(of: "Optional(", with: "")
    .replacingOccurrences(of: ")", with: "")
    .replacingOccurrences(of: "<", with: "")
    .replacingOccurrences(of: ">", with: "")
    .replacingOccurrences(of: " ", with: "")

guard let urlHexData = Data(fromHexEncodedString: urlHex) else { return }
guard let url = String(data: urlHexData, encoding: .utf8) else { return }
extension Data {

    // From http://stackoverflow.com/a/40278391
    init?(fromHexEncodedString string: String) {
        func decodeNibble(u: UInt16) -> UInt8? {
            switch(u) {
            case 0x30 ... 0x39:
                return UInt8(u - 0x30)
            case 0x41 ... 0x46:
                return UInt8(u - 0x41 + 10)
            case 0x61 ... 0x66:
                return UInt8(u - 0x61 + 10)
            default:
                return nil
            }
        }

        self.init(capacity: string.utf16.count/2)
        var even = true
        var byte: UInt8 = 0
        for c in string.utf16 {
            guard let val = decodeNibble(u: c) else { return nil }
            if even {
                byte = val << 4
            } else {
                byte += val
                self.append(byte)
            }
            even = !even
        }
        guard even else { return nil }
    }
}

Replies

Turns out that the NSSecureCoding content was the URL, but in hexadecimal. Here's how I'm converting it to String:

let urlHex = dict.debugDescription
    .replacingOccurrences(of: "Optional(", with: "")
    .replacingOccurrences(of: ")", with: "")
    .replacingOccurrences(of: "<", with: "")
    .replacingOccurrences(of: ">", with: "")
    .replacingOccurrences(of: " ", with: "")

guard let urlHexData = Data(fromHexEncodedString: urlHex) else { return }
guard let url = String(data: urlHexData, encoding: .utf8) else { return }
extension Data {

    // From http://stackoverflow.com/a/40278391
    init?(fromHexEncodedString string: String) {
        func decodeNibble(u: UInt16) -> UInt8? {
            switch(u) {
            case 0x30 ... 0x39:
                return UInt8(u - 0x30)
            case 0x41 ... 0x46:
                return UInt8(u - 0x41 + 10)
            case 0x61 ... 0x66:
                return UInt8(u - 0x61 + 10)
            default:
                return nil
            }
        }

        self.init(capacity: string.utf16.count/2)
        var even = true
        var byte: UInt8 = 0
        for c in string.utf16 {
            guard let val = decodeNibble(u: c) else { return nil }
            if even {
                byte = val << 4
            } else {
                byte += val
                self.append(byte)
            }
            even = !even
        }
        guard even else { return nil }
    }
}

Thank you for the hint. This is what I did.

var url = secureCodingItem as? URL
if url == nil {
    let data = secureCodingItem as? Data
    if let data,
       let absoluteString = String(data: data, encoding: .utf8)
    {
        url = URL(string: absoluteString)
    }
}