Unity Instagram sharing

Unity Instagram sharing image

Recently in one of our Unity projects, we wanted to allow the user to share an image on Instagram. This sounds like a fairly straightforward process, and it should be, given we’re talking about one of the largest social networks.

Unfortunately, the information for this process is scarce and far from consistent.

Since the release of our project, some Unity plugins appeared on the Asset Store that supposedly help you do this. However, given our hacker nature we feel a lot more comfortable when we know exactly what’s going on inside our code, right?

That’s why today I bring you a brief explanation on how to accomplish this on iOS.

Step 1 – Create the native code

The header file (InstagramShare.h):

#import <Foundation/Foundation.h>

void _handshake();
void _postToInstagram(const char *, const char *);

@interface InstagramShare : NSObject <UIDocumentInteractionControllerDelegate>
{
	UIWindow *nativeWindow;
}

@property (nonatomic, retain) UIDocumentInteractionController *dic;

+(InstagramShare*)sharedInstance;

-(void)handshake;
-(void)postToInstagram:(NSString*)message WithImage:(NSString*)imagePath;

@end

The implementation file (InstagramShare.m):

#import "InstagramShare.h"

void _handshake() 
{
    [[InstagramShare sharedInstance] handshake];
}

void _postToInstagram(const char * message, const char * imagePath)
{
    NSString *m = [NSString stringWithUTF8String:message];
    NSString *i = [NSString stringWithUTF8String:imagePath];
    [[InstagramShare sharedInstance] postToInstagram:m WithImage:i];
}

@implementation InstagramShare

@synthesize dic;

static InstagramShare *sharedInstance = nil;
+(InstagramShare*)sharedInstance {
    if( !sharedInstance )
        sharedInstance = [[InstagramShare alloc] init];
    
    return sharedInstance;
}


-(id)init 
{
    if (self = [super init]) 
    {
        nativeWindow = [UIApplication sharedApplication].keyWindow;
    }
    
    return self;
}

-(void)handshake 
{
    NSLog(@"Handshake completed!");
}

-(void)postToInstagram:(NSString*)message WithImage:(NSString*)imagePath;
{
    NSURL *appURL = [NSURL URLWithString:@"instagram://app"];
    if([[UIApplication sharedApplication] canOpenURL:appURL])
    {
        // Image
        UIImage *image = [UIImage imageWithContentsOfFile:imagePath];
        
        // Post
        [UIImageJPEGRepresentation(image, 1.0) writeToFile:[self photoFilePath] atomically:YES];
        NSURL *fileURL = [NSURL fileURLWithPath:[self photoFilePath]];
        
        self.dic = [UIDocumentInteractionController interactionControllerWithURL:fileURL];
        self.dic.UTI = @"com.instagram.exclusivegram";
        self.dic.delegate = self;
        if (message)
            self.dic.annotation = [NSDictionary dictionaryWithObject:message forKey:@"InstagramCaption"];
        
        [self.dic presentOpenInMenuFromRect:CGRectZero inView:nativeWindow.rootViewController.view animated:YES];
    }
    else
    {
        NSLog(@"Instagram not installed!");
    }
}

-(NSString*)photoFilePath 
{
    return [NSString stringWithFormat:@"%@/%@",[NSHomeDirectory() stringByAppendingPathComponent:@"Documents"], @"tempinstgramphoto.igo"];
}

-(UIDocumentInteractionController*)setupControllerWithURL:(NSURL*)fileURL usingDelegate:(id<UIDocumentInteractionControllerDelegate>)interactionDelegate 
{
    UIDocumentInteractionController *interactionController = [UIDocumentInteractionController interactionControllerWithURL:fileURL];
    interactionController.delegate = interactionDelegate;
    return interactionController;
}

@end

The really important part to retain here is this:

NSURL *appURL = [NSURL URLWithString:@"instagram://app"];

if([[UIApplication sharedApplication] canOpenURL:appURL])
{
    // Image
    UIImage *image = [UIImage imageWithContentsOfFile:imagePath];
    
    // Post
    [UIImageJPEGRepresentation(image, 1.0) writeToFile:[self photoFilePath] atomically:YES];
    NSURL *fileURL = [NSURL fileURLWithPath:[self photoFilePath]];
    
    self.dic = [UIDocumentInteractionController interactionControllerWithURL:fileURL];
    self.dic.UTI = @"com.instagram.exclusivegram";
    self.dic.delegate = self;
    if (message)
        self.dic.annotation = [NSDictionary dictionaryWithObject:message forKey:@"InstagramCaption"];
    
    [self.dic presentOpenInMenuFromRect:CGRectZero inView:nativeWindow.rootViewController.view animated:YES];
}

We start by asking the OS if it can open an Instagram file by using the “canOpenURL” method of the UIApplication instance. If so, we grab the image path sent by Unity, create a UIImage from it and pass it to the UIDocumentInteractionController.

The UIDocumentInteractionController is the king here and what it does is provides in-app support for managing user interactions with files in the local system – i.e. you can use it to open any kind of file or image with an appropriate application.

Notice you’ll have to save the header and the implementation files on your Assets folder under “Assets/Plugins/iOS”.

Step 2 –  Create the bridge:

using UnityEngine;
using System.Runtime.InteropServices;

public class InstagramShare
{
	static string imagePath = Application.temporaryCachePath + "/temp.png";

	static bool HasHandshook = false;

	[DllImport("__Internal")]
	static extern void _handshake();

	public static void HandShake() 
	{
		_handshake();
		HasHandshook = true;
	}

	[DllImport ("__Internal")]
	static extern void _postToInstagram (string message, string imagePath);

	public static void PostToInstagram(string message, byte[] imageByteArr)
	{
		if(!HasHandshook)
			HandShake();

		System.IO.File.WriteAllBytes(imagePath, imageByteArr);
		_postToInstagram(message, imagePath);
	}
}

This file is where the magic happens by making the bridge between Unity and the native code in Objective-C. Since the methods are static you can call them from anywhere on your code – really handy!

Step 3 – Wrap it up

Although using the files we just created we can share any image (converted to a byte array), here we’ll show you how to grab a shot from any in-game camera – we’ll use the main one for demonstration purposes.

using UnityEngine;
using System.Collections;

public class ShareButton : MonoBehaviour 
{
	Texture2D _texture;
	RenderTexture _renderTexture;

	void OnGUI()
	{
		if (GUI.Button(new Rect (10, 10, 80, 50), "SHARE"))
		{
			StartCoroutine(PostToInstagram());
		}
	}

	byte[] GrabScreenshot()
	{
		if(_texture != null)
			Destroy(_texture);

		// Initialize and render
		_renderTexture = new RenderTexture(Screen.width, Screen.height, 24);
		Camera.main.targetTexture = _renderTexture;
		Camera.main.Render();
		RenderTexture.active = _renderTexture;
		
		// Read pixels
		_texture = new Texture2D(Screen.width, Screen.height, TextureFormat.RGB24, false);
		_texture.ReadPixels(new Rect(0,0,Screen.width,Screen.height), 0, 0);
		
		// Clean up
		Camera.main.targetTexture = null;
		RenderTexture.active = null;
		DestroyImmediate(_renderTexture);
		
		return _texture.EncodeToPNG();
	}

	IEnumerator PostToInstagram()
	{
		yield return new WaitForEndOfFrame();

		InstagramShare.PostToInstagram("Hello from Unity!", GrabScreenshot());
	}
}

You’ll notice the “PostToInstagram” method is a co-routine, and that’s because we need to wait until the end of the current frame to capture a render texture.

Unity Instagram Share
Unity Instagram Share

And here’s our glorious app! On the left, the Unity app with the share button. On the middle, the UIDocumentInteractionController asking us what to do with the file. And on the right, our beautiful image inside Instagram with a hipster filter to make it look even cooler!

That’s it. You can now share your images on Instagram from your Unity projects! Hope this is handy for you and let us know if you have any questions or suggestions for upcoming posts!

KEEP MOVING FORWARD

Luis Fonseca / code