Cocos2d-xでTwitter、Facebook、LINEの簡易的なシェア機能を実装する

プロジェクトが変わるたびにいつも同じようなコードを書いている気がするので、シェアの機能を実装したついでにここにコードをメモしておきます
ちなみにSDKを使った本格的な実装は面倒だったのでopenURLやintentなどを使った簡易的なものになります
まずは基本のクラスから

Share.h

#ifndef Share_h
#define Share_h

#include <string>

class Share{
public:
    static void facebook(const std::string& message, const std::string& imagePath = "", const std::string& url = "");
    static void twitter(const std::string& message, const std::string& imagePath = "", const std::string& url = "");
    static void lineSendMessage(const std::string& message);
    static void lineSendImage(const std::string& imagePath);
    
private:
    
    static constexpr auto LINE_URL_SCHEME = "line://msg/%s/%s";
    static constexpr auto CONTENT_IMAGE = "image";
    static constexpr auto CONTENT_TEXT = "text";
};

#endif /* Share_h */

とりあえずシェアするものはメッセージと画像とURLにしています
LINEはそれぞれ画像かメッセージかのどちらか片方しか送れないので一つずつ関数を用意しています

Share.cpp

#include "Share.h"
#include "cocos2d.h"
#include <iomanip>

std::string url_encode(const std::string &value) {
    std::ostringstream escaped;
    escaped.fill('0');
    escaped << std::hex;
    
    for (auto i = value.begin(); i != value.end(); ++i) {
        unsigned char c = (*i);
        
        if (isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~') {
            escaped << c;
            continue;
        }
        
        escaped << '%' << std::setw(2) << int(c);
    }
    
    return escaped.str();
}

void Share::lineSendMessage(const std::string& message) {
    std::string encodedMessage = url_encode(message);
    std::string urlString = cocos2d::StringUtils::format(LINE_URL_SCHEME, CONTENT_TEXT, encodedMessage.c_str());
    cocos2d::Application::getInstance()->openURL(urlString);
}


#if CC_TARGET_PLATFORM != CC_PLATFORM_IOS && CC_TARGET_PLATFORM != CC_PLATFORM_ANDROID

void Share::facebook(const std::string& message, const std::string& imagePath, const std::string& url) {
    CCLOG("This OS is not implemented yet");
}

void Share::twitter(const std::string& message, const std::string& imagePath, const std::string& url) {
    CCLOG("This OS is not implemented yet");
}

void Share::lineSendImage(const std::string& imagePath) {
    CCLOG("This OS is not implemented yet");
}
#endif

LINEのメッセージ送信のみiOSとAndroidで実装を共通化可能なためここで実装しています
LINEのメッセージはURLエンコーディングする必要があるのでエンコードしてからopenURLを叩いています
URLエンコードの実装はほぼStackOverflowからパクっています
iOSとAndroid以外では一応ビルドエラーが出ないようにしています(英語は適当)

ここから下はOSごとの実装になります

Share_iOS.mm

#include "Share.h"
#include "cocos2d.h"
#import "AppController.h"
#import <Social/Social.h>

void SLComposeViewShare(NSString* serviceType, const std::string& message, const std::string& imagePath, const std::string& url) {
    
    SLComposeViewController *composeViewController = [SLComposeViewController composeViewControllerForServiceType:serviceType];
    void (^completion) (SLComposeViewControllerResult result) = ^(SLComposeViewControllerResult result) {
        [composeViewController dismissViewControllerAnimated:YES completion:nil];
    };
    [composeViewController setCompletionHandler:completion];
    [composeViewController setInitialText:[NSString stringWithUTF8String:message.c_str()]];
    if (!imagePath.empty()) {
        UIImage* image = [UIImage imageWithContentsOfFile:[NSString stringWithUTF8String:imagePath.c_str()]];
        [composeViewController addImage:image];
    }
    if (!url.empty()) {
        [composeViewController addURL:[NSURL URLWithString:[NSString stringWithUTF8String:url.c_str()]]];
    }
    
    AppController* appController = [UIApplication sharedApplication].delegate;
    [appController.viewController presentViewController:composeViewController animated:YES completion:Nil];
}


void Share::facebook(const std::string& message, const std::string& imagePath, const std::string& url) {
    SLComposeViewShare(SLServiceTypeFacebook, message, imagePath, url);
}
void Share::twitter(const std::string& message, const std::string& imagePath, const std::string& url) {
    SLComposeViewShare(SLServiceTypeTwitter, message, imagePath, url);
}

void Share::lineSendImage(const std::string& imagePath) {
    UIImage* image = [UIImage imageWithContentsOfFile:[NSString stringWithUTF8String:imagePath.c_str()]];
    
    UIPasteboard *pasteboard;
    
    if ([[UIDevice currentDevice].systemVersion floatValue] >= 7.0) {
        pasteboard = [UIPasteboard generalPasteboard];
    } else {
        pasteboard = [UIPasteboard pasteboardWithUniqueName];
    }
    [pasteboard setImage:image];
    
    std::string urlString = cocos2d::StringUtils::format(LINE_URL_SCHEME, CONTENT_IMAGE, [pasteboard.name UTF8String]);
    cocos2d::Application::getInstance()->openURL(urlString);
}

iOS側のObjective-C++の実装になります
TwitterとFacebookのシェアではSLComposeViewControllerを使っているため、必ずSocial.frameworkをBuild Phasesから追加してください
LINEの画像のシェアはline://のURLスキームでopenURLから行いますが、iOSの場合はペーストボード名をURLに含める必要があります

Share_Android.cpp

#include "Share.h"
#include <jni.h>
#include "platform/android/jni/JniHelper.h"
#include "cocos2d.h"

#define  CLASS_NAME "org/cocos2dx/cpp/Share"

USING_NS_CC;

void Share::facebook(const std::string& message, const std::string& imagePath, const std::string& url) {
    if (imagePath.empty() && url.empty()) {
        //Facebookは画像かURLのどちらかのみシェア可能
        return;
    }
    JniMethodInfo t;
    if (JniHelper::getStaticMethodInfo(t, CLASS_NAME, "facebook", "(Ljava/lang/String;Ljava/lang/String;)V")) {
        jstring jUrl = t.env->NewStringUTF(url.c_str());
        jstring jFilePath = t.env->NewStringUTF(imagePath.c_str());
        
        t.env->CallStaticVoidMethod(t.classID, t.methodID, jFilePath, jUrl);
        
        t.env->DeleteLocalRef(jFilePath);
        t.env->DeleteLocalRef(jUrl);
        t.env->DeleteLocalRef(t.classID);
    }
    
}
void Share::twitter(const std::string& message, const std::string& imagePath, const std::string& url) {
    
    JniMethodInfo t;
    if (JniHelper::getStaticMethodInfo(t, CLASS_NAME, "twitter", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V")) {
        jstring jMessage = t.env->NewStringUTF(message.c_str());
        jstring jFilePath = t.env->NewStringUTF(imagePath.c_str());
        jstring jUrl = t.env->NewStringUTF(url.c_str());
        
        
        t.env->CallStaticVoidMethod(t.classID, t.methodID, jMessage, jFilePath, jUrl);
        
        t.env->DeleteLocalRef(jMessage);
        t.env->DeleteLocalRef(jFilePath);
        t.env->DeleteLocalRef(jUrl);
        t.env->DeleteLocalRef(t.classID);
    }
}

void Share::lineSendImage(const std::string& imagePath) {
    std::string urlString = cocos2d::StringUtils::format(LINE_URL_SCHEME, CONTENT_IMAGE, imagePath.c_str());
    cocos2d::Application::getInstance()->openURL(urlString);
}

Android側のJNI他の実装になります
このファイルはiOSのビルド対象に含めないでください
AndroidのTwitterとFacebookのシェアはIntentを使ってシェアしますが、Facebookはメッセージのシェアは行えないので、画像かURLがあった場合のみシェアを行います
AndroidのLINEはURLにファイルパスを含めるだけで画像のシェアを行えます
ただ、Androidの画像のシェアはgetWritablePathで取得するファイルパスが他のアプリから参照できないため、Cocos2d-xで保存した画像をシェアする場合はファイルパスを工夫する必要があります

Share.java

package org.cocos2dx.cpp;

import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.webkit.MimeTypeMap;

import org.cocos2dx.lib.Cocos2dxActivity;

import java.io.File;

public class Share {

    public static void twitter(String message, String filePath, String url) {
        Context con = Cocos2dxActivity.getContext();

        Intent intent = new Intent(Intent.ACTION_SEND);

        File file = new File(filePath);
        if (file.exists()) {
            int index = file.getName().lastIndexOf('.');
            String ext = (index >= 0) ? file.getName().substring(index + 1).toLowerCase() : "";
            String mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(ext);
            intent.setType(mimeType);
            intent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(file));
        } else {
            intent.setType("text/plain");
        }

        if (url.isEmpty()) {
            intent.putExtra(Intent.EXTRA_TEXT, message);
        } else {
            intent.putExtra(Intent.EXTRA_TEXT, message + " " + url);
        }
        intent.setPackage("com.twitter.android");

        con.startActivity(intent);
    }


    public static void facebook(String filePath, String url) {
        Context con = Cocos2dxActivity.getContext();

        Intent intent = new Intent(Intent.ACTION_SEND);

        File file = new File(filePath);
        if (file.exists()) {
            int index = file.getName().lastIndexOf('.');
            String ext = (index >= 0) ? file.getName().substring(index + 1).toLowerCase() : "";
            String mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(ext);
            intent.setType(mimeType);
            intent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(file));
        } else {
            intent.setType("text/plain");
            intent.putExtra(Intent.EXTRA_TEXT, url);
        }

        intent.setPackage("com.facebook.katana");

        con.startActivity(intent);
    }
}

AndroidのJNIで呼ぶJavaの実装になります
TwitterはURLがある場合はメッセージの後ろに空白を入れてURLを入れています
Facebookは画像とURLが両方ある場合は画像を優先してシェアするようにしています

これで一応一通りシェアが可能になるかと思います
最初はgithubで公開しようかと思ったのですが、既に似たようなものがあったのでやめました
Androidの画像のファイル保存先の取得については次の記事でやろうと思います

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です