iosswiftuiactivityviewcontroller

UIActivityViewController - How can I identify selected option and link specific data type to it?


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.


Solution

  • 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.