1

I'm struggling with a problem, which was discussed in earlier threads too (e.g. UIActivityViewController - is there a way to know which activity was selected?), but in my understanding not fully solved yet.

I want to share different data types based on the different options that can be selected in the share dialogue of the UIActivityController: a) if "mail", "print" or "message" is selected, I want to share a NSAttributedString b) if "airdrop", "save to Files" is selected, I want to share data file (identified by an URL to a temporarily created file).

I tried to handle this via:

public func activityViewControllerPlaceholderItem(_ activityViewController: UIActivityViewController) -> Any {
    return [NSAttributedString().self, URL.self] as [Any]
}

public func activityViewController(_ activityViewController: UIActivityViewController, itemForActivityType activityType: UIActivity.ActivityType?) -> Any? {
  if let activityType = activityType {
     switch activityType {
     case .airDrop, .copyToPasteboard: return [URL.self] as [Any]
     case .mail, .message, .print: return [NSAttributedString().self] as [Any]
     default: return [NSAttributedString().self, Array<EKLPosition>.self, URL.self] as [Any]
     }
  } else {
      return [NSAttributedString().self, URL.self] as [Any]
  }
}

Unfortunately, this doesn't work. When I select e.g. "mail", the generated e-mail contains the NSAttributedString (as intended) but also the data file as an attachment. If "Save to Files" is selected, a file with the text of the NSAttributedString and the data file is stored. If "airdrop" is selected, only the data file is shared as intended.

Hence I have two questions?

  1. What am I doing wrong and how can I fix this problem?
  2. How can I identify if "Save to Files", "Save to Dropbox", etc is selected, because in these cases I only want to share the data file too. In the UIActivity.ActivityType list I couldn't find any identifier for it.

Thanks for your support.

carlson
  • 89
  • 11

1 Answers1

1

Not sure why you are returning those arrays of types in itemForActivityType. You should be returning the items for the given activity type.

For example:

class MyActivity: NSObject, UIActivityItemSource {
    let attributedString: NSAttributedString = ...
    let url = ...
    
    func activityViewControllerPlaceholderItem(_ activityViewController: UIActivityViewController) -> Any {
        // use one of the two things as a placeholder
        url
    }
    
    func activityViewController(_ activityViewController: UIActivityViewController, itemForActivityType activityType: UIActivity.ActivityType?) -> Any? {
        // return one of the two things - URL or attributed string
        if activityType == .copyToPasteboard {
            return attributedString
        } else {
            return url
        }
    }
}

More generally:

class ActivityItemPair: NSObject, UIActivityItemSource {
    
    let item1: Any
    let item2: Any
    
    let item1Types: [UIActivity.ActivityType]
    let item2Types: [UIActivity.ActivityType]
    
    let defaultItem: Any?
    
    init(item1: Any, item2: Any, item1Types: [UIActivity.ActivityType], item2Types: [UIActivity.ActivityType], defaultItem: Any? = nil) {
        self.item1 = item1
        self.item2 = item2
        self.item1Types = item1Types
        self.item2Types = item2Types
        self.defaultItem = defaultItem
    }
    
    func activityViewControllerPlaceholderItem(_ activityViewController: UIActivityViewController) -> Any {
        defaultItem ?? item1
    }
    
    func activityViewController(_ activityViewController: UIActivityViewController, itemForActivityType activityType: UIActivity.ActivityType?) -> Any? {
        if let activityType, item1Types.map(\.rawValue).contains(activityType.rawValue) {
            return item1
        } else if let activityType, item2Types.map(\.rawValue).contains(activityType.rawValue) {
            return item2
        } else {
            return defaultItem ?? item2
        }
    }
}

As for how you can identify the activity type, you can print the rawValue of the activity type in itemForActivityType:

print(activityType?.rawValue)

Then, you can select "Save to Files" and see what gets printed. For "Save to Files", it is:

com.apple.DocumentManagerUICore.SaveToFiles

You can use this raw value to initialise an AcitivityType too:

UIActivity.ActivityType("com.apple.DocumentManagerUICore.SaveToFiles")

Then you can pass this to the item1Types and item2Types parameters in ActivityItemPair.

Sweeper
  • 213,210
  • 22
  • 193
  • 313
  • @carlson Pass an instance of `ActivityItemPair` to `activityItems`: `UIActivityViewController(activityItems: [ActivityItemPair(...)], applicationActivities: nil)`. Pass in your URL and attributed string, and their corresponding activity types, in the `...` part. – Sweeper Jul 18 '23 at 11:10
  • perfect - thanks – carlson Jul 18 '23 at 11:14