iOSにてfullResoultionImageを扱う際の問題点
前回とは別の話題になります。
iOSでALAssetから画像を取得する際には、メソッドとして
・fullScreenImage
・fullResolutionImage
の2つがありますが、後者のfullResolutionImageを扱う際にハマりポイントがあったので書きます。
ハマりポイント???
iOS5から、iPhone標準のカメラアプリにおいて画像編集ができるようになりました。
普通にサムネとか取得する際には問題ないのですが、fullResolutionImageを取得する際には、この編集結果が適用されないという問題があります。
で、この編集情報が、ALAssetRepresentationのmetadata(辞書形式)の中に、AdjustmentXMPとして格納されています。
XMPの中身
<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="XMP Core 4.4.0"> <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"> <rdf:Description rdf:about="" xmlns:aas="http://ns.apple.com/adjustment-settings/1.0/"> <aas:AffineA>1</aas:AffineA> <aas:AffineB>0</aas:AffineB> <aas:AffineC>0</aas:AffineC> <aas:AffineD>1</aas:AffineD> <aas:AffineX>-331</aas:AffineX> <aas:AffineY>-161</aas:AffineY> <aas:CropX>0</aas:CropX> <aas:CropY>0</aas:CropY> <aas:CropW>1938</aas:CropW> <aas:CropH>1420</aas:CropH> </rdf:Description> </rdf:RDF> </x:xmpmeta>
・AffineA~AffineYまでがアフィン変換用情報
・CropX~CropHがクロップされた領域情報
なので、自力でパースしてCGAffineなんちゃらしてClipすればいいのですが、iOS6からこのXMLを食わせると良い感じの画像を出力するメソッドがあるらしいのでメモしておきます。
XMP変換君
CIFilterにfilterArrayFromSerializedXMPって関数があるので、コレを使います。以下実装例
- (CGImageRef)getFullResolutionImage { ALAssetRepresentation *representation = [self.asset defaultRepresentation]; NSString *XMPString = representation.metadata[@"AdjustmentXMP"]; NSData *XMP = [XMPString dataUsingEncoding:NSUTF8StringEncoding]; if (XMP) { /// iOS5にはないので if ([[CIFilter class] respondsToSelector:@selector(filterArrayFromSerializedXMP:inputImageExtent:error:)]) { CIImage *image = [CIImage imageWithCGImage:[representation fullResolutionImage]]; NSArray *filterArray = [CIFilter filterArrayFromSerializedXMP:XMP inputImageExtent:image.extent error:nil]; CIContext *context = [CIContext contextWithOptions:nil]; // add the filters to an image for (CIFilter *filter in filterArray) { [filter setValue:image forKey:kCIInputImageKey]; image = [filter outputImage]; } // create a CGImageRef which has been edited return [context createCGImage:image fromRect:image.extent]; } // iOS5の場合でもXMPが取れるので、頑張るひとはここから頑張る感じ。 // 今回はfullScreenImageでお茶を濁す。そろそろiOS5切っていいんじゃね・・・? return [representation fullScreenImage]; } return [representation fullResolutionImage]; }
ちなみにXMPってAdobeの規格らしいっすよ。ただここらへんのカメラ寄りの規格ってちゃんとしたドキュメント無いような気がするのですが、僕の調べ方が悪いんですかね。
追記
わかってるとおもいますが、XMPに入ってる情報は回転とクロップだけなので、画像補正系には対応できません。画像補正すると写真そのものを弄るのかな・・・?
AmazonS3とiOS間でファイルをやりとりする(その1)
お久しぶりです。
AmazonにS3っていうストレージサービスがあるんですが、それをiOSで使う機会があったのでメモしておきます。
準備
・AWSアカウント
・AWS iOS SDK
・AWSアカウントのアクセスキー
参考文献:http://www.atmarkit.co.jp/ait/articles/1203/09/news120_2.html
Exceptionを発生させないようにする
How Not to Throw Exceptions with the AWS SDK for iOS - https://mobile.awsblog.com/post/Tx2PZV371MJJHUG/How-Not-to-Throw-Exceptions-with-the-AWS-SDK-for-iOS
で、どうやらiOSにおいては、Exceptionはユーザー体験に影響をおよぼすところでは使われない模様です。(Facebook SDKの場合も、NSErrorのポインタ渡してごにょるって操作してた)
iPhoneアプリの通信エラー処理を考える - iOS Advent Calendar 2011 - ninjinkun's diary
というわけで、AWSのブログのように、Exception投げないでね設定をしておきましょう。
Amazon S3の簡単な説明
こちらの7ページ目を参考にすると、バケットを作ってそこにオブジェクトを置いていく感じになります。
S3 iOS SDKにはバケットを作るAPIもあるのですが、ユーザーにそれはさせなくて良いという判断で、バケットだけは先にコチラで作って置きます。
なので、次に説明するアップロード、ダウンロードのフローチャートは、バケットが既に用意されていることを前提に書いてます。
ファイルアップロード、ダウンロードのフローチャート
シンプル!
次回はサンプルコードを載せます。
Facebook iOS SDK3.5で投稿処理を行う際の流れ(その2)
※Facebook iOS SDKはコロコロ仕様が変わる(メソッドがいきなり非推奨になったり)ので、お使いのSDKのバージョンには気をつけましょう。
フローチャート
この図の各部分の処理を書いていきます
セッションが開いているか
FBSession.activeSession.isOpen
コレがBOOL返すので、それで判断
投稿する権限があるか
if([FBSession.activeSession.permissions indexOfObject:@"publish_actions"] == NSNotFound)
セッションが持ってる権限は、FBSession.activeSession.permissionsにコレクションとして格納されているので、それを見て判断する。
iOS Versionを判断する
NSArray *aOsVersions = [[[UIDevice currentDevice]systemVersion] componentsSeparatedByString:@"."]; iOsVersion = [aOsVersions[0] intValue];
これで取得できる(らしい)
iOS6以上で、端末にFacebookアカウントを設定しているかどうか
#import <Accounts/Accounts.h> ACAccountStore* accountStore = [ACAccountStore new]; ACAccountType* accountType = [accountStore accountTypeWithAccountTypeIdentifier:ACAccountTypeIdentifierFacebook]; NSDictionary *options = @{@"ACFacebookAppIdKey": APP_ID_KEY, @"ACFacebookPermissionsKey" : @[@"email"], @"ACFacebookAudienceKey" : ACFACEBOOK_AUDIENCE_KEY}; [accountStore requestAccessToAccountsWithType:accountType options:options completion:^(BOOL granted, NSError *error) { if (granted) { NSArray* accountArray = [accountStore accountsWithAccountType:accountType]; if (accountArray > 0) { /// iOS6かつ、Facebookアカウントが設定されている場合の処理を開始する } else { /// iOS5と同じ処理に移動する } } else { DLog(@"error"); /// iOS5と同じ処理に移動する } }];
わざわざAccountStore使って判断するのはめんどくさいのですが、とりあえずはコレで判断できるみたい・・・ここ自信ないです。
activeなセッションに対して、投稿権限を要求する
[FBSession.activeSession requestNewPublishPermissions:@[@"publish_actions"] defaultAudience:PUBLISH_AUDIENCE_KEY completionHandler:^(FBSession *session, NSError *error) { if (error) { /// Handle new permissions request errors } else { /// 権限が取得できたので、投稿する処理に以降する } }];
publishPermissionと一緒に、セッションをオープンする
[FBSession openActiveSessionWithPublishPermissions:[NSArray arrayWithObject:@"publish_actions"] defaultAudience:PUBLISH_AUDIENCE_KEY allowLoginUI:YES completionHandler:^(FBSession *session, FBSessionState status, NSError *error) { if (!error && status == FBSessionStateOpen) { [self performPublishAction]; } else { [self performSelectorOnMainThread:@selector(setFBError:messages:) withObject:error withObject:[self createAuthErrorMessage:error] waitUntilDone:YES]; } }];
このメソッドが、Facebookアカウントを設定済みのiOS6と、Facebookアカウントを設定していないiOS6,iOS5で挙動が変わります。
OSにアカウントを設定している場合には、そちらから取得するので、アプリ内で解決します。 そうでない場合には、safariが起動して、権限を要求するページが開きます。その後、アプリに戻ってきます。
セッションをオープンする
[FBSession openActiveSessionWithAllowLoginUI:YES];
Facebookはちょっと前の仕様変更で、publish権限を取得剃る前にまずread権限を取得しないといけないという制限があります。iOS5とIDを設定してないiOS6は上記のrequestNewPublishPermissionsを呼べば、一度safariがオープンして、read権限とpublish権限の承認を両方得ることができます。
逆にIDを設定したiOS6では正式にreadを要求してから、publishを要求するというプロセスを踏む必要があるので、上記のメソッドをコールしたあとにpublish権限の取得にうつります。
Facebookに投稿する
FBRequest* request = [FBRequest requestForUploadPhoto:self.photo]; [request.parameters addEntriesFromDictionary:@{@"message": self.message}]; FBRequestConnection* connection = [[FBRequestConnection alloc] initWithTimeout:TIMEOUT_INTERVAL]; [connection addRequest:request completionHandler:^(FBRequestConnection *connection, id result, NSError *error) { if (error) { /// 投稿に失敗した [self performSelectorOnMainThread:@selector(setFBError:messages:) withObject:error withObject:[self createAPICallErrorMessage:error] waitUntilDone:YES]; } else { /// 投稿に成功した [self performSelectorOnMainThread:@selector(setFBError:messages:) withObject:error withObject:[self createSuccessMessages] waitUntilDone:YES]; } }]; [connection start];
単純に写真を上げるだけなら、uploadPhoto的なメソッドをコールするだけですが、写真にコメントも付けたかったので、requestにメッセージも付加しています。
まとめ
コールバック地獄ですね。エラー処理は各Handlerに帰ってくるのでそれをインスタンス変数で持っておいて使う側で判断する形が一般的でしょうか?おそらくもっとスマートなやりかたがあると思うので、詳しい方コメントお願いします・・・
Facebook iOS SDK3.5で投稿処理を行う際の流れ(その1)
もっと簡単な方法あると思うんですが、公式でもちょっとドキュメント探しづらかったりするので、メモを残します。
サポートiOS Version:iOS5以上
フローチャート
処理の流れを図にしてみました。
解説
FacebookSDKで、FBSessionのクラスメソッドに"openActiveSessionWith..."というメソッドがあるのですが、コレがiOS5と、iOS6でもFacebookアカウントを端末に設定しているかどうかで処理が微妙に変わるので、それに対応する必要があります。
なので上記のフローチャートのように、ちょっとめんどくさい分岐が発生します。
次回は具体的に各分岐と処理にどんなapiを使ってるかを紹介したいと思います。
iOS6以上でFacebookに投稿をする方法
iOS6からFacebook連携がデフォルトで入るようになったので、"Social Framework"と"Acounts Framework(これはiOS5から)"なるものを入れれば、別にFacebook SDKを使わなくてもFacebookのGraph apiを叩けるみたいです。
超カンタンな方法
SLComposeViewControllerを呼ぶだけ。以上。Facebookにアプリ登録をする必要もありません。
コレはググれば出てくるのでいいでしょう。
ちょっとめんどくさい方法
上記の方法だとfacebookの投稿がvia iOSになります。それが嫌って時はちゃんとFacebookにアプリ登録して、アプリIDを取得しましょう。
今日は眠いので、軽くメモっておきます。
参考文献:http://www.slideshare.net/i2key/socialframeworkaccountframework-twtrhack
このスライドにあるように、いきなり書き込み権限を取りに行くと怒られます。めんどくさ!なので処理の流れとしては
1. read権限を取得する(ついでにアカウント情報を取得する)
2.write権限を取得する
3.取得できたら、graph apiを叩いて投稿する
こんな感じです。あとはtokenが有効かどうかを確認して更新リクエストを行う処理を適当なタイミングでやりましょう。
サンプル
1.read権限を取得 + アカウント情報を取得
ACAccountStore* accountStore = [ACAccountStore new]; ACAccountType* facebookType = [accountStore accountTypeWithAccountTypeIdentifier:ACAccountTypeIdentifierFacebook]; NSDictionary* options = @{ACFacebookAppIdKey : @"アプリのid", ACFacebookPermissionsKey : @[@"email"], // read権限の例 ACFacebookAudienceKey : ACFacebookAudienceOnlyMe}; // 公開範囲 [accountStore requestAccessToAccountsWithType:facebookType options:options completion:^(BOOL granted, NSError *error) { if (granted) { NSArray *accounts = [accountStore accountsWithAccountType:facebookType]; _fbAccount = [accounts lastObject]; // facebookのアカウント情報を取得 ACAccountCredential *fbCredential = [_fbAccount credential]; NSString *accessToken = [fbCredential oauthToken]; NSLog(@"fbAccessToken : %@", accessToken); // token取れるので、ここで有効かどうか確認してもいいかも } else { NSLog(@"error getting permission email : %@", error); } }];
2.write情報を取得
1.のソースのoptionsを変えるだけ
3.投稿する
NSString* urlString = [NSString stringWithFormat:@"https://graph.facebook.com/%@/photos", [[_fbAccount valueForKey:@"properties"] valueForKey:@"uid"]]; /// facebook apiの種類に応じてurlを変える必要アリ NSURL* url = [NSURL URLWithString:urlString]; NSDictionary* params = [NSDictionary dictionaryWithObject:@"facebook photo upload test" forKey:@"message"]; SLRequest *request = [SLRequest requestForServiceType:SLServiceTypeFacebook requestMethod:SLRequestMethodPOST URL:url parameters:params]; /// addMultipartDataで、写真を投稿する NSData* photo = [[NSData alloc] initWithData:UIImagePNGRepresentation([UIImage imageNamed:@"namekuji700.jpg"])]; [request addMultipartData:photo withName:@"withname" type:@"multipart/form-data" filename:@"filename"]; /// アカウント情報を付加 [request setAccount:_fbAccount]; [request performRequestWithHandler:^(NSData *responseData, NSHTTPURLResponse *urlResponse, NSError *error) { NSLog(@"response : %@", [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding]); }];
4.token更新処理
[_accountStore renewCredentialsForAccount:_fbAccount completion:^(ACAccountCredentialRenewResult renewResult, NSError *error) { if (renewResult == ACAccountCredentialRenewResultRenewed) { NSLog(@"renewed"); } else if (renewResult == ACAccountCredentialRenewResultRejected) { NSLog(@"rejected"); } else if (renewResult == ACAccountCredentialRenewResultFailed) { NSLog(@"failed"); } }];
大体こんな感じでどうにかなるんじゃないでしょうか?
自前で画面とエラーハンドリングしないといけないですが、graph api叩けるので、やりたいことはほぼ出来るんじゃないかなぁと。
もっと楽な方法あったら教えて下さい。
では。
最近haskell勉強してます。応用先が見つかりませんが頭の体操になりますね。
cocos2dでシューテイングゲームを作る
cocos2使い出して3week位でしょうか?なんとなく自分の中では使い方わかってきた気がするのでメモを残します。
先に現在の進捗を報告すると
- 自機は、タップした場所に一定の速度で移動しようとする
- moveGestureにも対応。
- 敵を倒すとアイテムを放出→3種類のパネルに応じて自機の発射イベントが変わる
- 画面上部に(今のところ仮置きしてる)ボスがおり、そいつに体当たりするとGameOver
だいたいこんな感じでソースコードは
こちら:https://github.com/assaulter/ShootingGame
クラス図はこんな感じ
前置きはこのくらいで
勉強した順番としては
- cocos2d本家wikiから辿れるチュートリアルをやる - http://www.cocos2d-iphone.org/wiki/doku.php/resources:iphone_recommended_reading
- スライドシェアを漁る。良さげだったやつをピックアップすると
- iOS Game Development with Cocos2D - http://www.slideshare.net/Greenwell/iphone-and-ipad-game-development-with-cocos2d
- チュートリアルやりつつコレ読めば、だいたいできることは分かると思います。
- あとはひたすら遊ぶ感じ
現時点の課題
- UnitTestやろうとするとライブラリ関係が相当めんどくさい。
- Kobold2Dの方が環境構築とか楽だった - 参考:Kobold2Dで始めるゲーム開発 - http://www.slideshare.net/giginet/devsap
- CCLayerの位置づけがあやふや(CCLayerについては下で軽く説明します)
気を取り直して、解説っぽいこと
上のスライドのIOS Game...で説明されてますが、Cocos特有のクラスとして以下の3種類があります
- CCScene - いわゆるゲームの1シーンを定義する(ex. メインメニュー画面、Stage1でそれぞれ別インスタンスのCCSceneを用意する)。CCLayerを管理する。
- CCLayer - 正直試行錯誤してる段階なのですが、今のところはCCSpriteに対して描画命令を行うのが主な仕事だと理解してます。レイヤーの名前の通り、画面を複数のレイヤーで構成するのが一般的みたいです。
僕の場合には、例えばアイテムSprite群を管理するItemLayer等、意味としてまとまりのあるSpriteのグループを管理する役割として使ってます。 - CCSprite - ゲームの中に出てくる描画対象を定義(自機、敵、弾)基本的には命令されるだけの立場なので、メンバ変数とそれに対する操作メソッドを持つ位?
次回予告
次回からはゲームの一部のシーンを切り出して、それをどう実装したかについて書いて行きたいと思います。
# GitHubで軽く検索したのですが、そもそもCocos2Dのソースを上げてる人少ないですね。。。あったとしてもサンプルに毛が生えた程度の物が多いので、9leapからソース落として読もうかな。
cocos2dで遊んでみた。
サンプル
画像をピンチインアウトで拡大縮小。
ローテートで回転するだけのアプリ。
https://github.com/assaulter/MoveOzawa3
アレにナニな画像使ってるけど大丈夫かな・・・
メモ
Scene, Layer, Spriteの関係 - http://ameblo.jp/hash-r-1234/entry-10934976484.html
今回はSpriteはシングルタッチのみ処理して、回転、拡大はLayer側でハンドルすることにしました。
参考資料
cocos2d 2.0 Single Touch Detection - http://zackworkshopios.blogspot.jp/2012/06/cocos2d-20-single-touch-detection-for.html
- CCSpriteでシングルタッチを認識する方法
UIGestureRecognizerでジェスチャーを識別する - http://blog.syuhari.jp/archives/2234
- よく見るiOSの方のジェスチャー判別方法