Pages

2011年10月29日土曜日

Xcode4.2のお作法

Xcode4.2になってからソースコードの書き方の作法も違ってきてるみたいです。
前からもそうだったのかもしれませんが、初めて気づきました。

これまでは、InterfaceBuilderでリンクするアウトレットプロパティとかは、
ヘッダファイルに下のように宣言していました。

@interface ViewController : UIViewController {
  UILabel* titleLabel;
  UIButton* closeButton;
}

@property (nonatomic, retain) IBOutlet UILabel* titleLabel;
@property (nonatomic, retain) IBOutlet UIButton* closeButton;

@end


外から参照する必要のないプロパティもヘッダに宣言するという
ある意味、多少気持ちの悪い書き方でしたよね。
@privateとかいう記述もありましたが、結局ヘッダに書いています。

これが、Xcode4.2では、ヘッダに書かなくていいようになっています。
どう書くかというと、.mファイルに次のように記述します。

@implementation ViewController {
  __strong IBOutlet UILabel* titleLabel;
  __strong IBOutlet UIButton* closeButton;
}

この__strongっていうのは、今までヘッダにretainを指定していたのと同じ意味みたいです。
__strongはデフォルトで省略してもいいみたいです。
__strongを書くと後で説明するリンクのマークが表示されないみたいなので、
私は書かない方向にシフトしました。

この__strongに対して__weakっていうのもあるみたいです。
これは参照カウントを加算しないのでしょうか、
結局InterfaceBuilderでリンクしてもそのオブジェクト自体が表示されませんので、
この場面では実質使うことはないでしょうね。


ソースコードの左の部分、InterfaceBuilderとのリンク状況が記されています。
白丸はリンクされていないもの、二重丸はリンクされているものです。

ちなみに、ボタンのTouch Up InsideのセレクタonCloseButtonも
ヘッダでは宣言していませんが、IBAction宣言しておけば、ちゃんと使えています。

Xcode4.2 ARC

Xcode4.2では、ARCという機能が追加されています。

下の画像のようにプロジェクト作成時にUse Automatic Reference Counting
という項目にチェックしていれば有効になります。


要は名前からもわかるように参照カウントを自動でやってくれる機能ということです。
ガベージコレクション(GC)というやつですね。

これまでは、retainとかreleaseとかautoreleaseとかを使って
自分でメモリの管理をやらないといけませんでした。
下手をするとメモリリークを招いてしまうということです。

その昔、C言語をずっと使っていた私にとっては、
自分でメモリ管理するのが普通で、間違えさえしなければ問題が起こることは無く、
さらに自分でどうなっているかを常に把握できるということで、
この方式以外と性に合ってるなって思ってました。

逆にJavaとかだと解放したいときにできないような気がして不安でした。
でも、やってるうちに慣れては来たんですけどね。

Xcode4.2になって、iOSアプリの開発でもメモリ管理を任せられるようになった
ということのようですね。チェックを外せば従来通りです。

その証拠に



retainとかreleaseというメソッドは呼び出せないようになっています。
deallocも使いません。

だから下の二つは結果同じことになるということですね。

object1 = [[NSString alloc] initWithString:@"STRING"];
object1 = [NSString stringWithString:@"STRING"];

また、スタック変数にいくらallocとinitで代入してもメソッドを出ると解放されてしまいます。

- (void)method {
  NSString* string = [[NSString alloc] initWithString:@"STRING"];
}

今までは、メソッド内でこんなことやって、releaseせずに放っておくと
メモリ内にずっと残り続けることになっていたのですが、
今回のARCでは、スタック変数が破棄された時点で参照カウントがゼロになるので、
結果としては、autoreleaseと同じことになるってことみたいですね。

2011年10月28日金曜日

iPhoneの数字キーパッドを閉じる術

iPhoneアプリでUITextFieldに入力するためにキーパッドを表示させて
入力が完了した後にキーパッドを非表示にさせるのにどんな方法を使ってますか?

iPadだとキーパッドに非表示にするためのボタンが備わってます。
iPhoneでもEnterキーがある場合には、EnterキーにDoneとかを設定しておくと
イベントセレクタ等でEnterキーの入力を受け取って非表示にしたりできます。

でもNumberPadの場合にはEnterキーがありません。
しかもキーパッドの上に完了ボタンがあったりするのって
よく見かけますよね。

私の使ってる方法を紹介します。
テストアプリを作る要領で順を追って説明してみます。

まず、Single View Applicationでプロジェクトを作成します。


InterfaceBuilder大好きで、今回も使います。
Storyboardは、まだまだ私の理解度が低いので今回は使いません。

デフォルトでできたViewController.mで@implementationのところに
UITextFieldと完了ボタンを表示するビューを宣言します。
これ、一応、私の中ではXcode4.2風の書き方です。
またこれは何かの記事で書きたいと思います。

@implementation ViewController {
  __strong IBOutlet UITextField* textField;
  __strong IBOutlet UIView* doneView;
}

それから、InterfaceBuilderの出番です。


UITextFieldを一つ置いただけです。
それと独立したビューとして完了ボタンを含んだビューを定義します。
完了ボタンはあえて似せた画像を作ってみました。
ビュー背景は黒の半透明にしてみました。

UITextFieldと完了ボタンのUIViewは、
さきほど宣言したアウトレットプロパティとリンクさせておきます。

で、完了ボタンビューを操作するためのオブザーバーを設定します。
オブザーバーの削除もちゃんと仕込んでおきます。

@interface ViewController(PrivateSelectors)
- (void)keypadDidAppear:(NSNotification*)notification;
- (void)keypadWillDisappear:(NSNotification*)notification;
@end

- (void)viewDidLoad {
  [super viewDidLoad];

  // キーパッドが表示された直後に呼び出されるセレクタを設定する。
  [
   [NSNotificationCenter defaultCenter]
   addObserver:self
   selector:@selector(keypadDidAppear:)
   name:UIKeyboardDidShowNotification
   object:nil
   ];

  // キーパッドが非表示になった直後に呼び出されるセレクタを設定する。
  [
   [NSNotificationCenter defaultCenter]
   addObserver:self selector:@selector(keypadWillDisappear:)
   name:UIKeyboardWillHideNotification
   object:nil
   ];
}

- (void)viewDidUnload {
  // オブザーバーを削除する。
  [[NSNotificationCenter defaultCenter] removeObserver:self];
}

※横スクロールで見難くなるのを避けるため、
いつもより余計に改行を入れております。
余計に見難いかもしれません・・・・・

で、keypadDidAppearがキーパッド表示直後に呼び出されますので
次のようにして完了ボタンビューをアニメーションを使って表示します。

- (void)keypadDidAppear:(NSNotification *)notification {
  // 基本のウィンドウ
  UIWindow* window = (UIWindow*)[[[UIApplication sharedApplication] windows] objectAtIndex:0];

  // キーパッドが表示される領域を取得する。
  CGRect kbRect = [
     [notification.userInfo objectForKey:UIKeyboardAnimationDurationUserInfoKey]
     CGRectValue
     ];

  // 完了ビューをキーパッドの上端に配置する。
  [window addSubview:doneView];
  doneView.frame = CGRectMake(kbRect.origin.x, kbRect.origin.y, kbRect.size.width, 37);
  doneView.hidden = NO;

  // 完了ビュー表示アニメーション
  [UIView beginAnimations:@"appear" context:nil];
  doneView.frame = CGRectOffset(doneView.frame, 0.0, -37.0);
  [UIView commitAnimations];
}

ここでは、キーパッドが表示された直後に完了ボタンビューがピョコっと
かわいく出てきます。この辺は好みでアレンジしていただいたらいいと思います。
オブザーバーもUIKeyとかもあるのでいろいろ楽しめると思います。

完了ボタンがタップされたときの処理はこんな感じ。

- (IBAction)onDoneButton:(id)sender {
  [textField resignFirstResponder];
  [self setEditing:NO animated:YES];
}

KeypadWillDisappearでは、完了ボタンビューを非表示にしているだけです。

- (void)keypadWillDisappear:(NSNotification *)notification {
  // 完了ビューを非表示にする。
  [doneView removeFromSuperview];
  doneView.hidden = YES;
}

こんな感じになります。


オブザーバーの種類は、

UIKeyboardWillShowNotification
UIKeyboardDidShowNotification
UIKeyboardWillHideNotification
UIKeyboardDidHideNotification
キーパッド情報としては、
UIKeyboardFrameBeginUserInfoKey
UIKeyboardFrameEndUserInfoKey
UIKeyboardAnimationCurveUserInfoKey
UIKeyboardAnimationDurationUserInfoKey
が用意されていますので、アレンジ次第ではかなり楽しめるのではないでしょうか?


Xcode4.2 UIPageViewController

Xcode4.2で選べるプロジェクトの種類にPage-Based Applicationというのがあります。

これを使えば、あのiBooksのページめくりのエフェクトを使ったアプリが
簡単に作れちゃうみたいです。


新規プロジェクト作成して、そのまま走らせてみたらこんな感じです。



ざっとソースを見た程度で理解した範囲のことを書きますと、
UIPageViewControllerDelegateとUIPageViewControllerDataSource
というプロトコルが定義されています。

UITableViewと似てますね。

データソースの方には、
viewControllerBeforeViewController、viewControllerAfterViewController
っていうコールバックメソッドが用意されていますので、
これで前のページのUIViewControllerや次のページのUIViewControllerを
渡してやれば、こういうアニメーションができるみたいです。

UIPageViewControllerには、navigationOrientationというプロパティがあって、
UIPageViewControllerNavigationOrientationHorizontalと
UIPageViewControllerNavigationOrientationVerticalの2つの値があります。

水平だと上の画像のように水平にページをめくります。
垂直を指定するとレポート用紙のように垂直方向にページをめくります。

まぁ、ちらっと見て理解できたのは、ここまでです。

あと、doubleSidedとかspinLocationとか、いくつかプロパティがあるので、
これを使えば両面開きとか、縦書き本のような右から左にめくるのも
できそうな感じです。

でも、やっぱり、iOS5しかダメなんですよね。

Xcode4.2 Tabbed Application

前の記事でMaster-Detail Applicationについて触れましたが、
4.2の新しい機能であるStoryboard。
Tabbed Applicationでも活躍してくれます。

これ。


UITabBarControllerと各UIViewControllerをRelationshipで関連付けてます。

このRelationship、張るときはInterfaceBuilderでControl押しながら
マウスドラッグというお馴染みの操作でできるみたいですね。

マウスボタンを離したらRelationshipのスタイルを選択する窓が出ます。
一番上のRelationship - viewControllersが通常のタブによる遷移の
Relationshipになります。


私、もともとInterfaceBuilder大好きです。

人によっては、InterfaceBuilderを使わずにアプリを作成するのがいいって
いう意見も結構聞いたりしますが、私はとにかく多用します。

で、Xcode4.2では、各UIViewControllerに.xibファイルを付けるのではなく、
このStoryboardで一気にやってしまうという方向にシフトしようとしてるんですかね。

たしかに直観的に理解しやすいような気もしますが、
一つの画面に沢山のものが並んで結果スクロールしまくりの状態になりそう。

と言いながら、いずれは慣れてしまうんでしょうね。

でも、このStoryboard使ってしまうと、iOS5以上でないと動かないものしかできません。
まだ、うちでは、iOS4でも動くようなアプリの開発が主流ですので、
しばらくは練習のみで、実践では従来の.xib方式でやります。


Xcode4.2 選べるプロジェクトの種類が変わってる

Xcode 4.2 使い始めていますが、いろいろと変わっているみたいですね。
とりあえずはドキュメントとか読まずに手探りで始める男なんで始めてみました。

新規プロジェクト作成・・・と。
おっ、新規プロジェクト作成の際に選べるプロジェクトの種類も変わってます。


選べるのは、
・Master-Detail Application
・OpenGL Game
・Page-Based Application
・Single View Application
・Tabbed Application
・Utility Application
・Empty Application
の7種類。

4.1のころと呼び名も変わっていますね。
多分、Single View Applicationってのは、View-based Applicationに相当するんでしょう。

目に付いたのは、Master-Detail ApplicationとPage-Based Application。
Navigation-based Applicationが無くなっているので、アイコンから推察するに
Master-Detail Applicationがそれに近そうですね。

なので、とりあえずMaster-Detail Applicationでプロジェクトを作ってみます。



Use Storyboardなる見慣れない項目が。
デフォルトでチェック入ってたし、新しいものなら、とにかく使ってみよう。
Use Automatic Reference Countingというのもありますが、
これについては別記事で書きます。

アイコンからの推察で多分、iPadではSplit Viewを使っているだろうから
iPhone、iPadどちらのパターンも見てみたいということでUniversal。

で、作ってみると、



.xibファイルが一つもない。
あるのは、MainStoryboard_iPhone.storyboardとMainStoryboard_iPad.storyboard。

これやな。そのStoryboardとかいうやつは・・・。
中身はこんなの。


InterfaceBuilderに3つのコントローラが出てきました。
UINavigationController、MasterViewController、DetailViewController
の3つがRelationshipというコネクタで接続されています。
これで画面遷移なんかを定義できるみたいです。
UINavigationControllerとMasterViewControllerのRelationshipは、
UINavigationControllerのrootViewControllerがMasterViewControllerである
と定義しているようなものでしょう。

そして、MasterViewControllerには、UITableView<が載っていて、
UITableViewCellが一つ定義されています。
InterfaceBuilderでセルも追加できるんですね。
このあたりも見慣れない雰囲気。また調べてみなくては。

このUITableViewCellとDetailViewControllerにもRelationshipが張られています。
RelationshipのStyleはPushとなっています。
UINavigationControllerにpushViewControllerするってことでしょうね。

他には、Modal、Customってのが選択肢としてありました。
Modalは、きっとpresentModalViewControllerでしょう。

このデフォルトのままで走らせると予想通りの動作をしています。

MasterViewControllerには、didSelectRowAtIndexPathなど
UITableViewのデリゲートメソッドは一切書かれておりませんでした。
このStoryboardの記述だけで、これだけ遷移が書けるんですね。

で、iPadの方はというと、こんな感じです。


やはり、UISplitViewControllerが使われているみたいです。

走らせてみました。


んっ?なんか思ってた動きと違うぞ。
UIPopoverViewControllerがぴょこっと出てくるだけ。
UISplitViewControllerとか使ったことないから、よくわからん。

iPadの方は、ちょっと調べてみる必要があるみたいですね。
直観的なイメージとはズレてました。

※このStoryboard使うと、iOS5でしか走らないのでご注意を!!

2011年10月17日月曜日

Smarty 3.0系と3.1系での変数の挙動の違い

Smartyで、ちょっと不思議な現象に気づいたので書いておきます。

まず、以下のようなSmartyのサブクラスを作ったとします。

class MySmarty extends Smarty{
  public $property;

  public function __construct(){
    $this->registerPlugin('function', 'updateProperty', array($this, 'updateProperty'));
    $this->property = 0;
    $this->assign('property', $this->property);
  }
  public function updateProperty(){
    ++ $this->property;
    $this->assign('property', $this->property);
  }
}

updateProperty() というメソッドは、単にインスタンス変数をインクリメントするだけの単純なものです。それをテンプレート側から呼び出せるようにしただけ。


で、テンプレート側は以下のようにしておきます。

{$property}
{updateProperty} {$property}

インクリメント前後で変数の値を表示するだけ。


で、これを、Smarty 3.0.9 で実行してみると
一行目が"0"
二行目が"1"
と期待通りの結果になりますが、

Smarty 3.1.3 で実行してみると、どちらも"0"と表示されます。
メソッドは確かに実行されているみたいですし、メソッド内でgetTemplateVars()で値を見るときちんとインクリメントされているみたいですが、テンプレート内ではそれが反映されずに、最初の値が維持されたままのようです。

とりあえず、現時点では有効な対策や解決方法は見つかっておらず、オブジェクト等の参照渡しの変数を使うか、配列で assignByRef() を使うかでお茶を濁しています。

どうなっているのか、時間のあるときにでソースを見てみます。

2011年10月12日水曜日

MacOSX Lion で iPhoneDisk

今日、初めて気が付いたのですが、iPhoneDiskが使えなくなっていました。
MacOSX Lion にアップグレードしてから使った覚えが無いので
もしかしたら互換性が無くなってしまったのかもしれません。

とりあえず、MacFUSE、iPhoneDisk を再度インストールしてみました。

ダメでした。

MacFUSEって2008年の12月からアップグレードされてないんですね。

いろいろ調べているうちにOSXFUSEっていうのがあるというのが分かりました。

さっそくインストールします。


ダウンロードしてきてインストーラを起動します。
途中カスタムインストールの画面が出てきますので、
MacFUSE Compatibility Layer にチェックします。
これが無いとダメでした。



出ました。
無事マウントされました。