Search code examples
swiftcopyuiactivityviewcontrolleruipasteboard

UIActivityViewController to add additional types to UIPasteboard for 'copyToPasteboard' action


My app's primary data structure complies with the 'Codable' protocol and can use it to serialise its data to/from JSON. I would like to be able to have this data included on the general pasteboard (UIPasteboard.general) when the user selects 'Copy' in a UIActivityViewController.

However, I cannot figure out how to do this. Adding the JSON to the activity controller's items, either as a Data or a String or in a Dictionary, does not help. Printing the types of the pasteboard item just shows what you'd expect for an attributed string:

["com.apple.uikit.attributedstring", "com.apple.rtfd", "com.apple.flat-rtfd", "public.utf8-plain-text"]

According to the documentation for UIActivity.ActivityType.copyToPasteboard:

When using this service, you can provide NSString, UIImage, NSURL, UIColor, and NSDictionary objects as data for the activity items.

So I had expected that including a Dictionary in the 'items' would automatically add the dictionary items to the pasteboard (with the dictionary keys as the pasteboard item types). But this does not appear to work in this case, even if I use just a simple [String:String] dictionary.

The code I'm trying is below.

    func shareStuff() {
        let attrString = someThing.attrString()
        let printer = CustomPrintPageRenderer(someThing)

        var pasteboardDict: [String:Any] = [:]
        let jsonEncoder = JSONEncoder()
        jsonEncoder.outputFormatting = .prettyPrinted
        let jsonData = try? jsonEncoder.encode(someThing)
        let jsonString = String(data: jsonData!, encoding: .utf8)
        print(jsonString!)
        if jsonData != nil {
            pasteboardDict["jsonData"] = jsonData
            if jsonString != nil {
                pasteboardDict["jsonString"] = jsonString
            }
        }

        let items = [printer, attrString, pasteboardDict] as [Any]
        let activity = UIActivityViewController(activityItems: items, applicationActivities: nil)
        present(activity, animated: true)
    }

I have also tried including'self' in the 'items' and using the 'UIActivityItemSource' protocol, but this doesn't seem to make any difference to what's on the pasteboard either (and I have confirmed that the line that returns a Dictionary does get called):

    func activityViewController(_ activityViewController: UIActivityViewController, itemForActivityType activityType: UIActivity.ActivityType?) -> Any? {
        if activityType == .copyToPasteboard {
            return ["blah": "some stuff"]
        } else {
            return nil
        }
    }

Is there any way to use a UIActivityViewController's 'Copy' activity to add extra types to the pasteboard?

Or do I have to create my own separate 'Copy' action outside of the activity view controller (and exclude 'copyToPasteboard' from the activity controller to avoid confusion)?


Solution

  • I've found a solution and my 'paste' process now recognises that there is both 'jsonData' and 'jsonString' on the pasteboard now, as well as attributed string and plain text.

    Instead of adding 'self' to the 'items', I had to have 'self' as the ONLY item. Then I can define what items are returned by the protocol function:

        func activityViewController(_ activityViewController: UIActivityViewController, itemForActivityType activityType: UIActivity.ActivityType?) -> Any? {
            guard let recipe = recipe else { return nil }
            let attrString = someThing.attrString()
            switch activityType {
            case UIActivity.ActivityType.print:
                return SomePrintPageRenderer(someThing)
            case UIActivity.ActivityType.copyToPasteboard:
                var pasteboardDict: [String:Any] = attrString.pasteables()
                let jsonEncoder = JSONEncoder()
                jsonEncoder.outputFormatting = .prettyPrinted
                let jsonData = try? jsonEncoder.encode(someThing)
                let jsonString = String(data: jsonData!, encoding: .utf8)
                print(jsonString!)
                if jsonData != nil {
                    pasteboardDict["jsonData"] = jsonData
                    if jsonString != nil {
                        pasteboardDict["jsonString"] = jsonString
                    }
                }
                return pasteboardDict
            default:
                return attrString
            }
        }
    

    Note that I had to also explicitly handle the inclusion of the various plain and attributed string pasteboard types which I still require. (This has been done with a custom NSAttributedString extension with the function 'pasteables()' which returns a Dictionary of pasteable versions of the attributed string.)