2011年3月31日木曜日

Facebook iOS SDKを使ってiOSアプリからFacebookに画像をアップロードする方法

------------------------------------------------------
※ 注意
現在配布されているFacebook iOS SDKは認証に原則としてSingle Sign-On方式を採用しているため、この記事で記述されている手順とは必要な手順が異なります。

くわしくはFacebookのiOS Tutorialをご覧下さい。
------------------------------------------------------


FacebookのiOS SDKを使うとFacebookのGraph APIを簡単に使うことができます。
ただ、認証やデリゲートまわりでややこしいところがあるので簡単にまとめておきます。

個人的な理由で画像のアップロードの流れを書いてみます。
画像のアップロード以外でも
認証 & パーミッション付与 > Graph API呼び出し
という流れは基本的に一緒です。


GitHub - facebook/facebook-ios-sdk
https://github.com/facebook/facebook-ios-sdk


iOS SDKのセットアップなどは上のGitHubのページに詳しく説明されているのでそちらをどうぞ。


FacebookのアプリページAPI KeySecret Keyを確認しておきます。
static NSString *kFacebookAPIKey = @"API KEY";
static NSString *kFacebookSecretKey = @"SECRETI KEY";


とりあえずヘッダーファイルはこんな感じです。
@interface FacebookViewController : UIViewController
<FBRequestDelegate,  FBSessionDelegate,  FBDialogDelegate>
{
    FBSession *facebookSession;
    FBRequest *facebookUploadRequest;
}

@property (nonatomic, retain) FBSession *facebookSession;
@property (nonatomic, retain) FBRequest *facebookUploadRequest;

- (void)upload:(NSData *)uploadImageData;

@end


まずはFBSession を初期化して認証をします。FBSessionを認証やAPIの呼び出しで使い回します。
@implementation FacebookViewController

- (id)init {
    self = [super init];
    if (self != nil)
    {
        self.facebookSession = [FBSession sessionForApplication:kFacebookAPIKey
                                                                                secret:kFacebookSecretKey 
                                                                             delegate:self];
        
        // 認証済みかどうか確認
        // 認証済の場合 FBSessionDelegatesession:didLogin: が呼ばれる。
        [facebookSession resume];
    }
    return self;
}

- (void)viewDidAppear:(BOOL)animated {
    [super viewDidAppear:animated];

    if (![facebookSession isConnected])
    { 
        // 未ログインの場合はログインダイアログを表示する
        // ダイアログ内のアクションは FBSessionDelegate でハンドリングする。
        FBLoginDialog *dialog = [[FBLoginDialog alloc] initWithSession:facebookSession];
        [dialog show];
        [dialog release];  
    } 
}

@end

ログイン/ログアウト時の処理はFBSessionDelegateでハンドリングします。
#pragma mark -
#pragma mark FBSessionDelegate

- (void)session:(FBSession*)session didLogin:(FBUID)uid {
    // ログイン成功時に呼ばれる。

    if (![[NSUserDefaults standardUserDefaults] boolForKey:@"has_facebook_photo_upload_perm"]) 
    {
        // 画像アップロードには「写真」のパーミッションが必要。
        // もしパーミッションが与えられていない場合はパーミッションを確認するダイアログを開く。
        FBPermissionDialog * dialog;
        dialog = [[[FBPermissionDialog alloc] initWithSession:facebookSession] autorelease];
        dialog.delegate = self;
        dialog.permission = @"photo_upload,user_photos"; // 「写真」関連のパーミッションを指定
        [dialog show];
    }
}

- (void)sessionDidNotLogin:(FBSession*)session {
    // ログインせずにダイアログを閉じた場合に呼ばれる。
}
- (void)session:(FBSession*)session willLogout:(FBUID)uid {
    // ログアウトする前に呼ばれる
}

- (void)sessionDidLogout:(FBSession*)session {
   // ログアウト後に呼ばれる
}


パーミッションの処理はFBDialogDelegateでハンドリングします。
#pragma mark -
#pragma mark FBDialogDelegate

- (void)dialogDidSucceed:(FBDialog*)dialog
{
    // パーミッション与えられた場合
   [[NSUserDefaults standardUserDefaults] setBool:YES
                                                                     forKey:@"has_facebook_photo_upload_perm"];
}



認証が通ってパーミッション(写真)があれば画像のアップロードができます。
- (void)upload:(NSData *)uploadImageData
{
    self.facebookUploadRequest = [FBRequest requestWithDelegate:self];
    [facebookUploadRequest call:@"facebook.photos.upload"
                                      params:params
                                dataParam:uploadImageData];
}


アップロード処理のハンドリングはFBRequestDelegateでおこないます。
#pragma mark -
#pragma mark FBRequestDelegate

- (void)requestLoading:(FBRequest*)request {
    // リクエスト送信直前に呼ばれる
}

- (void)request:(FBRequest*)request didReceiveResponse:(NSURLResponse*)response {
    // サーバーがレスポンスデータを返し始める時に呼ばれる
}

- (void)request:(FBRequest*)request didLoad:(id)result {
    // サーバーがレスポンスを返し、レスポンスデータがオブジェクトにパースされた時に呼ばれる
}

- (void)request:(FBRequest*)request didFailWithError:(NSError*)error {   
    // エラー時に呼ばれる
}

- (void)requestWasCancelled:(FBRequest*)request {
    // キャンセル時に呼ばれる
}

これで画像がFacebookに送信されます。


ただ、FBRequestDelegateにはアップロードの進捗度を通知してくれるものがないので、このままではプログレスバーを表示することができません。


ちょっと小細工してアップロードの進捗度をチェックできるようにしてみます。

FBRequestは内部的にNSURLConnectionを利用してサーバーと通信しています。
NSURLConnectionには connection:didSendBodyData:totalBytesWritten:totalBytesExpectedToWrite: というデリゲートメソッドがあって、こいつを利用できればアップロードの進捗度が確認できます。

FBRequestのサブクラスを作ったり、ライブラリのコードを直接改変したり(!!)、方法はいくつかあると思うのですが
ここではObjective-Cのカテゴリを使って FBRequest に connection:didSendBodyData:totalBytesWritten:totalBytesExpectedToWrite: メソッドを追加してみます。
@interface FBRequest (Upload)
@end

@implementation FBRequest (Upload)

- (void)connection:(NSURLConnection *)connection
  didSendBodyData:(NSInteger)bytesWritten 
 totalBytesWritten:(NSInteger)totalBytesWritten
totalBytesExpectedToWrite:(NSInteger)totalBytesExpectedToWrite
{
    FacebookViewController *vc = (FacebookViewController *)_delegate;
    [vc didSendBodyData:bytesWritten
         totalBytesWritten:totalBytesWritten 
  totalBytesExpectedToWrite:totalBytesExpectedToWrite];
}

@end

@implementation FacebookViewController

- (void)didSendBodyData:(NSInteger)bytesWritten
           totalBytesWritten:(NSInteger)totalBytesWritten
totalBytesExpectedToWrite:(NSInteger)totalBytesExpectedToWrite
{
    NSLog(@"bytesWritten : %d, totalBytesWritten : %d, totalBytesExpectedToWrite: %d",
                bytesWritten , totalBytesWritten, totalBytesExpectedToWrite);
}

@end

2011年3月28日月曜日

[iOS]キーボードのフレーム(CGRect)を知る方法メモ

例えばUITextViewがfirstResponderになった時、画面の下の方からにゅっとキーボードが表示されます。
それはそれで全然問題ないのですが、キーボードのフレーム(CGRect)を知ろうにも、UITextViewDelegateなどではそれを知ることができません。

調べてみたらキーボードの表示/非表示時に通知(NSNotification)を受け取ることができ、キーボードのフレームを教えてくれるようです。

まずはinitなどのメソッド内でオブザーバーを追加。

NSNotificationCenter *nc =[NSNotificationCenter defaultCenter];
[nc addObserver:self
             selector:@selector(keyboardWillShow:) 
                 name:UIKeyboardWillShowNotification  // 表示時の通知
               object:nil];
[nc addObserver:self 
             selector:@selector(keyboardWillHide:) 
                 name:UIKeyboardWillHideNotification // 非表示時の通知
               object:nil];
こうするだけでUITextViewがfirstResponderになった時に通知してくれます。
- (void)keyboardWillShow:(NSNotification *)notification {
    CGRect keyboardRect; // キーボードのフレーム
    [[notification.userInfo valueForKey:UIKeyboardFrameEndUserInfoKey] getValue:&keyboardRect];
}
UITextViewのinputAccessoryViewを利用している場合は、それを含むフレームになっています。

iOSアプリはキーボードの表示/非表示時に、ユーザーが入力しやすいように画面を変更することがあると思うのでけっこう使う局面はあるかと。

2011年3月23日水曜日

Cameramatic 1.2.0 をリリースしました。

アップデートのお知らせです。



+++ フィルター / フレームの追加

フィルター (4種類)

-- Baltazar
-- Firebird
-- Berrychrome
-- Retrochrome


フレーム (1種類)

-- White 05 (細身の白いボーダー)



+++ フィルター開発機能

今回のアップデートの目玉機能として独自のフィルターを開発できる機能を追加しました。

コントラストや色味を好きなように調整してカスタム・フィルターとして保存すると、後から何度でもそのフィルターを使うことができます。



Light Box」内で写真を選択して「編集」画面を開くと、写真の左上にスパナアイコンが表示されます。
そのアイコンをタップするとフィルター編集画面が開きます。



ひとつのフィルターには「プロセス」と「レイヤー」を最大ふたつずつ、「周辺減光(Vignette)」をひとつ登録できます。


PhotoshopやGIMPを使ったことのある方ならなんとなく使い方をイメージしていただけるかと思います。

完成したら右上の「保存(save)」ボタンをタップすると、「フィルター名」と「説明」を入力してフィルターを保存することができます。
保存されたフィルターは「カスタム・フィルター」として後から選択することが可能になります。




+++ フィルター開発機能 (ランダム・モード)

「フィルターを開発できるのはいいけど、調節の仕方などよく分からない」という方のために、フィルターのランダム・モードを追加しています。


先程の編集画面で、写真を上にフリックするとランダム・モードに入ります。


ランダム・モードはランダムに作成されたフィルターを写真に適用します
さらに写真を上にフリックすると、別のフィルターを作成して適用します。
お気に入りのフィルターに出会うまでどんどんフリックしてみてください。

気に入ったフィルターが見つかったら「保存(save)」ボタンをタップすると「カスタム・フィルター」として保存することができます。

2011年3月17日木曜日

ディレクトリにあるPNGファイルのサムネイルを一括作成するPythonスクリプト

某iOSアプリの新しい機能を色々試行錯誤している過程で、あるディレクトリ内の画像ファイルを一括してサムネイル化できたら楽できそうだったのでそういうスクリプトを書いてみました。

PILのインストールが必要です。

import os, sys
import Image

thumbnail_dir_name = 'Thumbnail' // サムネイルを格納するディレクトリ名

dir = os.getcwd()
if len(sys.argv) > 1: dir = sys.argv[1]

try:
    files = os.listdir(dir)

    // サムネイル用のディレクトリ(存在しなければ作成する)
    thumb_dir = os.path.join(dir, thumbnail_dir_name)
    if not os.access(thumb_dir, os.F_OK): os.mkdir(thumb_dir)

    for infile in files:
        try:
            f, e = os.path.splitext(infile)                   
            if e.lower() != '.png': continue // 拡張子がpng以外のファイルははじく

            im = Image.open(infile)
            if im.mode != "RGBA": im = im.convert("RGBA")

            size = 30,30 // サイズ: 30px x 30px
            im.thumbnail(size, Image.ANTIALIAS)

            // サムネイルのファイル名に"-thumb"というサフィックスを付けて保存
            im.save(os.path.join(thumb_dir, f + "-thumb.png"), 'png')
        except IOError:
            print "cannot convert %s", infile
except OSError, e:
    print "OSError: %s" % e
このスクリプトを例えばmake_thumbnails.pyという名前で保存して、画像が置いてあるディレクトリに放り込みます。
$ python make_thumbnails.py
ターミナルで上のコマンドを叩けば、そのディレクトリ内にThumbnailという新しいディレクトリが作成され、その中にサムネイル一式が作成されます。

個人的な理由から上のスクリプトは下のような制限があります。
書き直すのは簡単なのでご利用の際はご注意を。
  • PNGファイル限定
  • サムネイルサイズ固定
  • サムネイルのディレクトリ名やファイル名が決め打ち

2011年3月15日火曜日

[iOS] UITableViewを水平方向にスクロールさせる方法

リスト表示にとても便利なUITableViewですが、ひとつ弱点があります。
それは垂直方向へのスクロール(vertical scrolling)しか認められていないこと。

大抵の場合はそれでも問題ないでしょうが、iPhoneの限られた画面に埋め込む場合、横長のパーツを作って水平方向へスクロール(horizontal scrolling)させたい時があります。

UIScrollViewのサブクラスを作ってごにょごにょさせるのも面倒だし簡単な方法はないものかと色々探していたら、これはと思う方法を見つけたのでメモしておきます。


簡単にいえば、UITableViewのtransformをいじってviewをごそっと回転させます

Horizontal UITableView – Rankometer 4Google

元ネタは上のブログ記事です。
ただ、元ネタのソースをコピーすると、データソースの先頭が右側になるので、回転させる方向を変更しています。

    CGRect tableViewRect = CGRectMake(0.0, 0.0, 50.0, 320.0);
    self.tableView = [[UITableView alloc] initWithFrame:tableViewRect style:UITableViewStylePlain];
    tableView.center = CGPointMake(self.view.frame.size.width / 2, self.view.frame.size.height / 2);
    tableView.delegate = self;
    tableView.dataSource = self;

    // テーブルを逆時計回りに90度回転させる。     
    tableView.transform = CGAffineTransformMakeRotation(-M_PI / 2);
 
    // スクロールバー(インジケーター)が上部に出るので、気になる場合は消しておく
    tableView.showsVerticalScrollIndicator = NO;
これでテーブルビューが水平方向にスクロールするようになります。
ただ、このままだとテーブル内のセルが逆時計回りに90度回転してしまっているので、セルの向きを補正します。
- (UITableViewCell *)tableView:(UITableView *)aTableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    UITableViewCell *cell;
    cell = [tableView dequeueReusableCellWithIdentifier:@"identifier"];
    if (cell == nil) {        
        cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle
                                       reuseIdentifier@"identifier"] autorelease];

        // セルを時計回りに90度回転させる。
        cell.contentView.transform = CGAffineTransformMakeRotation(M_PI / 2);
    }
    
    return cell;
}