一般公開「iPhoneアプリ初級脱出Online:高橋政明」
(MOSADeN Onlineでは、掲載日から180日を経過した記事を一般公開しています。)- 第30回 最終回(2010/12/28:掲載)
- 第29回 初級脱出のための参考書籍(2010/12/14:掲載)
- 第28回 二次元描画 その10 高解像度対応(2010/11/23:掲載)
- 第27回 二次元描画 その9 アニメーション(2010/10/28:掲載)
- 第26回 二次元描画 その8 表示を変化させる(2010/10/12:掲載)
- 第25回 二次元描画 その7 表示されない場合のチェックポイント(2010/09/30:掲載)
- 第24回 二次元描画 その6 画像ファイル表示(2010/09/14:掲載)
- 第23回 二次元描画 その5 フォントと文字表示(2010/08/24:掲載)
- 第22回 二次元描画 その4 色と透明度とグラデーション(2010/08/10:掲載)
- 第21回 二次元描画 その3 図形(2010/07/28:掲載)
- 第20回 二次元描画 その2 描画環境(2010/07/13:掲載)
- 第19回 二次元描画 その1 カスタムビューの表示(2010/06/28:掲載)
- 第18回 メモリ管理 その4(2010/06/16:掲載)
- 第17回 メモリ管理 その3(2010/05/25:掲載)
- 第16回 メモリ管理 その2(2010/05/11:掲載)
- 第15回 メモリ管理 その1(2010/04/27:掲載)
- 第14回 iPhoneのイベント その4(2010/03/26:掲載)
- 第13回 iPhoneのイベント その3(2010/03/16:掲載)
- 第12回 iPhoneのイベント その2(2010/02/23:掲載)
- 第11回 iPhoneのイベント その1(2010/02/09:掲載)
- 第10回 起動時の動作 その4(2010/01/26:掲載)
- 第9回 起動時の動作 その3(2010/01/12:掲載)
- 第8回 起動時の動作 その2(2009/12/22:掲載)
- 第7回 起動時の動作 その1(2009/12/08:掲載)
- 第6回 ユーザーデフォルト(2009/11/24:掲載)
- 第5回 プロパティリスト その2(2009/11/09:掲載)
- 第4回 プロパティリスト その1(2009/10/27:掲載)
- 第3回 デリゲート その3(2009/10/13:掲載)
- 第2回 デリゲートその2(2009/09/21:掲載)
- 第1回 デリゲートその1(2009/09/04:掲載)
-
第30回 最終回
この記事は、2010年12月28日に掲載されました。2009年9月から連載を開始したiPhoneアプリ初級脱出Onlineも今回が最終回です。
まずは前回の続きで参考書籍の紹介
iOS SDK HACKSオライリー・ジャパン 2010年10月発行 吉田 悠一、高山 征大、UICoderz 著 ISBN978-4-87311-472-9
日本語のiOS関連書籍はタイトルはとてもふえましたが入門あるいは概要が多数を占めています。そんな中で登場した濃い内容の一冊がこれです。
安易に使うと副作用がありそうなテクニックとUndocumented APIを含めて紹介されていまが、それはこの本が中上級者向けに書かれているためです。自分のアプリで使う場合は必ず実際の環境で動作を確認することをお勧めします。なお基本的にはUndocumented APIを使っているアプリはAppStoreでリジェクトされます。各Hackには難易度を示す温度計のアイコンがあるので目安にしてください。
使える機種が限られているHackも多数紹介されています。これは自分のテスト環境では問題なくても対象機種の切り分けが正しくないとクラッシュなど問題が発生します。複数の実機でテストできない場合は注意が必要です。C&R研究所 2010年3月発行 林 晃著 ISBN978-4863540514
先日のMSM2010で講師をお願いした林さんの著書です。850ページを超える圧倒的なボリュームです。
Objective-Cにスポットを当てているためiOSに限らずMacOSでも使える内容です。Mac App Storeにも参入を考えている場合にも最適の一冊ですね。私の手元には第2刷がありますが、webの訂正情報を見るとさらに増刷されているようです。この訂正情報でiOS SDK 4.1で動作検証済みと発表されています。
二色刷りの見やすい構成で、目次から目的のページを見つけやすいですね。さすが逆引きです。本としての分厚さから読破するのは大変そうですが、先頭ページから順に読んでも役立つ情報満載であることが実感できました。Objective-Cの基礎知識の章だけは最初に読んでおくことをおすすめします。iOS開発者必携ですね。前回ご紹介した 詳解 Objective-C 2.0 の改訂版が発売になっていました。赤い文字で「改訂版」と表示されています。これから買うなら改訂版をお勧めします。改訂版の方が少しお安くなってます。書店によっては古い方が並んでいるのでご注意ください。
最後に
本連載のタイトルは『iPhoneアプリ初級脱出Online』でしたが1年以上の連載期間の中にiOSと名称も変わりましたね。連載期間中にOSの守備範囲もiPhone/iPod touchにiPadが加わり、RetinaディスプレイのiPhone4が登場しハードのバリエーションが増えました。OS機能も拡張が続き追いつくのが大変です。iOSのバージョンも4.2となるなどなど変化は続いています。
さらにiOSだけでなくMac AppStoreもはじまり、引き続きCocoaは注目されそうです。新たな開発者(特に若い人)の参入も続いているようです。日本語の資料はAppleからの公式のものに加え、新しい書籍や雑誌記事など情報量も大幅に増えています。
変化が激しいので最新情報を消化し続けるのは大変ですね。新たにiOSを始めた方々にとってはハードルが刻々と高くなるわけですが、まずは基礎固めをおすすめします。激しい変化に対応するためにもCocoaの基本は欠かせません。サンプルを動かしてみるレベルから基礎を固め独自のアプリケーションを開発するための情報をこの連載でお届けしたつもりですがお役に立ったでしょうか?
無料の開発環境とビジュアルなInterface Builder.appで手軽にはじめることができますが、技術の取得に近道はありません。幸いこの連載は基本的な内容ですのでいろいろな新機能とは独立してるはずです。
年末年始のお休みでまとまった時間がとれたら新たなアプリの開発や情報収集にあてられてはいかがでしょうか?
これからも情報収集や開発者仲間の交流の場としてMOSAを役立てていただければ幸いです。
私の連載はこれで終わります。新年(2011年)から『Objective-C逆引きハンドブック』の著者:林 晃さんの『新フレームワーク探求記』がはじまります、ご期待ください。 -
第29回 初級脱出のための参考書籍
この記事は、2010年12月14日に掲載されました。iPhoneアプリ初級脱出Onlineの連載も最後に近づきました。今回は参考書籍をご紹介します。
iPhoneやiPadが広く一般に認知されiOSへの関心が高まりました。そのおかげでiOS開発環境関連の技術書もたくさん出ています。書店では迷ってしまう程多くの書籍が並ぶようになりました。
最も多いのは入門のための参考図書です。今回は本格的な開発を続けられる皆さん向けに、私が入手して役にたったものを中心にご紹介します。リックテレコム 2010年1月発行 所 友太著 3,800円+税 ISBN978-4-89797-844-4
著者の所さんには今年のMSMでセミナーをご担当いただきました。
解説が具体的で詳しくiOS開発者必携の一冊と言えます。SDKはオンラインで最新ドキュメントがすべて読めますが英語のハードルとともにドキュメントの数が多い事く調べるのに時間がかかってしまいがちです。その点この本はまとまっていて、必要な情報にアクセスしやすいですね。私も常用しております。
執筆はちょうど一年前の2009年12月ですが内容はほぼ現役と言えるでしょう。
お値段は高めですが、価値ある一冊です。
秀和システム 2010年12月発行 國居貴浩著 1,800円+税 ISBN978-4-7980-2805-7
新刊です。手にして驚いたのはXcode4について書かれていた点です。Xcode4はまだプレビュー中なのでフライングですね。書籍としての寿命を考えたのでしょうが、正式バージョンが出たら画面や操作が変わる可能性はあります。(もちろん開発環境は常に変わるのでこの問題はすべての書籍や記事で共通の問題ですが)
内容はデバッグと最適化手法だけではなく、その背景や原理の解説にもかなりのページ数を裂いてます。辞書的に日々のコーデイングで参考にすると言うより、開発スキルを高めるために目を通しておき、必要な時にもう一度開く本です。補足資料も盛りだくさんでコストパフォーマンスに優れた一冊です。ソフトバンククリエイティブ 2008年6月発行 荻原 剛志著 ISBN978-4-797346800
Objective-C の日本語リファレンスとして貴重な本です。カテゴリ、プロトコルなどについての説明も詳しく解説されています。もちろんメモリ管理 や宣言プロパティ、浅いコピーと深いコピーさらに例外などの説明も載っています。
残念ながら UI 関係や描画等の解説は少ないです。
この本は 2008 年に加筆 復刻したもので Mac 用ですが iPhone にもそのまま使えるものがほとんどです。『iPhoneプログラミングUIKit詳解リファレンス』と両方そろえれば日本語のリファレンスはかなり充実します。
★この本の改訂版がもうすぐ出るそうです。改訂版ももちろん要注目ですね。開発環境はどんどん変わるので古い本は不利ですが次の書籍も良書です。 iOSのバージョンは2、3、4と上がってきました。iOS3あるいはiOS4から開発を開始した方には貴重な情報があるかも知れません。 逆に新しいOSでは不要になったテクニックなどもあるので参照する場合にはバージョンを確認し、動作もご自身の環境で確認する事をおすすめします。
ソフトバンククリエイティブ デイヴ・マーク、ジェフ・ラマーチ著 鮎川不二雄訳 ISBN978-4797354010
教科書として最初から読み進むと大変役立つ事請け合いです。ただしSDKのバージョンは2.2.1で書かれています。このためXcodeやInterface Builderの画面は変わっている部分もあるでしょう。「はじめての」とありますが内容は高度なものも少なくありません。(だからこそ今でも役に立つわけです)
ただし辞書的な使い方は難しいです。各章のタイトルの付け方がしゃれていますが、iOSデバッグ&最適化技法の著者に影響を与えているかもしれません。アスキー・メディアワークス 2009年9月発行 木下誠著 ISBN978-4048679916
この本の著者木下さんにもMSM2010でセッションをご担当いただきました。
iPhone SDK 3.0 で加わった部分の解説が載っています。広範囲にわたって丁寧に解説されています。
iOSは多くの機能を持っているため一人ですべてをカバーすることは難しいものです。web、マップ、音声再生、アニメーション、OpenGLなど得意分野以外の情報を得るための第一歩には最適です。
iPhone SDKやObjective-Cについても解説しているので、Cocoaがはじめての場合にも役立つでしょう。オライリー・ジャパン 2009年9月発行 Jonathan Zdziarski 著 近藤誠監訳 武舎広幸武舎るみ訳 ISBN978-4873114170
高度なアプリケーションを開発する際の基礎となる多くの情報が得られる本です。深い内容も含み付録Aと付録Bには日本語版オリジナルの部分もあります。手軽に開発をはじめようとする書籍とは違い本格的に取り組むための内容です。最後に
apple社の公式日本語訳ドキュメントもふえてきましたが、これらの書籍を入手するといつでも参照できる点が強みですね。一冊の情報量の多さも魅力です(そのぶん読むのが大変ですが)。最初の頃に読んだ本でもある程度経験を積み後から見直すとすっきり理解できたり、見落としていた記述や理解できなかった部分など新しい発見があるはずです。
ここに挙げた本以外にも書店には多くのiOS開発関連書籍が並んでいます。実際に見比べて自分に合ったものを選んでください。 -
第28回 二次元描画 その10 高解像度対応
この記事は、2010年11月23日に掲載されました。技術評論社のSoftware Design 2010年12月号に記事(iPhone OSアプリ開発者の知恵袋【8】やってみてわかったiPad用アプリ開発のつぼ)を書きました。10月号の小池さん、11月号の中野さんとモサ伝Online執筆陣が続いています。第2特集はiOS4ですのでぜひご覧ください。
第20回二次元描画 その2 描画環境 で【この連載はiPhone4に最適化していません。互換性はありますが従来の画面解像度を前提に解説します。】と書きました。この連載はもともとMOSAで開催した初級脱出セミナーのテキストを元にしていて、そのセミナーはiPhone4発表前に開催だったためです。
Whiteは未だに発売されませんがiPhone4は大変な勢いで普及したようです。また画面の美しさもきわだち、同じく高解像度のiPod touchも発売になりました。そこで二次元描画の最後に高解像度対応をまとめます。高解像度とは
iPhone4は画面サイズは同じで表示するピクセル数が縦横ともに二倍の960×640となっています。解像度326ppiです。57×57のアイコンを57×57ピクセルで表示すると画面上の大きさは縦横半分に見えるはずです。
iPhone4登場前に液晶解像度が960×640らしいと噂が流れたとき「高解像度で縦横1/2に小さく表示されてもiPhoneならピンチ操作で簡単に拡大できるので大丈夫」のようなことを書いていたブログを目にしました。しかしAppleはそのような事をするはずはありません。同じ大きさに表示しています。かつて72DPIのMacの画面と300DPIのレーザープリンタ出力がWYSIWYG同じ大きさに出力したように。
大きさは同じでも4倍の情報量で文字表示などくっきり表示します。高解像度は表示品質を高めるための改良です。見かけの大きさは同じなのでタッチイベント関連の調整や変更は必要ありません。
既にiOS4.2SDKも正式になりましたが、4.1SDKのシミュレータでも縦横2倍のiPhone4をメニューで選べます。ハードウェア>デバイス>iPhone4また実機iPhone4で画面キャプチャを撮ると縦横のピクセル数は従来の二倍になります。カメラ内蔵のiPad touchも高解像度となっていますが、ソフトウェア的にはiPhone4と同じ環境です。(ただし搭載メモリは異なります)
ベクトル描画は高い互換性
UIBezierPathやCGPathRefなど直線や矩形などベクトル描画は高解像度にそのまま対応しています。円や斜めの線で特に効果を発揮しますが、水平垂直線であっても線幅や始点終点位置が整数でない場合もくっきり表示できます。iPhone4専用のアプリであれば積極的に始点終点座標などで0.5を活用する事も考えられます。3GSなどもサポートする場合は従来の組み方そのままで問題ありません。
文字描画も互換性あり
UIKitなどで表示する文字列は高解像度対応です。テキストビューとテキストフィールド、ボタンのタイトル、テーブル、ピッカーの文字アイテムなどを含めて自動的に対応し美しく表示してくれます。何も変更は必要ありません。
問題は画像表示
高解像度を生かす表示で問題となるのは画像の表示です。アプリケーションが持っている画像以上に高解像度には表示できないためです。一方ベクトル描画や文字描画は実行時にレンダリングするためOS側が対応処理をやってくれます。
通常アプリケーションはファイルサイズとメモリそれに表示時間をむだにしないため表示解像度に対応した解像度の画像を組み込みます。標準のToolbar itemのアイコンは自動的に高解像度で描いてくれます。カスタムのitemがある場合は注意が必要です。高解像度のものと並ぶと差が目立ちます。
高解像度にも対応する場合は『高解像度用画像』を別に用意して実行環境により使い分けます。この使い分けについては便利な仕組みが用意されていて高解像度画像のファイル名の末尾を@2xにするだけで対応となります。画像ファイルの例: image_a.png ←従来用のpng画像ファイル image_a@2x.png ←高解像度用のpng画像ファイル 利用するソースコードは UIImage *a = [UIImage imageNamed:@"image_a"]; // これだけで高解像度自動対応
高解像度画面のデバイスでは、UIImageクラスのimageNamed:メソッド、imageWithContentsOfFile:メソッド、 initWithContentsOfFile:メソッドは自動的に、要求された画像のうち名前に@2xの修飾子が付いているバージョンを探します。実行環境の判断はアプリケーション側では不要です。手軽で確実に高解像度対応となります。
ただし縦横2倍でメモリは4倍消費するのでメモリ不足には注意が必要です。高解像度を生かした表示
このように従来手法のアプリケーションであってもUIKitが高解像度に対応していることから高い互換性で美しい表示ができます。
高解像度に特化した表示としてはたとえば従来の1/2の線幅の極細線などが考えられます。ただし実行環境で切換える対応をしなければ従来機種では薄い線となってしまいます。アプリケーションのアイコン・設定/検索アイコンと起動画面は解像度別に用意することができます。
なおここまでは二次元描画で解説した範囲が前提でOpenGLやCore Animationのレイヤを直接扱う場合などでは別途高解像度に対応する必要があります。
-
第27回 二次元描画 その9 アニメーション
この記事は、2010年10月28日に掲載されました。前回は表示を変化させる方法とUIImageViewの簡易アニメーションをご紹介しました。UIImageViewの簡易アニメーションは『アニメーション』ではありますがぱらぱらアニメでした。drawRect:メソッド内での描画を繰り返して変化を描くため滑らかな表現では限界があります。今回はiPhoneではおなじみの、グラフィックプロセッサが処理するなめらかな表示のアニメーションです。
アニメーションブロック
UIViewのReferenceにAnimating Viewsのタイトルでクラスメソッドが15載っています。
アニメーションブロックはbeginAnimations:context:ではじまりcommitAnimationsで終わります。
アニメーションブロック内でアニメーション終了時の状態を設定します。commitAnimationsでアニメーションが開始します。
※iOS4.0以降ではこのメソッドではなくblocksベースのアニメーションが推奨されています次のサンプルコードはボタンのアクション内でシンプルなアニメーションを指定する例です。ViewControllerにこのアクションメソッドを記述したとして、ViewControllerのviewの背景色を黄色に変更しボタンが移動するアニメーションを開始します。
- (IBAction)button2Action:(id)sender { // アニメーションブロック開始 ここではデリゲートを使わないのでIDは指定していない [UIView beginAnimations:nil context:NULL]; [UIView setAnimationDuration:3.0]; self.view.backgroundColor = [UIColor yellowColor]; CGRect frame = [sender frame]; frame.origin.x += 100.0; frame.origin.y += 200.0; [sender setFrame:frame]; // アニメーションブロックの終わり RunLoopに戻るとアニメーション開始 [UIView commitAnimations]; }
アニメーションの表示
アニメーション表示は自動的に別のスレッドで制御されなめらかに行われます。アニメーション開始時と終了時に呼ばれるデリゲートメソッドで特定の処理を設定する事もできます。
setAnimationWillStartSelector:
setAnimationDidStopSelector:
一般的にはアニメーション中のタッチ操作などはできないようにするべきでしょう。操作を受け付けると表示のつじつまが合わなくなる可能性があります。アニメーションのパラメータ
パラメータもクラスメソッドで設定します。呼ばない場合はデフォルトが適用されます。
+ (void)setAnimationCurve:(UIViewAnimationCurve)curve
4種類のアニメーション変化パターンのなかから一つを指定できます。デフォルトはUIViewAnimationCurveEaseInOutです。
+ (void)setAnimationDuration:(NSTimeInterval)duration
アニメーションの継続時間を秒単位で設定します。デフォルトは0.2秒です。
+ (void)setAnimationRepeatCount:(float)repeatCount
繰返し回数を設定します。デフォルトは繰り返しなしです。
+ (void)setAnimationRepeatAutoreverses:(BOOL)repeatAutoreverses
自動的に逆転アニメーション表示するかを設定します。デフォルトはNOです。
アニメーション可能なプロパティ
UIViewのアニメーション可能なプロパティは
frame 大きくなる、小さくなる、移動する bounds 拡大縮小、スクロール center 移動する transform 回転する、変形する、移動する alpha フェードアウト、フェードイン
となっています。どれもオフスクリーンのビットマップを書き換えることなく可能なものばかりです。これらの組み合わせだけにすると滑らかなアニメーションとなります。
最初のサンプルコードは3秒間かけて背景色とボタンの位置がx方向y方向ともに変化するアニメーションでした。確認してみてください。
トランジション
現在4つのトランジションアニメーションを指定できます。
+ (void)setAnimationTransition:(UIViewAnimationTransition)transition forView:(UIView *)view cache:(BOOL)cache
UIViewAnimationTransitionCurlUpでは現在の表示がめくれ上がり、下からviewパラメータで指定したビューがあらわれます。
UIViewAnimationTransitionCurlDownでは逆に現在の表示の上にカールしていたviewパラメータで指定したビューがUIViewAnimationTransitionCurlUpとは逆のアニメーションであらわれます。cacheパラメータは実際に試してみてください。最初のサンプルコードにトランジションを加えてみます。
- (IBAction)button2Action:(id)sender { // アニメーションブロック開始 ここではデリゲートを使わないのでIDは指定していない [UIView beginAnimations:nil context:NULL]; [UIView setAnimationDuration:3.0]; self.view.backgroundColor = [UIColor yellowColor]; CGRect frame = [sender frame]; frame.origin.x += 100.0; frame.origin.y += 200.0; [sender setFrame:frame]; // カールアニメーション [UIView setAnimationTransition:UIViewAnimationTransitionCurlUp forView:self.view cache:NO]; // アニメーションブロックの終わり RunLoopに戻るとアニメーション開始 [UIView commitAnimations]; }
cacheパラメータをNOにしておくとボタンの位置と背景色はめくれるとともに徐々に変化します。ところがこのパラメータをYESにするとボタン位置と背景色はアニメーションが完了してから一瞬で切り替わります。
キャッシュしない場合は計算量が増えCPUの負荷になる可能性があります。UIViewAnimationTransitionFlipFromLeft、UIViewAnimationTransitionFlipFromRightはXcodeのテンプレートUtility Applicationで確認してください。
アニメーションしない場合
UIViewのアニメーションはどれもクラスメソッドで特定のビューに対するものでない事に注意してください。
見えているviewが変化しない場合:
アニメーション可能なプロパティを変化させているか確認してください。トランジションで指定したviewがあらわれない場合:
指定したviewがwinndowや背景viewのsubviewになっているか確認してください。view階層に含まれないと表示されません。もちろんframeの位置と幅か高さがゼロでないかも確認してください。
setAnimationTransition:forView:cache:の場合はforViewはもちろんcacheパラメータに注意してください。
アニメーションもRunLoopにもどる必要があります。処理を抜けてRunLoopに戻っていることを確認してください。 -
第26回 二次元描画 その8 表示を変化させる
この記事は、2010年10月12日に掲載されました。表示タイミング
drawRect:メソッドに描画のコードを記述したUIViewを継承したクラスのインスタンスに、setNeedsDisplayメッセージを送ってもただちに画面に表示されるわけではありません。setNeedsDisplayメソッドは再表示が必要なフラグをセットするだけで実際にはdrawRect:を処理しません。
ついsetNeedsDisplayメソッドが表示しているような気がしてしまいますが、フラグをセットしているだけであることは重要です。
静止画を表示するのであればどこかでsetNeedsDisplayメッセージを送ってさえあれば問題はありません。
表示を変化させるにはsetNeedsDisplayメッセージを送った処理(メソッド)からreturnしてRunLoopに戻らなければなりません。時間による変化はタイマーなどの処理が必要
ぱらぱらアニメーションをやるとするとタイマーを使うかタッチなどでタイミングを取る必要があります。
あるメソッド内のループ処理で表示パラメータを変化させても実際に表示されるのはRunLoopに戻ってからですので、最後のパラメータで一度だけ表示することになります。これが画面が変化しない原因です。なおあらかじめ画像を複数用意したぱらぱらアニメーションは後述するUIImageViewの簡易アニメーションで実現できます。パラメータを少しずつ変化させアニメーション表示するにはタイマーかアクションメソッドで表示パラメータを変更してsetNeedsDisplayを繰り返します。
この方法はdrawRect:メソッド内の描画処理が複雑になると一こまの表示に時間がかかります。複雑な表示をなめらかなアニメーションにするには工夫が必要になります。タッチの移動で表示を変化させる
次のようにコーディングするとタッチ位置m_touchPointが変化すると画面が変化します。
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { UITouch *touch = [touches anyObject]; m_touchPoint = [touch locationInView:self]; [self setNeedsDisplay]; } - (void)drawRect:(CGRect)rect { // 描画環境を得る CGContextRef myContext = UIGraphicsGetCurrentContext(); // 線色・線幅を指定し直線を描く CGContextSetRGBStrokeColor(myContext, 0.6, 0.0, 0.0, 1.0); CGContextSetLineWidth(myContext, kLineWidth); CGContextMoveToPoint(myContext, 0.0, 0.0); CGContextAddLineToPoint(myContext, m_touchPoint.x, m_touchPoint.y); CGContextStrokePath(myContext); }
タッチ移動時に呼ばれるメソッドで変化したタッチ位置をインスタンス変数にしまってからsetNeedsDisplayを呼び、すぐにRunLoopに戻っています。
touchesMoved:withEvent:はタッチ位置が変化すると呼ばれます。変化しない場合は呼ばれません。このメソッドが呼ばれないとsetNeedsDisplayも呼ばれないので無駄な再表示はされません。たったこれだけで滑らかなラバーバンド表示が可能なので、ぜひサンプルプログラムを作り上記のコードを試してください。
iPhoneのアニメーション
iOSで使う事のできるアニメーションを列挙すると
UIImageViewの簡易アニメーション
UIViewのプロパティ変化のアニメーション
UIViewのトランジションアニメーション
CAAnimationとCATransition
などがあります。
UIViewのプロパティ変化は次回に詳しくご紹介します。
CAAnimationとCATransitionはアップルのサンプル『ViewTransitions』があります。UIImageViewの簡易アニメーション
UIImageViewクラスは静止画を手軽に表示できますが、それだけでなく簡易アニメーション表示も可能です。
animationImagesプロパティに複数の画像を格納したNSArrayを設定します。
有限の繰返し回数でアニメーションを止める場合にはanimationRepeatCountプロパティに回数をセットします。
animationDurationに全体の表示時間を秒単位で設定します。
(5枚の画像で5.0秒だと1枚を1秒表示する簡易アニメーションになります)UIImageViewにstartAnimatingメッセージを送るとアニメーションを開始します。利用するアプリケーションがタイマーを準備したりする必要はないので手軽に使う事ができます。
止めるためのメソッドはstopAnimatingです。アニメーション中かどうかはisAnimatingメソッドで判断できます。 -
第25回 二次元描画 その7 表示されない場合のチェックポイント
この記事は、2010年09月30日に掲載されました。プログラム中にコードを書いているのに意図したとおり表示されずに困ることがあります。違った位置に表示される場合は比較的容易に修正できますが、何も表示されない場合に時間のロスにならないようチェックポイントをまとめました。
表示されない場合のチェックポイント
表示されるとかどのような状態でしょうか? あまりに当たり前ですが次の二つが必要です。
画面内でなければ表示されない。
サイズが1ピクセル以上で色の違いがなければ表示されない。
つまり 位置 と 色 が問題です。【□1 drawRect:が呼ばれているか】
drawRect:は表示が必要で表示可能になったタイミングでRunLoopから呼ばれます。プログラムからdrawRect:を呼ぶ事はできません。
通常setNeedsDisplayメッセージをviewに送ることでRunLoopが適切なタイミングでdrawRect:を呼びます。複数回setNeedsDisplayメッセージを送ってもdrawRect:は一回だけ処理されます。
この点は重要です。特にiOSプログラミングをはじめて間もない場合は見落としがちです。
drawRect:内に描画プログラムを記述してもdrawRect:メソッドが呼ばれなければ表示されることはないのです。プログレスインジケータが変化しない
ループで処理している最中にUIProgressViewのprogressパラメータを設定しても表示は変化せず、最終結果だけを表示します。
プログレス表示を変化させるにはprogressパラメータを設定するごとにRunLoopに戻る必要があります。UIProgressViewのdrawRect:メソッドもRunLoopが呼ぶ事に変わりはありません。【□2 画面内か】
viewのframeは画面内か
矩形を確認するのはNSLog(@"tRect=%@ self.bounds=%@",NSStringFromCGRect(tRect), NSStringFromCGRect(self.bounds));
などとNSStringFromCGRect()関数を使ってNSLog出力するのが手軽で確実です。
実際の画面は矩形の枠を表示して確認します。それで確認できない場合は対角線を描くか特定の色で塗りつぶします。どちらも矩形がboudsよりも大きい場合の確認になります。回転などの設定
今回の連載では説明していませんが、座標変換のマトリックスを指定して変形や回転が可能です。この設定により回転前に表示された座標が画面の外になる場合があります。
回転は強力ですが、一度でも回転が入ると座標の把握がとたんに困難になります。まずは回転させずに表示されることを確認し、回転後も確実に画面内に収まる回転角度で確認するのが得策です。view内の描画位置(上下左右逆・xy逆)
xとy座標を逆にする、相対位置と絶対位置の取り違えなど座標のミスで画面内に収まらないことはしばしば発生します。外接矩形を表示してみるなど確認することで無駄な時間を減らしましょう。【□3 色と透明】
背景色と同じ色で描画していないか
白い背景で白く塗りつぶしは『消す』処理となります。背景色と同じ色で描いても見えません。
赤など背景色とは完全に違う色をしてして確認しましょう。
色指定の『枠線(ストローク)』と『塗り』の違いも重要です。文字は『塗り』の色指定になります。alpha 透明で描いていないか
同様に透明度0.0では何も表示されません。またviewの透明度が0.0で見えない場合もあります。
アニメーションのために透明にしたビューには描いても画面に表示されません。【□4 文字のチェックポイント】
フォントサイズ過大過小
フォントサイズが巨大すぎて文字が画面外になると見えません。また小さすぎて気づかない場合もあります。文字列は存在するか
文字列オブジェクトがnilの場合、文字数がゼロの場合は当然表示されません。
部分文字列切り出しの失敗や、ローカライズ文字列の設定忘れなどにご注意ください。【□5 画像のチェックポイント】
画像は存在するか
画像オブジェクトがnilの場合、画像の高さか幅がゼロの場合は当然表示されません。
対応できない画像フォーマットではオブジェクトがnilの場合があります。巨大な画像はメモリ不足で読み込めない場合もあるかも知れません。
アプリケーションのバンドル内への組み込みに失敗している場合、実機では表示できないなどの問題が発生する場合があります。背景色と同じ色の画像ではないか
外部の画像を表示する場合背景色に注意が必要です。背景が透明で黒い輪郭の画像を黒い背景に描くと区別できません。iPhoneは透明な画像も扱えるので『画像は白地に描かれている』との思い込みがあると表示されない原因を突き止めるのに時間がかかってしまいます。(これは私の体験談です)
表示位置等が正しいのに見えない場合は背景色を極端に変更して確認しましょう。透明画像ではないか
存在する画像であっても、透明だったり、表示する背景と同じべた塗りの可能性もゼロではありません。白い背景に白い丸を表示しても見えません。表示する画像は別のアプリで確認しましょう。このように表示されない原因や状況は様々でも、チェックが必要な項目は限られています。基本に戻って確認しましょう。
★それでも駄目な場合は散歩など気分転換が効果的かもしれません。 -
第24回 二次元描画 その6 画像ファイル表示
この記事は、2010年09月14日に掲載されました。画像データはUIImage
UIKitで画像はUIImageクラスで扱います。
クラスメソッドの
+ (UIImage *)imageWithContentsOfFile:(NSString *)path
でオートリリースのオブジェクトを得るのが手軽です。NSString *path = [[NSBundle mainBundle] pathForResource:@"graduation1" ofType:@"png"]; UIImage *thumbNail1 = [UIImage imageWithContentsOfFile:path];
そのほかNSDataやCGImageから変換することも可能です。
+ (UIImage *)imageNamed:(NSString *)name
クラスメソッドのimageNamed:は画像ファイルのフルパスではなく、ファイル名だけ渡せば良いので手軽ですが、キャッシュされます。常時再表示に必要な画像などでなければメモリリークと同じ状態となるので注意して使いましょう。
注意:クラスメソッドなのでimageNamed:が返すオブジェクトはrlease不要です。UIImageで扱える画像フォーマット
UIImageが扱える画像フォーマットはClass Referenceに明記されています。
※今後iOSのバージョンで扱える画像が変更になるかも知れません。正式なリファレンスを参照してください。iPhoneではpngがネイティブフォーマットとされています。ただしMacのpngとはrgbaの並び順が違うようです。Xcodeでデバイス用にビルドする時にiPhone用に最適化されてアプリケーションのバンドルにコピーされます。このためアプリケーションバンドル内のpngファイルはMacでは正常に表示できません。大きな画像を含むアプリケーションを実機用にビルドする場合この最適化に時間がかかる場合があります。
drawInRect:メソッドで表示
UIImageの表示はdrawInRect:とdrawAtPoint:があります。私は主にdrawInRect:を使っています。好みの問題ですが、座標がフリップしているとdrawAtPointでは指定した点が画面内でも表示する文字や図形がはみ出す場合があるのです。
drawInRect:メソッドは指定した矩形の中に描かれるので、表示されない場合の確認がひとつ少なくて済みます。(矩形は対角線が表示されれば画面内である事がすぐに確認できます)drawInRect:メソッドは画像・文字共通です。
ブレンドモードの指定等が可能なメソッドもありますのでドキュメントを参照してください。
UIImageViewの利用
UIImageViewは画像表示専用のビューです。一つの画像だけ表示する場合は手軽に使えます。特に固定画像を表示する場合はIBで設定するだけでコードは不要です。
IBのmodeはUIViewのcontentModeプロパティです。いろいろなモードでUIImageViewのフレームと画像の関係を確認してください。
modeをUIViewContentModeScaleAspectFillにするとIBではクリップされますが、アプリケーションをシミュレータで実行するとviewのframeを超えて表示されます。(iOSのバージョンで挙動が変わるかも知れません)
frame内だけ表示するにはClip Subviewsをチェックします。UIImageViewはデフォルトではタッチを受け付けません。
UIScrollViewで画像を扱う場合にはタッチ操作をすべてUIScrollViewでおこなうことになるので問題ありません。UIImageViewのimageプロパティはretain属性です。
UIIvageViewは画像の配列を渡し簡易アニメーション表示も可能です。さらにアクセシビリティにも対応していて、ラベルとヒントを読み上げる事ができます。ラベルやヒントはIBで設定する事ができます。
-
第23回 二次元描画 その5 フォントと文字表示
この記事は、2010年08月24日に掲載されました。iPhoneのフォント
iPhone/iPadでもフォントそのものはMacと同じですが、iPhoneのフォントはユーザーが追加インストールすることはできません。システムのフォントファイルはサンドボックスの外です。(アプリケーションが個別にフォントを持つ事は可能です)
フォントのクラスはUIFontです。フォントファミリー
フォントはファミリーに分かれています。ファミリーは太さ(ウエイト)や斜体などだけが違う同一書体です。日本語ではヒラギノ角ゴシックやヒラギノ明朝がファミリーです。
MacのFontBookで見た英文書体Optimaファミリー
このファミリーにはイタリック、エクストラブラック、ボールド、ボールド・イタリック、レギュラーがあります。どのようなフォントが使えるかはUIFontのクラスメソッド
+ (NSArray *)familyNames
で得るフォントファミリーの名称から
+ (NSArray *)fontNamesForFamilyName:(NSString *)familyName
でフォント名を得る事で確認できます。
次のソースは利用可能な全フォントをダンプするコード例です。// フォントの確認 NSArray *families = [UIFont familyNames]; id aFamily; for (aFamily in families) { NSLog(@"\nfamilyName=%@\n fontname=%@", aFamily, [UIFont fontNamesForFamilyName:aFamily]); }
applicationDidFinishLaunching: などに上記コードをペーストして確認してみてください。
通常はフォント名の指定は省略し
+ systemFontOfSize: + boldSystemFontOfSize:
で十分でしょう。日本語はヒラギノ角ゴシックの二種類のウエイトしか使えずそれぞれこのメソッドで指定できるのです。
文字描画
文字表示はNSString UIKit Additionsを使います。この記事の最後に掲載したサンプルではdrawInRect:withFont: を使っています。
drawInRect:withFont:lineBreakMode:alignment:を使うと矩形に収まらない場合の省略と字詰めを設定できます。Getting the Drawing Rect of a String
– sizeWithFont: – sizeWithFont:forWidth:lineBreakMode: – sizeWithFont:constrainedToSize: – sizeWithFont:constrainedToSize:lineBreakMode: – sizeWithFont:minFontSize:actualFontSize:forWidth:lineBreakMode:
Drawing String Objects
– drawAtPoint:withFont: – drawAtPoint:forWidth:withFont:lineBreakMode: – drawAtPoint:forWidth:withFont:fontSize:lineBreakMode:baselineAdjustment: – drawAtPoint:forWidth:withFont:minFontSize: actualFontSize:lineBreakMode:baselineAdjustment: – drawInRect:withFont: – drawInRect:withFont:lineBreakMode: – drawInRect:withFont:lineBreakMode:alignment:
これらのメソッドでは文字ごとに異なるサイズや色の指定はできません。必要な場合はサイズや色別に文字列を分割し描画します。
なお文字をCoreGraphicsのAPIだけで表示するのはかなり大変です。上記のメソッドを利用する事をおすすめします。日本語禁則処理
下記サンプルで示すように禁則処理は– drawInRect:withFont:lineBreakMode:のlineBreakModeパラメータで指定します。
UILineBreakModeWordWrapで禁則処理有効状態です。
UILineBreakModeCharacterWrapでは文字で改行となります。UILable
UILabeleは独立した文字表示専用viewです。アクセシビリティにも対応していて、日本語での読み上げが可能です。
テーブルビューなどでは標準部品なので重宝します。独自描画と組み合わせることも可能です。特質を検討して採用してください。サンプルソースコード
- (void)drawRect:(CGRect)rect { // nibで指定した背景色を使う NSString *str1 = @"いろいろな方法があるがdrawInRect:がおすすめ"; CGRect tRect = CGRectMake(10.0, 10.0, 70.0, 20.0); [str1 drawInRect:tRect withFont:[UIFont systemFontOfSize:14]]; UIRectFrame(tRect); NSLog(@"tRect=%@ self.bounds=%@",NSStringFromCGRect(tRect), NSStringFromCGRect(self.bounds)); // 文字色指定 [[UIColor redColor] setFill]; tRect = CGRectMake(10.0, 40.0, 300.0, 20.0); [str1 drawInRect:tRect withFont:[UIFont systemFontOfSize:14]]; UIRectFrame(tRect); // 枠色指定 [[UIColor grayColor] setStroke]; // 文字色 [[UIColor lightTextColor] setFill]; tRect = CGRectMake(10.0, 70.0, 70.0, 20.0); [str1 drawInRect:tRect withFont:[UIFont systemFontOfSize:14] lineBreakMode:UILineBreakModeTailTruncation]; UIRectFrame(tRect); tRect.origin.x += 80.0; [str1 drawInRect:tRect withFont:[UIFont systemFontOfSize:14] lineBreakMode:UILineBreakModeMiddleTruncation]; UIRectFrame(tRect); // 文字色 [[UIColor darkGrayColor] setFill]; tRect.origin.x += 80.0; [str1 drawInRect:tRect withFont:[UIFont systemFontOfSize:14] lineBreakMode:UILineBreakModeHeadTruncation]; UIRectFrame(tRect); // 文字色 [[UIColor blackColor] setFill]; tRect = CGRectMake(10.0, 110.0, 140.0, 20.0); [str1 drawInRect:tRect withFont:[UIFont boldSystemFontOfSize:30] lineBreakMode:UILineBreakModeTailTruncation]; UIRectFrame(tRect); // BreakModeと日本語の禁則処理 NSString *str2 = @"アイフォーン"; tRect = CGRectMake(5.0, 150.0, 150.0, 90.0); [str2 drawInRect:tRect withFont:[UIFont boldSystemFontOfSize:36] lineBreakMode:UILineBreakModeWordWrap]; UIRectFrame(tRect); tRect = CGRectMake(165.0, 150.0, 150.0, 90.0); [str2 drawInRect:tRect withFont:[UIFont boldSystemFontOfSize:36] lineBreakMode:UILineBreakModeCharacterWrap]; UIRectFrame(tRect); // // 描画環境を得る CGContextRef myContext = UIGraphicsGetCurrentContext(); // 背景色を指定してview全体を塗りつぶす CGContextSetRGBFillColor(myContext, 0.8, 0.8, 1.0, 1.0); CGContextFillRect(myContext, CGRectMake(4.0, 250.0, 312.0, 206.0)); // 線色を指定 CGContextSetRGBStrokeColor(myContext, 0.3, 0.3, 0.3, 1.0); // 文字の色はFillColor CGContextSetRGBFillColor(myContext, 0.7, 0.0, 0.0, 1.0); str2 = @"初級脱出完了"; tRect = CGRectMake(10.0, 260.0, 300.0, 40.0); [str2 drawInRect:tRect withFont:[UIFont boldSystemFontOfSize:36] lineBreakMode:UILineBreakModeTailTruncation]; UIRectFrame(tRect); // 文字色変更 CGContextSetFillColorWithColor(myContext, [UIColor lightGrayColor].CGColor); tRect = CGRectMake(10.0, 310.0, 300.0, 40.0); [str2 drawInRect:tRect withFont:[UIFont boldSystemFontOfSize:36] lineBreakMode:UILineBreakModeTailTruncation alignment:UITextAlignmentCenter]; UIRectFrame(tRect); // 文字色変更 CGContextSetFillColorWithColor(myContext, [UIColor darkTextColor].CGColor); tRect = CGRectMake(10.0, 360.0, 300.0, 40.0); [str2 drawInRect:tRect withFont:[UIFont boldSystemFontOfSize:36] lineBreakMode:UILineBreakModeTailTruncation alignment:UITextAlignmentRight]; UIRectFrame(tRect); }
-
第22回 二次元描画 その4 色と透明度とグラデーション
この記事は、2010年08月10日に掲載されました。色はUIColorとCGColor
CoreGraphicsの色指定はCGColorですが、UIKitではUIColorが便利です。UIColorのプロパティで簡単にCGColorを得ることができます。
UIColorにsetFillメッセージを送るとその色を塗りつぶしに設定できます。同様にsetStrokeメッセージを送るとその色を枠線の色に設定できます。どちらも色を指定するだけで実際の描画はその後に必要なメソッドまたはAPIコールが必要です。
色は単純な色のほかに+ (UIColor *)groupTableViewBackgroundColorのようなべた塗りでないパターン(iPadではグラデーション)も可能です。iOS4になってからgroupTableViewBackgroundColorの挙動が変わったようです。使っている場合は動作を確認してみてください。任意のイメージからパターンを作成するメソッド
+ (UIColor *)colorWithPatternImage:(UIImage *)image
もあります。このメソッドは応用範囲が広く利用価値が高いです。
色と透明度
透明度はrgbに次ぐ第4のパラメータで設定できます。一般にiPhoneでは透明度を効果的に利用することが多いので色は次のメソッドで作ります。
- (UIColor *)initWithRed:(CGFloat)red green:(CGFloat)green blue:(CGFloat)blue alpha:(CGFloat)alpha
ほかにもいろいろなメソッドがあるので適したものを使ってください。各パラメータは0.0から1.0の範囲で与えます。rgbの各成分がすべて0.0なら黒、すべて1.0なら白です。
rだけが1.0でほかが0.0なら赤になります。redColorなど既存の色に透明度を追加した新しい色を得るためのメソッドcolorWithAlphaComponent:もあります。(これは新しいオートリリースのUIColorオブジェクトを返します)
色は奥が深い
カラースペースなど色は奥が深いですが、ここでは単純化しrgbaだけ扱います。効果的なグラデーション
グラデーションは視覚効果が高い表現です。
ただしグラデーションを背景に文字を表示する場合等には色のコントラストに注意が必要です。階調が美しくても文字が認識しにくくてはマイナス評価になります。
Appleのサンプル「QuartzDemo」にもグラデーション表示のサンプルがあります。古いバージョンのソースはかなり複雑になっていますので、ぜひ最新の「QuartzDemo」のQuartzRendering.mを確認してください。グラデーションはCGGradientRefを用います。
サンプルのようにCGGradientCreateWithColorComponents()関数がわかりやすいと思います。
このサンプルは通常のテンプレートから作ったプロジェクトで、最初からCoreGraphics.frameworkがリンクされています。実際にグラデーションで塗りつぶすAPIは
void CGContextDrawLinearGradient( CGContextRef context, CGGradientRef gradient, CGPoint startPoint, CGPoint endPoint, CGGradientDrawingOptions options );
です。これは直線的に変化するもので「QuartzDemo」は同心円に変化するCGContextDrawRadialGradientも使われています。
CGGradientRef作成時のstartとendの色がそれぞれの点位置に対応します。グラデーションの要素
グラデーションの要素は(Linearの場合)
始点の色
終点の色
始点の位置
終点の位置
描画オプション(グラデーション範囲の前後を塗りつぶすかどうか)
となります。グラデーションと色の使い分け
グラデーションは効果的ですが、実行時の計算量が多く負荷が高いはずです。
色は描画の基本的な要素ですが、グラデーションは特別です。どんなに複雑なグラデーションでも通常解像度ならたかだか320*480ピクセルの画像に収まりますので、グラデーション処理するか画像で持つか検討しましょう。
-
第21回 二次元描画 その3 図形
この記事は、2010年07月28日に掲載されました。線と多角形
CoreGrapicsの世界ではパスと呼ばれる線で図形を表し、線の幅や色の指定、塗りつぶすなどして描画します。パスは強力でパラメータも多いので詳しくはご自身で確認してください。
前回サンプルコードが直線です。矩形枠の表示と塗りつぶしのサンプルコードを最後に掲載します。
今回のコードで多角形は四角までです、星形などはAppleのサンプル「QuartzDemo」を参照してください。(QuartzDemoはたびたびバージョンアップされています。)線色指定 CGContextSetRGBStrokeColor
線幅指定 CGContextSetLineWidth
始点設定 CGContextMoveToPoint
次点設定 CGContextAddLineToPoint
枠線描画 CGContextStrokePathUIKit関数
矩形(Rect)は頻繁に利用するため関数が用意されています。
ただし塗りつぶし・枠線ともに色の透明度は指定できません。
矩形枠線描画 void UIRectFrame (CGRect rect);
矩形塗りつぶし void UIRectFill (CGRect rect);
高さまたは幅1の矩形の枠線描画で簡易直線も描くことは可能です。塗りつぶしと枠線
UIRectFrameで枠線を描くと指定したrectの内側だけに描きます。
一方CGContextStrokePathで枠線を描くと数学的に各点を結んだ直線を中心線とする外側にも内側にも幅を取るように描きます。
このため半透明な色で枠も塗りつぶしも描くと枠との交差部分の色がかわります。
不透明な色で描くと枠と塗りつぶしどちらを先にするかで結果が異なるので注意が必要です。
枠を先に描き後から塗りつぶすと枠の幅が半分になります。塗りつぶしを先にして後から枠を描くと塗りつぶしの矩形が枠線の幅の1/2だけ小さくなります。
CGContextDrawPath()は最後のパラメータで塗りつぶしと枠線を同時に描く指定も可能です。交差するパスの塗りつぶしに関しても複数の指定が可能です。角丸矩形、円弧、円
角丸はMacでは伝統的にボタン等UI部品の基本要素です。
「QuartzDemo」サンプルでは4辺と4つの円弧を連続して描いています。これをサブルーチン化すると利用価値が高いでしょう。
下記サンプルでは円弧と円は割愛しました「QuartzDemo」を参照してください。カーブ
カーブは有名なポストスクリプトでも採用された制御点を指定することで柔軟な描画が可能です。iPhoneでもiOS 3.2からUIBezierPathクラスが利用できるようになりました。サンプルは割愛しました。「QuartzDemo」を参照してください。UIKit Function Reference
Graphics関連のUIKit関数の一覧がドキュメントにあります。頻繁に使うものが多いのでドキュメントの存在は覚えておきましょう。
UIGraphicsGetCurrentContext
UIGraphicsPushContext
UIGraphicsPopContext
UIGraphicsBeginImageContext
UIGraphicsGetImageFromCurrentImageContext
UIGraphicsEndImageContext
UIRectClip
UIRectFill
UIRectFillUsingBlendMode
UIRectFrame
UIRectFrameUsingBlendMode
があります。サンプル
#define kLineWidth (1.0) - (void)drawRect:(CGRect)rect { // 描画環境を得る CGContextRef myContext = UIGraphicsGetCurrentContext(); // 矩形を描く その1 // 枠色指定 #if 1 CGContextSetRGBStrokeColor(myContext, 1.0, 0.0, 0.0, 0.5); #else CGContextSetRGBStrokeColor(myContext, 0.5, 0.0, 0.0, 1.0); #endif // 塗り色指定 CGContextSetRGBFillColor(myContext, 0.0, 1.0, 0.0, 0.8); CGRect r1 = CGRectMake(10.0, 10.0, 70.0, 30.0); UIRectFill(r1); // 枠線幅指定 CGContextSetLineWidth(myContext, 3.0); r1 = CGRectOffset(r1, 71.0, 0.0); // 色に注意 透明でない UIRectFrame(r1); CGContextSetLineWidth(myContext, 6.0); r1 = CGRectOffset(r1, 71.0, 0.0); UIRectFrame(r1); // 矩形を描く その2 CGContextSetLineWidth(myContext, 6.0); CGFloat x = 10.0; CGFloat y = 50.0; CGFloat w = 70.0; CGFloat h = 30.0; CGContextMoveToPoint(myContext, x, y); CGContextAddLineToPoint(myContext, x + w, y); // 1 CGContextAddLineToPoint(myContext, x + w, y + h); // 2 CGContextAddLineToPoint(myContext, x, y + h); // 3 CGContextAddLineToPoint(myContext, x, y); // 4 CGContextStrokePath(myContext); CGContextBeginPath(myContext); x += w + 7.0; CGContextMoveToPoint(myContext, x, y); CGContextAddLineToPoint(myContext, x + w, y); // 1 CGContextAddLineToPoint(myContext, x + w, y + h); // 2 CGContextAddLineToPoint(myContext, x, y + h); // 3 CGContextClosePath(myContext); // 始点と結ぶ CGContextStrokePath(myContext); CGRect r2 = CGRectMake(10.0 + w + 7.0 + w + 7.0, y, w, h); CGContextStrokeRect(myContext, r2); r2 = CGRectOffset(r2, w + 7.0, 0.0); UIRectFill(r2); CGContextAddRect(myContext, r2); CGContextStrokePath(myContext); // 角丸四角形(QuartzDemoより) // If you were making this as a routine, you would probably accept a rectangle // that defines its bounds, and a radius reflecting the "rounded-ness" of the rectangle. CGRect rrect = CGRectMake(210.0, 90.0, 60.0, 60.0); CGFloat radius = 10.0; // NOTE: At this point you may want to verify that your radius is no more than half // the width and height of your rectangle, as this technique degenerates for those cases. // In order to draw a rounded rectangle, we will take advantage of the fact that // CGContextAddArcToPoint will draw straight lines past the start and end of the arc // in order to create the path from the current position and the destination position. // In order to create the 4 arcs correctly, we need to know the min, mid and max positions // on the x and y lengths of the given rectangle. CGFloat minx = CGRectGetMinX(rrect), midx = CGRectGetMidX(rrect), maxx = CGRectGetMaxX(rrect); CGFloat miny = CGRectGetMinY(rrect), midy = CGRectGetMidY(rrect), maxy = CGRectGetMaxY(rrect); // Next, we will go around the rectangle in the order given by the figure below. // minx midx maxx // miny 2 3 4 // midy 1 9 5 // maxy 8 7 6 // Which gives us a coincident start and end point, which is incidental to this technique, but still doesn't // form a closed path, so we still need to close the path to connect the ends correctly. // Thus we start by moving to point 1, then adding arcs through each pair of points that follows. // You could use a similar tecgnique to create any shape with rounded corners. // Start at 1 CGContextMoveToPoint(myContext, minx, midy); // Add an arc through 2 to 3 CGContextAddArcToPoint(myContext, minx, miny, midx, miny, radius); // Add an arc through 4 to 5 CGContextAddArcToPoint(myContext, maxx, miny, maxx, midy, radius); // Add an arc through 6 to 7 CGContextAddArcToPoint(myContext, maxx, maxy, midx, maxy, radius); // Add an arc through 8 to 9 CGContextAddArcToPoint(myContext, minx, maxy, minx, midy, radius); // Close the path CGContextClosePath(myContext); // Fill & stroke the path CGContextDrawPath(myContext, kCGPathFillStroke); // 矩形表示 その3 CGContextSetRGBFillColor(myContext, 0.0, 1.0, 0.0, 0.4); CGContextSetLineWidth(myContext, 10.0); r1 = CGRectMake(10.0, 200.0, 40.0, 30.0); UIRectFill(r1); r1 = CGRectOffset(r1, 50.0, 0.0); CGContextFillRect(myContext, r1); r1 = CGRectOffset(r1, 50.0, 0.0); UIRectFrame(r1); r1 = CGRectOffset(r1, 50.0, 0.0); CGContextStrokeRect(myContext, r1); // 簡易直線表示 CGContextSetLineWidth(myContext, 1.0); r1 = CGRectMake(10.0, 260.0, 120.0, 1.0); UIRectFrame(r1); r1 = CGRectMake(10.0, 265.0, 1.0, 100.0); UIRectFrame(r1); }
実行例
-
第20回 二次元描画 その2 描画環境
この記事は、2010年07月13日に掲載されました。どこに表示するか
iPhoneの描画の基本的な仕組みはQuartz 2Dと呼ばれるMacと同様のものです。もともとはディスプレイポストスクリプトまでさかのぼることのできる、ベクトル系の描画環境です。Framework名はCoreGraphics(CGの接頭辞)です。このフレームワークはCベースのAPIです。
XcodeのテンプレートではCoreGraphics.frameworkは最初からプロジェクトに含まれています。
CoreGraphicsの描画環境はCGContextです。ここに表示します。
描画環境には描画に使う線の色などのパラメータとキャンバスに相当するデバイスの情報がカプセル化されています。描画環境なしに描画APIは使えません。描画パラメータ
Quartz 2D Programming Guide p24よりCurrenttransformationmatrix(CTM) Clippingarea Line: width, join, cap, dash, miterlimit Accuracyofcurveestimation(flatness) Anti-aliasingsetting Color: fill andstrokesettings Alphavalue(transparency) Renderingintent Colorspace: fill andstrokesettings Text: font, fontsize, characterspacing, textdrawing mode Blendmode
座標とピクセル
iPhoneの基本的な座標系は左上を原点としています。Macは左下を原点としていてこの点iPhoneでは変わっています。これは歴史的な経緯があり、y座標には注意が必要です。
y座標の反転が必要な場合はCurrenttransformationmatrix(CTM) を使います。座標は実数で表現します。iPhoneで数学的な座標はピクセルの左上にあると考えるべきでしょう。
これは1ピクセル幅の直線を描く場合に考慮しなければなりません。
【この連載はiPhone4に最適化していません。互換性はありますが従来の画面解像度を前提に解説します。】
次のコードで確認してください。#define kLineWidth (1.0) - (void)drawRect:(CGRect)rect { // 描画環境を得る CGContextRef myContext = UIGraphicsGetCurrentContext(); // 線色・線幅を指定し直線を描く CGContextSetRGBStrokeColor(myContext, 0.6, 0.0, 0.0, 1.0); CGContextSetLineWidth(myContext, kLineWidth); CGContextMoveToPoint(myContext, 20.0, 20.0); CGContextAddLineToPoint(myContext, 120.0, 20.0); CGContextStrokePath(myContext); return; // ここをコメントにするとすべての直線を描きます CGContextMoveToPoint(myContext, 20.0, 40.5); CGContextAddLineToPoint(myContext, 120.0, 40.5); CGContextStrokePath(myContext); CGContextMoveToPoint(myContext, 20.5, 44.0); CGContextAddLineToPoint(myContext, 20.5, 140.0); CGContextStrokePath(myContext); CGContextMoveToPoint(myContext, 20.0, 42.0); CGContextAddLineToPoint(myContext, 100.0, 70.0); CGContextStrokePath(myContext); }
最初の状態では直線を1本描くだけです。
CGContextSetRGBStrokeColor(myContext, 0.6, 0.0, 0.0, 1.0);
で線の色を赤に設定し
CGContextSetLineWidth(myContext, kLineWidth);
で線の幅を1.0に設定しています。
この状態で表示しても実際には2ピクセルの幅になっています。【画面2倍拡大図(return;をコメントにしてすべて描画した場合)】
直線は始点終点を結ぶ数学的な直線の両側に幅を取り描画されるのでこのような結果になります。
複数のピクセルにまたがる場合各ピクセルの色はアンチエイリアス処理されます。(各ピクセルは一つの色しか表現できません。半分塗るようなことはできないのです。)上記実行結果で斜めに描いた直線で確認できます。
上記サンプルコード後半(return;以降)はy座標を40.5として幅1ピクセルの直線が二つのピクセルにまたがらないようにしています。
CGContextMoveToPoint(myContext, 20.0, 40.5);
CGContextAddLineToPoint(myContext, 120.0, 40.5);iPhoneで使える手段
UIKitのクラスで描画機能を持つものがあります、これらは直接利用できます。
通常の描画はCoreGrapicsのAPIを利用します。線の色を設定してから、線を描くなどAPIの呼び出し順序の影響を受けます。OpenGLも利用可能ですが、このセミナーでは説明しません。drawRect:のひな形
drawRect:のひな形は上記のサンプルコードです。UIGraphicsGetCurrentContext()関数でCGContextRefを得て、それを各描画関数に渡します。
iPhone OSがdrawRect:を呼び出す時あらかじめ描画環境をセットしてくれます。画面のビットマップメモリは自分のアプリケーションのものではありません。このため通常はメモリ保護されていて、書き込み(つまり描画)できないのです。drawRect:メソッドは描画の準備が整ってから呼ばれます。アプリケーションの任意の部分でdrawRect:メソッドを実行できないのはこのためです。UIKitでCGContextRefを渡す必要がない描画メソッドや関数もありますが、内部でUIGraphicsGetCurrentContext()を使っているものと推測されます。
-
第19回 二次元描画 その1 カスタムビューの表示
この記事は、2010年06月28日に掲載されました。今回から二次元描画を解説します。
ちょうど高解像度画面が売りのiPhone 4が発売されました。iOSはベクターグラフィックスと文字描画については基本的には高解像度に対応する高い互換性を持っています。これはMac OSにおいて画面表示処理と印刷処理が兼用でき、しかも印刷の場合にはプリンタの高解像度が適用されるのと同じです。■カスタムビューの表示
表示にはviewが必要
iPhoneで表示するためには『紙』に相当するviewが必要です。viewはもちろんUIViewそのものもしくはそれを継承したクラスのインスタンスです。
iPhoneのviewはMacのCocoa(AppKit)のViewとは違ってiPhoneに最適化され、アニメーションなど最新の要素を最初(iPhone OS 2.0)から持っています。viewのおさらい
viewには他のviewオブジェクトを貼付けることができます。また他のviewに張り付き下位のviewになることもできます。
UILabel、UIImageView、など特定の表示に特化した利用頻度の高いviewはUIKitにすでに用意されています。これらのviewを組み合わせアプリケーションを実装することが基本です。UIKitのviewはアクセシビリティにも対応していて、シンプルで強力です。
既存のviewの組み合わせでは実現できない場合はカスタムビューを作り、描画は自前でおこなうことになります。drawRect:で表示
表示のためのメソッドはdrawRect:です。
- (void)drawRect:(CGRect)rect
このメソッドだけですべての表示を行います。重要なのはこのメソッドをプログラムのコードから呼ぶことはない点です。drawRect:はシステムが呼び出すメソッドです。
drawRect:は内部で[super drawRect:rect];する必要はありません。
superを呼ばなくてもbackgroundColorで塗りつぶしが行われる仕様です。
実際にはrectの内部だけ描画する
今回のサンプルではdrawRect:のrectパラメータは利用していません。- (void)setNeedsDisplayInRect:(CGRect)invalidRect
メソッドで一部だけの再表示を行う場合drawRect:のrectパラメータに限定し効率良く描画します。
- (void)drawRect:(CGRect)rect
のrectパラメータと交差しない文字や図形は表示する必要はありません。表示とタイミング
各viewは表示用バッファメモリであるオフスクリーンのビットマップを持っています。
実際の表示はこのビットマップにdrawRect:が描画を1度だけおこない、iPhone画面の表示はオフスクリーンビットマップが使われます。ビットマップの更新が必要になった時に利用するメソッドは
- (void)setNeedsDisplay
です。
注意しなければならないのはsetNeedsDisplayメソッドを呼んだ時点ですぐに再描画されない事です。setNeedsDisplayは文字通り再描画が必要なフラグを立てるだけで、実際の描画は必要な準備が全て整ってからRunLoopの処理側で行われます。
setNeedsDisplayは複数回呼んでも実際の描画は一度だけしか行われない、効率の良いしくみです。コントロールなどの組み合わせ
viewは階層化可能です。複数のviewを組み合わせて利用するのが普通です。
通常はwindowが一番下で、その上に背景となるview、背景の上に部品と配置します。viewの階層関係はInterface Builderを操作することで実感できると思います。UIKitの標準部品を組み合わせることで、ユーザーは使い方がすぐにわかり、プログラミングもIBを使って効率よく、標準的なレイアウトが可能です。
一方で他のアプリケーションとは違った画面にして目立たなければ購入してもらえませんので、どこまでを標準にしてどのような独自表示を作るかが工夫のしどころです。■QuickDraw
余談ですが初代MacのQuickDrawのプログラミング解説の最初に数学的な座標と実際のピクセルの説明が載っていました。(Inside Macintoshより)
QuickDrawの座標は整数だったことと、1980年代はまだグラフィック環境になじみのないプログラマが少なくなかったためと思われます。
-
第18回 メモリ管理 その4
この記事は、2010年06月16日に掲載されました。WWDC10は大混雑のまま幕を閉じました。混雑は世界のiPhoneプログラマの熱気そのものでした。iPadとiPhone4を加えiOS端末所有者は増え続けApp Storeはますます巨大になっています。
iPhoneアプリ開発で重要なメモリ管理の話題の4回目。今回はプログラミング中の『心配事』とメモリ不足についてです。■メモリ管理にまつわる心配事
複雑な処理をするがautoreleaseのオブジェクトは途中で消えないのか
通常は、処理がメインのイベントループ内でイベントループに戻らなければ不意にリリースされる心配はありません。ただし関数から抜ける(returnする)とその戻り先はRunLoopかもしれないのでいつ解放されても文句は言えません。例外はスレッド処理です。別スレッドの場合はそれぞれオートリリースプールを作らなければなりません。スレッド処理中にメインのRunLoopがプールを解放する可能性があるためです。
オートリリースプールの解放では登録済みオブジェクトにreleaseメッセージが送られます。この時点でリテインカウントが2以上のものはdeallocされずに残ります。
autorelease済みであってもretainすることで所有しRunLoopに関係なく使い続けることはもちろん可能です。autoreleaseをどうやって見分けるか
オブジェクトの出所で判断するしかありません。allocとcopy以外のメソッドが返すオブジェクトはautoreleaseの可能性があります。自分が所有していても誰かが書き換える場合はある ★メモリ管理とは別の問題 インスタンス変数など自分がオーナーのオブジェクトでもよそからも参照されていれば書き換えられる可能性があります。 特に文字列の場合可能性も高く影響もてきめんなので注意してください。 書き換えを完全に防ぐにはセッターでコピーする 文字列はセッターで受け取った時にコピーする、あるいはプロパティでcopy属性にすると安心です。 ただしコピーはメモリとパフォーマンスで注意が必要 もちろんコピーするとよぶんにメモリを消費します。コピー処理にCPUのクロックも使うので時間と電池を消耗します。
■メモリ不足
iPhoneのメモリ管理
iPhoneのハードのメモリ管理は仮想記憶の仕組みでメモリのスワッピングを行いません。このためアプリケーションが実行時に利用可能なメモリーは実際のRAMサイズに制限されます。
スワップのかわりにメモリ不足をソフトウェアで通知してきます。これがメモリ不足警告です。自分のアプリケーション以外の状態にも影響を受けます。
SafariやMailなど一部のアプリケーションはバックグラウンドに残り続けることで有名です。これらのアプリケーションのメモリ消費の状態や、日本語ではキー入力をどれだけ行ったかでアプリケーションの使えるメモリ量がかわります。「メモリ警告をシミュレート」
iPhone SDKのシミュレータは実行中のアプリケーションへメモリ警告を通知できます。
実行中シミュレータは仮想記憶の動作しているMac OS X環境なので実際にはメモリ不足は発生しません。(仮想記憶が動作しない程HDの空きが足りない場合はMac OS X自体が正常に動作できません)
メモリ警告が発生するとアプリケーションは表示していないビューを消すなどの対応処理が動きます。「メモリ警告をシミュレート」した後にすべての操作を確認し、例えばタブの切替えやナビゲーション切替えなどすべてのビューが正常に表示できるか確認してください。
メモリ不足に対応する
メモリ不足の状態になるとアプリケーションデリゲートメソッドの
- (void)applicationDidReceiveMemoryWarning:や各UIViewControllerの
- (void)didReceiveMemoryWarningメソッドが呼ばれます。これらのメソッドでメモリ不足に対応しなければアプリケーションは強制終了されます。
消せるものは消す
メモリ不足が発生した時点で直接利用していないオブジェクトでもう一度作成可能なものはできるだけ消します。もちろん大きなオブジェクトを消すのが効果的です。
何を消すか
ファイルに保存してある画像やサウンドデータをまず検討しましょう。消したあとnilにしておきます。利用するコードはnilであればファイルから読み込むコーディングをしておけば必要となるまで読み込まれないコードになります。
キャッシュデータも要検討です。中間結果等もう一度計算可能なデータは消せないか検討しましょう。まとめ:ディスクから読むまたは再計算する事で再作成可能なものは消す
追加情報
英語版のView Controller Programming Guide for iPhone OSが新しくなりメモリ管理関連も情報が増えているようです。
Custom View Controllers の Managing Memory Efficiently機種が増えて複雑さが増す 最小メモリで動くようにする
メモリ不足に関して実機での確認は実装メモリの少ない古い機種が必要です。アプリケーションがターゲットとしている機種の最も実装メモリの少ない機種で確認しましょう。
なお古い機種を持っていない場合、Appleの整備済み製品を利用すると安く入手することができます。さらにiPhone 4が新たに加わります。iOS4ではマルチタスキング対応となるため物理的にRAMの多い機種でも動作状況によりメモリ不足が発生する可能性が高まると思われます。性能を引き出すためプログラマはこれからもしばらくテクニックを駆使する必要がありそうです。
-
第17回 メモリ管理 その3
この記事は、2010年05月25日に掲載されました。今回はautorelaseを解説します。プログラミング指南の『Cocoaのメモリ管理のキホン』記事と重複しますが、重要なのでいろいろな切り口から理解を試みてください。
autorelase オートリリース
とりあえず使いたい時 勝手に消える奇術!?
autoreleaseは『まだ使うけど、後で消してね』を実現します。スタンフォード大学iTunes UのiPhone講座では『マジック』と言っていました。私もそうでしたがautoreleaseの仕組みを自分のものにするまでは奇術のように感じるでしょう。ただし『勝手に消える』などと安易に使うと痛い目を見ることになるのでじっくり自分のものとしてください。
この仕組みはメモリがふんだんにあれば便利ですが、iPhoneのようにシビアな管理が必要な場合には十分注意が必要です。
autoreleaseを制御するためNSAutoreleasePoolクラスがあります。すべてのインスタンスはどれでもautoreleaseメッセージを送ると、最も最近作られたNSAutoreleasePoolのインスタンスに登録されます。ここではこのインスタンスを便宜上オートリリースプールと呼びます。
オートリリースプールが解放される時に、登録されているインスタンスにreleaseメッセージを送ることで解放します。RunLoopに戻るまで残っている
スタンフォードのpdf Lecture3 52〜61がわかりやすい説明図なので引用します。スタンフォード大学iTunes U Lecture 3 Slides (January 12, 2010).pdfより
autoreleaseしたオブジェクトはRunLoopに戻ったタイミングで解放されます。
実際にはオートリリースプールがRunLoop でイベント処理の最後に解放されそのタイミングで解放されます。この仕組みを実現するため、RunLoopは常に
1)オートリリースプールを作る
2)イベント処理を行う
3)オートリリースプールを解放する
を繰り返します。
右図はスタンフォードのpdfですが、RunLoopの動きがよくわかるので動画はともかくpdfだけでも見ることをおすすめします。全部オートリリースだとメモリ使用のピークが高くなる
いったんオートリリースにすると必要なくなっても解放できません。(releaseするとRunLoopに戻ってクラッシュする)
このためループの中で中間的にオートリリースのオブジェクトが繰り返し生成されないか注意が必要になります。
このような場合はオートリリースを使わないか、ローカルなオートリリースプールを用意して対応できます。autoreleaseはMagicに見えますが、すべてオートリリースにはできません。後でまとめてreleaseされるため、RunLoopに戻る直前が最もメモりを多く消費した状態になります。
autoreleaseメッセージを送ることは後からの解放の依頼だけで、オブジェクトは残っている事を意識してください。
メモリ消費のピークを下げるには
自分でオートリリースプールを作り、処理を行い、そのオートリリースプールを開放すればオートリリースプールに登録済みのオブジェクトをRunLoopに戻る前に解放できます。NSAutoreleasePool * localPool = [[NSAutoreleasePool alloc] init]; // ループ処理など // 深いネストであってもlocalPoolが使われます [localPool release];
ループ処理の後に別の処理を行うような場合に利用可能なテクニックです。
オートリリースの区別
メソッド名で区別するしか方法はありません。allocやcopy以外のメソッドを使って得たオブジェクトはオートリリースの可能性があります。
オートリリースのオブジェクトを返すメソッドが多数あります。retainContのような属性では判別できません。後から、あるいは別のメソッド内で引数等のオブジェクトがaurorelease済みかを確認する手段はありません。プログラミング時点で明確に区別して管理することが必要です。2度オートリリースすると → クラッシュする
二度オートリリースプールに登録することは可能です。その場合プールが解放されるタイミングでそのオブジェクトに二度releaseが送られることになります。当然二度目は解放済みの無効なオブジェクトのアドレスへのメッセージになりクラッシュします。
autorelaseしていてもretainは可能です。retainすればautorelaseを取り消したのと同じことになります。ただしプール開放のタイミングでrelaseは送られるので無駄な処理となります。オートリリース以外で管理不要な場合
オーナーが管理してくれている間は安心して(retainもせずに)参照してかまいません。alloc とcopy以外のメソッドが返すオブジェクトはrelease不要です。autoreleaseで実際に消えるのか
冒頭で『まだ使うけど、後で消してね』と書きましたが、実際には『後でreleaseメッセージを送る』だけです。retainCountが1でなければautoreleaseしてもretainCountがひとつ減るだけで存在し続けます。オートリリースプールに登録済みのオブジェクトであってもretainすれば普通に使えます。逆にretainだけおこなってその後のreleaseを忘れるとリークすることになります。スタンフォード大学iTunes U
※iTunesを開きます:1月の講義にかわっているようです。ビデオの43分あたりから上記の図を使って説明してます。 -
第16回 メモリ管理 その2
この記事は、2010年05月11日に掲載されました。Cocoaで必ず学ぶメモリ管理のためのメソッドretainやrelease。これらはすべてNSObjectのメソッドです。NSObjectを継承しているすべてのクラスのインスタンスで使えます。
メモリ管理はすべてNSObjectのメソッド
alloc
allocはNSObjectクラスのクラスメソッドです。(+ (id)allocプラスではじまります)
まだインスタンスがないからクラスメソッドを使います。メモリ管理の面ではallocで確保できましたが、オブジェクトのライフサイクルから見ると初期化(init)が必要です。
allocした状態で参照カウンタは1です。retain
retainはインスタンスメソッドです。参照カウンタを一つ増やします。
release
参照カウンタを一つ減らします。
retainとreleaseはバランスさせなければなりません。所有権を得たら必ず解放します。
参照カウンタがゼロになったらdeallocが呼ばれます。RunLoopに戻ってからではありません。解放を意識し解放したらnilを代入しましょう。dealloc
インスタンスを解放します。
直接deallocを呼び出すプログラミングをすることはありません。参照カウンタがゼロになったら呼ばれます。
解放したオブジェクトにメッセージを送るとクラッシュします。
つまりallocとdeallocは必ず対応しなければクラッシュかリークとなります。autorelease
autoreleaseもNSObjectのメソッドです。
直近(スタックの一番上)のオートリリースプールに登録します。copy
allocと同様コピーしたインスタンスは参照カウンタ1の状態です。★コピーしてもリテインカウントだけはコピーしません。
mutableCopyも同様です。有限の資源を賢く使う = メモリ管理
使う前にretain、終わったらrelease リテインカウントを増減
使う人(オブジェクト)がオーナーになる考え方がCocoaのメモリ管理の基本です。
オーナーでなければ『使う前にretain、終わったらrelease』します。ただし複数のオーナーがいると必要なくなった時条件が煩雑になる弊害が発生します。そこで自分が使う期間中、誰かがオーナーなら自分はretainせずに使うことで解放されない弊害を避けるテクニックがあります。基本方針 長く使う人(たとえばMVCのM)がオーナーになる
テクニック:オーナーが明確で解放される心配がなければretainせずに使うことも可能
ただし注意が必要
参考:Cocoaメモリ管理プログラミングガイドp16「オブジェクトへの弱い参照」セッターメソッド
別のオブジェクトを受け取り、自分のインスタンス変数として保持するセッターのひな形がCocoaにはあります。このひな形に従わないとリークが発生したりクラッシュしたりするためです。
ひな形の一例:- (void)setTestObj:(TestObj *)inObj { if (m_obj != inObj) { //m_objをセットしようとした時のクラッシュ防止 [m_ojb release]; m_obj = [inObj retain]; } }
インスタンス変数が文字列の場合には単にretainするのではなくコピーするかどうか検討しましょう。
プロパティ
アクセッサメソッドを生成してくれるプロパティは属性でセッターメソッドの動作を指定できます。
プロパティを指定するとセッターメソッドを自動生成してくれます。ドット式に代入はセッターメソッドのコールに置き換えられます。retain属性のプロパティはそのクラスが管理
プロパティでretainを指定すると上記のセッターメソッドと同等の処理でそのクラスが所有権を持ちます。
注意
self.ageは[self setAge:x]に変換されるため、次のように記述すると無限ループしてしまいます。- (void)setAge:(int)newAge { self.age = newAge; //無限ループする }
コンテナクラス アレイや辞書
アレイや辞書に登録するとリテインカウントが増えます。つまりそのコンテナがクラスのインスタンスがオーナーになります。
内容をコンテナだけが所有している場合はコンテナクラスのインスタンスを解放すると内容も解放されます。
mArrayがNSMutableArrayのインスタンスの場合
解放される場合id obj = [[NSObject alloc] init]; [mArray addObject:obj]; [obj release]; // このようにmArrayだけが所有するようにすること [mArray relese];
複製と参照
copyする場合に浅いコピーと深いコピーがあります。両者は区別しなければなりません。カスタムクラスでcopy対応にする場合はObjective-C 2.0などの参考文献を参照してください。
使うときだけロード
nibが複雑な場合はできるだけ分割しそれぞれを使う直前にロードしましょう。まだ使わないオブジェクトでメモリを消費するのはメモリがぎりぎりの場合には避けなければなりません。
アプリケーション起動時にはロードはなるべく少なくしましょう。複雑で大規模なnibのロードと展開はメモリだけでなく、起動時間も長くします。実機でできれば古い機種で起動時間を確認しましょう。 -
第15回 メモリ管理 その1
この記事は、2010年04月27日に掲載されました。4月前半はお休みしてしまいました。申し訳ありません。今回からメモリ管理を解説します。
ちょうど高橋真人さんのプログラミング指南で『Cocoaのメモリ管理のキホン』が連載中です。同じ内容を違った切り口から解説したものを読むのも理解を助けると思いますので読み比べてみてください(笑)。メモリ管理にはiPhone独自の部分もあります。こちらの連載はiPhoneに特化した内容です。
普通のiPhoneプログラミング講座では早い時期にメモリ管理の説明を済ませるのが普通ですが、初級脱出セミナーでは全体で6回のセミナーの最後から二番目にしました。それは規模の小さなアプリケーションの場合メモリ管理関連のトラブルに巻き込まれる確率が比較的小さいことと、ある程度Cocoaに慣れてからからでないと実感しにくいからです。目標
最初に今回からのメモリ管理解説の目標ですが、Cocoaメモリ管理プログラミングガイド※に書かれているメモリ管理規則を実感すること、これしかありません。
「alloc」または「new」で始まる名前のメソッドや、「copy」を含む名前のメソッド(たとえば、alloc、newObject、mutableCopy)を使用してオブジェクトを作成した場合、またはオブジェクトにretainメッセージを送信した場合は、そのオブジェクトの所有権を取得できます。
その場合は、releaseまたはautoreleaseを使用してオブジェクトの所有権を放棄する責任があります。それ以外の方法でオブジェクトを受け取った場合は、そのオブジェクトを解放してはなりません。※:このページの末尾を参照
メモリ確保とリテインカウント
値とオブジェクト
値:ここでは変数の値のつもりです。変数は型が決まっていて、型により長さ(必要なメモリのビット長)が決まります。
まず、「値」と「オブジェクト」の違いを復習しましょう。オブジェクト:クラスのインスタンスのつもりで「オブジェクト」としています。インスタンスの実態はヒープ領域に確保され、その先頭アドレスをオブジェクトとして利用します。
具体例としてCGFloat型の変数とTestObject*型変数を用意しました。
CGFloatは決まった長さで最初から確保されているためメモリ管理不要です。オブジェクト内の固定領域とも言えます。TestObject*はメモリ管理が必要な外部オブジェクトのポインタです。
必要になったら「作る」オブジェクト
「オブジェクト」は必要になったら作り、そのアドレスを覚えて利用します。Cocoaではプログラミング時にInterface Builder.appで作りnibに保存して利用する事も一般的です。
オブジェクトを作るのはallocメソッドです。作ったら必ずinitメソッドで初期化しなければなりません。メモリは有限なので作れるオブジェクト数には限界あり
iPhoneは使えるメモリが少なく仮想記憶もスワップしません。このためメモリ管理がMacやPCよりもシビアでかつ重要です。リテインカウント(参照カウント)
すべてのオブジェクトは参照カウンタを持っています。この参照カウンタを使ってメモリ管理します。
オブジェクトを作った時参照カウンタは1になります。使っているオブジェクトの数
参照カウンタはそのオブジェクトを使っている人の数(所有権を持っているオブジェクトの数)です。誰も使わなくなったら解放するため、そして誰かが使っているのに解放される事を防ぐための仕組みです。
使う時にカウントを一つふやす
使わなくなったら一つへらすこれだけのシンプルなルールでメモリ管理します。カウントを増やすことは所有権を持つ事でもあります。
Appleのwebからオブジェクトライフサイクルの図を引用します。この図は実際の流れに近いです。
誰も使わなくなったらdealloc
参照カウンタがゼロになったらオブジェクトを解放します。解放時の処理はdeallocで記述しますが、直接deallocメソッドを呼ぶことはありません。Cocoaではdeallocはreleaseメソッドから呼ばれます。
ガベージコレクションはない
iPhoneにはガベージコレクションがありません。コードの中でメモリ管理するしかありませんのでしっかり身につけてください。
最新ドキュメント
Cocoaメモリ管理プログラミングガイド
日本語版pdf
この資料重要ですががちょっと難しいかも知れません。MacのCocoaと兼用のためiPhoneの場合不要な(ガベージコレクション)などの話もあるため説明が少しだけ複雑になっています。
一度に理解できなくても最初から最後まで何度か読んでみることをお勧めします。(日本語版は最新ではないのでその点はご注意ください) -
第14回 iPhoneのイベント その4
この記事は、2010年03月26日に掲載されました。マルチタッチ
マルチタッチはデフォルトではオフ
マルチタッチはオプションです。viewオブジェクトに対してsetMultipleTouchEnabled:メソッドにYESを送る(もしくはプロパティにセットする)ことで有効になります。どのパラメータを使うか
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)eventなどタッチを処理するメソッドは二つのパラメータを持っています。二つ目のeventパラメータからタッチオブジェクトを取り出すことができます。今回の解説ではイベントから取り出したタッチオブジェクトを使っています。
これは特にtouchesBegan:withEvent:で重要です。
(右図はiPhone Application Programming Guideより)複数のタッチは完全に同時にタッチされるとは限りません。厳密には同時となる場合はすくないと考えるべきです。このため最初にtouchesBegan:withEvent:メソッドが呼ばれた時にはまだ一つしかタッチしていない前提で、そのような場合でも正しく動作しなければいけません。
iPhoneシミュレータではオプションキーを押して2点タッチをシミュレートします。このため2点タッチで時間差のある場合の対応はシミュレータでは実機とは少し違い、マウスボタンを押してからオプションキーを押し少しマウスを移動する操作が必要です。ただしこのシミュレータ操作は将来SDKのバージョンが変わると変更になる可能性があります。
UIEventのタッチ関連メソッド
三つあります。
– allTouches
今回サンプルで使いました。すべてのタッチを得ます。viewの構成がシンプルな場合(ひとつのViewしかない場合)は全てのタッチが処理対象で問題ありません。– touchesForView:
viewを指定して関連するタッチのみ得るためのメソッドです。
(右図はiPhone Application Programming Guideより)– touchesForWindow:
windowを指定して関連するタッチのみ得るメソッドです。マルチタッチの参考書
『はじめてのiPhoneプログラミング』が参考になります。ジェスチャーについても詳しく解説されています。
最近の書籍ではiPhoneプログラミングUIKit詳解リファレンスもおすすめです。2点タッチ対応例
二点を結ぶ直線を描くカスタムビューを例とします。
ソースプログラムの構成は、タッチイベントを受け描画するためにTouchTwoViewクラスをView-based Applicationテンプレートから作成したプロジェクトに追加しています。
viewクラスのソース例
インスタンス変数のタッチ位置は二つにしています。このサンプルでは始点と終点の区別はしていません。@interface TouchTwoView : UIView { CGPoint m_touch1Point; ///< タッチ位置その1 CGPoint m_touch2Point; ///< タッチ位置その2 } @end
準備
- (void)awakeFromNib { self.multipleTouchEnabled = YES; m_touch1Point = m_touch2Point = CGPointZero; }
awakeFromNibメソッドでマルチタッチのプロパティをセットし座標をクリアしています。
マルチタッチの座標取得
UIEventクラスのallTouchesメソッドでイベントに関連するすべてのタッチを取り出しその数が2の場合だけ処理しています。allTouchesメソッドはNSSetオブジェクトを返すのでNSEnumeratorを得てから二つのタッチオブジェクトの座標を取り出しています。
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { UITouch *touch; if (2 == [[event allTouches] count]) { NSEnumerator *enumerator = [[event allTouches] objectEnumerator]; touch = [enumerator nextObject]; m_touch1Point = [touch locationInView:self]; touch = [enumerator nextObject]; m_touch2Point = [touch locationInView:self]; [self setNeedsDisplay]; } }
このようにviewクラスを実装しておくとdrawRect:メソッドでタッチ座標m_touch1Pointとm_touch2Pointを利用できます。
イベントのまとめ
デリゲートやアクションメソッドはRunLoopと密接に関係する
必要な処理だけを実行しすぐに制御をもどそう。
そうしないとイベントに反応しない/タイマーが動作しないなどの影響が出ます。
時間のかかるループ処理などは御法度です。イベントに対する反応を表示するにはRunLoopに戻らなければならない
ラベルに文字をセットしただけでは再表示されません。
setNeedsDisplayメッセージを送っただけでは表示されません。
パラメータを変更しsetNeedsDisplay、少し変更しsetNeedsDisplay、さらに変更してsetNeedsDisplayとしてもアニメーションにはなりません。
drawRect:を直接呼ぶ事はできません。自分が処理しないイベントは廃棄せず次へ渡す
通常はイベントに対応するため気づかないが、次へ渡さないとイベントが廃棄され処理されない。
シェイクでundoで注意。実機での動作確認が必要
OSのバージョンにより動作が異なる場合あり。
特に2.2(以前)と3.0両方サポートする場合は両方の実機で確認しよう。 -
第13回 iPhoneのイベント その3
この記事は、2010年03月16日に掲載されました。イベントの3回目はviewとモーションイベントとキー入力、それにタイマーを説明します。
■タッチ座標とview階層
viewの座標
viewの原点は左上です。MacのCocoaとは異なるのでMac用のサンプルを流用するような場合には注意してください。
(図はiPhoneアプリケーションプログラミングガイドp64より)
window座標とview座標
GUIの座標全般に言えることですが、タッチの座標もwindow座標とview座標を明確にわけてプログラムしなければなりません。カスタムビューではビュー座標を意識しないとビューの外側に描画してしまいビュー内には何も表示されない場合があります。完全に見えない場合でなくてもタッチ位置と表示位置がずれるとかなり不自然です。座標変換はUIViewクラスにメソッドが用意されています。
– convertPoint:toView: – convertPoint:fromView: – convertRect:toView: – convertRect:fromView:
frameのoriginから計算することは可能ですが、階層関係を変更する場合にそなえてこれらの変換メソッドを利用するのが良いでしょう。ただしこのメソッドで変換できるviewは同じwindow上に存在しているviewに限られます。
利用例:wpt = [self convertPoint:CGPointZero fromView:nil];
fromViewがnilなのでウインドウの座標をselfの座標に変換しています。
タッチを処理するのはカスタムビュー
viewにタッチ処理を実装する場合ほとんどはカスタムビューを作ることになると思います。タッチに反応する表示が必要になる場合はカスタムビューにしなければなりません。表示が必要ない場合、位置による処理も不要でタッチしたかタッチが終了したかだけで十分な場合はUIButtonで実装できないか検討すると良いでしょう。UIButtonは枠を表示しないこともできるのでタッチの検出には便利に使える場合があるはずです。
カスタムビューはUIViewを継承
カスタムビューを作る場合はUIViewを継承するのが無難です。
UIImageViewやUILavelはデフォルトではイベントを処理しません。iPhoneアプリケーションプログラミングガイドp82に明記されています。UIImageViewを継承してしまうとイベントを受け取れません。すなおにUIViewを継承し表示は独自に実装しましょう。■モーションイベントとキー入力
モーションイベント
モーションイベントはiPhone OS 3.0ではシェイクのみです。
モーションイベントはUIResponderのメソッドで受け取ります。レスポンダーチェーンを伝わりますがこのためにはファーストレスポンダを意識しなければなりません。
レスポンダチェーンの途中にモーションイベントの処理を記述するとイベントが受け取れない場合があるので注意深く実装してください。モーションイベントとカットペースト
iPhone OS 3.0以降ではカットコピーペーストがサポートされ、本体のシェイク動作でUndoとなります。これはデフォルトですが、モーションイベントを途中で止めてしまうとシェイクでUndoできません。
UIWindowでモーションイベントを受け取るメソッドを実装する場合は[super motionBegan:motion withEvent:event];でモーションイベントを止めないようにします。文字の入力はテキストフィールド
iPhoneアプリではキー入力はUITextViewかUITextFieldで受けることになります。イベントは発生しません。キー入力に関する情報は得られません。ファーストレスポンダがキー入力を受け取る
ファーストレスポンダのUITextFieldまたはUITextViewがキー入力を受け取ります。文字入力に関連するデリゲートが複数ありこれらを利用して仮想キーボードでかくれないようにスクロールしたり、文字入力の確定を検出したりします。
★参考:iPhoneアプリケーションプログラミングガイドP127キーボードの下に隠れているコンテンツを移動するキーボードは自動表示
仮想キーボードは自動表示です。ファーストレスポンダになると自動表示しUITextFieldがファーストレスポンダでなくなると仮想キーボードは消えます。文字入力のデリゲートメソッド
テキストフィールドとテキストビューはそれぞれデリゲートメソッドがありキー入力関連の処理をカスタマイズできます。これらのデリゲートで文字入力は検出できますが、イベントではありません。
UITextFieldDelegate– textFieldShouldBeginEditing: – textFieldDidBeginEditing: – textFieldShouldEndEditing: – textFieldDidEndEditing: – textField:shouldChangeCharactersInRange:replacementString: – textFieldShouldClear: – textFieldShouldReturn:
UITextViewDelegate
– textViewShouldBeginEditing: – textViewDidBeginEditing: – textViewShouldEndEditing: – textViewDidEndEditing: – textView:shouldChangeTextInRange:replacementText: – textViewDidChange: – textViewDidChangeSelection:
UITextInputTraits
キーボードの状態や動きを制御するためUITextInputTraitsプロトコルがあり UITextField・UITextViewともに採用しています。autocapitalizationType autocorrectionType enablesReturnKeyAutomatically keyboardAppearance keyboardType returnKeyType secureTextEntry
この設定はIBでも行う事ができます。
■タイマー
繰り返しまたは一度だけ
指定遅延時間後あるいは指定した時間間隔で指定したメソッドを呼び出す仕組みがタイマーです。タイマーを利用して特定のオブジェクトへ指定したメッセージを送ることができます。この機構は強力でさまざまな応用が可能です。ボタンなどのターゲットアクションのメカニズムと似ているのでわかりやすいと思います。ボタンをタッチするかわりがタイマーの時間経過です。
繰返しに指定したタイマーを指定遅延時間前に発動したり、繰返しを中断したり自由にできます。NSTimerクラス
iPhoneでもタイマー処理はMacと共通のNSTimerを使います。タイマーオブジェクトの作成
scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:を使ってタイマーオブジェクトを作るのが手軽です。このメソッドはタイマーオブジェクトを作ると同時にRunLoopに登録してくれます。
遅延時間パラメータは秒単位の実数です。NSTimeInterval型の実態はdoubleです。repeatsパラメータにYESを渡すと繰り返します。指定時間前の発動と中止
タイマーオブジェクトにfireメッセージを送ると指定遅延時間の前に発動させることができます。
タイマーオブジェクトにinvalidateメッセージを送ると中止できます。登録したLunLoopから取り除かれ通常タイマーオブジェクトは解放されます。呼ばれる処理
- (void)timerFireMethod:(NSTimer*)theTimer のメソッドを指定します。
NSTimerオブジェクトのパラメータを一つだけ持つ関数値を返さない任意のメソッドを指定できます。実行時に指定したオブジェクトが指定メソッドを持っていないと例外が発生します。
発動時にこのメソッドにタイマーオブジェクト自身を渡します。theTimerパラメータにuserInfoを渡すことができます。このuserInfoをうまく使うと応用の幅が広がります。
メモリ管理には注意が必要です。
タイマーが発動するときは必ずRunLoopは回っていて、タイマーオブジェクトを作成したときのオートリリースのオブジェクトは解放された後です。リテインしていないオートリリースオブジェクトにアクセスするとクラッシュします。時間精度
タイマーは強力で便利ですが、RunLoopから呼ばれます。指定時刻に割り込み(CPUの割り込み処理)で処理される訳ではありません。このため気軽に使えるのですが、時間精度は期待できません。(ドキュメントには50から100ミリ秒とあります)
何か別の処理、この処理もRunLoopから呼ばれた処理のはずですが、繰返しなど処理時間が長いなどRunLoopに戻っていない場合指定時刻にタイマーを起動できません。 -
第12回 iPhoneのイベント その2
この記事は、2010年02月23日に掲載されました。今回はiPhoneアプリケーションでイベント処理を担当するUIResponderクラスの説明です。どの入門書にも解説があると思います。利用者の操作を受け取るアプリケーションには必ず利用するしくみで、アプリケーションの根幹部分ですのでじっくり理解してください。
■イベント処理はUIResponder
レスポンダチェーン
ボタンなどのUI部品では、タッチしたUI部品がイベントを処理せず上位やまわりのビューへ処理を渡す事はありません。むしろそのような曖昧さをゆるさない部品サイズと配置間隔になるようプログラムするべきで、Interface Builder(以下IB)を操作するとそのように導かれます。
現実にはボタンが並ぶだけでなくさらに複雑な場合もあります。たとえばSafariで表示したwebページのように、リンクのタッチかフレームのダブルタップかでまったく違う処理が必要な場合です。
このような処理はリンクではない場合上位のフレームにイベントを渡すことで対応しているはずです。
イベントを受けて処理するオブジェクトをレスポンダと呼び、イベント受け渡しの連鎖をレスポンダチェーンと呼びます。UIResponder
レスポンダの実態はUIResponderクラスです。
UIViewとUIViewControllerはUIResponderを継承しています。ファーストレスポンダ
レスポンダチェーンの先頭がファーストレスポンダです。
文字入力はキャレットが点滅するのでファーストレスポンダであることが明確にわかります。タッチ操作でファーストレスポンダは実行時に切り替わります。
ファーストレスポンダはIBでアクションメソッドの接続先として利用可能です。
ファーストレスポンダになることができるかどうかは– canBecomeFirstResponderメソッドで返します。デフォルトはNOです。イベント処理の順序
レスポンダチェーンをたどることでイベントを処理する順序は決まります。単純にviewをさかのぼるだけでなくviewコントローラもイベントを処理できる点はMacのCocoaと違います。
iPhone Application Programming Guideよりイベントの廃棄と受け渡し
通常はイベントの処理を記述することでタッチなどのイベントに対応します。そしてイベントをそこで廃棄します。
何も処理をしていないのに廃棄? と疑問を持たれるかもしれません。次へ受け渡しをしないので廃棄なのです。
この点が『協調的なイベント処理のためのCocoaメカニズム』と呼ばれる所以です。タッチやモーションイベントを受け取る空のメソッドだけを記述するとそこでイベントが破棄されます。■View階層とイベントの流れ
見えているviewがタッチイベントを受け取る
viewは階層構造になっています。下の層は上に配置されたviewのSuperViewです。描画は下のviewから行われることで上位のviewが上書きされ階層を視覚的に利用者へ示します。
レスポンダチェーンは描画とは逆の順に階層をさかのぼって行きます。iPhoneシミュレータの設定>Safariを見てみましょう。
UITableViewの典型的な表示です。
JavaScriptなどのスイッチの部分をタップすると切り替わります。
同じ位置をタッチしてもそのまま上下に動かすとスクロールします。
例としては苦しいですが、とにかくスイッチが処理しない上下タッチ位置移動イベントは引き渡されUISwitchではなくUITableViewで処理されるのです。スイッチで処理されないイベントは
カラムに引き渡され
カラムでも処理されなければテーブルビューに引き渡されるview階層
UIViewはUIWindowまたは別のUIView上に存在します。
UIViewはsuperviewとsubviewsのプロパティを持っています。
テーブルビューの場合はテーブルビューのsubviewsがセルになります。各セルのsuperviewがテーブルビューです。
上の画面では「JavaScript」セルの上にスイッチがあります。「JavaScript」セルのサブビューがスイッチで、スイッチのsuperviewは「JavaScript」セルです。
各ビューは自分自身を描いた後に各サブビューを描きます。上の例ではスイッチが最後に描かれます。最後に描かれるので「上」に乗って見えます。イベントの処理は描くのとは逆の順です。見えている「上」のビューに最初にイベントが渡されます。
■タッチ
マウスとの違い
タッチはマウスのクリックと似ているようにも思えますがプログラミングはかなり異なります。マウスはボタンを押していない状態でも位置情報を持ちますがタッチはまったく異なります。マウスは複数のボタンを持ちますが、タッチは複数任意のタイミングで発生します。
指先でタッチするため少なくとも44ピクセル×44ピクセル程度の面積が必要とされています。3つのメソッドとタッチのキャンセル
UIResponderにはタッチを処理するための3つのメソッドとキャンセル対応のためのメソッドがあります。touchesBegan:withEvent: touchesMoved:withEvent: touchesEnded:withEvent: touchesCancelled:withEvent:
タッチ処理はこれらのメソッドを必要により実装します。
touchesMoved:withEvent: はタッチ位置が変化した場合に呼ばれます。
touchesCancelled:withEvent: はタッチ中に電話の着信や電池切れが発生した場合の対処を記述します。キャンセルをただしく実装しないとタッチの状態と画面表示が矛盾するなど意図しない状況になる可能性があります。
プログラミングの注意
touchesBegan:withEvent: もtouchesMoved:withEvent: も『タッチ中処理を続けるループ』を記述してはいけません。タッチイベント関連のメソッドはそのような使い方をするためのものではありません。またRunLoopに戻らなければ再描画もされません。タッチは独立したオブジェクト
touchesBegan:withEvent:などタッチ用メソッドの最初のパラメータはタッチオブジェクトです。UITouch
タッチオブジェクトはタッチ中存在します。
タッチ位置に対応するviewとwindow、タップ数、タイムスタンプ、フェーズのプロパティを持っています。locationInView:とpreviousLocationInView:メソッドで現在の座標と前の位置を取り出す事ができます。
現在のiPhoneは5つまでのタッチを同時に扱う事ができます。(ただしiPhoneシミュレータでは2点まで)タッチ状態はユーザーの操作でダイナミックに変わるため複数のタッチはNSArrayではなくNSSetで渡す仕様となっています。
タッチのフェーズ情報はタッチ数が1の場合はメソッドでイベントが既に区別されているのでフェーズを参照する機会はあまりありません。UIEvent
一方二つ目のパラメータであるイベントオブジェクトはそれぞれのイベントに対応して渡されます。座標、タイムスタンプ、タッチ回数などの情報を持っています。タッチ回数とタッチ数
しっかり区別しなければならないのがタッチ回数とタッチの数です。
ダブルタップなどはタッチ回数でtapCountプロパティで参照できます。一方タッチ数はNSSetのcountで判別します。ジェスチャー
タッチの位置情報だけが問題であればプログラムはシンプルですが、動きが関連してくると考慮しなければならない事が増えます。ジェスチャーの実装については『はじめてのiPhoneプログラミング』が詳しく解説しています。 -
第11回 iPhoneのイベント その1
この記事は、2010年02月09日に掲載されました。今回からiPhoneのイベントの説明に入ります。イベントと言えばタッチですね。タッチ以外もユーザー操作に対応する処理を説明します。
■RunLoop
MacやiPhoneのアプリケーションはタッチ(マウス)操作などのイベントで動きます。イベントをきっかけにそのイベントに対応付けられた処理を行います。
RunLoopは『イベントを待ち 対応する処理を呼び出す』を繰り返すループです。
第8回の連載で説明したUIApplicationMain()関数が大元となってRunLoopは動き出します。
RunLoopを直接扱う場合はめったにありません。NSRunLoopクラスが用意されてはいます。+ currentRunLoop
メソッドで現在のRunLoopにアクセスすることは可能です。
イベント 描画 オートリリースプール
『イベントを待ち 対応する処理を呼び出す』はとてもおおざっぱな説明です。実際にはiPhoneアプリケーションの動作に書かせないメモリ管理(オートリリースプール処理)と画面の描画もRunLoopが管理します。
概念的には
オートリリースプールの確保
イベントをキューから取り出す
イベントに対応した処理
再レイアウトが必要なviewの処理
再描画が必要なviewの描画
オートリリースプール解放処理
となります。正式なドキュメントになっているわけではないので、今後のバージョンで変化するかもしれません。何かのイベントがきっかけで処理を行う場合も、できるだけ早く処理を終了しRunLoopにもどらなければいけません。処理が数秒であってもその間RunLoopに戻らなければ別のイベントに反応できなくなります。
割り込み
iPhoneは電話の着信、SMSメッセージ受信、カレンダー通知、スリープなど割り込みが入る可能性が常にあります。iPod touchでもカレンダー通知、スリープは可能性があります。スリープは利用者がスリープボタンを押した場合のほか電池切れでも発生します。
★ここでの『割り込み』はCPUの割り込み処理のことではありません。紛らわしくて申し訳ありません。処理の分岐&呼び出し
現在iPhoneアプリケーションが受け取る事のできるイベントには タッチ と モーションイベント の二つしかありません。
pdf版iPhoneアプリケーションプログラムガイドp85に明記されています。仮想キーボードでのタイピングでMacのような文字入力イベントが発生することはありません。(これはiPhone OS 3.1の説明です)
ホームボタンのオンオフはアプリケーションでは検出できません。
イベントの種類別の分岐は必要ありません。タッチとモーションそれぞれの処理メソッドを実装するだけでイベントに対応できます。描画もRunLoopから呼ばれます。 ★これが最も直感と違う部分です! 通常は強制再表示はありません。
イベントではないイベント
矛盾した表現ですが加速度センサーはゲームのコントロールなどで『イベント』として使っていますがRunLoopが処理するイベントではなくUIAccelerometerクラスを介してUIAccelerometerDelegateのデリゲートメソッドが呼ばれます。
同様に磁気コンパスの方位角もデリゲートで処理するそうです。今回のセミナーでは実機は対象外なので加速度センサーと方位角についてはこのセミナーでは触れません。
また外部アクセサリが「イベント」を発生させる場合もあります。これらは通知またはデリゲートで処理されるそうです。外部アクセサリについてもこのセミナーでは触れません。デバイスの向きの変化
これもイベントではありません。UIViewControllerのメソッドをオーバーライドして対応します。
shouldAutorotateToInterfaceOrientation:で回転可能かを返し– willRotateToInterfaceOrientation:duration: – willAnimateRotationToInterfaceOrientation:duration: – didRotateFromInterfaceOrientation: – willAnimateFirstHalfOfRotationToInterfaceOrientation:duration: – didAnimateFirstHalfOfRotationToInterfaceOrientation: – willAnimateSecondHalfOfRotationFromInterfaceOrientation:duration:
で回転を処理します。
現在の向きはinterfaceOrientationプロパティです。
少なくとも– didRotateFromInterfaceOrientation:で回転後の向きで再表示すればデバイスの向きの変化に対応可能です。
ただし回転は90度だけとは限りません、180度回転もあり得ますしホームボタンを上に向ける場合もありますので注意してください。■ターゲットアクション
ターゲットアクションはUIKitの中で多用されています。直感的に理解できる仕組みと思います。直感通り動きますがアクションを呼び出すのはRunLoopであることは忘れないでください。
受けたイベントをもとにアクションが呼ばれる
ターゲットアクションの最もわかりやすい例はボタンの例でしょう。ボタンをタップする「操作」に対して、ボタンにターゲットのオブジェクトを設定しそのオブジェクトに「アクション」メッセージが送られる仕組みです。
タッチイベントはタッチ座標からボタンに対するイベントと判断され、ボタンに渡されます。ボタンはボタン内でタッチがはなされたら※アクションメソッドを呼びます。
※UIControlEventTouchUpInsideの場合アクションメソッドの実装
アクションメソッドとUI部品が一対一ならアクションメソッド内で分岐の必要はありません。サンプルのように複数のボタンに一つのアクションメソッドを指定する場合はアクションメソッドのセンダーパラメータを利用してどのUI部品からのアクション起動かを判別し処理を分岐する事ができます。
アクションメソッドからreturnしたら最終的にはRunLoopに戻ります。RunLoopは毎回メモリプールの解放を行います。。UIControlのイベント処理
Interface Builderでボタンのアクションを接続しようとするとEventが多くの種類から選択できます。
同様にaddTarget:action:forControlEvents:メソッドでソースコードでターゲットアクションを接続する場合最後のパラメータでUIControlEventsを指定できます。
ボタンの場合はUIControlEventTouchUpInsideにしておくと間違ってタッチした場合ボタンの外でタッチをはなすとキャンセルできて便利です。タップ(すぐにはなす)の場合意図通り動作するので問題ありません。 -
第10回 起動時の動作 その4
この記事は、2010年01月26日に掲載されました。今回で起動時の動作の説明を終えようとおもいます。nibなしで動くiPhoneアプリをどう作るかを説明することで、起動時の動作のおさらいをしましょう。
nibを使わない場合
nibなしでも動く
Xcodeのテンプレートではすべてnibを利用していますが、nibを使わなくてもiPhoneアプリケーションは動作します。
nibはCocoaオブジェクトのアーカイブであることを考えると当然ですね。Interface Builder(IB)でオブジェクトを用意するかわりに実行時に作れば良いのです。nibが何をやっているかを知らなければnibなしにできない
必要なのは アプリケーションデリゲートを指定する事 と 表示環境(view)を正しく作ること の二つです。第8回と9回で示したようにデリゲートはnibで指定され、主要NibファイルはInof.plistファイルで指定されています。この方法を使わない場合にはUIApplicationMain関数の最後のパラメータでデリゲートクラスを指定します。アプリケーションデリゲートを作り指定する
XcodeでView-based Applicationテンプレートを使用して作成したプロジェクトからnibの指定を削除した手順を説明します。
Xcodeを起動し ファイル>新規プロジェクト メニューでIPhoneアプリの View-based Applicationテンプレートを選び「NonNib」の名称で保存した場合の説明です。(まだ10.5環境です、スノーレパードだと若干違うかもしれません)
保存されたプロジェクトのResourcesグループからMainWindow.xibとNonNibController.xibを削除し、NonNib-Info.plist、main.m、NonNibAppDelegete.mを修正しました。主要Nibファイルの指定はXcodeでターゲットの情報ウインドウを表示し『主要Nibファイル』のnibファイル名を消しても可能でした。
main.mの修正箇所
// main.m int main(int argc, char *argv[]) { NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; // delegateClassNameを@"NonNibAppDelegate"に指定する ★NSStringで指定すること int retVal = UIApplicationMain(argc, argv, nil, @"NonNibAppDelegate"); [pool release]; return retVal; }
デリゲートのクラス名(この例では『NonNibAppDelegete』)を指定します。
実行すると NonNibAppDelegete の applicationDidFinishLaunching: メソッドが呼ばれます。ただしテンプレートはnibがある前提のものですから、この状態での実行結果は画面は黒い状態でviewもwindowも存在しません。(NSLogで確認してみてください。nilのはずです)▼注意:UIApplicationMainの最後の二つパラメータはNSStringでなければ実行時にエラーとなります。『@』をつけずにコンパイルするとこのようにワーニングを表示します。
ウインドウとビューを適切に作る
次に applicationDidFinishLaunching: メソッドで- (void)setupWindowメソッドを呼びUIWindowを作りましょう。// NonNibAppDelegate.m - (void)setupWindow { UIWindow *localPortraitWindow; localPortraitWindow = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; // @propertyでretainしている self.window = localPortraitWindow; [localPortraitWindow release]; // 背景色を設定する window.backgroundColor = [UIColor groupTableViewBackgroundColor]; } - (void)setupVC { if (nil == viewController) { viewController = [[NonNibViewController alloc] init]; } NSLog(@"-036- viewController.view=%@", viewController.view); [window addSubview:viewController.view]; } - (void)applicationDidFinishLaunching:(UIApplication *)application { [self setupWindow]; // [self setupVC]; // Override point for customization after app launch [window addSubview:viewController.view]; [window makeKeyAndVisible]; }
説明のためにローカル変数localPortraitWindowを使っています。このように変更してから実行すると背景色で塗りつぶしウインドウが存在する事がわかります。
次に- (void)setupVCメソッドでボタンを一つ配置したviewを作ってみましょう。ソースのコメントをはずしてください。デフォルトのviewはコントローラが作ります。
// NonNibViewController.m - (IBAction)btnAction:(id)sender { NSLog(@"ボタンが押された"); } - (void)setupButton { // 読み込みボタンを作る UIButton *btn = [UIButton buttonWithType:UIButtonTypeRoundedRect]; btn.frame = CGRectMake(20, 181, 280, 37); // アクションを設定する [btn addTarget:self action:@selector(btnAction:) forControlEvents:UIControlEventTouchUpInside]; btn.titleLabel.font = [UIFont systemFontOfSize:15]; // ★文字リテラルは使わず正しくローカライズしてください [btn setTitle:@"テスト" forState:UIControlStateNormal]; // コントローラのviewに貼る このボタンはアクションのみ(アウトレットなし) [self.view addSubview:btn]; } /** * 初期化 * このサンプルでは、viewの背景色を設定しボタンを貼っています * @param なし * @return なし */ - (id)init { self = [super init]; if (self) { // 背景色を設定 ★IBで設定する他の属性も設定可能 self.view.backgroundColor = [UIColor orangeColor]; // ボタンを作る [self setupButton]; } return self; }
このプログラムをNonNibViewController.mに追加して実行するとオレンジ色の背景にボタンが一つ表示されるはずです。ボタンをタップするとデバッガコンソールに『ボタンが押された』と表示するはずです。
これでnibなしでも表示とボタン操作が可能になりました。最もシンプルはiPhoneアプリです。nibを使う利点と使わない利点
利点 UI部品配置が楽
IBを使って配置できるので圧倒的に楽です。Xcodeのテンプレートをそのまま使える点も有利です。利点 コーディングが減らせる
背景色、フォント、文字サイズ、表示する文字列の指定など、適切に初期設定されるため、必要な項目だけ表示を見ながら設定するだけで完了します。ただし設計段階でIBを利用する事はお勧めできません。ペーパープロトタイピングのような、あらゆる可能性を排除しない検討方法をとることでより画期的なアプリケーションが生まれると思います。
欠点 ロードに時間がかかる
nibファイルを読み込み再現するのに時間がかかります。特に複雑な構成の場合顕著です。
古い機種では処理が遅いので実機での確認が欠かせません。(ファイルの読み込みも遅く展開も遅いためと思われます。)早い段階で実際に近いデータでの実機確認をお勧めします。欠点 構造が見えにくい
アウトレットやアクションの接続関係がわかりにくいのが欠点です。表示されていないviewなどがあるとさらにわかりにくくなります。経験を積むと予想がつく場面が多いです。アップルのさまざまなサンプルなど、多くのnibの構造を見て研究してください。欠点 ローカライズの数だけnibが必要【実装による】
ローカライズ作業を開始してからデザインを変更が難しくなり、修正漏れ等も発生しやすくなります。
MainWindow.xibなどを各言語にコピーしてローカライズする場合に注意してください。変更はすべての言語で忘れずに行う必要があります。言語により文字の幅や説明文の文字数が異なるために個別の対応が必要な場合もあります。文字サイズの自動調整などを活用してください。バランスが重要
小規模のアプリケーションの場合はテンプレートまたはサンプルの一つを土台にnibを利用するのが開発効率が高い事は明らかです。機能が複雑になるに従いnibも複雑になったら、nibファイルまわりのリファクタリングが必要かもしれません。UI部品の配置が終了していればnibを使わない実装に切替えることも選択肢の一つでしょう。プロトタイピングとは違い、ぎりぎりの性能を出す必要に迫られた場合はあらゆる工夫をしなければなりません。そのときnib無しでの実装方法を身につけていると有利なはずです。
部分的にnibなし はよく利用
nibを使わずinitWithFrame:メソッドでviewを作る処理はテーブルのセルなどでしばしば使います。このようにnibを使わないUI部品の配置は手軽に使えるテクニックです。 -
第9回 起動時の動作 その3
この記事は、2010年01月12日に掲載されました。初級脱出セミナーは1月14日第6回が最終回です。
さて連載は起動時の動作の3回目です。Info.plistの復習とUIKitのクラス関係を説明します。重要な情報のリスト
Info.plistはアプリケーションの起動などに必要な重要な情報のリストです。
XcodeのプロジェクトにはResourcesのグループに『プロジェクト名-Info.plist』の名称で自動的に作られています。
シミュレータ用にアプリケーションをビルドすると、アプリケーションのバンドル直下に『Info.plist』の名称で保存されます。Info.plistには 主要クラス、主要Nibファイルの指定 アイコンファイルの指定 などのiPhoneアプリケーションとして重要な情報があります。
Info.plistの設定
ターゲット>アプリケーション の「情報」ウインドウの「プロパティ」タブ で設定します。Xcodeでは間接的な指定で自動で設定されます。Xcodeで『プロジェクト名-Info.plist』を開いた例
このウインドウで直接編集することもできます。■UIViewとUIViewController
ここではIBで配置しアウトレットとアクションを接続するUIKitのクラスを確認しましょう。
UIView
まずUIViewです。描画とイベントハンドリングのクラスです。ユーザーが直接見て操作するので最も頻繁に使われる重要なクラスです。
UIViewはその中に複数のUIViewを張り込むことができます。独自の表示やイベント対応を行うカスタムビューにも発展可能ですが今回のサンプルも単純にボタン等のUI部品を貼るだけのシンプルな使い方をしています。
UIButtonはUIControlを継承していますが、UIControlはUIViewを継承しています。つまりボタンなどのUI部品はUIViewでもあります。UIWindow
次はUIWindowです。iPhoneはマルチウインドウではないのでウインドウはMacなどデスクトップ用アプリケーションの場合のように頻繁に使う事はありません。
iPhoneではUIWindowはUIViewを継承しています。通常は一つのウインドウで動作しています。UIViewController
iPhoneではview用のコントローラはきっちりUIViewControllerとして標準化されています。メモリ不足の対応や、本体の回転等の管理をおこなうためにはUIViewControllerのインスタンスは一つは必要です。iPhoneの標準機能の中核でもあります。
UIViewControllerはUIResponderを継承している点も重要です。viewのイベント処理をコントローラ側で記述することもできます。(イベントについて詳しくは後のセミナーのテーマです)
一つのUIViewControllerのインスタンスがデータソースなど他の機能を持つ事は可能です。デリゲートを兼ねることも頻繁にあります。nibロードによる初期化と動き出すきっかけ
- (void)awakeFromNib
nibファイルからロードされた場合の初期化のタイミング用のメソッドです。
Macでも共通のメソッドですが、iPhoneの場合は[super awakeFromNib];を必ず呼ばなければなりません。- (void)viewDidLoad
awakeFromNibから呼ばれます。
UIViewControllerのviewアウトレット(プロパティ)が有効でなければなりません。▼注意:nibを使わない場合は上記二つのメソッドはよばれません。allocとinitWithFrame:でインスタンスを作り必要によりプロパティ等を設定します。
nibで作成した場合とは違いデフォルトでは何も表示しない場合がありますので注意してください。色や属性等も細かく設定が必要な場合があります。nibの使用にかかわらず最重要
UIViewとUIViewControllerの関係はnibを使うかどうかに関わらず、iPhoneでは最も重要です。
タブやナビゲーションの仕組みはviewではなくコントローラを指定することからもわかります。
Xcodeのテンプレートを使うと最初からコントローラとビューが組み込まれているので実感しにくいかもしれませんが、少しずつ機能を追加しながら身につけてください。 -
第8回 起動時の動作 その2
この記事は、2009年12月22日に掲載されました。初級脱出セミナーは最終回となる第6回二次元描画を1月14日に開催します。Appleのサンプルプログラム『QuartzDemo』を応用できるようになるための基礎知識のほか文字表示やアニメーションの基本を押さえましょう。
さて連載は起動時の動作の2回目です。main.mの主役UIApplicationMain()関数とnibファイルの関係を解説します。■UIApplicationMain()関数
UIApplicationMain()を日常のプログラミングで意識する事はほとんどありません。書籍でも取り上げられる事は少ないようです。
UIApplicationMain()はUIApplicationとUIApplicationDelegateオブジェクトを作りイベントループを開始するのに加え、Info.plistの主要nibファイルをロードするタイミングを与えます。この関数無しにはアプリケーションは動きません。パラメータ
4つのパラメータを持っています。
最初の二つはmainのパラメータを引き渡します。一つ目がパラメータ数で、二つ目がパラメータの可変長リストです。
3つ目はUIApplicationクラス名です。Xcodeの設定では『主要クラス名』となっています。あえてサブクラスを指定する必要の無い場合はnilとします。(ここがnilの場合はInfo.plistのNSPrincipalClassを使います)
4つめはデリゲートクラス名です。nibファイルで指定するばあいはnilとします。
最後の二つのパラメータはC文字列ではなくNSStringオプジェクトでなければなりません。関数値
整数値で定義されていますが、アプリケーションに終了機能がないのでこの関数から戻ることは無いようです。nibファイル名の指定
Xcodeで指定します。プロジェクトの情報 プロパティ 主要Nibファイルでnibファイルを指定します。
この指定はInfo.plist内に設定されます。
Xcodeでテンプレートを選ぶと最初から設定済みの状態で保存されます。内部動作は
UIApplicationMainがUIApplicationのインスタンスを作り
UIApplicationのインスタンスがInfo.plistを読み込み
メインnibファイルをロードする
の順番に動作します。Info.plistのファイル名は決まっています。
nibのApp Delegeteでデリゲートクラス指定済み
main.mがどれも同じなのは 1)メインのnibをロードする 2)nibでデリゲートがわかる この二つを前提としています。XcodeのIPhone OSのアプリケーションテンプレートを選ぶと、自動でデリゲートが作られ指定済みになります。そのデリゲートの名称は作成するプロジェクト名を反映したものになります。
テンプレートのmain.mは固定の記述で、Info.plistのnibファイルをカスタマイズして対応するよう作られているわけです。
IBのFileメニューNewでCocoa touchのApplicationテンプレートを選ぶとApp Delegeteが含まれています。ここを自分のアプリケーションに合わせてください。
nibはインスタンス化によりメモリにロードされ、awakeFromNibで動く準備を整えるタイミングが得られます。
■nibファイルはアーカイブ
nibはオブジェクトのアーカイブ
アーカイブを読み込んでオブジェクト(インスタンス)を再現します。
アーカイブについては連載の『第5回 プロパティリスト その2』で説明しました。SDKの書籍なども参考にしてください。アーカイブから再現される場合にはinitやinitWithFrame: でなくinitWithCoder:メソッドが使われます。このことは重要です。(UIViewの場合initWithFrame:で初期化されたオブジェクトをアーカイブしているのです)
準備完了はawakeFromNib
IBはカスタムviewの設定を完全にできません。このような場合のためawakeFromNibメソッドが用意されています。
読み込み完了でawakeFromNibメソッドが呼ばれます。awakeFromNibをカスタマイズしてインスタンス変数の設定等をおこなうことができます。
そのほかのnibが読み込まれ再現されたタイミングとしてはviewdidloadedメソッドも使う事ができます。コントローラとアウトレット
アウトレットはnib上のオブジェクトをコードから使うために必要です。アウトレットにはnibから再現されたインスタンスのアドレスが入ります。コントローラの中でinitした場合と同様にIBで配置したオブジェクトにアクセスできるようになります。
MVCのVとCをIB操作だけで接続済みにすることを可能にしています。標準的なコントローラ
標準的なコントローラはIB操作だけで配置し接続が可能です。
UIViewControllerのほかにUIImagePickerController、UINavigationController、UITabBarController、UITableViewControllerがあります。これらはIBでも配置できます。外部のnibを参照
nibの View Controllerの設定で nibファイル(拡張子はxib)名を指定する事もできます。
-
第7回 起動時の動作 その1
この記事は、2009年12月08日に掲載されました。初級脱出セミナーは第5回メモリ管理は12月10日に第6回二次元描画を1月14日に開催します。第6回でひとまずセミナーは終了することにしました。モサ伝onlineの連載では3時間のセミナーの数分の一の情報しかお伝えできません(全部やろうとするとセミナー一回分が半年近くかかりそうです)。実際のセミナーに近い内容をお伝えする手段は別途検討します。
連載は今回からアプリの動作とnibの回で取り上げた内容をお届けします。
始まりはmain()
main()は基本的にiPhoneでも同じです。まだmainと各クラスの関係がわかりにくいと思いますが今回の連載で疑問を解消してください。
重要なのはint retVal = UIApplicationMain(argc,argv,nil,nil);
の一行です。
必要情報
アプリケーションの実行に必要な情報は
nibファイル名
アプリケーションデリゲート
の二つです。Info.plistにその情報があります。Info.plistにnibファイル名
Info.plistを見るとnibファイル名がわかります。(次回の連載で説明しますが、nibにはApplicationDelegateが指定されています。)
起動時の様子が※iPhoneアプリケーションプログラミングガイドのアプリケーションのライフサイクルに解説されています。起動時の動きと終了
ホームでアプリケーションアイコンをタップする
ユーザー操作で起動します。ホームが消え デフォルトイメージ表示
ホームが消え、同時にデフォルトイメージが拡大するアニメーションを表示します。
アプリケーションのバンドル内にDefault.pngがあれば起動直後にそれを表示してくれます。表示してからアプリケーションの起動処理がはじまりUIWindowの準備ができたらそちらが表示されます。
Default.pngがない場合はステータスバーのみ表示されますが画面は黒い状態です。main() 呼び出し
iPhoneアプリケーションのエントリーポイントはやはりmain()ですが、ソースコードはmain.mです。拡張子の「m」はobjective-Cのソースプログラムであることをあらわします。#import <UIKit/UIKit.h> int main(int argc, char *argv[]) { NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; int retVal = UIApplicationMain(argc, argv, nil, nil); [pool release]; return retVal; }
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
と
[pool release];
はメモリ管理のための記述です。メモリ管理は第5回の【初級脱出】セミナーで取り上げます。UIApplicationMain() に処理がわたる
プログラマのコードが動き始めるきっかけです。
こちらが今回のセミナーのひとつのポイントです。次回の連載で説明します。起動はこれだけです。
イベントは第4回の【初級脱出】セミナーであつかいました。
起動した後はタッチ等のイベントがプログラム(ロード済みのインスタンス)に渡り、それに対応した動作となります。終了機能はない ホームボタンや電話着信などで外部から終了される
アプリケーション内に終了ボタンなどは不要です。(アプリケーションに『終了』のための仕組みを組み込むとガイドラインに反するのでリジェクトの可能性があるようです)
ゲーム等の移植で「終了」ボタンなどがあったら、その機能は実装しないように注意してください。
applicationWillTerminate:
アプリケーションデリゲートメソッドのapplicationWillTerminate:が終了前に呼ばれます。
このメソッド内に終了処理を記述します。
電話の着信などが優先です。アプリケーションの都合で終了を先延ばしにしてもらう方法はありません。ここでの終了処理も最小限に止めるべきでしょう。
このデリゲートメソッドはオプションで今回のサンプルに使ったテンプレートでも使われているものはありません。テンプレートのmain.mはそのまま使える
テンプレートのmain.mはそのまま使えるようになっています。
各アプリケーションごとに異なる部分はアプリケーションのInfo.plistとnibファイルに書き込まれています。
iPhone用アプリケーションはUIApplicationDelegateのメソッドを実装したクラスを持たなければならなりません。テンプレートではデリゲートまでの外枠は出来上がって保存されます。
※iPhoneアプリケーションプログラミングガイド7.9Mバイトのpdfファイルです。
-
第6回 ユーザーデフォルト
この記事は、2009年11月24日に掲載されました。【初級脱出】セミナーについて
初級脱出セミナー第5回は12月10日に メモリ管理 を開催します。XcodeやInstrumentsを使い直接確認しメモリ管理を実感してもらうつもりです。今回はいろいろなクラッシュを発生させその原因追求手順もご紹介する予定です。【初級脱出】シリーズのセミナーは経験者が効率的にiPhoneの開発スキルを高めることも念頭に置いた内容でご好評いただいております。
アプリケーションの初期設定
Cocoaではアプリケーションの初期設定はNSUserDefaultsクラスで扱います。
初期設定のインスタンスはアプリケーションごとに一つしかありません。それを得るには
[NSUserDefaults standardUserDefaults];
です。初期設定の初期値
ユーザーがアプリケーションをインストールした直後は出荷時の設定を適用します。(そうしないとユーザーサポートが忙しくなるでしょう)
出荷時の設定を用意するタイミング:
アプリケーションデリゲートの
+ (void)initialize;
を使います。#define MOSA_default1_Key @"default1" + (void)initialize { NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; NSDictionary *appDefaults = [[NSDictionary alloc] initWithObjectsAndKeys: [NSNumber numberWithInt:0], MOSA_default1_Key, nil]; [defaults registerDefaults:appDefaults]; [appDefaults release]; }
具体的にはNSUserDefaultsクラスのregisterDefaults:メソッドを使い登録します。
これだけで、アプリケーションは初期設定が変更されたかどうかに関係なく、アプリケーションのインストール直後から初期設定を読み込み利用できます。起動時の初期設定読み込みと復元
初期設定の参照は専用のメソッドが型別にありますのでそれを使います。
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; NSUinteger default; default = [defaults objectFor integerForKey:MOSA_default1_Key];
詳しくはNSUserDefaultsのドキュメントで確認してください。
設定の保存
設定を変更した時は変更した値とキーの組み合わせで登録します。
これも型別にメソッドがあります。
最後にsynchronizeメッセージを送ります。synchronizeを送ることで二次記憶に保存されます。synchronizeメソッドはBOOLの保存結果を返しますが、サンプルではこの値は利用していません。
保存はアプリケーション終了時に行う方法もあるでしょうが、設定の項目数が少ない場合は変更の都度行う事もできます。こうすると万一アプリケーションがクラッシュしたとしても最後に変更した設定は残ります。[[NSUserDefaults standardUserDefaults] setInteger:currentValue forKey:MOSA_default1_Key]; [[NSUserDefaults standardUserDefaults] synchronize];
設定画面をアプリケーション内に持つ場合はスイッチなど変更用コントロールのアクションで上記の処理を行います。
設定アプリケーションを利用する場合
iPhoneアプリケーションの設定は、環境設定インターフェイスで設定アプリを利用するように記述されています。この方法はユーザーが設定を見つけにくい問題があると思います。例えば「天気」アプリケーションのように、そのアプリケーションの中で設定できると直感的です。
Settingsバンドルの問題
ユーザーが見つけにくいほかにSettingsバンドルを用いる方法には限界があります。それは決められた形式しか処理できない事です。独自のプログラムを組み込む事ができないためピッカーなどはつかえません。基本的にスイッチ、文字入力、スライダ、それに項目選択だけになります。
文字入力の処理もないので入力を確定してもキーボードが残るのも使い勝手が悪く問題の一つです。
また項目選択も文字だけです。せめてアイコンを指定できると(特に色の指定など)ありがたいのですが…Settingsバンドルの利点
設定画面と設定の変更や保存をコーディングしなくてすみます。synchronizeメソッドの事は忘れてかまいません。iPhoneアプリのガイドラインにも従うことになります。
出荷時設定をきちんとしておくと、インストール直後に設定アプリケーションを開くと出荷時の設定になっています。
設定アプリケーションで設定するかは、設定項目などからアプリケーションごとに判断して良いようです。Settingsバンドルを使わなくてもそれを理由にリジェクトになる事はないようです。(ただし保証はできません)環境設定画面以外での利用
設定画面以外の状態、たとえばどのタブを使ったかなどを保存する場合もよくあります。
アプリケーションの状態を復元するために必要な情報は設定の保存とまったく同じ方法で保存し、アプリケーション起動時やビューの表示時に再現します。 -
第5回 プロパティリスト その2
この記事は、2009年11月09日に掲載されました。標準で対応していないデータはどうするか
UIColorなどはプロパティリストでは直接扱うことができません。このような場合は利用可能などれかのクラスに変換します。具体的には文字列かバイナリデータです。
文字列で保存する
文字列に変換して保存すると「Property List Editor」でも内容が簡単に確認でき便利です。
ただし文字列に変換する事で精度など情報の欠落が発生しないかはあらかじめ検討しなければなりません。精度を保つために文字数が増えてしまう場合はバイナリデータでの保存を検討した方が良い場合もあるでしょう。
色データの場合利用する色の種類を固定することで、色の名前や番号で管理する事が出来ます。
またRGBAの各値を保存し再現する方法も可能です。バイナリデータで保存する
Cocoaのオブジェクトを保存する場合NSDataに変換します。
セミナーで提供したサンプルソースプログラムではUIColorの値をNSDataに変換し保存し、それを読み出しています。
NSDataの変換のためにはNSKeyedArchiverとNSKeyedUnarchiverクラスがあります。ただしNSDataに変換するには変換(アーカイブ)しようとするクラスがNSCodingプロトコルに適合していなければなりません。このあたり話が込み入っていますが我慢してください。標準の組み合わせに分解する
文字列で保存するでも触れましたが色の場合はRGBAの要素に分解してNSNumberで保存することももちろん可能です。
数が多い場合はどの方法が処理時間と使用メモリなどの観点から適切なのか実験してみきわめてください。プロトコルについて
ここでプロトコルについて簡単に説明します。
プロトコルはメソッド宣言の集まりです。使い方はプロトコルを採用したクラスがプロトコルのメソッドを実装するだけです。
プロトコルのメソッドが用意されていれば、そのプロトコルに適合していると言います。
Cocoaではプロトコルの書式があり、コンパイル時にプロトコルの適合をチェックできるようになっています。
Xcode参照できる各クラスの解説の最初の部分を見るとどのクラスを継承しているかと、どのプロトコルに適合しているかが明記されています。
Xcodeのクラスの解説画面
■アーカイブとNSCoding
データとしてのオブジェクトの扱いを標準化したNSCodingはCocoaオブジェクトの保存には欠かせないものです。アーカイブはオブジェクトのフリーズドライなどと表現されますがそれは、アーカイブがオブジェクトを完全に復元できる情報であり、ファイルに保存していつでも復元できるからです。
オブジェクトをアーカイブしたりもとに戻したりするためにNSKeyedArchiverとNSKeyedUnarchiverがあります。この二つのクラスを利用するためにはNSCodingプロトコルに適合していなければなりません。なおnibファイルはCocoaのオブジェクトのアーカイブです。このため柔軟性も高くInterface Builder/iPhone Simulatorで動作確認なども実現できています。
NSCoding
NSCodingプロトコルは二つのメソッドからなります。
Initializing with a Coder- (id)initWithCoder:(NSCoder *)decoder
Encoding with a Coder
- (void)encodeWithCoder:(NSCoder *)encoder
独自クラスの対応
NSCodingプロトコルに適合しinitWithCoder:とencodeWithCoder:を実装しておくとアーカイブに対応できます。@interface MosaObject : NSObject <NSCoding>
のようにヘッダで記述します。
-
第4回 プロパティリスト その1
この記事は、2009年10月27日に掲載されました。連載は今回から『データ保存入門』の回で取り上げた内容をお届けします。
使い勝手の良い保存方法『プロパティリスト』
iPhoneでも「プロパティリスト」ファイルはMacと同じです。拡張子からplistとも呼ばれます。プロパティリストのファイルはMac上で「Property List Editor」で作成/表示/修正が可能です。ですのでプロトタイプの作成やテストの時にもいろいろ便利です。ファイルは共通ですのでiPhone開発でも有効です。
またプロパティリストで保存するとエンディアンの違いを考えずに済む点も助かります。
今後新たなデバイスが出たとしても大丈夫です(笑)。
開発中はアプリケーションのInfo.plistが最もお世話になるプロパティリストですね。
拡張子plistのMac上でのファイルアイコン
ファイル形式
プロパティリストファイルにはASCII 形式、XML形式、バイナリ形式の三つがあります。ASCII 形式は古い形式で、iPhoneのプログラミングで使う必要はなさそうです。今回のセミナーでもASCII 形式には触れません。
XML形式
XML形式は通常のテキストエディタで開く事ができます。XMLはテキストエディタで編集可能ですが、読み込み時に展開処理が必要です。バイナリ形式
バイナリ形式ですとより高速に開くことができます。ファイルサイズもコンパクトです。初期設定で利用
プロパティリストは柔軟な構造で、必要により定義できます。初期設定(アプリケーションの環境設定の保存ファイル)にも使われています。詳しくは次回以降で説明しますが、アプリケーションが定めたキーと値の組み合わせ(辞書)を保存します。
状態再現でも利用
環境設定画面以外にもアプリケーションの状態を再現するためにはいろいろな情報を保存しなければなりません。適切に状態を保存していなければ、次回起動時に同じ状態に表示はできません。必要な情報の保存用に初期設定ファイルに項目を追加することが可能です。
またウインドウが一つに限られるiPhoneアプリケーションは比較的規模が小さく、「書類」の概念もかなり簡略化することになります。一つの書類が一つのウインドウに対応するMacの実装はiPhoneにはそのままは使えません。アプリケーションにもよりますが、規模が小さければすべてのデータをプロパティリストファイルに保存するプログラミングも可能です。決まったデータに対応
プロパティリストはアプリケーションの設定やInfo.plistなどで最も頻繁に利用するファイル形式の一つです。頻繁に使うだけに扱いやすさも備えています。基本的なデータは含まれていますが、次に示す決まったデータしか扱えません。
文字列:NSString
日付: NSDate
数値: NSNumber
データ:NSData
配列: NSArray
辞書: NSDictionary
配列と辞書は入れ子(配列の要素に配列または辞書を入れる)が可能です。文字列データも柔軟な応用が可能です。対応していないデータは変換して保存する
対応していないデータは、対応可能な型、具体的には文字列かデータ(NSData)に変換することでプロパティリストに保存できます。
適さないデータ
変換する事でどのようなデータでもプロパティリストに保存する事は可能です。しかしプロパティリストに向かないデータもあります。例えば(大きな)画像や音声データ・動画データなどはプロパティリストに適当ではありません。これらは独立したファイルとして保存しそのパスまたはファイル名をプロパティリストに持たせるのがより自然です。
またパスワードなど機密が求められるデータもプロパティリストには向きません。バイナリ形式で保存
プロパティリストの保存形式をバイナリ形式にすることでコンパクトに(容量が小さく)なり読み書きも早くなります。早い反応が求められるiPhoneでは重要です。iPhoneアプリケーションプログラミングガイド※p137に説明があります。
一方テキストで保存すると任意のEditorで確認や修正が可能となるメリットがあります。iPhone用アプリケーションのInfo.plistファイルはバイナリになっています。※iPhoneアプリケーションプログラミングガイド
2009年10月に日本語訳版が更新されています。(7.9MBのpdfファイルです)つづく
-
第3回 デリゲート その3
この記事は、2009年10月13日に掲載されました。●デリゲートが動作しない原因
しくみを確認したところで、どのような場合にデリゲートがうまく動かないかをおさらいしましょう。ひとつはデリゲートメソッドのスペルの間違い、もう一つはデリゲート(インスタンス)の指定間違いです。デリゲートが動作しない原因のかなりの部分はこの二つではないでしょうか。
しくみのところで説明したようにCocoaの既存のクラスは決まったデリゲートメソッドの存在を確認し呼びます。スペルが違うと既存のクラスからはそのデリゲートメソッドが存在していない状態と認識されます。このため処理されません。(オプションのデリゲートメソッドは存在する場合にだけ呼ばれます)
デリゲートメソッドのスペルミスを防ぐには確実に動作しているソースまたは、リファレンスからコピーしましょう。綴りの読みは合っていても大文字小文字の間違いを犯してしまう場合もあるからです。デリゲート(インスタンス)の指定間違いはさらに二つの場合があります。デリゲートを指定し忘れた場合とデリゲートメソッドを記述したのとは別のクラスのインスタンスへ指定する場合です。第2回の例ではdがnilの場合と指定したdが対応するデリゲートメソッドを持たない場合に相当します。
[d applicationDidFinishLaunching:self];
nibでデリゲートを指定する場合、その指定を忘れても積極的に確認するまでは気付きにくいものです。
ソース上で指定する場合は検索で指定箇所を確認できるので安心ではあります。●デリゲートの動作を確認する
まず記述したデリゲートメソッドが呼ばれるかを確認しましょう。
デバッガでブレイクポイントを置くのが確実です。実行時にブレイクポイントで止まらなかったら正しく呼ばれていません。ひとつずつ確認し原因を追及しましょう。
まずはスペルですね、レファレンスからコピーし直して確認してください。
スペル違いが原因でなければ、デリゲートメソッドを記述したクラスのインスタンスが存在するか確認しましょう。確実に実行される初期化などのメソッド内にブレイクポイントを置きます。
デリゲート(インスタンス)の指定が正しいかも確認しましょう。どこで指定しているかを確認してください。デリゲートとして指定したインスタンスはデリゲートメソッドを記述したクラスでしょうか?●サンプルも間違う
デリゲートメソッドではありませんが第1回のセミナーでご紹介したApple社のサンプルにメソッドを違うクラスに記述したものがありました。http://developer.apple.com/iphone/library/samplecode/TouchCells/index.html
AppDelegate.m
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation) interfaceOrientation { return YES; }
MyTableViewController.m の shouldAutorotateToInterfaceOrientation: メソッドは正しいですが、AppDelegate.m のこのメソッドは呼ばれる事はありません。呼ばれる事がないので気がつきませんが、このソースをもとに別のプロジェクトを作ると『shouldAutorotateToInterfaceOrientation:でYESを返すようにしているのになぜ回転しない???』と悩むことになります。
●デリゲートのおさらい
ここまでの説明でデリゲートに対して親しみが増していただけたでしょうか。
iPhoneAppProgrammingGuide.pdfの21ページの説明をおさらいとして転記します。これはデザインパターンとしてのデリゲーションの説明です。デリゲーションデザインパターンは、複雑なオブジェクトをサブクラス化せずに変更する方法の1つです。サブクラス化する代わりに、複雑なオブジェクトをそのまま使用し、そのオブジェクトの動作を変更するためのカスタムコードを別のオブジェクト(これを、デリゲートオブジェクトと呼ぶ)の内部に配置します。あらかじめ定義されたタイミングで、この複雑なオブジェクトは、デリゲートオブジェクトのメソッドを呼び出して、カスタムコードを実行する機会を提供します。
デリゲートは重要で頻繁に使います。複雑なオブジェクトをそのまま使うことで、自分のソースコードはシンプルに保つ事ができます。
次回からプロパティリストを解説する予定です。
-
第2回 デリゲートその2
この記事は、2009年09月21日に掲載されました。●デリゲートは何のためにつかう?
第1回ではあっさりとデリゲートを『Cocoaで用意されたクラスの動作を、クラスを継承せずにカスタマイズするための柔軟な仕組みです』と書きました。この点はCocoaの特徴としても重要ですし、デリゲートが何のためかの答えそのものでもあるので復習しましょう。デリゲートはCocoaの機能をカスタマイズするための仕組みなのです。
オブジェクトオリエンテッドなプログラム作りに親しんでいる皆さんには『継承してカスタマイズする事』が普通かも知れませんがそれはCocoaの流儀ではありません。もちろん継承してカスタマイズするテクニックもあちこちで使いますが、デリゲートを使うと継承せず単に使用するだけで済む場合が多いのです。●デリゲートのしくみ
ここでapplicationDidFinishLaunching:を例にデリゲートのしくみを説明します。このメソッドはUIApplicationクラスのデリゲートメソッドです。【注1】
デリゲートメソッドapplicationDidFinishLaunching:は UIApplication が呼びます。
UIApplicationは自分のデリゲート(ここではデリゲートオブジェクトを d とします)がapplicationDidFinishLaunching:メソッドを持つか調べます。Cocoaの特徴として実行時にクラスの持っているメソッドを調べる事ができるのです。【注2】
持っている場合は[d applicationDidFinishLaunching:self];
を実行します。これで見事UIApplicationとは別のオブジェクト d のデリゲートメソッドが呼ばれます。
このしくみで、デリゲートメソッドを記述する事で自在にカスタマイズができ、デリゲートメソッドがなければ標準動作となるのです。●デリゲートを利用する
第1回セミナーではデリゲートをAppleの『WhichWayIsUp』サンプルを例に説明しました。WhichWayIsUpAppDelegate.hから抜粋します。@interface WhichWayIsUpAppDelegate : NSObject <UIApplicationDelegate> { IBOutlet UIWindow *window; IBOutlet CrateViewController *crateViewController; }
この記述は WhichWayIsUpAppDelegate クラスは NSObject を継承し UIApplicationDelegate プロトコルを採用していると呼ぶそうです。
- (void)applicationDidFinishLaunching:(UIApplication *)application;
はUIApplicationDelegate Protocolで宣言されていますので、ヘッダファイルに個々のメソッドを記述することなしに利用できます。
プロトコル
ProtocolもCocoaの用語の一つです。プロトコルは(ごく簡単にいえば)メソッド宣言の集まりです。【注3】 ずいぶん乱暴な説明ですがまずはCocoa用語としての『プロトコル』の存在を頭の片隅に置くだけで十分です。詳しくは用語説明などで確認してください。デリゲートを実装するクラスはデリゲートのプロトコルを採用していればどのようなクラスでもかまいません。
慣れないうちは(慣れてきても油断すると)デリゲートメソッドを記述しているはずなのに、意図どおりカスタマイズできないといった現象に遭遇する場合があります。次回はどのような場合にデリゲートが正しく動作しないか説明します。
【注1】UIApplication
UIApplicationのインスタンスは各アプリケーションに一つしか無く [UIApplication sharedApplication] でいつでも得る事ができます。デリゲートのしくみのおかげで通常は UIApplication を継承してカスタマイズする必要はめったにありません。【注2】実行時にクラスの持っているメソッドを調べることができる
具体的には – (BOOL)respondsToSelector:(SEL)aSelector メソッドを使います。(と『ヒレガス本』【Mac OS X Cocoaプログラミング】に書いてあります)
存在しないメソッドを実行しようとすると例外が発生しますので実行時に変化する外部のオブジェクトの利用前に確認は必須です。【注3】UIApplicationDelegateの中身
ビルドした状態でXcodeのエディタ上の@interface WhichWayIsUpAppDelegate : NSObject <UIApplicationDelegate> {
の UIApplicationDelegate をコマンドキーを押しながらダブルクリックするとUIApplication.hが開き
@protocol UIApplicationDelegate<NSObject>
の行が選択されるはずです。このようにXcodeでは『コマンドキーを押しながら変数名などをダブルクリック』で定義を表示します。
-
第1回 デリゲートその1
この記事は、2009年09月04日に掲載されました。●はじめに
今年(2009年)5月【初級脱出】公式サンプルから学ぶiPhone UIの定石セミナーを東京で行いました。このセミナーは続きを9月から毎月行う事になりました。
モサ伝Onlineの連載では最初のセミナー内容から順次ダイジェストをお届けしたいと思います。初級脱出セミナー
【初級脱出】のタイトルのとおり、まったくの初心者の方には少し難しいかも知れません。『乗り越えなければならない壁』のひとつを乗り越えていくためのセミナーです。第1回開催時には確定していませんでしたが、今後月一回のペースで9月を含めあと6回開催を予定しています。
第2回「データ保存入門」は9月10日です。
●Cocoaのわかりにくいところ
Cocoaは実用的なフレームワークです。実用的に使えるためには当然規模が大きくなっています。大規模なフレームワークをあっさりと完全に理解することは至難の業です。特になじみのない概念や機構は理解しにくいと思います。
今回は私の経験上わかりにくかった部分を重点にご説明します。Cocoaをはじめた当初なじめなかったり、うまく動かない原因がすぐにわからなかったことを列挙すると…デリゲート と カテゴリー
デリゲートはCocoaで重要で頻繁に使います、しかし慣れないと落とし穴が多いので注意が必要です。
カテゴリーは作らなくてもプログラミングは可能です。また使うだけならまったく意識する必要はなく難しくはありません(がすぐにはなじめませんでした)。データソース
直感的ですが実装が必須のメソッドと、意図する動作や状況で実装不要なメソッドがあります。
識別には単純な整数だけでなくNSIndexPath型も使われます。iPhoneの場合でも階層が深くなる場合があるのでその時は注意が必要です。nib(xib)ファイル
Interface Builder(以下IBと略します)とnibファイルはとても強力で便利ですが、その反面『何が』『どこで』設定されているのかわかりにくいのが現実です。このため
1)全体を把握しにくい
2)関係が目にみえない
この二つの問題があると思います。でもIBも改善されています。(もうひとつIBは文章で説明しにくいという問題もあります)いくつも『わかりにくい』ところやすぐにはなじめないところがあります、どこかでつまずいても落ち込む必要はありません。ひとつづつマスターして行きましょう。
●デリゲートについて
まずデリゲートを説明しましょう。ぜひ自分のものにしてください。Delegation
「iPhoneアプリケーションチュートリアル」pdfの10ページDelegationに概要説明があります。この説明は抽象的でわかりにくと思います。具体的にみると難しくないのでご安心ください。
iPhoneアプリケーションチュートリアル(iPhone Dev Centerへのログインが必要です)突き詰めると特定の仕組みを実現するために用意されたメソッド(たいてい複数あります)です。Cocoaで用意されたクラスの動作を、クラスを継承せずにカスタマイズするための柔軟な仕組みです。
デリゲート
依頼を受け取るオブジェクトをデリゲートと呼びます。デリゲートは一度に一つだけ指定できます。IBでもソースリストでもどちらの方法でも設定できます。
あるオブジェクトはひとつしかデリゲートを持てませんが、複数のオブジェクトが一つのオブジェクトをそれぞれのデリゲートとして指定することができます。
nibでデリゲートを指定している場合どのオブジェクトを指定しているか、あるいは指定するのを忘れているか、わかりにくいので注意が必要です。デリゲートメソッド
デリゲートメソッドを単に『デリゲート』と呼ぶ事も少なくないので区別してください。(これもわかりにくいと感じる原因の一つですね)
デリゲートメソッドは綴りが厳密であることが要求されます。大文字小文字も違うと正常に処理されません。
プログラムする際はレファレンスからコピーするか、候補を自動表示させて利用するのが間違いを防げ確実です。
正しく動作しない場合はブレイクポイントを置いて、呼ばれるべき時に呼ばれているか確認してください。デリゲートの設定と参照
次にデリゲートの指定を行うメソッドやプロパティを説明します。- (void)setDelegate:(id)delegate
デリゲートをnibではなくプログラム内で設定するにはsetDelegate:メソッドを使います。渡すdelegateは保持されず参照するだけです。
@property(assign, nonatomic) id
delegate プロパティでも同様です。
現在のデリゲートを参照するためのメソッドは
- (id)delegate
です。
nibで設定しているデリゲートをソース内で変更するようなプログラミングは動作がわかりにくくなるので避けましょう。同様に実行時にデリゲートを切替えるようなことも行わない方が無難です。次回に続きます。