I am using UIActivityViewController to share text to Outlook, everything is working fine except for two things, one I can't set a subject line and two I cant set a title of that I am sharing, my text is html table and it is currently shown the first part of it in my implementation (see screenshot) although I'd like to show a preview title.
Is there a way to set the title and a way to set the subject?
Here is my code:
let activityViewController = UIActivityViewController(activityItems : [htmlString], applicationActivities: nil)
activityViewController.popoverPresentationController?.barButtonItem = self.shareButton
self.present(activityViewController, animated: true, completion: nil)
I have tried to set the subject like so:
activityViewController.setValue("This is my subject", forKey: "subject")
But it didn't work
What am I doing wrong?
Here is the screenshot of the title I was talking about:
But it didn't work
Whatever view controller triggered the sharing should conform to UIActivityItemSource protocol in order to setup: preview title, email subject and as well the content of the email.
You can try out your self this example which triggers sharing on a button tap:
class ViewController: UIViewController, UIActivityItemSource {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
#IBAction func sharePressed(_ sender: Any) {
let item = [self, "Preview Title"] as [Any]
let activityViewController = UIActivityViewController(activityItems: item, applicationActivities: nil)
self.present(activityViewController, animated: true, completion: nil)
}
func activityViewControllerPlaceholderItem(_ activityViewController: UIActivityViewController) -> Any {
return ""
}
func activityViewController(_ activityViewController: UIActivityViewController, itemForActivityType activityType: UIActivity.ActivityType?) -> Any? {
return "<html><body><p style=\"background-color: red;\">Email body message with red background</p></body></html>"
}
func activityViewController(_ activityViewController: UIActivityViewController, subjectForActivityType activityType: UIActivity.ActivityType?) -> String {
return "Email Subject"
}
}
To have this expected output:
Some apps like Outlook and Gmail in different iOS Versions (iOS 11, iOS 12..) behave differently: some take the first line of the body and set it as the Subject. Just be sure to test it out for the iOS version you are targeting and the app that you want to target the sharing functionality to behave correctly. You could also set multiple itemForActivityType to target logic for different apps when sharing.
Related
I'm trying to present the UIActivityViewController in multiple parts of my app by enclosing it in a helper class. The following code works fine, I can call the showActivityController() method from any other view controllers in my app, and the UIActivityViewController gets presented as expected.
My question is, do I really need to enclose the code to present the UIActivityViewController within the DispatchQueue.main.async as shown below?
I tried it without it and it works fine but I want to make sure that leaving it there won't cause any issues later on.
class HelperClass: UIViewController, UIActivityItemSource{
static let shared = HelperClass()
func showActivityController(){
DispatchQueue.main.async {
let items = [self]
let activityController = UIActivityViewController(activityItems: items, applicationActivities: nil)
UIApplication.shared.keyWindow?.rootViewController?.present(activityController, animated: true)
}
}
func activityViewControllerPlaceholderItem(_ activityViewController: UIActivityViewController) -> Any {
return "Return Type"
}
func activityViewController(_ activityViewController: UIActivityViewController, itemForActivityType activityType: UIActivity.ActivityType?) -> Any? {
if activityType == .message{
return "Text for iMessage"
}else if activityType == .mail{
return "Text for Email"
}else{
return "Text for all other apps"
}
}
func activityViewController(_ activityViewController: UIActivityViewController, subjectForActivityType activityType: UIActivity.ActivityType?) -> String {
return "Email Subject"
}
}
Usage from other View Controllers
HelperClass.shared.showActivityController()
You don't need when you are in main thread only. So writing DispatchQueue is redundant
But in case if you want to present from another thread it will be a must for you presenting the UI using DispathchQueue
Suppose , you are making a network call and and after the completion of network call you have to show UI
From my point of view, you should remove the dispatch queue from the method.
func showActivityController(){
let items = [self]
let activityController = UIActivityViewController(activityItems: items, applicationActivities: nil)
UIApplication.shared.keyWindow?.rootViewController?.present(activityController, animated: true)
}
But when you have to present the UI from other thread, just call the method in main thread
DispatchQueue.main.async {
HelperClass.shared.showActivityController()
}
Better to keep all interface related code on the main thread, so I would leave it with the DispatchQueue.main.async as at some point you can call showActivityController() from closure that is executed on a background thread causing runtime error. Calling DispatchQueue.main.async is safer and not causing any overhead until you call this method million times in a row.
I am implementing UIActivityViewController to share text and image through other apps. My problem starts when WhatsApp doesn't accept text and image together, so I want to remove text (if exists) when user chooses to share with WhatsApp.
How can I remove some activity items after the destination app was choosen in UIActivityViewController?
First of all, you need to create a class that conforms to UIActivityItemSource, and use it instead of passing the text or image directly to UIActivityViewController.
So instead of
UIActivityViewController(activityItems: [image, text])
We would pass the new item sources
UIActivityViewController(activityItems: [ImageItemSource(image), TextItemSource(text)]
The item source classes will look something like this:
class TextItemSource: NSObject, UIActivityItemSource {
private let text: String
init(text: String) {
self.text = text
super.init()
}
func activityViewControllerPlaceholderItem(_ activityViewController: UIActivityViewController) -> Any {
text
}
func activityViewController(_ activityViewController: UIActivityViewController, itemForActivityType activityType: UIActivity.ActivityType?) -> Any? {
text
}
}
class ImageItemSource: NSObject, UIActivityItemSource {
private let image: UIImage
init(image: UIImage) {
self.image = image
super.init()
}
func activityViewControllerPlaceholderItem(_ activityViewController: UIActivityViewController) -> Any {
image
}
func activityViewController(_ activityViewController: UIActivityViewController, itemForActivityType activityType: UIActivity.ActivityType?) -> Any? {
image
}
}
Now that we have control over what we'll share, we can choose what exactly we share with each app by checking activityType before returning the data, and if we think we should not send anything at all, we can simply return nil.
In your case, if you want to share the text item with all apps except WhatsApp, you can simply do this:
class TextItemSource: NSObject, UIActivityItemSource {
...
func activityViewController(_ activityViewController: UIActivityViewController, itemForActivityType activityType: UIActivity.ActivityType?) -> Any? {
if activityType?.rawValue.starts(with: "net.whatsapp.WhatsApp.") == true {
// We'll return `nil` to WhatsApp forcing it to
// ignore this whole text item and only show the image item instead
return nil
}
return text
}
...
}
The below code intends to share different text and different images (UIImage and NSData) depending on the different platform selected. The trouble is that none of the images are being passed from activityImageShare() to the UIActivityViewController.
Question:
What’s could be wrong here exactly?
How can I correct the code below to ensure that activityImageShare()
shares the images, and ensure they are picked up as an activityType
by UIActivityViewController?
Code:
1 - Four images, three UIImage and one NSData.
var myImage1: UIImage!
var myImage2: UIImage!
var myImage3: NSData!
var myImage4: UIImage!
2 - Define images based on platform selected to pass to UIActivityViewController.
class activityImageShare: NSObject, UIActivityItemSource {
#objc func activityViewControllerPlaceholderItem(activityViewController: UIActivityViewController) -> AnyObject {
return ""
}
#objc func activityViewController(activityViewController: UIActivityViewController, itemForActivityType activityType: String) -> AnyObject? {
switch activityType {
case UIActivityTypePostToFacebook:
return myImage1
case UIActivityTypePostToTwitter:
return myImage2
case UIActivityTypePostToWeibo:
return myImage3
default:
return myImage4
}
}
}
3 - Define text based on platform selected to pass to UIActivityViewController.
class activityTextShare: NSObject, UIActivityItemSource {
#objc func activityViewControllerPlaceholderItem(activityViewController: UIActivityViewController) -> AnyObject {
return ""
}
#objc func activityViewController(activityViewController: UIActivityViewController, itemForActivityType activityType: String) -> AnyObject? {
switch activityType {
case UIActivityTypePostToFacebook:
return "This is my text 1."
case UIActivityTypePostToTwitter:
return "This is my text 2."
case UIActivityTypePostToWeibo:
return "This is my text 3."
default:
return "This is my text 4."
}
}
}
4 - Present UIActivityViewController based on text and images defined in activityTextShare() and activityImageShare().
func myShareActivity() {
let activity = UIActivityViewController(activityItems: [activityTextShare(), activityImageShare()], applicationActivities: nil)
self.presentViewController(activity, animated: true, completion: nil)
}
Try return placeholder image in activityViewControllerPlaceholderItem(activityViewController: UIActivityViewController) instead of ""
I am attempting to share an image with a hashtag using UIActivityViewController and I am encountering some strange behavior when attempting to share to Twitter, Facebook and Instagram. There does not seem to be a lot of documentation about these service's share extensions.
Scenario 1: Init controller with activity item array with image and text
If I initialize the controller like so, Twitter and Facebook will show up in the controller (no Instagram as it does not support text items), and both will programmatically pre-populate the hashtag in the text entry field:
let activityVC = UIActivityViewController(activityItems: [myHashtagString, myImage], applicationActivities: nil)
Scenario 2: Init controller with only image
In this scenario, all networks show up, but I (obviously) lose the automatic hashtag feature:
let activityVC = UIActivityViewController(activityItems: [myImage], applicationActivities: nil)
Scenario 3: UIActivityItemSource subclass
If I make my own UIActivityItemSource subclass, I can almost get everything to work. However, and this is what I cannot figure out, using the protocol methods as I have below results in the automatic hashtag working for Facebook, but not Twitter. How can this be possible -- is there a special key needed for Twitter? There must be a way for it to work if it works in Scenario #1...
Interestingly, this method works for both Twitter and Facebook if I insert a URL (commented out). So why on earth won't the text work for Twitter!?
let activityItem = CustomItemSource(image: image, message: "#TestTag")
let activityVC = UIActivityViewController(activityItems: [activityItem], applicationActivities: nil)
...
class CustomItemSource: NSObject, UIActivityItemSource {
private var image: UIImage!
private var message: String!
// MARK: Init
init(image: UIImage, message: String) {
super.init()
self.image = image
self.message = message
}
// MARK: Item Source Protocol
func activityViewControllerPlaceholderItem(_ activityViewController: UIActivityViewController) -> Any {
return image
}
func activityViewController(_ activityViewController: UIActivityViewController, itemForActivityType activityType: UIActivityType) -> Any? {
if activityType == .postToTwitter || activityType == .postToFacebook {
//return ["url": URL(string: "https://www.google.com")!, "image": image]
return ["text": message, "image": image]
}
else {
return ["image": image]
}
}
}
Define two UIActivityItemSource classes, one for Image and one for Text.
In first one only return the image.
In second one return NSObject() for placeHolder, and return Text or nil depending on activity. By returning NSObject(), UIActivity will allow all services to be available.
UIActivityViewController(activityItems: [ImageProvider(), TextProvider()], applicationActivities: nil)
and providers:
class TextProvider: NSObject, UIActivityItemSource {
func activityViewControllerPlaceholderItem(_ activityViewController: UIActivityViewController) -> Any {
return NSObject()
}
func activityViewController(_ activityViewController: UIActivityViewController, itemForActivityType activityType: UIActivityType) -> Any? {
if activityType == .postToTwitter || activityType == .postToFacebook {
return "Tweet with #Hashtag"
}
return nil
}
}
class ImageProvider: NSObject, UIActivityItemSource {
func activityViewControllerPlaceholderItem(_ activityViewController: UIActivityViewController) -> Any {
return UIImage(named: ...)
}
func activityViewController(_ activityViewController: UIActivityViewController, itemForActivityType activityType: UIActivityType) -> Any? {
return UIImage(named: ...)
}
}
Explaination
First of all, Keys are not really sensitive, The only sensitive-key was "subject" for email and apps supporting it, which is implemented in UIActivityController's API and we can set it directly. It doesn't matter if you provide UIImage with key "image" or "1".
As it turns out, Twitter activity will not work if it's text is not returned directly in ...itemForActivity... method. So the solution is to separate item sources.
Twitter activity also will not work, if placeholder receives anything other than String, but by returning String Instagram activity will not work, So by returning NSObject() Type will be ignored and all services will be available.
if you want to limit some services use UIActivityViewController.excludedActivityTypes
Im trying to send data (NSData) from my app on one iOS Device to another via AirDrop using the UIActivityViewController. I have created a new CSM (custom data type) in my apps plist. The public.filename-extension = ppm.
So how do I add the ppm extension to the NSDate object I'm trying to send ?? Am I right in thinking that when a you present a UIActivityViewController, my apps Icon will not be displayed in the UIActivityViewController window if the object Im sending does not have my apps public extension (ppm) ??.... yea, I'm really confused !!
Heres the code I'm using to present UIActivityViewController
#IBAction func shareButton(sender: AnyObject) {
// myData is the object I want to send to be used in my app on another device
let vc = UIActivityViewController(activityItems: [myData],applicationActivities: [])
presentViewController(vc, animated: true, completion: nil)
}
Basically, all I'm trying to do is send custom data to be used in my app
You should take a look at the AirDrop sample code that covers the case of defining your own file type and sharing that with your app on the other device. The key part if you want to share raw data is that you have to create an instance of UIActivityItemSource and pass that to UIActivityViewController. Something like this:
class DataActivityItemSource: NSObject, UIActivityItemSource {
let myData: NSData
let typeIdentifier: String
let subject: String
let previewImage: UIImage
init(myData: NSData, typeIdentifier: String, subject: String, previewImage: UIImage) {
self.myData = myData
self.typeIdentifier = typeIdentifier
self.subject = subject
self.previewImage = previewImage
}
// called to determine data type. only the class of the return type is consulted. it should match what -itemForActivityType: returns later
#objc func activityViewControllerPlaceholderItem(activityViewController: UIActivityViewController) -> AnyObject {
return myData
}
// called to fetch data after an activity is selected. you can return nil.
#objc func activityViewController(activityViewController: UIActivityViewController, itemForActivityType activityType: String) -> AnyObject? {
return myData
}
// if activity supports a Subject field. iOS 7.0
#objc func activityViewController(activityViewController: UIActivityViewController, subjectForActivityType activityType: String?) -> String {
return subject
}
// UTI for item if it is an NSData. iOS 7.0. will be called with nil activity and then selected activity
#objc func activityViewController(activityViewController: UIActivityViewController, dataTypeIdentifierForActivityType activityType: String?) -> String {
return typeIdentifier
}
// if activity supports preview image. iOS 7.0
#objc func activityViewController(activityViewController: UIActivityViewController, thumbnailImageForActivityType activityType: String?, suggestedSize size: CGSize) -> UIImage? {
// look at suggestedSize and resize image (see AirDrop sample code for how to do this)
return previewImage
}
}
#IBAction func shareButton(sender: AnyObject) {
// myData is the object I want to send to be used in my app on another device
let itemSource = DataActivityItemSource(myData, "com.foo.ppm.typeIdentifier", "My Amazing Journey", aPreviewImage)
let vc = UIActivityViewController(activityItems: [itemSource],applicationActivities: [])
presentViewController(vc, animated: true, completion: nil)
}