[ANE][iPhone]Air for iOSにて、スクリーンショットをカメラロールに保存するANEを作ってみた

aneを使ってAir for iOS(AIR3.3SDK)でスクリーンショットを撮るお話。

ありそうでなかった、というかあったけどもうまく動かなかったので作ってみたらちょっとハマりました。


まず、タイトルどおりのaneのコードが公開されてました。
abhisek-mishra / ScreenShot

が、実装してみても想定通りに動作しない。。
Objective-C部分のコードを見てみると、UIGetScreenImage()というapple非公開APIを使用してる。
非公開APIを使うとリジェクト対象だそうでこれは使えないと。


とりあえず下の記事を参考にスタンダードな実装に変更してみる。
iPhoneアプリから画面のキャプチャー画像を取得する2つの方法

aneファイルをビルドしなおして再テスト。
が、画像はカメラロールに保存されたのは真っ白の画像。。

ちなみにObjective-CでiOSプロジェクトを作って、Xib上に適当にボタンとか画像とか配置した状態で上記の方法を実行すればスクリーンショットは問題なく取れるから根本的に間違ってるわけではなさげ。


肝はFlashの描画領域がどこにあるのか、ということ。
上記の記事のコードだと、

[app.keyWindow.layer renderInContext:UIGraphicsGetCurrentContext()];

UIWindowのkeyWindowのレイヤーをキャプチャしてるのだからFlashで言えばStage上のメインタイムラインをキャプチャしてるようなものなはずだから良さげな感じだけども結果は前述したとおりに真っ白。


で、結論。
上記の方法は使えないみたい。
app.xmlでのrenderMode要素をdirectを指定する場合はObjective-C側ではOpenGLESでのスクリーンショットを撮るような処理をしないといけないっぽい。
具体的なコードはここに書かれてるものを参考にしました。
GLPaint save image

このコードで取得したUIImageをキャプチャすることでとスクリーンショットを撮ることができました。


では、renderModeをcpu指定にすれば最初のコードでもスクリーンショットは撮れるのではないか?と思いましたが結果は否。
renderMode:cpuでは、上述のどちらのコードでもスクリーンショットを撮ることはできませんでした。
謎。。。
まぁrenderModeをcpuにする理由なんてない気もするのでまぁいいか。


各コードの詳細はQuiitaにあげてあります。
Qiita:AIR for iOSでスクリーンショットをカメラロールに保存するANEを作ってみたけど真っ白な画面しか取れない
(コメント欄で自己解決してます)

p.s.
ちなみに今回スクリーンショットを撮るのが最終目的ではなく、iOS5からのネイティブなTwitterAPIを使ってのスクリーンショットつきのTweet機能を実装したかったのでした。
これも無事にうまく実装できたので、また後で記事を書きます。

[iPhone]UISliderにてvalue値固定でmaximumValueを変更した際にはUISliderの見た目が変化しないみたい。そんなときの無理やりな対処の仕方。

UISliderの挙動に悩みました。

やりたかったこと。
たとえば、UISliderが二つあって(sliderA,sliderBとする)、
初期値はこんな感じで、

sliderA.maximumValue = 200;
sliderA.minimumValue = 100;
sliderA.value = 150;
sliderB.maximumValue = 100;
sliderA.minimumValue = 0;
sliderA.value = 50;

sliderAのvalue値をsliderBのmaximumValueに対応させたい

//sliderAに対するValue changedなメソッド
-(IBAction) changeSliderAValue:(UISlider* )slider
{
    sliderB.maximumValue= sliderA.value;
}

sliderBはmaximumValueは変化するがvalueは変化しない。
この状態でsliderBの見た目(つまみの位置)が相対的に変化して欲しい。
が、変化しない。。

sliderBのvalueを変数で保持しておいて、明示的にsilderB.valueに代入してみてもだめ。

float sliderBValue;
//sliderAに対するValue changedなメソッド
-(IBAction) changeSliderAValue:(UISlider* )slider
{
    sliderB.maximumValue= sliderA.value;
    sliderB.value = _sliderBValue;
}
//sliderBに対するValue changedなメソッド
-(IBAction) changeSliderBValue:(UISlider* )slider
{
    _sliderBValue = sliderB.value;
}

どうも挙動を確認してみるとslider.valueには現在地と同じ値を明示的に代入しても見た目の変化は起きないようだった。
現在地と違う値を入れてあげれば、maximumValueに対応した見た目の変化も発生する。

で、考えた苦肉の策はこちら

float sliderBValue;
//sliderAに対するValue changedなメソッド
-(IBAction) changeSliderAValue:(UISlider* )slider
{
    sliderB.maximumValue= sliderA.value;
    //わざとランダムな数値を一度代入することで現在値を更新する
   sliderB.value =  arc4random() % 100;
    //改めて_sliderBValueを代入することで正しい見た目にする
    sliderB.value = _sliderBValue;
}
//sliderBに対するValue changedなメソッド
-(IBAction) changeSliderBValue:(UISlider* )slider
{
    _sliderBValue = sliderB.value;
}

もっといい方法がありましたら、教えてくださいませ。。

[iPhone]UISliderのつまみ(thumb)を独自の画像に変更するときの注意

簡単にできたようで出来てなくてちょっとはまったのでメモ

基本的にはメソッドが用意されます。

//setThumbImage
//使用例
//speedLevelSliderというUISliderが宣言してあって、Resourceに"sliderThumb.pngがあるとして
//sliderThumb.pngはwidth:100px,height:100pxとする
//通常状態
[speedLevelSlider setThumbImage:[UIImage imageNamed:@"sliderThumb.png"] forState:UIControlStateNormal];
//つまみをタッチ状態も登録しておかないとデフォルトが表示されちゃう
[speedLevelSlider setThumbImage:[UIImage imageNamed:@"sliderThumb2.png"] forState:UIControlStateHighlighted];

これだけで指定した画像につまみが変更される。
が、UISliderをInterfaceBuilderで配置した場合にはこれだけではうまくいかない場合がある。
デフォルトのUISliderのheightは23になるのだけど、自分で用意したつまみ画像の高さが23を超える場合は、はみ出した部分はタッチ領域にならない。
上記の例だとつまみの高さは100pxなので見た目はでかいつまみになっているけど、実機でタッチできる領域は中心の23px分しか反応しないことになる。
InterfaceBuilder上からheightを修正してあげればいいかと思うと、Hの領域だけグレイアウトされていて修正ができない.

なのでframeプロパティを修正して描画領域を書き換えてあげないといけないのでした。

//変更前
//Sliderのx,y座標やwidthはInterfaceBuilderから確認
speedLevelSlider.frame = CGRectMake( 10, 200, 200, 23);
//変更後
//(実際は左上基準なので、つまみが大きくなった分、y座標も修正する必要あります)
speedLevelSlider.frame = CGRectMake( 10, 200, 200, 100);

/////////////////////////////////////////////////////////////////////////////////////////////////////////
というようなことが下記のブログには、しっかり書かれていましたのですが、、
スライダーバー(UISlider)のカスタマイズ的:いまログ
細かいところが理解できずに結構はまってしまった。。。

[iPhone]Objective- Cの勉強メモ:xibファイルとはどういうものなのか

・xibファイルとは、
XML Interface Builderの略。
その名の通り、xml形式のファイル。
これをxcodeから開くとInterfaceBuilder(アプリケーション:以下IB)が立ち上がってビジュアル的にxmlの中身を編集できる。
(ということだと思う)

・Flashでいったらxibファイルって何にあたるの?
とTwitterでつぶやいたら、
エライ人から「FLEXでいうところのMXMLみたいなもの」と教えてもらった。
なるほど、FLEXでもMXMLも表示オブジェクトの配置情報を持っているし、MXMLを使わなくてもASで表示オブジェクトの配置することもできる。
(でもMXMLも使い方をよくわかってない)

ちょっと調べてみようと思ったらかなり分かりやすい素敵記事発見。

iPhone:Xib/Nib ファイル(その1):「支出管理」サポート
iPhone:Xib/Nib ファイル(その2) :「支出管理」サポート

xibファイルは、UIApplicationかUIViewControllerのサブクラスによってロードされるものなのね。
どのクラスにも設定できちゃうから、この辺りの仕組みがよくわからなかった。
ありがたやありがたや。

[iPhone] Objective- Cの勉強メモ:Xcodeショートカット:ヘッダーファイルとソースファイルの切り替え

.h/.mファイルの切り替え
cmd + option + 上キー(交互に切り替わる)

[iPhone] Objective- Cの勉強メモ:NSNotificationCenterでイベント通知の管理

Objective-CはASと違ってデリゲートとという機能がある。
デリゲートとは別に色んなクラスが通知するイベントを一括で管理してくれるクラスがあるみたい。
それが、NSNotificationCenterクラス。

手順としては、
1.NSNotificationCenterのインスタンスを生成
2.通知の登録(インスタンスに通知先、リスナー関数、通知の種類、通知の送信元)
3.通知の開始
4.(通知の終了)
5:(通知の登録解除)

例)

//playerはMPMusicPlayerControllerのインスタンス

//通知センタのインスタンを取得	
NSNotificationCenter* noteCenter = [NSNotificationCenter defaultCenter];
//addObserver:通知先
//selector:リスナー関数
//name:通知してほしい通知の種類?
//object:通知の送信元	
[noteCenter 
addObserver:self 
selector:@selector(hoge:)
name:MPMusicPlayerControllerNowPlayingItemDidChangeNotification 
object:player];
//通知開始を指示
[player beginGeneratingPlaybackNotifications];

nameはnilにすると全ての種類のイベントが取得対象になる。
(そもそも通知の送信元がどのようなイベントを発信してるかを把握してる必要あり?)
(ドキュメントみれば書いてあるのかな?)
objectはnilにすると、全ての送信元からイベントを受け取る。

あとNSNotificationCenterはシングルトンになるので、インスタンス変数の保持はしなくてもいいみたい。

[iPhone] Objective- Cの勉強メモ:カテゴリという概念を使って元クラスの拡張(メソッドの追加)できる

Objective-CにはASにはない機能でカテゴリという概念がある。
これはクラスファイルにメソッドを追加することができる機能。

通常クラスを拡張するにはそのクラスを継承したサブクラスを定義すればいいと思う。
でも根本的に弄りたくなった場合、元のクラスを書き換えちゃうということもできる。
(ASだと、Flash8でAS2のときは直接編集できたけど、AS3ではSWCになってるから無理ぽ)
でも書き換えちゃうと何か不具合がおきたときとかバックアップとってないと元に戻すのが大変。

というわけでカテゴリという概念は、元クラスを簡易的に拡張できるという概念。
ただし追加できるのはメソッドだけ。
変数は追加できない。
手順は拡張用のクラス(?)ファイルを作ってそこで定義する。

例)UIViewを拡張する、UIViewExファイルを生成する場合
//ヘッダーファイル

#import <UIKit/UIKit.h>
//ここでの()の中の名前は任意(ファイル名と同じでなくてよい)
@interface UIView (AnimationExtension) 
//メソッドの定義
//ここに追加したいメソッドを書く
-(void) hoge;
@end

//実行ファイル

#import "UIViewExtention.h"
@implementation UIView(AnimationExtension)
//メソッドの実装
    //ここで定義したメソッドを実装する
 -(void) hoge{
    //処理
}
@end

書き方で注意するのは、ヘッダーファイルでのinterfaceのくだり。
変数は定義できないので、{}は書く必要なし。

これを反映されるには、反映させたいクラスファイルにて

#import "UIViewExtention.h"

を記述すればよい。
これが記述されたクラス内では、UIViewインスタンスにUIViewExで追加したメソッドが普通に使えるようになる。

//例

-(void) init{
//imageViewはUIImageVIewクラスのインスタンス
//(UIImageViewのスーパークラスはUIImageなのでhogeメソッドが使える)
[imageView hoge];
}

[iPhone]Objective- Cの勉強メモ:乱数の取り扱い

まずはObjective-CというかC言語での乱数の発生させる方法。
使える関数は2種類
まず
int rand(void);
という関数でint型の乱数を返すことができる。
このときに返る値は、0~馬鹿でかい数という感じ。
馬鹿でかい数はコンパイラによって変わるらしい。
(ASのMath.random()のように、0~1未満というわけでない)

int i;
i = rand();
printf(%d,i);//0から馬鹿でかい数の間の間

ただ、このままだと乱数は実行するたびに常に同じ値を返すことになるので、初期化処理という行為が必要になる。
そこで使うのがsrand();
void srand(unsigned int 初期値);

使い方としては、rand()関数を実行する前にsrand()関数を実行することで初期化すればよし。
初期値には、絶対にかぶらない値を日付データから取ればよし。

//time(NULL)で現在の時刻が1970年1月1日午前0時0分0秒からの通算秒数で得られる。
srand(time(NULL));
rand();

以上が、C言語での設定方法

Objective-Cではsrandは以下のようにするらしい。

//NSDateインスタンスを作って、本日の日付けを秒数に換算してる
srand([[NSDate date] timeIntervalSinceReferenceDate]);
rand()

※C言語のやり方でも問題なさげ。

[iPhone]Objective- Cの勉強メモ:プロパティ宣言:@propertyの意味

ヘッダーファイルにて、プロパティ宣言をすることでそのプロパティについてのゲッターセッターが実装される。
プロパティ宣言をしなくても、自分で実装処理を書いてもよい

プロパティの書式は、

//ヘッダーファイルに書く
//@property (属性 , 属性, ・・・) データ型, 名前
//例
@property ( nonatomic, retain ) NSTimeZone* timeZone;

//ソースファイルに書く
@synthesize timeZone;

上の例を自分で実装した場合は

//ソースファイルに書く
//ゲッター
-(NSTimeZone*) timeZone{
//同時実行不可のための処理:属性nonatomicに相当
@synchronized(self){
return timeZone;
}
}
//セッター:読み書きについて属性が未設定なのでセッターまで生成される
-(void) setTimeZone:(NSTimeZone *)zone{
//同時実行不可のための処理:属性nonatomicに相当
@synchronized(self){
if(timeZone != zone){
[timeZone release];
timeZone = [zone retain];//保持する際は保持カウントをアップさせる:属性retainに相当
}
}	
}

※@synchronizedは排他制御するかしないか。
マルチスレッドで同時に実行して欲しくない処理があるとき、これを指定することで片方のスレッドを停止することができる。
上の例では排他制御に対応しており、プロパティの属性でnonatomicを指定すると、排他制御はしなくなる。

[iPhone]Objective-Cの勉強メモ:dealloc

クラスファイルの最後の方にやたら登場するdeallocメソッド
NSObjectのメソッドで、それをオーバーライドしている。
dealloc実行のタイミングは保持カウントが0になったときに実行される。

保持カウントはインスタンスの生成、retainさせた参照の追加でカウントアップ。
releaseでカウントダウンされるのでカウント数には注意が必要。

例)

- (void)dealloc {
    [window release];
//スーパークラス(NSObject)のdeallocを実行することで実際のメモリの破棄
//C言語のfree関数のようなもの
    [super dealloc];
}