I am using UIActivityViewController for sharing information through email. We are able to send email with body, attachments with no problem. But how do we set the subject title for the email.
I notice this question: How to set a mail Subject in UIActivityViewController?
The accepted solution is using UIActivityItemSource with this following API activityViewController:subjectForActivityType:. However, our code doesn't conform to UIActivityItemSource because we are using UIActivityItemProvider.
UIActivityItemSource
You can use this protocol in situations where you want to provide the data from one of your app’s existing objects instead of creating a separate UIActivityItemProvider object.
So the complete question is:
How do I set the email subject if I am using UIActivityItemProvider instead of UIActivityItemSource?
Define your custom item provider:
#interface CustomProvider : UIActivityItemProvider
#end
Add to your implementation:
#implementation CustomProvider
// Some other code ... -(id)item and etc.
- (NSString *) activityViewController:(UIActivityViewController *)activityViewController
subjectForActivityType:(NSString *)activityType
{
return #"A dummy Title";
}
#end
Notice that UIActivityItemProvider will automatically conform to UIactivityItemSource protocol. The difference is, you don't have to implement those #required API for UIactivityItemSource protocol.
Just add this line after you instantiate your UIActivityViewController:
[activityController setValue:#"Your email Subject" forKey:#"subject"];
I am using it like this:
- (void)share {
NSArray *activityItems;
NSString *texttoshare = [NSString stringWithFormat:#"Hey bro! check this info.\n%#\n%#", self.infoBean.title, self.infoBean.desc];
UIImage *imagetoshare = imageView.image;//this is your image to share
if (imagetoshare != nil) {
activityItems = #[imagetoshare, texttoshare];
} else {
activityItems = #[texttoshare];
}
NSArray *exTypes = #[UIActivityTypeAssignToContact, UIActivityTypePrint, UIActivityTypeCopyToPasteboard, UIActivityTypeSaveToCameraRoll];
UIActivityViewController *activityController = [[UIActivityViewController alloc] initWithActivityItems:activityItems applicationActivities:nil];
activityController.excludedActivityTypes = exTypes;
[activityController setValue:#"Your email Subject" forKey:#"subject"];
[self presentViewController:activityController animated:YES completion:nil];
}
UIActivityItemProvider implements the UIActivityItemSource protocol. It's right there in the header.
#interface UIActivityItemProvider : NSOperation <UIActivityItemSource>
so you can simply use the method - (NSString *)activityViewController:(UIActivityViewController *)activityViewController subjectForActivityType:(NSString *)activityType to return the subject in your UIActivityItemProvider subclass.
Related
I see via sharing content from other apps that it is possible to set a different subject and body when using share sheet to share into the Gmail Mail app. I have implemented it and it works fine on the native mail app but not Gmail.
Going into Yelp and sharing a business then choosing gmail from the share sheet, I see that the subject and body are different. The subject contains the address of the business while the body contains the address + a link to the business on Yelp.
I have tried to replicate this logic with success on the native Mail app but not in the Gmail app.
I have tried the following:
Implementing UIActivityItemSource methods
UIActivityViewController *activityViewController = [[UIActivityViewController alloc] initWithActivityItems:#[self] applicationActivities:nil];
- (id)activityViewControllerPlaceholderItem:(UIActivityViewController *)activityViewController {
return #"";
}
- (id)activityViewController:(UIActivityViewController *)activityViewController itemForActivityType:(NSString *)activityType {
return #"body";
}
- (NSString *)activityViewController:(UIActivityViewController *)activityViewController subjectForActivityType:(NSString *)activityType {
return #"subject";
}
Result
Apple Mail Subject set to "subject", Body set to "body"
Gmail Subject set to "body", Body set to "body"
- (NSString *)activityViewController:(UIActivityViewController *)activityViewController subjectForActivityType:(NSString *)activityType
Is never called when sharing into the Gmail app.
I then try the more hack way of doing it
UIActivityViewController *activityViewController = [[UIActivityViewController alloc] initWithActivityItems:#[#"body"] applicationActivities:nil];
[activityViewController setValue:#"subject" forKey:#"subject"];
Result
Apple Mail Subject set to "subject", Body set to "body"
Gmail Subject set to "body", Body set to "body"
Any way to make Gmail Behave like Apple Mail?
Again, I have seen that other applications like Yelp and Safari have gotten the proper behavior out of the Gmail app through share sheet. Any advice would be appreciated, thanks.
[_activityViewController setValue:subject forKey:#"subject"];-Not supported way.
Correct way to set body and subject (iOS 7.0 and later)
- implement UIActivityItemSource protocol on item to share.
// EmailDataProvider.h
#interface EmailItemProvider : NSObject <UIActivityItemSource>
#property (nonatomic, strong) NSString *subject;
#property (nonatomic, strong) NSString *body;
#end
// EmailDataProvider.m
#implementation EmailDataProvider
- (id)activityViewControllerPlaceholderItem:(UIActivityViewController *)activityViewController {
return _body;
}
- (id)activityViewController:(UIActivityViewController *)activityViewController itemForActivityType:(NSString *)activityType {
return _body;
}
- (NSString *)activityViewController:(UIActivityViewController *)activityViewController subjectForActivityType:(NSString *)activityType {
return _subject;
}
#end
And than present it:
EmailDataProvider *emailItem = [[EmailDataProvider alloc]init];
emailItem.subject = #"This is Subject text.";
emailItem.body = #"This is Body,set by programatically";
UIActivityViewController *activityViewController =
[[UIActivityViewController alloc] initWithActivityItems:#[emailItem]
applicationActivities:nil];
[self presentViewController:activityViewController animated:YES completion:nil];
I am sharing Some content via UIActivityController.
It is working fine for other Options.
I am able to get subject and body in Default Mail App.
But when I use to share the content with gmail then my Subject of the mail is gone and I am getting Body content in Gmail Subject's section:
Here is my code:
NSString *body = #"I am Body";
NSString *tagLine = #"I am Subject";
NSArray *objectToShare = [NSArray arrayWithObjects:body, nil];
UIActivityViewController *activityVC = [[UIActivityViewController alloc] initWithActivityItems:objectToShare applicationActivities:nil];
[activityVC setValue:tagLine forKey:#"subject"];
NSArray *excludeActivities = #[UIActivityTypeAirDrop,
UIActivityTypePrint,
UIActivityTypeAssignToContact,
UIActivityTypeSaveToCameraRoll,
UIActivityTypeAddToReadingList,
UIActivityTypePostToVimeo];
activityVC.excludedActivityTypes = excludeActivities;
[self presentViewController:activityVC animated:YES completion:nil];
For better Picture Here is the screenshot:
With Default App:
With Gmail:
I also tried different answers on SO. But none of them works.
At the time of writing this, Google doesn't allow setting the subject of email. People have reported it as a bug multiple times, and it seems this feature is still not supported.
Looking at other Google-owned products, and trying to share some contents via Gmail, you will see that the Gmail shared activity doesn't have the subject (e.g Google Chrome), or it's the same as the email's body (Google Translator), while if you share them to the normal app it appears that some of them have a subject. So even Google products have the same behaviour.
If you use a breakpoint inside the subjectForActivityType function you will realise that the Gmail activity won't hit the breakpoint while default mail and other activities will attempt to read the subject.
#implementation EmailItemProvider
- (id)activityViewControllerPlaceholderItem:(UIActivityViewController *)activityViewController {
return _body;
}
- (id)activityViewController:(UIActivityViewController *)activityViewController itemForActivityType:(NSString *)activityType {
return _body;
}
- (NSString *)activityViewController:(UIActivityViewController *)activityViewController subjectForActivityType:(NSString *)activityType {
return _subject;
}
There is a extremely similar question asked by the following post: Different data for sharing providers in UIActivityViewController. But my question is different.
I know how to share different different data of the same type with different activities by using itemForActivityType. For example:
- (id) activityViewController:(UIActivityViewController *)activityViewController
itemForActivityType:(NSString *)activityType
{
if ( [activityType isEqualToString:UIActivityTypePostToTwitter] )
return #"This is a #twitter post!";
if ( [activityType isEqualToString:UIActivityTypePostToFacebook] )
return #"This is a facebook post!";
if ( [activityType isEqualToString:UIActivityTypeAirDrop] )
return #"Airdrop message text";
else
return nil;
}
However, my question is: what if I have different kind of data to share with different activities, what should I do?. For example, what if I would like to share:
a string on Twitter;
a array of an string and and image on Facebook;
the actual data of the image (e.g. NSData) with Airdrop.
P.S.:
I also looked at the following protocol function:
- (id)activityViewControllerPlaceholderItem:;
However, I cannot use it because we don't know the value of activityType here.
You'd want to create and share two or more objects that conform to the UIActivityItemSource, where one returns the String, another one an Image, etc. Then when the delegate callback requesting the item is called you check which activity type was selected (Facebook, Mail, AirDrop, etc) and have one or multiple of the ItemSource's return nil if that item doesn't apply to that activity. Make sure for any chosen activity that at least one of the item's return a non-nil value.
You can take a look at the airdrop sample code to get some examples of how to implement UIActivityItemSource
For anyone still looking for a solution in objective-c, this is for sharing different datasources, returning more than one object, and it works with whats'app share. In my case I wanted both picture and text for all itemForActivityType:
FIRST: create your UIActivityItemSource, 1 for text, and 1 for the image
MyShareImage.h
#protocol giveMeImageToShare
- (UIImage*)imageToShare;
#end
#interface MyShareImage : NSObject<UIActivityItemSource>
#property (weak,nonatomic) id<giveMeImageToShare> delegate;
#end
MyShareImage.m
#import "MyShareImage.h"
#implementation MyShareImage
- (id)activityViewControllerPlaceholderItem:(UIActivityViewController*)activityViewController{
return #"";
}
- (id)activityViewController:(UIActivityViewController *)activityViewController itemForActivityType:(UIActivityType)activityType{
return [[self delegate] imageToShare];
}
then,
MyShareText.h
#protocol givemetextToShare
- (NSString*)textToShare;
#end
#interface MyShareText : NSObject<UIActivityItemSource>
#property (weak,nonatomic) id<givemetextToShare> delegate;
#end
MyShareText.m
#implementation MyShareText
- (id)activityViewControllerPlaceholderItem:(UIActivityViewController *)activityViewController{
return #"";
}
- (id)activityViewController:(UIActivityViewController *)activityViewController itemForActivityType:(UIActivityType)activityType{
if ([activityType containsString:#"net.whatsapp.WhatsApp.ShareExtension"]) {
return nil;
}
return [[self delegate] textToShare];
}
And now the activityController:
- (void)shareAllPossible:(id)sender withThisImage:(UIImage*)immagineShare andThisText:(NSString*)testoShare{
immagine = immagineShare;
testo = testoShare;
MyShareText *myShareText = [MyShareText new];
myShareText.delegate = self;
MyShareImage *myShareImage = [MyShareImage new];
myShareImage.delegate = self;
NSAssert(immagineShare, #"The image must be loaded to share.");
if (immagineShare) {
UIActivityViewController *activityController = [[UIActivityViewController alloc] initWithActivityItems:#[myShareImage ,myShareText] applicationActivities:nil];
activityController.completionWithItemsHandler = ^(NSString *activityType, BOOL completed, NSArray *returnedItems, NSError *activityError) {
if (completed)
{
//NSLog(#"The Activity: %# was completed", activityType);
}
else {
//NSLog(#"The Activity: %# was NOT completed", activityType);
}
};
[self presentViewController:activityController animated:YES completion:nil];
}
}
Hope it helps.
* got inspiration from https://stackoverflow.com/a/37548529/1907742
Mchurch
I am using the following code to call action sheet sharing in my app:
- (IBAction)sendPost:(id)sender
{
NSArray *activityItems = nil;
UIImage *appIcon = [UIImage imageNamed:#"appIcon.png"];
NSString *postText = [[NSString alloc] initWithFormat:#"LETS ASSUME THIS STRING IS LONGER THAN 140 CHARACTERS THAT TWITTER PROHIBITS BUT CAN STILL BE SHARED VIA FACEBOOK, EMAIL, TEXT"];
activityItems = #[postText,appIcon];
UIActivityViewController *activityController = [[UIActivityViewController alloc] initWithActivityItems:activityItems applicationActivities:nil];
[self presentViewController:activityController animated:YES completion:nil];
}
The problem is this: postText is longer than 140 characters, so sharing via twitter will not be possible, the character count will be -x (red number of characters you are over in order to share via twitter), my question is this: How can I make an exception so that a different message say shortPostText will be the one used when twitter is selected for sharing?
And once the sendPost action is sent I don't see a way to explicitly set a string for twitter, once you are here:
Edit: I dont understand why someone would down-vote this question, I am not asking how to make an if/else statement or how to program. This is a genuine question, that needs a genuine answer.
UPDATE: I need a work around this because this is what I get when a user tries to share via twitter in my app:
A red/negative character indicator and a non-active post button, so unless that character count goes down to 0 or less it will not allow the post to go to twitter.
TL;DR Use UIActivityItemSource to special case payload depending on what the user selection was.
Try this instead:
- (IBAction)sendPost:(id)sender
{
UIImage *appIcon = [UIImage imageNamed:#"appIcon.png"];
NSString *postText = [[NSString alloc] initWithFormat:#"LETS ASSUME THIS STRING IS LONGER THAN 140 CHARACTERS THAT TWITTER PROHIBITS BUT CAN STILL BE SHARED VIA FACEBOOK, EMAIL, TEXT"];
TextItemSource *itemSource = [[TextItemSource alloc] initWithString:postText previewImage:appIcon];
UIActivityViewController *activityController = [[UIActivityViewController alloc] initWithActivityItems:#[itemSource] applicationActivities:nil];
[self presentViewController:activityController animated:YES completion:nil];
}
// ------- TextItemSource.h
#interface TextItemSource : NSObject <UIActivityItemSource>
- (id)initWithString:(NSString *)string previewImage:(UIImage *)previewImage;
#end
// ------- TextItemSource.m
#implementation TextItemSource
{
NSString *_string;
UIImage *_previewImage;
}
- (id)initWithString:(NSString *)string previewImage:(UIImage *)previewImage
{
self = [super init];
if (self) {
_string = [string copy];
_previewImage = previewImage;
}
return self;
}
- (id)activityViewControllerPlaceholderItem:(UIActivityViewController *)activityViewController
{
return _string;
}
- (id)activityViewController:(UIActivityViewController *)activityViewController itemForActivityType:(NSString *)activityType
{
NSString *string = _string;
if ([activityType isEqual:UIActivityTypePostToTwitter]) {
#pragma mark TODO: do smarter thing :)
string = [_subject substringToIndex:140];
}
return string;
}
- (UIImage *)activityViewController:(UIActivityViewController *)activityViewController thumbnailImageForActivityType:(NSString *)activityType suggestedSize:(CGSize)size
{
// might want to scale image to fit suggestedSize
return _previewImage;
}
#end
In the Photos app on the iPhone, when you select the Mail sharing option, the photo animates into the modal view controller that slides up. How is it possible to modify the behaviour of the built-in UIActivities? For example, I'd like to be able to set the subject field of the mail composer.
Unfortunately, customizing the subject field of the UIActivityViewController mail composer is not working yet.
There is a documented and reported bug regarding trying to customize this discussed here:
iphone - How do I set recipients for UIActivityViewController in iOS 6?
If this were working, according to the documentation, you would be able to customize these mail composer fields:
UIActivityTypeMail:
The object posts the provided content to a new email message. When
using this service, you can provide NSString and UIImage objects and
NSURL objects pointing to local files as data for the activity items.
You may also specify NSURL objects whose contents use the mailto
scheme.
So using the mailto scheme, when it is working, you should be able to customize those fields like this:
NSString *text = #"My mail text";
NSURL *recipients = [NSURL URLWithString:#"mailto:foo#bar.com?subject=Here-is-a-Subject"];
NSArray *activityItems = #[text, recipients];
UIActivityViewController *activityController = [[UIActivityViewController alloc] initWithActivityItems:activityItems applicationActivities:nil];
[self presentViewController:activityController animated:YES completion:nil];
If your looking for other ways to customize the UIActivityViewControllerthere is an excellent example project here:
https://github.com/russj/ios6ShareDemo
This is how I did it and it's working for me in iOS 7.
Create a class that conforms to the UIActivityItemSource protocol:
#interface CustomActivityItem : NSObject <UIActivityItemSource>
#end
In the implementation override the relevant methods:
- (id)activityViewControllerPlaceholderItem:(UIActivityViewController *)activityViewController
{
return #"";
}
- (NSString *)activityViewController:(UIActivityViewController *)activityViewController subjectForActivityType:(NSString *)activityType
{
if ([activityType isEqualToString:UIActivityTypeMail])
{
return #"Subject"
}
return nil;
}
- (id)activityViewController:(UIActivityViewController *)activityViewController itemForActivityType:(NSString *)activityType
{
if ([activityType isEqualToString:UIActivityTypeMail])
{
return #"body";
}
return nil;
}
Then present the activity view controller:
CustomActivityItem* activityItem = [[CustomActivityItem alloc] init];
UIActivityViewController* activityViewController = [[UIActivityViewController alloc] initWithActivityItems:#[activityItem]
applicationActivities:nil];
[self presentViewController:activityViewController animated:YES completion:nil];