外ぶらっとしに行って、ついでにiPadを買いそうになりましたw Wifiモデルでも並ばなきゃ買えなかったので、諦めましたが。
さて、UIPickerViewの続き。 今度は2つのコンポーネントを表示。
まずコンスタンスとして2つのコンポーネントの番号を設定しておく
#define kPlayerComponent 0
#define kPostionComponent 1
次に、データ配列を作成
- (void)viewDidLoad {
NSArray *playerArray = [[NSArray alloc] initWithObjects:
@"荒木", @"大島", @"森野", @"ブランコ",
@"和田", @"井端", @"セサル", @"谷繁",nil];
self.players = playerArray;
[playerArray release];
NSArray *positionArray = [[NSArray alloc] initWithObjects:
@"ピッチャー", @"キャッチャー", @"ファースト", @"セカンド",
@"サード", @"ショート", @"レフト", @"センター", @"ライト",nil];
self.positions = positionArray;
[positionArray release];
}
ここまではほぼ前と同じ。 UIPickerViewのデータは、この書籍では条件分岐で返してます。
#pragma mark Picker Data source method
-(NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView {
return 2;
}
-(NSInteger)pickerView:(UIPickerView *)pickerView
numberOfRowsInComponent:(NSInteger)component {
if (component == kPlayerComponent) {
return [self.players count];
} else if (component == kPostionComponent) {
return [self.positions count];
}
return 0;
}
#pragma mark Picker Delegate method
- (NSString *)pickerView:(UIPickerView *)pickerView
titleForRow:(NSInteger)row
forComponent:(NSInteger)component {
if (component == kPlayerComponent) {
return [self.players objectAtIndex:row];
} else if (component == kPostionComponent) {
return [self.positions objectAtIndex:row];
}
return 0;
}
うーん、汎用的にするには連想配列にすべきですね。 次は、NSDictionaryを利用した場合。
その前に参考書籍には、アメリカのZIPコードデータ「statedictionary.plist」が必要。 海外サイトにサンプルソースがあるらしいので検索。
ここのフォーラムにあるのだが、Regist & Loginしないとダウンロードできないです(^^;) さらにソースの場所もわかりにくいのでメモ・・・以下のフォーラムにあります。
Beginning iPhone Development ‹ Hey! Over here! Here's the source code to the book!!!
ダウンロードしたら「Resources」に「statedictionary.plist」を入れておく。 今回のサンプルは、基本は変わらないが、statedictionary.plistを読み込んでNSDictionaryとして扱うのがポイント。
- (void)viewDidLoad {
NSBundle *bundle = [NSBundle mainBundle];
NSString *plistPath = [bundle
pathForResource:@"statedictionary"
ofType:@"plist"
];
NSDictionary *dictionary = [[NSDictionary alloc] initWithContentsOfFile:plistPath];
self.stateZips = dictionary;
[dictionary release];
NSArray *components = [self.stateZips allKeys];
NSArray *sorted = [components sortedArrayUsingSelector:@selector(compare:)];
self.states = sorted;
NSString *selectedState = [self.states objectAtIndex:0];
NSArray *array = [self.stateZips objectForKey:selectedState];
self.zips = array;
[super viewDidLoad];
}
外部ファイルの読み込みは、Bundleを用意して
NSBundle *bundle = [NSBundle mainBundle];
Resourcesからのファイルパスを生成して、
NSString *plistPath = [bundle pathForResource:@"statedictionary" ofType:@"plist"];
NSDictionaryに格納する。
NSDictionary *dictionary = [[NSDictionary alloc] initWithContentsOfFile:plistPath];
NSArray、NSDictionaryの特記として
・allKeys ・objectForKey ・sortedArrayUsingSelector
@selector(compare:)の「compare」はNSComparisonResultのメソッド。 大文字小文字を区別しない「caseInsensitiveCompare」もある。
次に、ピッカーが選択された時の動作
-(void)pickerView:(UIPickerView *)pickerView
didSelectRow:(NSInteger)row
inComponent:(NSInteger)component {
if (component == kStateComponent) {
NSString *selectedState = [self.states objectAtIndex:row];
NSArray *array = [stateZips objectForKey:selectedState];
self.zips = array;
[picker selectRow:0 inComponent:kZipComponent animated:YES];
[picker reloadComponent:kZipComponent];
}
}
didSelectRow inComponentメソッドを記述してやるだけでOK。 条件がZipコンポーネントでなく、Stateコンポーネントなのが、思考的にややこしい。 引数のcomponentは動かしたコンポーネント番号ではなく、
全コンポーネントをチェック時のコンポーネント番号
らしい。 つまりstateデータを更新したければ、stateのコンポーネント番号でOK Eventのプログラム思考でいくと、何か変な感じですがw
何か1週間が早いです。 そして、知らず知らずに年を取っていく。。。 でも勉強してきますw
今日はUIPickerViewのシングルコンポーネントについて。 まず結果から。 2010年ドラゴンズのオーダーw
■InterfaceBuilder Xcode側で、UIPickerView用のOutletデータを定義しておく(説明は割愛)。 IBを開き、UIPickerViewをクリックしてdataSourceとdelegateをFile's Ownerにバインド。 UIPickerViewのOutletはsinglePickerとしてバインド。
■Xcode まず、viewDidLoadにUIPickerViewに表示するデータを確保する。
- (void)viewDidLoad {
NSArray *array = [[NSArray alloc] initWithObjects:
@"荒木", @"大島", @"森野", @"ブランコ",
@"和田", @"井端", @"セサル", @"谷繁",nil];
self.pickerData = array;
[array release];
}
pickerDataは、UIPickerViewのデータを扱うためにNSArrayとして予め定義した。
NSArrayで作成して、OutletのNSArrayに格納。 initWithObjectsって覚えなきゃいけないけど、この手の設定は 「init」って打てば補完候補が出てくるから楽だよね。 てか、あとはNSArray自体のソースを見れば実装がわかるし。
#pragma mark -
#pragma mark Picker Data Source Methods
-(NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView {
return 1;
}
-(NSInteger)pickerView:(UIPickerView *)pickerView
numberOfRowsInComponent:(NSInteger)component {
return [pickerData count];
}
#pragma mark Picker Delegate Methods
-(NSString *)pickerView:(UIPickerView *)pickerView
titleForRow:(NSInteger)row
forComponent:(NSInteger)component {
return [pickerData objectAtIndex:row];
}
まず「#pragma」はIDEへの指令らしいが、イマイチぴんとこないけどXcode上では、 以下のようにソースを分離してくれる。
さて、その他の実装の方は、おまじないみたいになってるw UIPickerViewに定義してあるメソッドなんですが、
numberOfComponentsInPickerView
は、UIPickerViewのコンポーネント数(ホイールスピンの数)を返す。 うーん、プロパティで設定しないんだね・・・。
numberOfRowsInComponent
は、表示するデータ数を設定。 これも、pickerData(NSArray)をバインドしたら自動的に計算してくれればいいのに・・・。
titleForRow forComponent
は、指定したスピンのインデックスを引数に、その行のデータ(NSString)を返す。
これらを実装しておかないと、Viewがロードされた時点でRuntimeErrorになってしまう。 しかし、同じView上にUIPickerViewが2つ以上でてきたら、この処理はダメだよね。 (pickerDataを直接記述してるから) まぁ、深い事は考えず先へ進むw
CoreAudioは使わずに、AudioToolboxで音を再生してみる。 ToolboxってOSX以前のレガシーなライブラリっすよね。
Resources/sound.wavを再生してみます。 まず、AudioToolboxライブラリを追加。 次にXcode ■SoundViewController.h
#import
#import
@interface SoundViewController : UIViewController {
CFURLRef soundURL;
SystemSoundID soundId;
}
@property (readwrite) CFURLRef soundURL;
@property (readonly) SystemSoundID soundId;
-(IBAction) buttonPressed;
-(void) playSound;
-(void) loadSound:(NSString *)soundName;
AudioToolboxをimportして、CFURLRef、SystemSoundIDを定義。 汎用的に音の読み込み関数「loadSound」、音再生用関数「playSound」も用意しておく。
■SoundViewController.m
#import "SoundViewController.h"
@implementation SoundViewController
@synthesize soundURL;
@synthesize soundId;
- (IBAction) buttonPressed {
[self playSound];
}
-(void)loadSound:(NSString *)soundName {
NSString *soundPath = [[NSBundle mainBundle] pathForResource:soundName ofType:@"wav"];
soundURL = (CFURLRef)[NSURL fileURLWithPath:soundPath];
AudioServicesCreateSystemSoundID (soundURL, &soundId);
CFRelease(soundURL);
}
-(void)playSound {
AudioServicesPlaySystemSound (soundID);
}
- (void)viewDidLoad {
[self loadSound:@"sound"];
}
- (void)dealloc {
AudioServicesDisposeSystemSoundID (soundId);
[super dealloc];
}
(1)viewDidLoadでファイル名を引数にloadSoundを実行して、Resources/sound.wavを読み込む。 ※wav限定 (2)IBActionでbuttonPressedされたら、playSoundを実行する。 (3)loadSoundでは、AudioServicesDisposeSystemSoundIDを利用してsoundIdを解放する。
サウンドファイル管理はまだ汎用化できそうですが、とりあえずこんな感じで。
iPadが発売ですね。 うーん、やっぱ開発者としては買うべきだが、3GかWifiかすげー迷って 結局、自分は予約できずじまい。
3Gでないと純正のGPSが使えないから、やっぱ3Gでプリペイドかなぁ?? でも、iPadでGPSの使用頻度は少ないだろうし。 さて、ちょっと開いてしまったが続き。
このブログで、HTML5 & JavaScriptを使った複数ファイルアップロード機能追加したので早速画像つきで! (画像編集機能はまだありませんが(汗))
Window-based Applicationでプロジェクト作成後、 Resourcesに各ビューのViewControllerファイルを作成しておく。 ※.hも同時に作成し、.h、.m、.xibファイルを作成
AppDelegateファイルにルートとなるControllerのアウトレットを宣言する。
@interface PikersAppDelegate : NSObject {
UIWindow *window;
UITabBarController *rootController;
}
@property (nonatomic, retain) IBOutlet UIWindow *window;
@property (nonatomic, retain) IBOutlet UITabBarController *rootController;
addSubviewで、PikersAppDelegateのメインウィンドウに rootController(UITabBarController)のviewを追加する。
@synthesize window;
@synthesize rootController;
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[window addSubview:rootController.view];
[window makeKeyAndVisible];
return YES;
}
- (void)dealloc {
[rootController release];
[window release];
[super dealloc];
}
@end
MainWindow.xibを開き、LibraryからTab Bar Controllerを MainWindow.xibにドラッグ&ドロップする。 すると、Tab Bar Controllerのウィンドウが開く。
Tab Bar Controller AttributeにはView Controllers(item1, item2)があるが、 ここでsubviewを増減できる。 これに連動して、Tab Bar Controllerウィンドウの最下部にタブが増減する。
Tab Bar ControllerのタブをクリックしてViewControllerの設定を行う。 ※2回クリックするとタブバー項目自体の設定になるので注意
Tab Bar Controller AttributeのNIB NAME欄で追加したViewControllerが設定できる。 Tab Bar Controller Identityに切り替え、Class欄は先ほど選択したViewControllerのClass名に変更する。 ※デフォルトはUIViewController
これで、UIViewControllerを継承したViewControllerを利用する事ができる。 Tab Bar Controllerのitem1を2回クリックして、タブバー項目設定に切り替える。
<
p>Imageに関しては、Resourcesに入れたpngが選択できる用意になっている。
複数のViewControllerを追加した結果は以下の通り。
最後に、App DelegatekからTab Bar ControllerへrootControllerアウトレットを設定する。
うは〜、ほぼノンコードで画面切り替えができました! 朝方ながら興奮してますw
こりゃぁ、手順さえ覚えればApple(InterfaceBuilder) > Adobe(FlexBuilder)かなぁ? FlexBuilderだとTabコンポーネントもあるからある意味同じか?
でも、ソースとGUIの親和性はAppleの方がやっぱ高い気がする。 言語の難易度的にはまだまだ、Objective-C > ActionScriptだけど・・・。
HTML版において、mixi OpenIDの機能を追加しました。
PHP OpenID Libraryを利用して、OpenIDの認証を実現します。 このライブラリはOpenID認証の面倒な処理を引き受けてくれます。
ここのサンプルを元に、プログラムを作成しました。
大きな流れとしては、 (1) 自分のサーバからOpenIDサービスのサーバに接続 ※この際、認証後のURLを指定 (2) OpenIDサーバの認証URLにリダイレクトされる (3) ユーザが認証する (4) 自分の指定したURLに、パラメータつき(GET)でリダイレクトされる
また、内部的にはサーバのstore_path(個別設定)にセッションデータが保持されています。
OpenIDサーバから返されるパラメーター(GET)は以下の通り。
janrain_nonce openid_mode openid_identity openid_op_endpoint openid_return_to openid_response_nonce openid_assoc_handle openid_ns_sreg openid_sreg_nickname openid_ns openid_signed openid_sig openid_claimed_id
あとは、自サーバでセッション管理で処理します。 mixiの「イイネ!」みたいな、「yoo!yoo!」って機能つけてみましたw
ログアウト後に、もう一度mixi OpenID認証すると「データがありません」となるのだけど、 まだ対処しきれてません。 憶測では、自サーバでログアウト時に、サーバのstore_pathのセッションを切ってやればいいと思いますが。
これ読んだmixiユーザの人、mixiでOpenID認証後に↓の【yoo!yoo!】をクリックしてみてください。
PHP + HTML5 + JavaScript + CSS3を使って、複数ファイルアップロードを作ってみた。 http://yoo-s.com/photo_album/ ※FireFox 3.6.3(win)、Safari 4.0.5(win)で確認
■JavaScript yoos.file_uploader-0.1.js
■HTML
ポイントは、multipleですね。 submitボタンがありませんが、ダイアログを表示するようJavaScriptに仕込んでます。 ダイアログを利用するには、
id="file_result"
のHTML要素を追加します。 これでファイル追加結果やアップロード、キャンセルの操作ができます。
その他の機能として、最大ファイル数や容量も、JavaScriptで設定できます。
CSSでデザインを変更することも可能です。 ■CSS yoos.file_uploader.css
あとは、機能としては非同期処理やファイルリストの削除とかかな?
Font互換は、OS間でずっと課題ですよね。
で、その問題を解決すべく「Google Font API」ってのが出ています。
Getting Startedにも記載されてる通り、headerに定義してCSSのfont-familyに設定するだけ。 [css] [/css] 実に簡単!
■WebFontの種類 http://code.google.com/webfonts
ちょっと使ってみたけど、あまり綺麗じゃない気がw あと2バイト文字はまだない。というか、あったとしても凄く重たそう・・・。
GoogleのWebGLプロジェクトとして公開されている「O3D」 WebGLに標準を統一し、O3DをJavaScriptライブラリとするようです。
うほー、これは凄い!!
SVN管理されている 「o3djs/base.js」をベースに作っていくみたいだ。
構造的には、
JavaScript → O3D → OpenGL,Direct3D →GPU
と最終的にちゃんとGPUで処理させる。
しかし、大規模なゲームだとクロスコンパイルして JavaScriptに書き出す手法じゃないと厳しいよなぁ、多分。。。
ドキュメントはここを見て頑張れとw
O3Dのサンプルをそのまんまですが、このサイトで乗っけてみました! ※初回はo3dプラグインのインストールを促します ※iPhoneはo3dが未対応
モデリングファイルは「o3dtgz」形式のファイルです。 ※o3dtgz の生成参照
daeファイルを出力して Google が公開しているo3dconverter.exeでo3dtgzを生成することができる
これでFlashでなくてもwin,mac共通の3Dゲームができますね。
このサイト(HTML版)のプルダウンメニューの作成をCSS3のみで実現してみました。
まず、HTMLの構造から ■HTML
ul、liの入れ子で階層を作っていきます。 最初のul(id=menu)をトップ階層とし、メインメニューはliで横並びにします。 次の階層のul,liがサブメニューになります。
■CSS gradientで背景をグラデーション、border-radiusで枠を角丸に [css]
display: inline-block;
margin: 15px 0 0 0;
padding: 5px 0 0 0;
line-height: 100%;
font-size: 14px;
background: -moz-linear-gradient(top, #406b9f, #588ab5);
background: -webkit-gradient(linear, left top, left bottom, from(#406b9f), to(#588ab5));
-webkit-border-radius: 6px;
-moz-border-radius: 6px;
} [/css] text-shadowでリンクテキストに影をつける
[css]
display: block;
margin: 0;
padding: 8px 20px;
color: #eeeeee;
text-decoration: none;
text-shadow: 5px 5px 5px rgba(0, 0, 0, 1.0);
}
color: #ff2222;
text-shadow: 2px 2px 2px rgba(0, 0, 0, 1.0);
} [/css]
▽第1階層 トップメニューはliを横並び [css]
position: relative;
float: left;
margin: 0 5px;
padding: 0 0 5px;
list-style: none;
} [/css]
liをロールオーバーした時に色を変更 [css]
background: -moz-linear-gradient(top, #406b9f, #588ab5);
background: -webkit-gradient(linear, left top, left bottom, from(#406b9f), to(#588ab5));
} [/css]
▽第2階層 ここがポイントで、第2階層にあたるulをvisibility=hiddenで予め隠しておく。 Fadeアニメーションするために、opacity=0と設定する。 [css]
visibility: hidden;
opacity: 0;
} [/css]
FireFox3.5ではまだアニメーションは未対応 また、アニメーションを利用するとiPhoneだとメインメニュークリック時に、 サブメニューが現れる前にリンクしてしまう。 逆に言えば、iPhoneはhoverの挙動はクリックすると状態がキープされるようだ。
次もポイントで、liをロールオーバーした時にul(第2階層)を表示する。 ここで第2階層をFadeアニメーションするために、opacity=1とtransitionを設定 [css]
visibility: visible;
opacity: 1;
-moz-transition: all 0.3s ease-in-out;
-webkit-transition: all 0.3s ease-in-out;
-o-transition: all 0.3s ease-in-out;
transition: all 0.3s ease-in-out;
} [/css]
「li:hover > ul」は新しい書き方ですね。 transitionのプロパティはActionScriptと同じぽいですね。 これもECMAScript準拠なのかね?
第2階層の外枠のレイアウトは#menuから絶対座標に指定 [css]
position: absolute;
font-size: 12px;
margin: 0;
padding: 0;
left: 0;
width: 185px;
border: solid 1px #ffffff;
-webkit-border-radius: 6px;
-moz-border-radius: 6px;
border-radius: 6px;
} [/css]
第2階層のメニューのレイアウト [css]
text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.8);
padding: 8px 10px;
}
float: none;
margin: 0;
padding: 0;
background: -moz-linear-gradient(top, rgba(66, 110, 161, .8), rgba(66, 110, 161, .8));
background: -webkit-gradient(linear, left top, left bottom, from(rgba(66, 110, 161, .8)), to(rgba(66, 110, 161, .8)));
} [/css]
第2階層の外枠に沿うように、最初と最後のメニューは上下の角丸をつける [css]
-webkit-border-top-left-radius: 6px;
-moz-border-radius-topleft: 6px;
-webkit-border-top-right-radius: 6px;
-moz-border-radius-topright: 6px;
}
-webkit-border-bottom-left-radius: 6px;
-moz-border-radius-bottomleft: 6px;
-webkit-border-bottom-right-radius: 6px;
-moz-border-radius-bottomright: 6px;
} [/css]
このサンプルの問題点は、階層が増える毎に追加してやらないといけないけど、 何階層にもなるメニューは現実的ではないので、ある程度力技でもいいのかなと? その分ソースわかりにくくなるけど。
level1、level2をclassとして作ってみたけど、WebKit側で思った動作が得られなかった。 どうしても複数階層に対応するなら、JavaScriptやPHP等で動的にCSSを書き出すって感じ?
本当は、CSSも使いまわしのできる関数・プロパティやクラス化できれば、 リファクタリングできてライブラリ化できそうなんだけどなぁ。
いきものがかりの吉岡聖恵ちゃんの声は個人的に好きだなぁ。
オールナイトニッポン(ANN)の水曜日担当で、カバーの曲を歌ってるみたい。 ラジオでしけかきけない(?)のでここはradikoだな!!
radikoのアプリはAdobe Airだから、自前でアプリが作れそうだ。 仕様とかちょいと調べてみる。
■番組表XMLの例 http://radiko.jp/epg/epgapi.php?area_id=JP13&mode=&station_id=TBS
うーん、XMLか。。。 JSONにして欲しいな。
▽area_id(必須) 国と都道府県コードの組み合わせ 例 JP13
・国:日本 = JP ・都道府県コード:東京 = 13
▽mode
・now = 放送中の番組表 ・なし = 1週間分の番組表
today、tomorrowとかあるらしいが、このAPIはPHPなので strtotimeのオプションで使えるものは全部使えたりして?
▽station_id
放送局ID:TBS,QRR...etc
※後述の「rtmp接続」を参照
■HTML Flashと以下のJavaScript解析すれば取れるかな? http://radiko-dl.ssdl1.smartstream.ne.jp/radiko-dl/1.1/player/js/player.js
▽swfで再生 http://radiko-dl.ssdl1.smartstream.ne.jp/radiko-dl/1.1/player/player_0.1.2.swf
これにパラメータ(statini_id)加えてやるといけるっぽい。
■rtmp接続 ここにradiko録音についてのってます。 普通のプログラミングだと、RTMPDumpってのをかましてやるらしいが、Airなら直でrtmpで繋げそう。
▽東京 TBSラジオ:rtmp://radiko.smartstream.ne.jp:1935/TBS%2f_defInst_/simul-stream 文化放送:rtmp://radiko.smartstream.ne.jp:1935/QRR%2f_defInst_/simul-stream ニッポン放送:rtmp://radiko.smartstream.ne.jp:1935/LFR%2f_defInst_/simul-stream ラジオNIKKEI:rtmp://radiko.smartstream.ne.jp:1935/NSB%2f_defInst_/simul-stream INTERFM:rtmp://radiko.smartstream.ne.jp:1935/INT%2f_defInst_/simul-stream TOKYO FM:rtmp://radiko.smartstream.ne.jp:1935/FMT%2f_defInst_/simul-stream J-WAVE:rtmp://radiko.smartstream.ne.jp:1935/FMJ%2f_defInst_/simul-stream
▽大阪 朝日放送:rtmp://radiko.smartstream.ne.jp:1935/ABC%2f_defInst_/simul-stream 毎日放送:rtmp://radiko.smartstream.ne.jp:1935/MBS%2f_defInst_/simul-stream ラジオ大阪:rtmp://radiko.smartstream.ne.jp:1935/OBC%2f_defInst_/simul-stream FMこころ:rtmp://radiko.smartstream.ne.jp:1935/CCL%2f_defInst_/simul-stream FM802:rtmp://radiko.smartstream.ne.jp:1935/802%2f_defInst_/simul-stream FM大阪:rtmp://radiko.smartstream.ne.jp:1935/FMO%2f_defInst_/simul-stream
やってみたがプログラミングが悪いのか?接続拒否される。 色々調べてたらwonderfulにソースのってました。 rtmp接続するURIが違ってたようだ。 [as3] private var streamUrl:String = "rtmpe://radiko.smartstream.ne.jp:1935/TBS/defInst";
public function onNetStatus(event:NetStatusEvent):void {
if(event.info.code == "NetConnection.Connect.Success") {
var stream:NetStream = new NetStream(connection);
var client:Object = new Object();
client.onMetaData = function ():void {};
client.onPlayStatus = function ():void {};
stream.client = client;
video.attachNetStream(stream);
var soundTransform:SoundTransform = new SoundTransform();
soundTransform.volume = .5;
stream.soundTransform = soundTransform;
stream.play('simul-stream');
}
} [/as3]