Tuesday, January 12, 2016

How To: Using SLRequest to Upload Image or Video To Twitter

Hi guys what's up. This is a simple tutorial so it doesn't have any downloadable project.

Today I'd like to share with you how to upload an image (like JPEG or GIF animation) to Twitter from the iOS app.

For a normal JPEG and status upload, you can use SLComposeViewController but you will face problem if you want to upload GIF animation or video file. That is because SLComposeViewController only has "addImage" method that takes up a UIImage object.

So you have to use SLRequest - a low level API communicator.

There was a simple API request called "upload_with_media" but Twitter has DEPRECATED it. It works now, but it may not work anymore in the near future and thus may break your app if you use that. So now you need to use 2 APIs to tweet text and image in one tweet. The processes involved is basically 2 - tiered jobs:

1. Upload image first - you will get the media id after upload completes
2. Update status with text and the media ID

As usual you have to request access to Twitter account before you can do anything with it. Once access is granted by user, you can then proceed to instantiate requests to twitter API host. Here's how to upload a media to Twitter's Media server:


NSData *imageData = [NSData dataWithContentsOfURL:fileURL];
NSURL *requestURL = [NSURL URLWithString:@"https://upload.twitter.com/1.1/media/upload.json"];
                    
SLRequest *postRequest = [SLRequest 
                                              requestForServiceType:SLServiceTypeTwitter
                                              requestMethod:SLRequestMethodPOST
                                              URL:requestURL parameters:nil;
                    
postRequest.account = twitterAccount;
[postRequest addMultipartData:imageData
                                         withName:@"media"
                                             type:@"image/gif"
                                         filename:@"test.gif"];
                    
[postRequest performRequestWithHandler:^(NSData *responseData,
                                                 NSHTTPURLResponse *urlResponse, NSError *error)
                     {

First, convert your image/video into NSData. Then create an SLRequest with NIL PARAMETERS with POST method as in code above. Set the request's account. Then add multipart data to the request.

The data type must be matching to what you're uploading. If it's a JPG then use "image/jpeg" if it's a GIF then use "image/gif" etc. Finally perform the request to start uploading the image to twitter's media server. This may take a while so you should have a UIActivityViewController pop up when doing this.

When the request is completed, the method will return responseData in JSON format. Convert this data into JSON.

Example of JSON data returned from twitter:
 
{
    "expires_after_secs" = 86400;
    image =     {
        h = 400;
        "image_type" = "image/gif";
        w = 400;
    };
    "media_id" = 687171095254859776;
    "media_id_string" = 687171095254859776; ---- THIS IS WHAT YOU NEED
    size = 705439;
}

Retrieve the media ID string as follows:
NSDictionary *json = [NSJSONSerialization JSONObjectWithData:responseData options:0 error:nil];
NSString *mediaID = [json objectForKey:@"media_id_string"];

Once you get the mediaID, you can then tweet a text with this ID using /statuses/update.json API.

 
NSURL *requestURL2 = [NSURL URLWithString:@"https://api.twitter.com/1.1/statuses/update.json"];
NSDictionary *message2 = @{@"status": @"Here is image",
                                                    @"media_ids": mediaID };
                         
SLRequest *postRequest2 = [SLRequest
                                                   requestForServiceType:SLServiceTypeTwitter
                                                   requestMethod:SLRequestMethodPOST
                                                   URL:requestURL2 parameters:message2];
postRequest2.account = twitterAccount;
                         
[postRequest2 performRequestWithHandler:^(NSData *responseData,
                                                      NSHTTPURLResponse *urlResponse, NSError *error)
                          {

The second request is just the same method performRequestWithHandler but with different requestURL. And that's how it's done.

Here is the complete code (including requesting access to twitter account):

 

-(void)uploadImageToTwitter {


        
        ACAccountStore *account = [[ACAccountStore alloc] init];
        ACAccountType *accountType = [account accountTypeWithAccountTypeIdentifier:
                                      ACAccountTypeIdentifierTwitter];
        
        [account requestAccessToAccountsWithType:accountType options:nil
                                      completion:^(BOOL granted, NSError *error)
        {
            if (granted == YES)
            {
                NSArray *arrayOfAccounts = [account
                                            accountsWithAccountType:accountType];
                
                if ([arrayOfAccounts count] > 0)
                {
                    ACAccount *twitterAccount =
                    [arrayOfAccounts lastObject];
                    
                    
                    NSURL *furl = [NSURL fileURLWithPath:NSTemporaryDirectory()];
                    NSURL *fileURL = [furl URLByAppendingPathComponent:@"animation.gif"];
                    NSData *imageData = [NSData dataWithContentsOfURL:fileURL];
                   
                    NSURL *requestURL = [NSURL URLWithString:@"https://upload.twitter.com/1.1/media/upload.json"];
                    
                    SLRequest *postRequest = [SLRequest 
                                              requestForServiceType:SLServiceTypeTwitter
                                              requestMethod:SLRequestMethodPOST
                                              URL:requestURL parameters:nil];
                    
                    postRequest.account = twitterAccount;
                    
                    [postRequest addMultipartData:imageData
                                         withName:@"media"
                                             type:@"image/gif"
                                         filename:@"test.gif"];
                    
                    [postRequest
                     performRequestWithHandler:^(NSData *responseData,
                                                 NSHTTPURLResponse *urlResponse, NSError *error)
                     {
                         
                         
                          NSDictionary *json = [NSJSONSerialization JSONObjectWithData:responseData options:0 error:nil];
                         
                         NSString *mediaID = [json objectForKey:@"media_id_string"];
                         
                         
                         if (mediaID!=nil) {
                             
                         
                             NSURL *requestURL2 = [NSURL URLWithString:@"https://api.twitter.com/1.1/statuses/update.json"];
                         NSDictionary *message2 = @{@"status": @"Here is the image",
                                                    @"media_ids": mediaID };
                         
                         SLRequest *postRequest2 = [SLRequest
                                                   requestForServiceType:SLServiceTypeTwitter
                                                   requestMethod:SLRequestMethodPOST
                                                   URL:requestURL2 parameters:message2];
                         postRequest2.account = twitterAccount;
                         
                         [postRequest2
                          performRequestWithHandler:^(NSData *responseData,
                                                      NSHTTPURLResponse *urlResponse, NSError *error)
                          {
                             // DONE!!!

                          }];
                             
                         }
                         
                     }];
                }
            }
         }];


That's all FOLKS!

Wednesday, November 18, 2015

How To: Pre-Process Tilemap Overlays in MKMapView

Hi all.

Time for another tutorial. Recently I was trying to modify a tilemap returned from a typical public map overlay server (such as openstreetmap.org) - basically I needed a way to change black areas of the tile into transparent so that I could nicely overlay it on top of MKMapView. I searched for this for a while, found some solutions but did not really work as I wanted. Some examples used MKTileOverlayRenderer. Somehow that gave me weird results that I do not understand.

Note: In this tutorial there are no downloadable project since it is a straight forward MKTileOverlay subclassing usage. (But, if u really need a sample project, do shout in the comment section).

In your custom MKTileOverlay, you return the URL to load according to map rect using the normal URLForTilePath as usual. Then in the loadTileAtPath method, you process the *data variable.

Here is the example of the method:


- (void)loadTileAtPath:(MKTileOverlayPath)path result:(void (^)(NSData *, NSError *))result
{
    if (!result)
    {
        return;
    }
   
        NSURLRequest *request = [NSURLRequest requestWithURL:[self URLForTilePath:path]];
        [NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
            
            if ([data length]<335) {
                
                result(nil,nil);
            } else {
                
                    CGSize sz = CGSizeMake(256,256);
                    CGRect rect = CGRectMake(0,0,256,256);
                    UIImage *img = [UIImage imageWithData:data];
                    
                    UIGraphicsBeginImageContext(sz);
                    CGContextRef context = UIGraphicsGetCurrentContext();
                    
                    [img drawInRect:rect];
                    
                    UInt8 *dat = CGBitmapContextGetData(context);
                    // int numComponents = 4;
                    long dataLength = CGBitmapContextGetHeight(context) * CGBitmapContextGetBytesPerRow(context);
                    
                    int  Comp1, Comp2, Comp3, Comp4;
                    
                    Comp1 = 0;
                    Comp2 = 1;
                    Comp3 = 2;
                    Comp4 = 3;

                    // This processes each pixels and convert the map to greyscale.

                    for(int index=0;index<dataLength;index+=4){
                        int aRed = dat[index+Comp1];
                        int aGrn = dat[index+Comp2];
                        int aBlu = dat[index+Comp3];
                        
                        float grey = (aRed+aGrn+aBlu)/3.0;
                        
                        dat[index+Comp1] = grey;
                         dat[index+Comp2] = grey;
                         dat[index+Comp3] = grey;
                        
                    }
                    
                    UIImage *tileImage = UIGraphicsGetImageFromCurrentImageContext();
                    UIGraphicsEndImageContext();
                    NSData *tileData = UIImagePNGRepresentation(tileImage);
                    result(tileData,nil); // return new data


                  
            }
        }];

}

As in the method above: Take *data and convert it to UIImage. Create a graphic context to draw on. Draw the map on that context. Then further manipulate the context pixels by pixels. Finally get the resulting image from the modified context and return as result.

Normal original openstreetmap tile (unmodified) loaded unto MKMapView as MKTileOverlay:




Change to grey (pixels to grey code given in the method above):



Change to invert color:


Change to High Saturation


Note: The air pollution markers are on another MKTileOverlay.

All these 4 map styles came from a single tileserver (openstreetmap.org).

Do take note that each time a tile (of size 256x256) is loaded, your custom class will process it with CoreGraphics with the code given. It is wise to keep the processing simple and too heavy burden on processor.


Related Posts Plugin for WordPress, Blogger...