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
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];
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
Twitter has deprecated its API 1.0 and implemented new API 1.1
As per the new API we need to authentication before fetching the public time line.
I read the new API documentation here: https://dev.twitter.com/docs/api/1.1
but I have not clear idea, how to implement it in iOS.
Can anyone tell me the best way to fetch public time-line form Twitter using the API 1.1
Thanks in advance.
First, you need to Authenticate your request (Get permission).
second, see follow these steps:
1.Download FHSTwitterEngine Twitter Library.
2.Add the folder FHSTwitterEngine" to your project and #import "FHSTwitterEngine.h".
3.add SystemConfiguration.framework to your project.
Usage : 1.in the [ViewDidLoad] add the following code.
UIButton *logIn = [UIButton buttonWithType:UIButtonTypeRoundedRect];
logIn.frame = CGRectMake(100, 100, 100, 100);
[logIn setTitle:#"Login" forState:UIControlStateNormal];
[logIn addTarget:self action:#selector(showLoginWindow:) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:logIn];
[[FHSTwitterEngine sharedEngine]permanentlySetConsumerKey:#"<consumer_key>" andSecret:#"<consumer_secret>"];
[[FHSTwitterEngine sharedEngine]setDelegate:self];
and don't forget to import the delegate FHSTwitterEngineAccessTokenDelegate.
you need to get the permission for your request, with the following method which will present Login window:
- (void)showLoginWindow:(id)sender {
[[FHSTwitterEngine sharedEngine]showOAuthLoginControllerFromViewController:self withCompletion:^(BOOL success) {
NSLog(success?#"L0L success":#"O noes!!! Loggen faylur!!!");
}];
}
when the Login window is presented, enter your Twitter Username and Password to authenticate your request.
add the following methods to your code:
-(void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[[FHSTwitterEngine sharedEngine]loadAccessToken];
NSString *username = [[FHSTwitterEngine sharedEngine]loggedInUsername];// self.engine.loggedInUsername;
if (username.length > 0) {
lbl.text = [NSString stringWithFormat:#"Logged in as %#",username];
[self listResults];
} else {
lbl.text = #"You are not logged in.";
}
}
- (void)storeAccessToken:(NSString *)accessToken {
[[NSUserDefaults standardUserDefaults]setObject:accessToken forKey:#"SavedAccessHTTPBody"];
}
- (NSString *)loadAccessToken {
return [[NSUserDefaults standardUserDefaults]objectForKey:#"SavedAccessHTTPBody"];
}
4.Now you are ready to get your request, with the following method(in this method I created a `Twitter` search for some `Hashtag`, to get the screen_name for example):
- (void)listResults {
dispatch_async(GCDBackgroundThread, ^{
#autoreleasepool {
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
// the following line contains a FHSTwitterEngine method wich do the search.
dict = [[FHSTwitterEngine sharedEngine]searchTweetsWithQuery:#"#iOS" count:100 resultType:FHSTwitterEngineResultTypeRecent unil:nil sinceID:nil maxID:nil];
// NSLog(#"%#",dict);
NSArray *results = [dict objectForKey:#"statuses"];
// NSLog(#"array text = %#",results);
for (NSDictionary *item in results) {
NSLog(#"text == %#",[item objectForKey:#"text"]);
NSLog(#"name == %#",[[item objectForKey:#"user"]objectForKey:#"name"]);
NSLog(#"screen name == %#",[[item objectForKey:#"user"]objectForKey:#"screen_name"]);
NSLog(#"pic == %#",[[item objectForKey:#"user"]objectForKey:#"profile_image_url_https"]);
}
dispatch_sync(GCDMainThread, ^{
#autoreleasepool {
UIAlertView *av = [[UIAlertView alloc]initWithTitle:#"Complete!" message:#"Your list of followers has been fetched" delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil];
[av show];
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
}
});
}
});
}
That's all. I just got the screen_name from a search Query, you can get a timeline for a user using the following methods:
// statuses/user_timeline
- (id)getTimelineForUser:(NSString *)user isID:(BOOL)isID count:(int)count;
- (id)getTimelineForUser:(NSString *)user isID:(BOOL)isID count:(int)count sinceID:(NSString *)sinceID maxID:(NSString *)maxID;
instead of the search method above.
Note: see the FHSTwitterEngine.h to know what method you need to use.
Note: to get the <consumer_key> and the <consumer_secret> you need to
to visit this link to register your app in Twitter.
You might be interested in the following library.
https://github.com/nst/STTwitter
It is a lightweight Objective-C wrapper for Twitter REST API 1.1.
Look at the ReadMe and Demo project for examples
This is just one of the many methods the library has:
- (void)getUserTimelineWithScreenName:(NSString *)screenName
successBlock:(void(^)(NSArray *statuses))successBlock
errorBlock:(void(^)(NSError *error))errorBlock;
I recently started working with UIActivity to share my app to the world, but I have few problems. First, I didn't find how to set the subject of my email. Is there any way? Second, when I set the body text of my email, there is a extra "enter" (first line of the email is blank and my text starts at the second line). Here's the code:
NSMutableArray *array = [[NSMutableArray alloc] initWithObjects: #"Test", nil];
UIActivityViewController *activityViewController = [[UIActivityViewController alloc]
initWithActivityItems:array applicationActivities:nil];
And in the email, it shows that:
"
Test
"
Third: is there a way to know which sharing method has been selected? Because I want to include a hashtag in my post when the user shares on twitter, but now it gets integrated in the email also, which obviously doesn't make sense.
Thanks!
For adding subject to the email using UIActivityViewController on iOS6, this is the best solution that anyone can use.. All you have to do is call the following while initializing UIActivityViewController.
UIActivityViewController *activityViewController = [[UIActivityViewController alloc] initWithActivityItems:activityItems applicationActivities:applicationActivities];
[activityViewController setValue:#"My Subject Text" forKey:#"subject"];
And your UIActivityViewController is populated with a subject.
In iOS7, this is possible by using -
activityViewController:subjectForActivityType:
When posting an item the service may provide for a separate subject
field and data field, such as an email message. Implement this method
if you wish to provide a subject field for services that support one.
Check - https://developer.apple.com/library/ios/documentation/uikit/reference/UIActivityItemSource_protocol/Reference/Reference.html#//apple_ref/occ/intfm/UIActivityItemSource/activityViewController:subjectForActivityType:
1 and 2: How do I set recipients for UIActivityViewController in iOS 6?
Although both provided methods are a bit of a hack, especially the first one, it is possible.
3: it is possible to share different content on different services, but the number of items and their types should be the same (but it is not a limitation, really, as you can return nil for items you don't need on particular service). You have to create sharing items after the service was selected using UIActivityItemSource protocol
Code I use:
Show UIActivityViewController with the current controller as provider of all items (it should have in .h file):
const int numberOfSharedItems = 5;
- (IBAction)shareAction:(id)sender
{
NSMutableArray *shareItems = [NSMutableArray new];
while ([shareItems count] < numberOfSharedItems)
[shareItems addObject: self];
UIActivityViewController *shareController =
[[UIActivityViewController alloc]
// actual items are prepared by UIActivityItemSource protocol methods below
initWithActivityItems: shareItems
applicationActivities :nil];
shareController.excludedActivityTypes = #[UIActivityTypeMessage, UIActivityTypePrint, UIActivityTypeCopyToPasteboard, UIActivityTypeAssignToContact, UIActivityTypeSaveToCameraRoll];
[self presentViewController: shareController animated: YES completion: nil];
}
Make placeholders for items that will be shared:
-(id)activityViewControllerPlaceholderItem:(UIActivityViewController *)activityViewController
{
static UIActivityViewController *shareController;
static int itemNo;
if (shareController == activityViewController && itemNo < numberOfSharedItems - 1)
itemNo++;
else {
itemNo = 0;
shareController = activityViewController;
}
switch (itemNo) {
case 0: return #""; // intro in email
case 1: return #""; // email text
case 2: return [NSURL new]; // link
case 3: return [UIImage new]; // picture
case 4: return #""; // extra text (via in twitter, signature in email)
default: return nil;
}
}
Make actual items that will be shared, differently for different services:
-(id)activityViewController:(UIActivityViewController *)activityViewController itemForActivityType:(NSString *)activityType
{
// the number of item to share
static UIActivityViewController *shareController;
static int itemNo;
if (shareController == activityViewController && itemNo < numberOfSharedItems - 1)
itemNo++;
else {
itemNo = 0;
shareController = activityViewController;
}
NSString *shareText = [self shareText]; // whatever you fancy
NSURL *shareURL = [self shareURL];
// twitter
if ([activityType isEqualToString: UIActivityTypePostToTwitter])
switch (itemNo) {
case 0: return nil;
case 1: return shareText; // you can change text for twitter, I add $ to stock symbol inside shareText here, e.g. Hashtags can be added too
case 2: return shareURL;
case 3: return nil; // no picture
case 4: return #"via #YourApp";
default: return nil;
}
// email
else if ([activityType isEqualToString: UIActivityTypeMail])
switch (itemNo) {
case 0: return #"Hi!\r\n\r\nI used YourApp\r\n";
case 1: return shareText;
case 2: return shareURL;
case 3: return nil; // no picture
case 4: return [#"\r\nCheck it out.\r\n\r\nCheers\r\n" stringByAppendingString: [self userName]];
default: return nil;
}
else // Facebook or something else in the future
switch (itemNo) {
case 0: return nil;
case 1: return shareText;
case 2: return shareURL;
case 3: return [self shareImage];
case 4: return nil;
default: return nil;
}
}
u can simply create a class as follows :
#interface MYNActivityProvider : UIActivityItemProvider <UIActivityItemSource>
#end
// implementation
- (id) activityViewController:(UIActivityViewController *)activityViewController
itemForActivityType:(NSString *)activityType
{
if ( [activityType isEqualToString:UIActivityTypePostToTwitter] ) {
return stringToPost;
}
if ( [activityType isEqualToString:UIActivityTypePostToFacebook] ) {
return stringToPost;
}
if ( [activityType isEqualToString:UIActivityTypeMessage] ) {
return #"SMS message text";
}
if ( [activityType isEqualToString:UIActivityTypeMail] ) {
return #"Email text here!";
}
if ( [activityType isEqualToString:#"Custom"] ) {
return #"app custom text";
}
return nil;
}
You might want to try OvershareKit.
We are frequently asked why someone would use OvershareKit instead of UIActivityViewController (UIAVC) and UIActivity. UIAVC is great for apps that know they’ll never have a need for any of the following:
Never need to integrate with more than one or two third party services.
Never need to tweak the UI for the activity sheet and sharing screens.
Never care to provide separate, media-specific content for each sharing type (email versus SMS, etc.)
Never need to have multiple items such as a Copy Text versus a Copy Link in the same sheet.
Don't mind that all non-system-provided activities get stuck with boring monochromatic icons.
Your situation is (3) - a need to care about different content for different sharing type.
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];