2012/11/22

XCodeプロジェクトに Facebook SDK for iOS を同梱する

https://developers.facebook.com/docs/getting-started/facebook-sdk-for-ios/3.1/
ここ↑のgetting-startedのインストール手順だと、SDKは~/Documents/FacebookSDKにインストールされて、プロジェクトとSDKとはリンクされてるだけです。

SDKの本体は、~/Documents/FacebookSDK/FacebookSDK.framework になるので、
Finder上で組み込みたいプロジェクト(.xcodeprojと同じ階層)にコピーしてから、同じようにプロジェクトに追加します。

XCodeで、TARGETS>BuildSettings>Framework Search Pathsの値がそのプロジェクトのあるパスになっていればOKです。
※値としてはこのような変数が設定されていればOK→ "$(SRCROOT)"
※既に一度getting-startedの手順にてインストールしたことがある場合は、SDKへのパスが絶対パスで設定されてしまっていると思います。それは削除しちゃいましょう

あとはFacebookSDKのimportがエラーにならなければ、正しく読み込めていることになります。

これで、プロジェクトにFacebook SDKを同梱できました。
開発マシンとリリース作業をするマシンが別な時などはこの方法で対応しようと思います。

2012/09/24

iPhoneアプリからPOSTしたデータの確認

iPhoneアプリからサーバへ画像をPOSTする機能を実装しているとき、サーバサイド(今回はPHP)の出力(printやechoなんか)がXCode側で見られなかったので(俺がやり方をしらないだけ?)、サーバ側のテストプログラムでファイルを出力して確認してた。

一応インターフェースは決まっているが、名前ぐらいなので、POSTができない場合に問題を切り分ける必要があった。
つまり、アプリからはちゃんと送れてるぞという主張の根拠を提示したかったわけだw

ネットでvar_dump()をファイル出力する方法を見つけたので、$_REQUESTと$_FILESの情報をそのままvar_dump()してファイルに書き出してみた。拡張子が.htmlなのは、var_dump()して出力される内容がhtmlだったから。ブラウザからも確認しやすくてスゲー便利。var_dump()サイコー!

<?php

/**
 ■POSTサンプル
 
*/ if (isset($_FILES['file_data']['tmp_name'])) { // リクエスト情報 $filename = '_REQUEST.html'; ob_start(); echo "var:\n"; var_dump($_REQUEST); $out = ob_get_contents(); ob_end_clean(); file_put_contents($filename, $out); // ファイル情報 $filename = '_FILES.html'; ob_start(); echo "var:\n"; var_dump($_FILES); $out = ob_get_contents(); ob_end_clean(); file_put_contents($filename, $out); // ファイルを移動 copy($_FILES['file_data']['tmp_name'], getcwd( )."/".$_FILES['file_data']['name']); }
※うーむ。SyntaxHighlighterがおかしいなぁ。コードの後に謎の空行がたくさん…

2012/07/23

文字実体参照を記号(文字)に置き換える

JSONやXMLデータを表示するクライアントを作った時などに、文字実体参照になっている記号がそのまま &amp; とか表示されたりする。
パーサがお利口な場合はよしなに記号に置き換えてくれたりするけど、そうじゃない場合もあるので、その場合は自前で文字列置換を行ってあげる必要がある。

自前のアプリでは今のところ下記について対応していて、必要があれば適宜追加していっている。

&quot;"
&#34;"
&amp;&
&#38;&
&apos;'
&#39;'
&lt;<
&#60;<
&gt;>
&#62;>

てなことを &amp; が表示されちゃってるTwitterアプリを見て思ったりしたw

2012/06/30

Corona SDKにもNativeExtentionがキター!?

6月はこのネタでブログ書けるなと思っていた申請中のアプリがIn Reviewのまま持ち越しとなってしまって、アプリ・リリースのエントリが書けないので別ネタを滑り込みでw

http://docs.coronalabs.com/native/enterprise/
というページがあるという情報を受け見に行ってみると、やっとCorona SDKを拡張できる仕組みが提供されるみたい。\(^O^)/
(Titanium Mobileでいうところの“Module”、AIRでいうところの“NativeExtention”みたいなものらしい)

そもそも私がCoronaを気に入っている理由はOpenGLベースの描画を使ったアプリをOpenGLの知識無しに比較的簡単に開発できる点にある。
本来Corona SDKは2Dゲーム用のミドルウェアなのでこういった作りなんだけど、ゲーム以外のアプリでも凝ったUIを作ろうとしていくと、結局のところ標準UIの設定をちょっと変えたぐらいでは実現できないという状況になり、ツール系アプリなんだけどOpenGLで作るか?という選択肢も出てきたりするのが実情である。(でもcocos2dにしろ生OpenGLにしろ敷居高いよね)

そんな時Corona SDKのようなツールだったら、画像ベースのパーツを組み合わせて動かすことによって、オリジナルのUIを作り易く速度も遅くない。(デザイン・ガイドラインに沿った上でのオリジナリティか、完全にオリジナルな世界観を持ったUIにするかは別問題なのでここではおいといて(/^^)/)
ただツール系アプリでは、提供されるSDKを利用するという場合も多いので、なかなか悩ましい状況であった。
※もしCorona SDKのようなツールでネイティブ拡張が可能なツールが出てくれば乗り換えてもいいと思っていたw

ここにNativeExtentionの登場である。

まだ動かして試してないし、ざっと見ただけなので勘違いもあるかもしれないが、Luaで書くCoronaのアプリに、自分でObjective-C(iOS)またはJava(Android)で書いたコードを使ったり、開発者向けに提供されているWebサービスや広告のSDKを組み込んだりできるようになるものらしい。

これでCorona SDKがかなり強力なツールになったといえる。
そしてObjective-Cを仕込んどいてよかったと本当に思う。

なんか動かせたらまた記事にします。

2012/05/09

Corona SDKでサーバから画像をダウンロードして使う

CoronaのAPIリファレンスを眺めていたところ display.loadRemoteImage( ) というAPIを見つけて、「あぁサーバにある画像を読み込んで表示するんだろうな」と内容を確認してみたところ、サンプルに
display.loadRemoteImage( "http://developer.anscamobile.com/demo/hello.png", "GET", 
                                               networkListener, "helloCopy.png", system.TemporaryDirectory, 50, 50 )
とURLとは別に画像ファイル名が指定されていた。もしやと思いざっと解説を確認してみたところ、どうやらサーバにある画像を読み込んで表示するだけではなく、保存もされるらしい。わずか一行で画像のDLと保存が可能だと!これは試しておかねばと思い立ったw

せっかくなので、複数ファイルを扱います。サンプルではコード内に画像のファイル名を書いていますが、実際に使う場合は画像のリストをWebから取ってきたり、DBからもらってきたりすると思います。まぁ、そういったコードはそれぞれの仕様にあわせて実装してくださいw
あと、画像がダウンロード済みかどうかの判定も必要ですね。
local _W, _H = display.contentWidth, display.contentHeight

local server = "http://example.com/img/"
local count = 0

local imgList = {
 {name = "01.jpg"},
 {name = "02.jpg"},
 {name = "03.jpg"},
 {name = "04.jpg"},
 {name = "05.jpg"},
 {name = "06.jpg"}
}

local networkListener = function(event)
 if event.isError then
  print("NETWORK ERROR: Download Failed.")
 else
  display.remove(event.target)  --(2)
  event.target = nil
  count = count + 1
 end
 print("RESPONSE: "..event.response)
 
 if count == #imgList then --(3)
  local img = display.newImage(imgList[1].name, system.DocumentsDirectory)
  img.x = _W/2
  img.y = _H/2
 end
end

for i = 1, #imgList do
 display.loadRemoteImage(server..imgList[i].name, "GET", 
     networkListener, imgList[i].name, system.DocumentsDirectory) --(1)
 i = i + 1
end
(1)順番にdisplay.loadRemoteImage()を読んで画像をダウンロード
(2)リスナーとしてnetworkListener()が呼ばれますが、そのままだと画像が表示されてしまうので、
display.remove(event.target)
event.target = nil
でリムーブします
(3)確認のため、全画像がダウンロードされたら1枚目の画像を表示を表示させてます

思いつきでやってみましたので、この方法が良いのかどうかわかりません。
ですが多分何かの役にたってくれそうな気がします。
最近Daily Buildで実装されたLuaFileSystem(LFS)と組み合わせるといいのかなぁ?

2012/04/25

今更ながらC言語を勉強した

Objective-Cを勉強するにあたって、その前段階としてC言語の勉強をしたので、一応成果的なものを晒しておきます。
※Objective-C内にCが書けたりするのは、Objective-CがCのコンパイラ拡張だからだって初めて知った
※つまりObjective-Cを理解するにはCを理解しておいた方がよいということ

今回ポインタやら構造体について理解できたので、Objective-C以外でも役に立ちそう。
今更だけどC言語やってよかった。

で、その成果ですが、「画像フィルター」的なものをつくってみました。
※当然コマンドラインのアプリケーションですが何か?

以下ソースです。
/* bmpfilter.c
 *
 * Simple BMP filter.
 * 2012/04/24 @keygx
 *
 * compile: $ gcc bmpfilter.c -o bmpfilter
 *
 * usage1: ./bmpfilter <.bmp filename> 
 *               flag: 1 -> Sepia
 *               flag: 2 -> Nega
 *               flag: 3 -> Mosaic
 *               flag: 4 -> Edge
 *               flag: 5 -> Instagram
 *
 * usage2: ./bmpfilter
 *               Interactive Mode
 *
 */

#include 
#include 
#include 

#define HEADER_SIZE 54
#define SIZE_POSI 18
#define SEPIA_FRAME 0.02
#define INSTAGRAM_FRAME 0.02
#define MIN_FRAME 10
#define MOSAIC 10

struct BMPFile {
    char name[256];
    int w;
    int h;
    char flag;
    unsigned char header[HEADER_SIZE];
}bmpfile;

void sepia(struct BMPFile *bmp);
void nega(struct BMPFile *bmp);
void mosaic(struct BMPFile *bmp);
void edge(struct BMPFile *bmp);
void instagram(struct BMPFile *bmp);
void output(struct BMPFile *bmp, unsigned char *img);


int main(int argc, char *argv[]) {
    struct BMPFile *bmp = &bmpfile;
    FILE *fp;
    
    // 起動時パラメータの有無で処理を変える
    if(argc < 3) {
        printf("変換するBMPファイル名を入力してください (*****.bmp)\n");
        scanf("%255s", bmp->name);
        
        printf("フィルタの種類を指定してください (1:セピア, 2:ネガ, 3:モザイク, 4:エッジ検出, 5:インスタグラム風)\n");
        scanf("%*c%c", &bmp->flag);
    } else {
        strcpy(bmp->name, argv[1]);
        bmp->flag = *argv[2];
    }
    
    printf("LOG>> InputFile: %s\n", bmp->name);
    printf("LOG>> InputFlag: %c\n", bmp->flag);
    
    // 指定されたBMPファイルのデータを取得
    if(!(fp = fopen(bmp->name, "rb"))) {
        printf("\n=エラー========\n");
        printf("ファイルが見つかりません\n");
        printf("============\n");
        return 1;
    }
    fp = fopen(bmp->name, "rb");
    fseek(fp, 0, SEEK_SET);
    fseek(fp, SIZE_POSI, 0);  // サイズデータの位置まで移動
    fread(&bmp->w, 4, 1, fp); // ヘッダから画像のwidthを取得
    fread(&bmp->h, 4, 1, fp); // ヘッダから画像のheightを取得
    fseek(fp, 0, SEEK_SET);
    fread(bmp->header, 1, HEADER_SIZE, fp); // ヘッダデータの取得
    fclose(fp);
    
    // 値が負の場合もあるので絶対値をとる
    bmp->w = abs(bmp->w);
    bmp->h = abs(bmp->h);
    
    printf("LOG>> OriginSize: W=%d H=%d\n", bmp->w, bmp->h);
    
    // 各画像処理へ
    if(bmp->flag == '1') {
        // セピア
        sepia(bmp);
    } else if(bmp->flag == '2') {
        // ネガ
        nega(bmp);
    } else if(bmp->flag == '3') {
        // モザイク
        mosaic(bmp);
    } else if(bmp->flag == '4') {
        // エッジ検出
        edge(bmp);
    } else if(bmp->flag == '5') {
        // インスタグラム風
        instagram(bmp);
    } else {
        printf("\n=エラー============\n");
        printf("フィルタの指定が正しくありません\n");
        printf("================\n");
        return 2;
    }
    
    return 0;
}


/* セピア調に変換 */
void sepia(struct BMPFile *bmp) {
    FILE *fp;
    unsigned char img[bmp->h][bmp->w][3];
    int i, j, color, frame;
    
    // ファイルから読む
    fp = fopen(bmp->name, "rb");
    fseek(fp, HEADER_SIZE, 0);
    fread(img, 1, bmp->w * bmp->h * 3, fp);
    fclose(fp);
        
    // セピア
    for(i=0; ih; i++) {
        for(j=0; jw; j++) {
            color = (img[i][j][0] + img[i][j][1] + img[i][j][2]) / 3; // モノクロ化
            img[i][j][0] = color; // B
            img[i][j][1] = color; // G
            img[i][j][2] = color; // R
            
            img[i][j][0] *= 0.4;
            img[i][j][1] *= 0.7;
            img[i][j][2] *= 0.9;
        }
    }
    
    // フレームをつける
    frame = bmp->w * SEPIA_FRAME;
    if(frame < MIN_FRAME){
        frame = MIN_FRAME;
    }
    // 下枠
    for(i=0; iw; j++) {
            img[i][j][0] = 230;
            img[i][j][1] = 238;
            img[i][j][2] = 238;
        }
    }
    // 上枠
    for(i=bmp->h-1; i>=bmp->h-frame; i--) {
        for(j=0; jw; j++) {
            img[i][j][0] = 230;
            img[i][j][1] = 238;
            img[i][j][2] = 238;
        }
    }
    // 左枠
    for(i=0; ih; i++) {
        for(j=0; jh; i++) {
        for(j=bmp->w-1; j>=bmp->w-frame; j--) {
            img[i][j][0] = 230;
            img[i][j][1] = 238;
            img[i][j][2] = 238;
        }
    }
    
    // ファイル出力
    output(bmp, img);
    
    printf("LOG>> OK: 1 / SEPIA\n");
}


/* ネガ調に変換 */
void nega(struct BMPFile *bmp) {
    FILE *fp;
    unsigned char img[bmp->h][bmp->w][3];
    int i, j;
    
    // ファイルから読む
    fp = fopen(bmp->name, "rb");
    fseek(fp, HEADER_SIZE, 0);
    fread(img, 1, bmp->w * bmp->h * 3, fp);
    fclose(fp);
    
    // ネガ
    for(i=0; ih; i++) {
        for(j=0; jw; j++) {
            img[i][j][0] = 255 - img[i][j][0]; // B
            img[i][j][1] = 255 - img[i][j][1]; // G
            img[i][j][2] = 255 - img[i][j][2]; // R
        }
    }
    
    // ファイル出力
    output(bmp, img);
    
    printf("LOG>> OK: 2 / NEGA\n");
}


/* モザイク調に変換 */
void mosaic(struct BMPFile *bmp) {
    FILE *fp;
    unsigned char img[bmp->h][bmp->w][3];
    unsigned char img_new[bmp->h][bmp->w][3];
    int i, j, size, x, y, count;
    int b, g, r;
    
    // ファイルから読む
    fp = fopen(bmp->name, "rb");
    fseek(fp, HEADER_SIZE, 0);
    fread(img, 1, bmp->w * bmp->h * 3, fp);
    fclose(fp);
    
    size = MOSAIC;
    
    // モザイク
    for(i=0; ih; i+=size) {
        for(j=0; jw; j+=size) {
            
            count = 0;
            b = 0;
            g = 0;
            r = 0;
            
            // 指定範囲の色を加算
            for(y=i; y bmp->h || x > bmp->w) {
                        break;
                    }
                    b += img[y][x][0];
                    g += img[y][x][1];
                    r += img[y][x][2];
                    count++;
                }
            }
            // 各色の平均を求める
            b = b / count;
            g = g / count;
            r = r / count;
            
            // 新しい配列に代入
            for(y=i; y bmp->h || x > bmp->w) {
                        break;
                    }
                    img_new[y][x][0] = b;
                    img_new[y][x][1] = g;
                    img_new[y][x][2] = r;
                }
            }
        }
    }
    
    // ファイル出力
    output(bmp, img_new);
    
    printf("LOG>> OK: 3 / MOSAIC\n");
}


/* エッジ検出に変換 */
void edge(struct BMPFile *bmp) {
    FILE *fp;
    unsigned char img[bmp->h][bmp->w][3];
    unsigned char img_temp[bmp->h][bmp->w][3];
    int i, j;
    
    // ファイルから読む
    fp = fopen(bmp->name, "rb");
    fseek(fp, HEADER_SIZE, 0);
    fread(img, 1, bmp->w * bmp->h * 3, fp);
    fclose(fp);
    
    // 画像データの配列を複製
    memcpy(img_temp, img, sizeof(img));
    
    // エッジ検出
    for(i=0; ih; i++) {
        for(j=0; jw; j++) {
            img[i][j][0] = abs(img[i][j][0] - img_temp[i][j-1][0]);
            img[i][j][1] = abs(img[i][j][1] - img_temp[i][j-1][1]);
            img[i][j][2] = abs(img[i][j][2] - img_temp[i][j-1][2]);
        }
    }
    
    // ファイル出力
    output(bmp, img);
    
    printf("LOG>> OK: 4 / EDGE\n");
}


/* インスタグラム風に変換 */
void instagram(struct BMPFile *bmp) {
    FILE *fp;
    unsigned char img[bmp->h][bmp->w][3];
    int i, j, b, g, r, frame;
    
    // ファイルから読む
    fp = fopen(bmp->name, "rb");
    fseek(fp, HEADER_SIZE, 0);
    fread(img, 1, bmp->w * bmp->h * 3, fp);
    fclose(fp);
    
    // インスタグラム風
    // 明るさ
    for(i=0; ih; i++) {
        for(j=0; jw; j++) {
            b = img[i][j][0] * 1.1; // B
            g = img[i][j][1] * 1.1; // G
            r = img[i][j][2] * 1.1; // R
            
            img[i][j][0] = (b > 255) ? 255 : b;
            img[i][j][1] = (g > 255) ? 255 : g;
            img[i][j][2] = (r > 255) ? 255 : r;
        }
    }
    // 色合い
    for(i=0; ih; i++) {
        for(j=0; jw; j++) {
            img[i][j][0] = img[i][j][0] + (255 - img[i][j][0]) * 0.25; // B
            img[i][j][2] = img[i][j][2] + (255 - img[i][j][2]) * 0.15; // R
        }
    }
    
    // フレームをつける
    frame = bmp->w * INSTAGRAM_FRAME;
    if(frame < MIN_FRAME){
        frame = MIN_FRAME;
    }
    // 下枠
    for(i=0; iw; j++) {
            img[i][j][0] = 20;
            img[i][j][1] = 10;
            img[i][j][2] = 5;
        }
    }
    // 上枠
    for(i=bmp->h-1; i>=bmp->h-frame; i--) {
        for(j=0; jw; j++) {
            img[i][j][0] = 20;
            img[i][j][1] = 10;
            img[i][j][2] = 5;
        }
    }
    // 左枠
    for(i=0; ih; i++) {
        for(j=0; jh; i++) {
        for(j=bmp->w-1; j>=bmp->w-frame; j--) {
            img[i][j][0] = 20;
            img[i][j][1] = 10;
            img[i][j][2] = 5;
        }
    }
    
    // ファイル出力
    output(bmp, img);
    
    printf("LOG>> OK: 5 / INSTAGRAM\n");
}


/* ファイル出力 */
void output(struct BMPFile *bmp, unsigned char *img) {
    FILE *fp;
    
    // 拡張子".bmp"を削除
    bmp->name[strlen(bmp->name)-4] = '\0';
    
    // ファイル名おの変更
    switch(bmp->flag){
        case '1':
            strcat(bmp->name, "_sepia.bmp");
            break;
        case '2':
            strcat(bmp->name, "_nega.bmp");
            break;
        case '3':
            strcat(bmp->name, "_mosaic.bmp");
            break;
        case '4':
            strcat(bmp->name, "_edge.bmp");
            break;
        case '5':
            strcat(bmp->name, "_instagram.bmp");
            break;
    }
    
    // ファイル出力
    fp = fopen(bmp->name, "wb");
    fwrite(bmp->header, 1, HEADER_SIZE, fp);
    fwrite(img, 1, bmp->w * bmp->h * 3, fp);
    fclose(fp);
    
    printf("LOG>> OutputFile: %s\n", bmp->name);
    
}

※コードの最後にSyntaxHighlighterの不具合?でゴミ(タグ)が見えてますが気にしない方向でお願いします。
※対応フォーマットはWindowsBMP形式のみです。
※例え拡張子が.bmpでもヘッダやデータがおかしなモノはダメです。

画像処理って難しいと思ってましたが、「ActionScript入門Wiki」のわかりやすい解説とサンプルコード(もちろんASですが)がとても参考になり、Photoshopや多くのカメラアプリのエフェクトも裏側ではこんなことやってるのかな?とか想像しつつCに移植して、実行しては「おーっ!」ってやってました。
画像処理、楽しいですね!

引き続き本題のObjective-Cの勉強をやっていきます。

■参考サイト

C言語で、画像を読み込むにはどのようにすれば良いですか?
http://detail.chiebukuro.yahoo.co.jp/qa/question_detail/q1020829250

Bitmapファイルフォーマット
http://www.umekkii.jp/data/computer/file_format/bitmap.cgi

C言語による画像処理プログラミング
http://coconut.sys.eng.shizuoka.ac.jp/bmp/

ActionScript入門Wiki(画像処理)
http://www40.atwiki.jp/spellbound/pages/166.html

2012/03/31

herokuデビューしました


Android版「ぐぐっと!急上昇ワード Ver.1.0.0」のリリースと同時に、サーバサイドがherokuへと切り替わり、herokuデビューをしました。

2010/10/07のポスト「ぐぐっと!急上昇ワード Ver.0.6.0」 にもあるとおり、これまでサーバサイド側はGoogle App Engineを使ってきた「ぐぐっと!急上昇ワード」ですが、プログラムをsinatraで書き直し、herokuで提供することにしました。
さらに今回サーバサイド側に機能追加を実施し、これまでのデータの加工および端末へのレスポンスに加え、過去のランキングデータを閲覧できるようになっています。

別にherokuに乗り換えたのはApp Engineが嫌いになった訳ではなく、機能追加を検討するにあたり、当初シンプルであったアプリもアップデートを重ね機能を追加しすぎた感があるので、これ以上アプリ側を複雑にしたくなかったというのがあり、Webアプリケーションとして用意したものと連携するようにした方が良いとの判断に至り、それならば、以前から興味のあったsinatraを使いたいなということで、公開サーバとしてVPSとherokuを検討した結果、herokuになったという訳です。

・iPhone/Android向けということでスマートフォン向けサイトになっています
・アプリからは上記右画面の最新ランキングへ誘導されるようになっています
・またこの機会に独自ドメインを取得しました。http://gugutto.com/
・それにしてもiPhoneもAndroidもバージョン間でブラウザの機能差がありすぎですねw

すぐに公開できるAndroid版が先行してherokuに切り替わっていて公開後半月程たちましたが、パフォーマンス的には問題なく稼働しているようです。(厳密にはApp Engineへのアクセスもまだまだあります。iPhone版とかアップデートしていないユーザのリクエストとか)
※追記:4/2にiPhone版もアップデートされました

開発の方はsinatraもherokuもネットで情報をかき集めてはつなぎ合わせ、トライアンドエラーを繰り返すことによって公開まで辿り着くことができました。herokuも既に古くて使えない情報があったり、Railsではスンナリできるらしいことが、sinatra(というか素のRack?)では方法が違ったりといったハマリポイントが多く、今回も色々と勉強をさせていただいた感じです。

sinatraは気に入ったので別のアプリもつくりたいと思ってます。第1候補もApp Engineでつくったコレなんですが…
それよりも「ぐぐっと!」にランキングのグラフを付けるほうが先だなぁ…

今回herokuを使ってみて、今まで当たり前だと思っていたApp Engineの管理コンソールの出来の良さを感じることができました。あれはすごいです。herokuにも欲しいです。有料でもかまいません。$5以下であればw
あとShared Databaseが5MB無料の次がいきなり20GB/$15になるのではなくて、5〜6GBで$5とかの設定が欲しいなと思いました。このあたりがネックになって、開発環境として無料でherokuを使い、本番はAWSやVPSを利用する人が多いのではないかと思いました。(今のままだとherokuさんにお金落ちないですよね。それともお金払ってくれるユーザさんからたくさんもらう方向なんでしょうか?)
ということに気付きながらも公開してしまった以上、今のところDBが5MBを超えたら課金予定です。そして広告費で賄える限りはherokuを使ってみるつもりです。

これで多少なりとも「heroku使ったことがある」「sinatra使ったことがある」「rubyでWebアプリ書いたことがある」と言えるようになったかなw

2012/02/09

「ぐぐっと!急上昇ワード Android」の誕生について。ちょっと昔話


10万ダウンロードというひとつの区切りを迎えた今、ちょっとこのアプリについて振り返ってみたいと思います。
(Androlibの情報によると、Android Marketにてダウンロード数が50,000を超えるアプリの割合は3.8%とのこと)

まずこのアプリをひとことで説明すると「トレンドキーワード検索・共有アプリ」です。
しかし最初からこのアイデアがあり、実現しようとアプリをつくったわけではありません。
(AndroidをターゲットにしたのはJavaの方がなじみがあったからですが)

まず最初に考えたのは「できるだけ端末から削除されないようなアプリをつくろう」というものでした。
一発ネタ的なアプリはおもしろいけどすぐ飽きてしまうし、誰もが使うような定番アプリは競争が激しい。
そこで何度も使えて、消さずに残しておいてもいいやと思ってもらえるようなアプリのネタを考えることにしました。

「Amazonのロングテールならぬロングターム戦略や!」と言ったとか言わないとかw

そしてネットをいろいろ巡っているときに「Google急上昇ワード」がネタとして良いかもしれないと思い、さらに調査を続け、キーワード・ランキングをツイートするTwitterBotがそこそこフォロワーがついていることを確認しました。
また同じようなアプリについて調べたところ、Androidには同種のアプリを見つけることができませんでした。(iPhoneにはランキングを紹介するアプリが1本だけありました。今ではiPhone/Androidとも、同種のアプリが多数あります)
「この方向はアリかも」と採用しました。

次にネタのベースが決まったところで、アプリとしてはどうしたらよいだろうかと考えていきました。
キーワードのランキングが見られるだけではアプリとして面白みに欠けるので、プラスアルファが欲しい訳です。

更新されるキーワードを眺めていると、時々知らないキーワードが出てきます。そうすると自然に検索して調べたくなります。
こうしてキーワードのランキングから任意のワードの検索ができるアプリのアイデアが生まれました。

結果このアイデアは大正解。検索という能動的な機能に結びつけたことが、当初のコンセプト(端末から削除されないようなアプリ)を実現できた一番の要因であると同時に、単なる情報収集ツールではなく、暇つぶしツールとしても「使える」アプリになり、10万ダウンロードを実現できた要因だと思います。

実際このアイデアが正しかったのは、その後同種のアプリがiPhone/Android向けに多数リリースされていることが証明していると思います。

爆発的にリリースされるアプリが増えている現在は、上記のようなアプリの作り方では、結果を出すのは難しいのではないかと思います。また既に多種多様なアプリがリリースされているので、差別化ばかりを考えると、ニーズのない誰得なアイデアばかりになってしまいがちです。
そんな状況においては、定番ジャンル・競争の激しいジャンルでも、後発だろうと先行者よりも良いアプリをつくってやろうという気概と実行力が必要ではないかと感じています。

ただし本質としては「ユーザに価値あるサービスを提供すること。そしてそれがたまたまアプリという形であった」になっていないとダメなのでしょう。

これまでアプリをつくってきて、思ったこと感じたことををつらつらと書いただけのエントリになってしましましたが、改めて、これからも楽しみつつアプリをつくっていきたいと思いました。

「ぐぐっと!急上昇ワード Android」10万ダウンロード突破

Androidアプリを公開しましたのエントリで初登場した「ぐぐっと!急上昇ワード」が10万ダウンロード突破しました。(昔のエントリには今みるとなつかしい画面がw)

お使いいただいているユーザの皆様をはじめ、少しでも興味を持ってダウンロードしてくれた方、レビューに取り上げていただいたサイトおよびメディアの方々に改めて感謝申し上げます。

公開してから1ヶ月で5000ダウンロードを超えたので、当時として順調な滑り出しと思います。
なにせAndroid OS 1.6の時代、ほとんどはdocomo SO-01B(Xperia X10)のユーザです。
(当時はアプリを日本にしか配信していませんでした)

そしてリリースから1年後、機能追加も一段落しダウンロード数は7万を超えています。
また、この時のアップデートでアイコンのデザインを変更しました。

そしてさらに10ヶ月、10万ダウンロード突破となりました。

始めてリリースしたアプリがここまで上手くいかなかったら、自身のスマートフォンに対する取り組み方も違っていたと思います。

機能についてのアイデアもまだまだあります。
引き続きのご愛顧を賜りますようお願い申し上げます。

Android Market「ぐぐっと!急上昇ワード」

2012/02/02

Corona SDKで日本語のアプリ名をつける方法

昨日の勉強会でも質問に挙がっていましたが、最初問題になるところですよね。
ということで現状私がやっている方法をまとめてみました。


まずはiOSの場合。

build.settingsplist"CFBundleLocalizations"の設定を追加します。
下記の場合は"ja"と"en"を設定しました。
※plist周りについてはネイティブでの開発の情報をググるといいと思います

--build.settings(抜粋)
settings =
{
  ...
  iphone =
  {
    plist =
    {
      CFBundleLocalizations = {
        "ja",
        "en",
      },
      ...
  },
  ...
}
さらに追加した言語にあわせ下記のようにファイル
ja.lproj > InfoPlist.strings
en.lproj > InfoPlist.strings
を作成します。


それぞれファイル内には"CFBundleDisplayName"を設定します。

・ja.lproj/InfoPlist.strings
CFBundleDisplayName = "なでうさぎ";


・en.lproj/InfoPlist.strings
CFBundleDisplayName = "NadeUsagi";


これでビルドするとiPhoneの言語環境が日本語の場合は「なでうさぎ」と表示され、英語の場合は「NadeUsagi」と表示されるアプリになります。


※日本語および英語以外の場合も「NadeUsagi」と表示されるようです。
 他2〜3言語で試した限りはそうでした
※言語コードを増やせば他言語のアプリ名も付けられるようになります
※Buildダイアログの「Application name:」は「nadeusagi」のまま変更は加えていません
※ひとつのバイナリで言語に合わせたアプリ名に出し分けることが可能ということになります


さて次はAndroidの場合です

Buildダイアログの「Application name:」を変更します。
日本語のアプリ名にしたい場合は「nadeusagi」を「なでうさぎ」と変更してビルドします。


簡単ですね。といきたいところですが、このアプリは日本語以外の環境でも「なでうさぎ」と表示されます。

※残念ながらひとつのバイナリで言語に合わせたアプリ名に出し分けることはできないようです
こうやればできるといった情報をお持ちの方がいらっしゃったら、ぜひご教授ください。

日本でしかリリースしないアプリであればこれでOKだと思いますが、ビジネス的にはそんなことは考えにくいので、アプリのリリース先(国)との兼ね合いで別アプリにするとか、英語名に統一するとかの判断が必要なのかも知れませんね。

そういえば以前Coronaの会のMLに投稿された日本語+半角英字のアプリ名も実機転送までは問題なくできました。すでに修正されてるみたいです。


ちなみに、アプリ内のテキストを言語毎に切り替えて多言語対応のアプリにしたい場合は、下記のブログで紹介されている方法がわかりやすかったです。

Corona SDK: Localization made easy - BLOG - Monkeybin
http://monkeybin.no/blog/archives/2011/09/18/corona-sdk-localization-made-easy/

Storyboard API [Corona SDK]

2/1に渋谷のGMOさんで開催されたCoronaSDK 勉強会 in 渋谷にてLTをしてきました。

日本Coronaの会の山本さんから「LTしませんか?」と振っていただいたので、せっかくの機会なので軽くお話しさせていただいた次第です。

お題は最近Corona SDKに実装された「Storyboard API」にしました。
※iOS5のStoryboardとは別物です

このAPIは以前紹介したDirector Classの機能を本家が実装したもので、自分自身でも調査の必要があったのと、"Hello World"の次には、こういった「画面遷移や画面管理の機能を勉強すべき」と思っているので、これからCorona SDKをやってみようという方には良いのでは?と思ったからです。

スライドはこちらになります。
最初は公開されているサンプルを説明したスライドを作成していたのですが、もう少し実用的なサンプルがあった方が良いよなぁと思いはじめてしまったので、追加で「CoronaSDKでゲームっぽいものを作ってみた」のアプリをStoryboard APIに直したものをGithubに公開しました。
https://github.com/keygx/PacPacDroid

Storyboadの他にもPhysicsやcollision等も使っていますので、これからCoronaでアプリをつくってみたいと思っている方や、"Hello World"の次に進みたいんだけど情報が無いwとお悩みの方などのお役に立てれば幸いです。
ただ、メモリ管理とかに気を配ってないので、もしかするとアレかも…

イベントの方は盛況であったと思います。
一番前に座ったのと、自分のLTばかりに気がいってあまり周りを見る余裕がなかった(反省点1)
あと自分のアプリをもう少し宣伝した方が良かったなぁ(反省点2)

色々と良い経験をさせていただきました。
ありがとうございました。

2012/01/11

Spriteloqを使ってみた

Corona SDKは2Dゲームエンジンといってよい(それだけじゃないですが)開発環境なので、Sprite Sheetの機能を持っています。
http://developer.anscamobile.com/reference/index/sprite

Sprite Sheetを利用することでアニメーションが実現できるわけですが、アプリ開発においては、そのアニメーションの素材となる画像をどうやって用意するかということも重要になります。

さて皆さんもご存じのように、Flashというアニメーション制作ツールがあります。
おそらく(間違いなく?)世界で最も使われているアニメーション制作ツールでしょう。

そこで『Spliteloq』です。
Flashで作成したアニメーションをCorona SDKのSprite Sheetへと出力できるツールです。
http://www.loqheart.com/spriteloq/

おおまかな手順としては以下のようになります。
・Flashでアニメーションを作成
・Flashのエクステンション(予めインストールしておく)にてSWFsと.luaを出力
・Spliteloqにて、SWFsを読込、1枚の画像へ
・SpliteloqからSprite Sheetの画像ファイルおよびコードを出力
・Corona側でSpliteloqのライブラリを利用してSprite Sheetとして扱えるようになる


なんだか文章で書くとよく分かりませんねw
Flash側のこともあるので、説明がしづらい…

ではサンプルをどうぞ



※Flashでのアニメーションの作り方、ライブラリでのシンボルの置き方にも決まりがあるようで、
 正しく表示されるまで試行錯誤が必要でした
※その辺り実はまだあまり良く分かってません
※このサンプルではうさぎと太陽がアニメーションしています。そして背景も含め、まとめて
 Flashから持ってこれました。これは便利!
※ワンクリックでFlashからCoronaへとはいきませんが、すべて手作業でやることに比べたら、
 かなりの省力化になると思います
※FlashとCoronaでアプリ開発の分業も可能かもしれません

2012/01/04

アイコン/スプラッシュ画像の確認にCorona SDKを使う

スマートフォン関連のデザインをやっていると、PCの画面で見ている時と、実際にデバイス上に落とし込んで見ている時の印象の違いに「ほぅ!」って思うことも多々あります。(※イメージと違うわーってことです)

特にアイコンやスプラッシュ画像をデザインする際は、ロゴが潰れてないか、文字が読めるのか、またiPhone/iPad/Androidで画面サイズや画角が異なっているので、それぞれにバランスを調整しつつレイアウトしたりと(単純にリサイズしているだけじゃないんですよー。下記の画像を見比べてみてくださいねw)、気を遣う部分も多いので、必ず実機にインストールした状態での確認が不可欠です。

自作のアプリであれば開発の最終段階あたりで作業するので、実機にインストールしての確認も何ら問題なくできるのですが、アイコン/スプラッシュ画像のデザインのみ依頼を受けた場合等、組み込むアプリが手元にない場合はダミーのアプリを作って確認することになります。

前置きが長くなりましたが、ここからが本題です。
確認用のダミーアプリには「Corona SDK」が向いているということに気付きました。

私の場合はiPhone/iPad/Androidのダミーアプリをそれぞれ用意し、スプラッシュ画像をスプラッシュとしてではなく、アプリの背景画像として使用することで、じっくり確認できるようにしています。そして、このダミーアプリの画像(複数解像度のアイコンおよびスプラッシュ)を上書き>ビルド>実機転送>確認のサイクルを繰り返してブラッシュアップを行っています。

もう少しメリットを具体的にいうと
コードが短い(さらにファイル一式用意してしまえば、後は画像の差し替えのみ)
アプリ名をビルド時に変えられる(フォルダ名を変えてしまうのでもOK)
IDEに縛られない(自由だし小回り効くし)

といったところでしょうか。
なお実機転送は無料トライアル版でも可能です。



こちらは昨年末、株式会社あゆた様のご依頼で作成したものです。上記以外にもストア/マーケット用の画像も作成させていただきました。ARを使ったおもしろいアプリですので、一度お試しあれ。詳細はリンク先ページにて。

[iPhone,iPad,Android] "Christmas on a table AR" / "机の上のクリスマスAR"

2012/01/01

2012年 新年のごあいさつ

あけましておめでとうございます。
本年もどうぞよろしくお願いいたします。

昨年は個人のスマフォアプリ開発に力を注いでいたといっても過言ではありませんでしたw
企画的な情報収集や実装方法のリサーチ、そして今までにやったことのないデザインテイストに挑戦するなどし、新たに4つのアプリをリリースすることができました。

そしてさらに新年早々、新アプリを申請しました。
これは昨年12月からほぼ1ヶ月で2本のアプリをつくりあげたことになります。

機能を最小限まで絞り込んでいるとはいえ、2〜3週間でアイデアを実現でき、世の中の評価を得られるという現実に、ワクワクしっぱなしです。

本年はぜひこれらを仕事に生かしていきたいと思う所存でございます。
何かございましたら、お声がけいただけますと幸いです。


keygxの アプリ 紹介サイト

お仕事のご相談は infonius.jp まで