いろいろ開発をしていて、EventListenerに引数を渡せると便利だということが多々ある。とっても良くあるので、ここにメモを残しておく。

これができると、大量のオブジェクトを扱う時、いちいち e.target.name で名前をとって、対応するIDを算出して...とかする必要がなくなる。便利君である。

 

btn1.addEventListener( MouseEvent.MOUSE_OVER, onMouseOver(1));
btn1.addEventListener( MouseEvent.MOUSE_OUT, onMouseOut(1));
btn1.addEventListener( MouseEvent.CLICK, onMouseClick(1) );

btn2.addEventListener( MouseEvent.MOUSE_OVER, onMouseOver(2));
btn2.addEventListener( MouseEvent.MOUSE_OUT, onMouseOut(2));
btn2.addEventListener( MouseEvent.CLICK, onMouseClick(2) );


function onMouseOver(id:int) { 
    return function (e:MouseEvent):void{
        trace("over  : "+id);
    };

function onMouseOut(id:int) { 
    return function (e:MouseEvent):void{
        trace("out   : "+id);
    };

function onMouseClick(id:int) { 
    return function (e:MouseEvent):void{
        trace("click : "+id);
    };
}

 

 

CoreDataを使っていて、リレーションなどごちゃごちゃやって、関連するデータを取り出してみたら、そのデータがソートされていない。単純にDBに保持されているものを持ってくるだけだから、並び替えしていないってことで、自前で並び替えなければならない。SQLが使えれば楽なのに。。。

で、調べてみたので、残しておく。

※ 下記ソースの簡単な説明として、CoreDataから1つのteamDataを取得し、それに紐付くNSSet型のmembersのデータ(Member *)を、複数の条件で並び替えすることを想定している。

// CoreDataのリレーションで関係づけられたデータは、NSSet型で保存される.

NSSet *members = teamData.members;


// ソートする条件を指定。

NSSortDescriptor *sort1 = [NSSortDescriptor

                           sortDescriptorWithKey:@"memberNo"

                           ascending:YES] ;

NSSortDescriptor *sort2 = [NSSortDescriptor

                           sortDescriptorWithKey:@"activeFlag"

                           ascending:NO] ;


// ソートする条件を配列で保持。

NSArray *sortDescriptors = [NSArray arrayWithObjects:

                            sort1,sort2,nil];

// NSSet型のmembersをソートして配列membersArrayに保持。

NSArray *membersArray = [members sortedArrayUsingDescriptors:

                         sortDescriptors];


// 結果を見てみよう。

for (Member* memberData in membersArray) {

    NSLog(@"memberData   %@ %@",

               memberData.memberNo,

               memberData.activeFlag);

}


これで並び替えができているはず。(ただし、このソースはサンプルで、実際にコンパイルはしていないので悪しからず)

また、もしソート条件が1つだけの場合には、arrayWithObject: を使えばよい。複数の場合の arrayWithObjects: は最後の"nil"が大事。忘れてコンパイルが通らなくて焦った。

NSArray *sortDescriptors = [NSArray arrayWithObject:sort1];



PHPだったらsortOn("memberNo"); とかやればいいので便利だけど、Objective-Cはまだ慣れない。。。


普通にDBを使っていて、プライマリキーとかユニークキーとか当たり前に使うものだと思っていたけど、CoreDataを利用するにあたってはその概念が当てはまらない。とはいえ、SQLiteをラップしているので、裏側ではちゃんとプライマリキーとか持っているようだが、表だって使うことができないのだ。

でもいろいろなviewを行き来するのに、そのテーブル(エンティティ)内の一意のデータのキーを持ちまわるのが妥当と判断したため、どうしても PRIMARY KEY に AUTO INCREMENT を設定したカラムを使いたかったのだが、いろいろ調べた結果、直接、PRIMARY KEY も AUTO INCREMENT も使うことができないことが判った。(実は良い方法があるのかもしれないが、そこまでCoreDataを熟知していない。SQLiteを直接実行する方法もあるのかな?)

で、もっと調べて、代わりに利用できそうなのが NSManagedObjectID である。これは、TableView(っていうかfetchedResultsController) で利用される indexPath と似ているので理解が早い。


使い方は簡単。
まず最初に、取得した ManagedObject から objectID を保持。
 
NSManagedObjectID *objID = [managedObject objectID]; 

NSLog(@"entryID == %d", objID );

 
保持した objectID を使って、managedObjectContext から目的の ManagedObject を取り出す。

NSManagedObject *managedObject

           = [managedObjectContext objectWithID:objID];


とりあえず、これで目的の挙動の代用とすることができる。

ただ、この objectID の注意点としては、 AUTO INCREMENT のように1からの連番ではないということ。
また、コンパイル毎に違う数字が返される。ってことは、DB上で持っている数字ではなく、システムで与えられる数字なのだろう。この点は TableView で利用される indexPath も同じことが言える。

 

まだまだ勉強中のため、間違いがあればご指摘ください。

CoreDataの勉強中でまだまだ自分のものにできていないのだが、そんな自分の強力なサポートとして、CoreDataの発行するSQLをデバッグログに表示してくれる方法がある。

ずいぶん前に設定したのだが、新しいプロジェクトに設定するのに忘れないよう残しておく。

まずは「プロジェクト」タブの一番下「アクティブな実行可能ファイル・・・」を選択。

screenshot_1.png

「引数」タブの「起動時に渡される引数」に下記のコードを追加。

-com.apple.CoreData.SQLDebug 1

screenshot_2.png
下の変数はデバッグ用のコード。

これでコンソールにCoreDataのログが出力される。

[31597:207] CoreData: sql: BEGIN EXCLUSIVE

[31597:207] CoreData: sql: SELECT Z_MAX FROM Z_PRIMARYKEY WHERE Z_ENT = ?

[31597:207] CoreData: sql: UPDATE Z_PRIMARYKEY SET Z_MAX = ? WHERE Z_ENT = ? AND Z_MAX = ?

[31597:207] CoreData: sql: COMMIT

[31597:207] CoreData: sql: BEGIN EXCLUSIVE

[31597:207] CoreData: sql: SELECT TBL_NAME FROM SQLITE_MASTER WHERE TBL_NAME = 'Z_METADATA'

[31597:207] CoreData: sql: DELETE FROM Z_METADATA WHERE Z_VERSION = ?

[31597:207] CoreData: sql: INSERT INTO Z_METADATA (Z_VERSION, Z_UUID, Z_PLIST) VALUES (?, ?, ?)

[31597:207] CoreData: sql: INSERT INTO ZBOOK(Z_PK, Z_ENT, Z_OPT, ZTITLE, ZWRITER) VALUES(?, ?, ?, ?, ?)

[31597:207] CoreData: sql: COMMIT

[31597:207] CoreData: sql: pragma page_count

[31597:207] CoreData: annotation: sql execution time: 0.0018s

[31597:207] CoreData: sql: pragma freelist_count

[31597:207] CoreData: annotation: sql execution time: 0.0012s

前回から1カ月以上たっているが、開発中のiPadアプリで、本体の回転に関するバグが発生。
前回の「iPhone SDK 本体を回転させた時の処理で悩んだのでメモ」の記事で本体の回転についての記事を書いているが、これには考慮不足があったため、改めてメモを残す。
 
要件としては、本体のローテート状態を取得する [[UIDevice currentDevice] orientation] について、orientationのプロパティ値について、本体画面の上・下・左・右の他に、水平方向の裏・表が存在しているというもの。
これを知らず、単純にIF分で「横向きの場合とそれ以外」と指定していたため、テーブルの上に置いて操作した場合に、画面が横向きにもかかわらず、処理の途中で縦向きの計算をしてしまっていたのだ。
 
で、判別するプロパティ値のイメージは以下の通り。

UIDeviceOrientation orientation = [[UIDevice currentDevice] orientation];

if (orientation == UIDeviceOrientationUnknown ){

    NSLog(@"デバイスの向きが不明");

}else if (orientation == UIDeviceOrientationPortrait ){

    NSLog(@"ホームボタンが下にある状態");

}else if (orientation == UIDeviceOrientationPortraitUpsideDown ){

    NSLog(@"ホームボタンが上にある状態");

}else if (orientation == UIDeviceOrientationLandscapeLeft ){

    NSLog(@"ホームボタンが右にある状態");

}else if (orientation == UIDeviceOrientationLandscapeRight ){

    NSLog(@"ホームボタンが左にある状態");

}else if (orientation == UIDeviceOrientationFaceUp ){

    NSLog(@"ホームボタンが真上を向いている状態");

}else if (orientation == UIDeviceOrientationFaceDown ){

    NSLog(@"ホームボタンが真下を向いている状態");

}


 
回避方法としては、willRotateToInterfaceOrientation()で、toInterfaceOrientationの値を変数に保持してしまい、各所の処理ではその値を利用するのが得策であるろう。
 
ちなみに、willRotateToInterfaceOrientation()での引数値には、UIDeviceOrientationFaceUp 、UIDeviceOrientationFaceDown の値は入ってこないようだ。これは、引数 toInterfaceOrientation が「これから画面の向きを変える方向」なので、水平の場合でも画面の向きが優先されると考えられる。
そうでないと、純粋に回転状況が取れず、どうしてよいか分からなくなるので助かる。
そんなこんなで覚えておきたい。
iPhone SDK でiPadアプリを製作中。
そこら中に落ちているサンプルを拾い集めながら何とか進めているけど、やはり理解しながら進めないと自分の実力にならないので、合間を見つけて勉強を進めている。

で、まずはメモとして、本体を回転した時にビューの向きを変える設定。というか、Controller.m に以下を書くだけ。

// 自動回転を有効にするサブクラス
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
  return YES;
}


これで、自動でビューを回転してくれる。
でも、これだけではビューを画面にフィットさせることはできない。
回転時に画像のサイズ変更とかボタンの表示位置とかを修正する必要がある。
で、それを実装するのが、 willRotateToInterfaceOrientation() か didRotateFromInterfaceOrientation() で、画面を回転させる処理を開始する前と完了した後に呼び出される関数である。この他に、willAnimateFirstHalfOfRotationToInterfaceOrientation()、willAnimateSecondHalfOfRotationFromInterfaceOrientation() という処理もあり、回転のアニメーションの前半と後半に分けた場合にそれぞれ呼び出される。これ以外にもいくつかあるけど試す余裕はない・・・。

で、実際に回転した時に縦方向とか横方向を判定する方法についてだが、引数で取得する回転方向が、正常・反転・右・左のどれにあたるかを判別すればよい。
ここでいう、正常・反転・右・左とは、ホームボタンの位置と考えれば判りやすい。

正常: UIInterfaceOrientationPortrait
反転: UIInterfaceOrientationPortraitUpsideDown
右 : UIInterfaceOrientationLandscapeRight
左 : UIInterfaceOrientationLandscapeLeft

以下に、willRotateToInterfaceOrientation()、 didRotateFromInterfaceOrientation() でのテスト用コードを書く。実際にはこのif文をいじって、ボタンの位置とかを変えればよい。

// 回転を開始する前に行っておく処理
-(void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
  NSLog(@"ローテイト");
  if(toInterfaceOrientation == UIInterfaceOrientationPortrait){
    NSLog(@"will to   Portrait");
  }else if(toInterfaceOrientation == UIInterfaceOrientationPortraitUpsideDown ){
    NSLog(@"will to   UpsideDown");
  }else if(toInterfaceOrientation == UIInterfaceOrientationLandscapeRight ){
    NSLog(@"will to   LandscapeRight");
  }else if(toInterfaceOrientation == UIInterfaceOrientationLandscapeLeft){
    NSLog(@"will to   LandscapeLeft");
  }else{
    NSLog(@"will 何が起きた?!");
  }
}


// 回転が完了した後に行っておく処理
-(void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation {
  if(fromInterfaceOrientation == UIInterfaceOrientationPortrait){
    NSLog(@"did  from Portrait");
  }else if(fromInterfaceOrientation == UIInterfaceOrientationPortraitUpsideDown ){
    NSLog(@"did  from UpsideDown");
  }else if(fromInterfaceOrientation == UIInterfaceOrientationLandscapeRight ){
    NSLog(@"did  from LandscapeRight");
  }else if(fromInterfaceOrientation == UIInterfaceOrientationLandscapeLeft){
    NSLog(@"did  from LandscapeLeft");
  }else{
    NSLog(@"did  何が起きた?!");
  }
}


ここで、注意しなければならないのは、それぞれの関数で受け取る回転方向の情報である。
will~の方は、toInterfaceOrientation で、did~の方は、fromInterfaceOrientation を受け取っている。
"to"と"from"の違いは、意味通り、回転前はこれから回転する方向で、回転後はどの方向から回転されたかを示すのだ。
この引数を意識していなかったため、NSLogの結果の相違にパニクってしまった。

これで本体の回転については理解することができた。

関連記事として iPhone SDK 本体の回転 [[UIDevice currentDevice] orientation] で落とし穴が を追加。合わせてお読みください。

久しぶりにPHPプログラムを書いてみて、改めてActionScriptとの違いに勘が戻るまで時間がかかってしまった。
つまらないことに時間を割かないよう、今回はPHPとActionScriptのお作法の違いを残しておこうと思うと思う。


まずは変数定義として、配列を例に掲載。
PHP

 $item_list = Array("バナナ","みかん","りんご","いちご");
 $cost_list = Array(80,50,120,90);
 $object = Array();

AS3.0

 $item_list = Array("バナナ","みかん","りんご","いちご");
 $cost_list = Array(80,50,120,90);
 $object = Array();

PHPの場合、変数定義に$を付ける。久しぶりに書いたからどうしても忘れてしまう。
あとはnewを付けるとかってところが違うだけ。("var"はPHPでも使うし)


続いて、for文と、配列への要素の追加
PHP

 for( $i = 0 ; $i < count($item_list) ; $i++ ){
  $tmpArray["item"] = $item_list[$i];
  $tmpArray["cost"] = $cost_list[$i];
  array_push( $object, $tmpArray );
 }

AS3.0

  for( var i = 0 ; i < item_list.length ; i++ ){
  var tmpArray = new Array();
  tmpArray["item"] = item_list[i];
  tmpArray["cost"] = cost_list[i];
  object.push( tmpArray );
 }

配列の要素数を数える方法が、PHPではcount(Array)で、ASではArray.lengthという違いがある。
配列への要素の追加方法はASの方が直感的で好き。

 

で、多次元配列になっている連想配列のソート方法はというと・・・
PHP

usort($object, create_function('$a,$b', 'return($a[\'cost\'] - $b[\'cost\']);'));

AS3.0

 object.sortOn("cost",Array.NUMERIC);

PHPの場合、usortというユーザー定義の比較関数を使ってソートするのだ。使い方がちょっと難しいのが難点。
それに比べ、ASではsortOn()で指定してあげるだけ。便利。

 

最後に、連想配列の時の要素の取り出し方。
PHP

 foreach($object as $key => $row){
  print($row["item"]." : ".$row["cost"]."\n");
 }

AS3.0

 その1
 for( var key in object ){
  trace(object[key]["item"]+" : "+object[key]["cost"]);
 }
 その2
 for each( var obj in object ){
  trace(obj["item"]+" : "+obj["cost"]);
 }

なるほど。
AS3で"for each"が追加されたそうなのだが、これだとキーが取り出せないんだよね。
それを考えると、この"for each"は用途が限定されてしまう。(その分、処理が早いのか?)
その点、PHPはキーも、それに対応するオブジェクトもいっぺんに取得できるのでよい。


そんな感じで、違いを残しつつソート部分は完全にメモ。

ビューンが復活

| コメント(0) | トラックバック(0)
iPad発売当初、色々な雑誌が読めると話題になったが、利用者が多すぎたせいか、重すぎて何も表示されず不評だったビューンが、Wi-Fi 対応のみで復活した。

mzl.jpqumane.480x480-75.jpg
現在はプレ配信期間ということで無料。また、正式再開後も30日間は無料で利用でき、それ以降は30日間で450円とのこと。
複数の新聞や雑誌の主要記事を読むことができ、この値段なら安いと思うが、現在Wi-Fiでしか利用できない。電車の中で見るには、その雑誌を最初に一通り表示してキャッシュに入れておけば、Wi-Fi接続していなくても読むことができるようだ。ただ、キャッシュの量がどのくらいかは未確認。

実際に試してみたところ、前のように読み込みが遅すぎるということもなく快適。操作性も悪くないし、目次表示+ページ移動などもできるので読みたい部分だけピンポイントで読むことができる。また、リアルタイムのニュースも表示されるのも便利である。
ただ、Wi-Fi につながっているはずなのだが、「Wi-Fi接続してご覧ください」とでた。何でだろう?

ビューン
URL:http://www.viewn.co.jp/
http://itunes.apple.com/jp/app/id372350361?mt=8
BetweenAS3の便利な機能として、イベント処理を利用することができる。

主にCOMPLETEイベントくしか使ったことはないのだが、PLAY,STOP,UPDATEと合わせて4種類が準備されているようだ。

使い方は簡単。対象のtweenに対して、addEventListener(TweenEvent.COMPLETE, 終了時の関数);  として設定するだけ。

import org.libspark.betweenas3.events.TweenEvent;   // 宣言に追加する必要あり

==============

var serialTween1:ITween = BetweenAS3.tween( mcA, { x:150 },{x:20,y:112}, 0.25);
var serialTween2:ITween = BetweenAS3.tween( mcA, { y:60 }, null, 0.15);
var serialTween3:ITween = BetweenAS3.tween( mcA, { x:200 },null, 0.2);

var tween = BetweenAS3.serial(serialTween1,serialTween2,serialTween3);

tween.addEventListener(TweenEvent.COMPLETE, endTween);

tween.play();


function endTween(){
    /*終了時の処理を書く*/
}

使い道としては色々あると思うけど、自分の場合、動いているときはボタン処理をさせたくないので、処理の開始前にボタンを利用できなく(removeEventListenerとか)して、tween完了時に再度、利用できるように(addEventListener)するといった具合に使っています。

前回のトゥイーン系ライブラリ BetweenAS3 (その2)で利用したサンプルでも、ボタンを連打できないように処理を入れています。




関連記事
ActionScript3.0  トゥイーン系ライブラリ BetweenAS3 (その1)
ActionScript3.0  トゥイーン系ライブラリ BetweenAS3 (その2)
BetweenAS3を使っていて、悩んだことがあったのでソースを調べてみた。

悩んだ内容は、逐次処理(シリアル)、並行処理(パラレル)を実行するときに、不特定の数を動作させたい時、そのトゥイーンを配列で渡せないかということ。

前回紹介したBetweemAS3の使い方では、シリアル、パラレル共に、その詳細のトゥイーンを引数につなげて設定して動かします。

// 順番に動かす場合はこんな感じ
var serialTween1:ITween = BetweenAS3.tween( mc2, { x:150 },{x:20,y:112}, 0.25);
var serialTween2:ITween = BetweenAS3.tween( mc2, { y:60 }, null, 0.15);
var serialTween3:ITween = BetweenAS3.tween( mc2, { x:200 },null, 0.2);

BetweenAS3.serial(serialTween1,serialTween2,serialTween3).play();

数も少なく、順番に動かす場合には、この方が直感的でわかりやすいと思います。
しかし、もっとシステマチックに動かさなければならない時は、これだと対処しきれないでしょう。

でも、BetweenAS3にはその対処法があったのです。素晴らしい!!

// シリアルはこれ
BetweenAS3.BetweenAS3.serialTweens( 配列 );

//パラレルはこれ
BetweenAS3.BetweenAS3.parallelTweens( 配列 );

簡単なサンプルは以下の通り
  // 動かすMovieClipを配列に保持
  var pointMcArray:Array; = [mc1,mc2,mc3,mc4,mc5,mc6];
  // 動作するtweenを保持する配列を定義
  var tweenArray = new Array();

  // for文でtweenを指定
  for(var i=0;i< pointMcArray.length ;i++){
    var tmpPointMc = pointMcArray[i];
    // tweenを定義
    var tmpTween:ITween = BetweenAS3.tween( tmpPointMc,
               { x:targetArray[i]["x"], y:targetArray[i]["y"] },
                null, 0.8, Cubic.easeOut);
    // tweenを配列に保持
    tweenArray.push(tmpTween);
  }

  // 配列を引数にtweenを設定(これはシリアル)
  var tween = BetweenAS3.serialTweens(tweenArray);
  tween.play();

下のサンプルFlashでは、Serial、Parallelボタンを押すと、ポッチがランダムな位置に(シリアル、パラレルで)移動します。
 

ごちゃごちゃした動きがある場合には、tweenを1つずつ指定するよりも、配列で指定した方が見やすいですね。


関連記事
ActionScript3.0  トゥイーン系ライブラリ BetweenAS3 (その1)
ActionScript3.0  トゥイーン系ライブラリ BetweenAS3 (その3)


2011年4月

          1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30