QuartzFun(7)~サンプルコードとの差異

2011. 08. 29
これまで説明した初版の内容と原著のサンプルコード(iOS 3.x版は『Beginning iPhone 3 Development Exploring the iPhone SDK』、iOS 4.x版は『Beginning iPhone 4 Development Exploring the iOS SDK』)との差異を見てみます。


●カテゴリの宣言

ランダム色を生成するメソッドrandomColorはUIColorクラスのカテゴリで宣言・実装していますが、iOS 4.x版のサンプルコードでは、ヘッダファイルUIColor-Random.hでのカテゴリ名が『UIColor_Random』となっています。
(ソースファイルUIColor-Random.mで実装する際のカテゴリ名は他と同じく『Random』)

正常に動作はしているようですが、単なるミスなのか、それともわざと変えてあるのか、だとすれば宣言と実装でカテゴリ名が異なっていても構わないのかは謎です。

またカテゴリのファリル名が『クラス - カテゴリ名』となっていますが、『クラス + カテゴリ名』と命名するのが一般的です。


●πの定義

今回は使用していませんが、定数を定義しているConstants.hで、度とラジアンを変換するdegreesToRadianで、初版では円周率を数値で直打ち(3.14159265358979323846)していますが、iOS 3.x/4.x版では数学関数math.hのマクロM_PIに置換されています。


●ビューコントローラでのカテゴリのインポート

iOS 4.x版では、ビューコントローラQuartzFunViewController.mでカテゴリUIColor-Random.hのインポートを行っていません。

確かに描画色を設定するアクションメソッドchangeColor:は(QuartzFunView.hで宣言している)フラグuseRandomColorは使用していますが、ランダム色を設定するメソッドrandomColorの呼び出しはしていないので、UIColor-Random.hをインポートする必要が無いからだと思われます。


●currentRectメソッドの宣言と実装

描画矩形を保持するcurrentRectメソッドは、初版では読み込み専用プロパティとして宣言し、手動実装するため@dynamicというコンパイラ指示子を使用していました。

iOS 3.x版ではQuartzFunViewクラス内でのみ使用する読み込み専用のメソッドということからか、ヘッダファイルで宣言せず、単に実装しています。

しかしiOS 4.x版では、初版同様に読み込み専用プロパティとして宣言している一方、@dynamicを使用せずに実装しています。

currentRectメソッドは動的に割り当てているわけではありませんので@dynamicは必要ではありません。

いずれの方法でも問題はありませんが、読み込み専用で手動実装ということを考えれば、iOS 3.x版のようにプロパティ宣言をせずに実装するのがシンプルかと思います。
(ただその場合、currentRectを呼び出すメソッドより前に実装するという制限が付きます)


●currentRectメソッドの内容

iOS  4.x版ではcurrentRect内の実装内容が異なります。

初版とiOS 3.x版では、始点firstTouchと終点lastTouchの座標を比較し、左上の原点に則して座標の入れ替えやサイズの絶対値補正をしています。

しかしCGGeometryの概要を見ると『矩形の原点が (0.0 , 0.0) でサイズが (10.0 , 10.0) のものと、矩形の原点が (10.0 , 10.0) でサイズが (-10.0 , -10.0) のものは全く同一になります。』とあり、サイズ指定が負の値、つまり始点から終点へのベクトルが右下方向でなくても構わないことが分かります。

したがってiOS 4.x版では特に補正はせず、原点には始点の座標を、サイズには終点と始点の差を単純に指定しています。


●drawRect:メソッドの内容

iOS 4.x版では、drawRect:メソッド内で描画色currentColorが設定されていない場合の初期化を行っていますが、nibファイルからビューを読み込むinitWithCoder:でcurrentColorの初期化を行っているので、drawRect:内で再度行う必要性は無いと思われます。
(仮に初期化がされていない場合、C言語定数であるColorTabIndexは暗黙的に0(kRedColorTab)になり、redColorに設定されます)

またiOS 3.x版では、drawRect:メソッド内でcurrentRect変数を(currentRectメソッドと同内容で)求めていますが、これも必要性は無いと思われます。


●touchesBegan:withEvent:メソッドでのredrawRectの初期化

iOS 3.x/4.x版では、touchesBegan:withEvent:メソッド内で再描画領域redrawRectの初期化を行っていません。

確かにtouchesBegan:withEvent:の場合は、setNeedsDisplayメソッドで画面全体を再描画するので再描画領域を設定する必要は無いからだと思います。


●touchesMoved:withEvent:メソッドでのredrawRectの補正

iOS 3.x版では、touchesMoved:withEvent:メソッドでも再描画領域redrawRectをCGRectInsetによって線幅分の補正を行っています。

iOS 4.x版では初版と同様にこの補正が行われていないため、ドラッグ中の図形が線幅分小さく描画されますので、この補正は追加するべきです。


●画像サイズ

実動作やソースコードに影響はありませんが、貼り付ける画像iphone.pngがiOS 3.x版は52 x 100ピクセル、iOS 4.x版は24 x 47ピクセルと違っています。

iOS 3.x版)
7515

iOS 4.x版)
7516



参考文献

はじめてのiPhone3プログラミングはじめてのiPhone3プログラミング
(2009/12/17)
Dave Mark、Jeff LaMarche 他

商品詳細を見る

Beginning Ios 6 Development: Exploring the Ios SdkBeginning Ios 6 Development: Exploring the Ios Sdk
(2012/12/26)
David Mark、Jack Nutting 他

商品詳細を見る

QuartzFun(6)~タッチイベントの編集

2011. 08. 28
iOS Viewプログラミングガイド』の『カスタムビューの定義』において、『タッチを自分で処理する場合は、touchesBegan:withEvent:、touchesMoved:withEvent:、 touchesEnded:withEvent:、およびtouchesCancelled:withEvent:メソッドをオーバー ライドします(他にどのタッチ関連のメソッドをオーバーライドするかにかかわらず、 touchesCancelled:withEvent:メソッドは常にオーバーライドが必要であることに注意し てください)。』とありますが、本書ではtouchesCancelled:withEvent:メソッドに触れておらず、(サンプルコードも含めて)実装もされていません。

今回はQuartzによるビューのカスタム描画が目的なので実装していませんが、ご自身でアプリケーションを作る際には注意してください。


7)touchesBegan:withEvent:メソッドの実装

touchesBegan:withEvent:メソッドはビューがタッチされた際の処理を記述するメソッドで、ここではランダム色の設定と、タッチされた座標を検出しドラッグの始点と終点として設定、再描画領域の設定と再描画の要請を行います。

最初にナビゲーションバーのセグメンテッドコントロールでランダム色が選択されているかを判別し、ランダム色の場合はUIColorのカテゴリメソッドのrandomColorを呼び出し、currentColorに設定します。
(これによりタッチする度に色が変わることになります)

次にタッチ情報のセットtouchesからanyObjectメソッドでオブジェクトを取り出し、UITouchのインスタンスとします。

そしてlocationInView:メソッドでタッチ位置を取得し、始点firstTouchと(ドラッグしなかった場合のため)終点lastTouchに設定します。

続いて再描画領域redrawRectの初期化を行います。

画像を貼り付けるImageが選択されていた場合は、タッチ位置を中心として画像を貼り付ける都合上、再描画領域の原点をオフセットで補正し、更に画像全体の領域を再描画領域として初期化します。

画像以外の場合は単純にタッチ位置を原点とし、領域は(0, 0)としています。

最後にsetNeedsDisplayメソッドでビュー全体を再描画(つまり既存の図形や画像を消去)します。


touchesBegan:withEvent:

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event

ビューまたはウィンドウ内に、指を一本以上タッチした時にレシーバに伝えます。

このメソッドはデフォルトでは何も実装しません。

しかしUIResponderはUIKitの直接のサブクラスであり、特にUIViewのレスポンダチェーンにメッセージを転送します。

次のレスポンダにメッセージを転送するには、以下のようにsuper(スーパークラスの実装)にメッセージを送信し、次のレスポンダに直接メッセージを送信しないでください。

[super touchesBegan:touches withEvent:event];

このメソッドを(一般的な使用パターンである)superの呼び出しをせずにオーバーライドする場合は、タッチイベント処理のため他のメソッドをスタブとして実装し、オーバーライドする必要があります。

マルチタッチはデフォルトでは無効になっています。

マルチタッチイベントを受け取れるようにするには、同じビューインスタンスのmultipleTouchEnabledプロパティをYESに設定する必要があります。

touches:eventによって表された、イベントの開始フェイズのためのタッチ操作を表す、UITouchインスタンスのセットを指定します。

event:タッチ操作が属するイベントを表すオブジェクトを指定します。


locationInView:

- (CGPoint)locationInView:(UIView *)view

指定したビューの座標系内におけるレシーバの現在位置を返します。

戻り値は、view内のレシーバの位置を示す点です。

このメソッドは、指定したビューの座標系内のUITouchオブジェクトの現在位置を返します。

タッチオブジェクトは他のビューから当該ビューへ転送されている可能性があるので、このメソッドはタッチされた位置を指定したビューの座標系へ必要な変換を実行します。

view:タッチ位置を必要とする座標系のビューオブジェクトを指定します。
カスタムビューで自身の座標系でタッチされた位置を取得してタッチの処理をする場合は、selfを指定することができます。
ウィンドウの座標系内でのタッチ位置を取得する場合はnilを渡します。


setNeedsDisplay

- (void)setNeedsDisplay

再描画が必要であるとして、レシーバの境界矩形全体をマークします。

ビューのコンテンツを再描画する必要がある場合に、システムに通知するためにこのメソッドまたはsetNeedsDisplayInRect:を使用することができます。

このメソッドは要求の印をマークすると、すぐにコードに制御を戻します。

ビューは次の再描画サイクルまでは実際に再描画されず、全て無効化された時点でビューが更新されます。

:ビューがCAEAGLLayerオブジェクトによって背景になっている場合、このメソッドは効果がありません。
これは(UIKitやCore Graphicsなど)ネイティブの描画技術を使用してコンテンツを描画したビューのみを意図しています。

ビューのコンテンツや外観を変更した時のみ、ビューの再描画を要求するためにこのメソッドを使用する必要があります。

単にビューのジオメトリを変更する場合は、通常ビューを再描画しません。

代わりにビューのcontentModeプロパティの値に基づいて、既存のコンテンツが調整されます。

再描画する必要の無いコンテンツの変更を避けることによって、既存のコンテンツの再表示のパフォーマンスを向上させることができます。


8)touchesEnded:withEvent:メソッドの実装

touchesEnded:withEvent:メソッドは、ビューからタッチされた指が離れた際の処理を記述するメソッドで、ここでは指が離れた座標を検出しドラッグの終点として設定し、再描画領域の拡張と再描画の要請を行います。

最初に前述のtouchesBegan:withEvent:と同様に、タッチ情報のセットtouchesからlocationInView:メソッドで指が離れた位置を取得し、ドラッグの終点lastTouchとして設定します。

次に再描画領域redrawRectの拡張を行います。

CGRectUnion関数を使用して、touchesBegan:withEvent:で初期化したredrawRectに終点lastTouchによって変わる領域を加えます。

画像を貼り付けるImageが選択されていた場合は、終点を中心とした画像全体の領域を加えます。

それ以外の場合は、ドラッグした始点と終点によって決定される矩形が加えられます。

その後、CGRectInset関数で再描画領域を線幅(drawRect:メソッド内でCGContextSetLineWidth関数で指定した値)の分を、x、yそれぞれの方向に2ポイントずつ拡大しています。

最後にsetNeedsDisplayInRect:メソッドで再描画領域の描画を要請します。


touchesEnded:withEvent:

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event

ビューまたはウィンドウから、指を一本以上離した時にレシーバに伝えます。

このメソッドはデフォルトでは何も実装しません。

しかしUIResponderはUIKitの直接のサブクラスであり、特にUIViewのレスポンダチェーンにメッセージを転送します。

次のレスポンダにメッセージを転送するには、以下のようにsuper(スーパークラスの実装)にメッセージを送信し、次のレスポンダに直接メッセージを送信しないでください。

[super touchesEnded:touches withEvent:event];

オブジェクトがtouchesEnded:withEvent:メッセージを受信した時、touchesBegan:withEvent:の実装内に確立した状態情報をクリーンアップする必要があります。

マルチタッチはデフォルトでは無効です。

マルチタッチイベントを受け取れるようにするには、同じビューインスタンスのmultipleTouchEnabledプロパティをYESに設定する必要があります。

このメソッドを(一般的な使用パターンである)superの呼び出しをせずにオーバーライドする場合は、タッチイベント処理のため他のメソッドをスタブとして実装し、オーバーライドする必要があります。

touches:eventによって表された、イベントの終了フェイズのためのタッチ操作を表す、UITouchインスタンスのセットを指定します。

event:タッチ操作が属するイベントを表すオブジェクトを指定します。


CGRectUnion

CGRect CGRectUnion (
    CGRect r1,
    CGRect r2
);

2つの元となる矩形を含む最小の矩形を返します。

両矩形は和集合の計算をするために、事前に標準化してください。

矩形のどちらかがnull矩形の場合、もう一方の矩形のコピーが返されます(両矩形がnullの場合は、結果にnull矩形が入ります)。

それ以外の場合は、完全に元の矩形を含んだ矩形が返されます。

r1:一つ目の元となる矩形を指定します。

r2:二つ目の元となる矩形を指定します。


CGRectInset

CGRect CGRectInset (
    CGRect rect,
    CGFloat dx,
    CGFloat dy
);

同じ中心点を持つ、元の矩形より小さいまたは大きい矩形を返します。

戻り値は、原点の値はx軸はdx引数によって指定された距離、y軸はdy引数によって指定された距離がオフセットされ、サイズが(2*dx, 2*dy)に調整された、元の矩形に比例した矩形になります。

dxとdyが正の値の場合は矩形のサイズが縮小します。

dxとdyが負の値の場合は矩形のサイズが拡大します。

矩形は標準化されてから挿入される引数が適用されます。

結果として得られる矩形が、負の高さまたは幅の場合は空の矩形が返されます

rect:元となるCGRect構造体を指定します。

dx:元の矩形の調整に使用するx座標値を指定します。
挿入する矩形を生成するには、正の値を指定します。
大きく囲む矩形を生成するには、負の値を指定します。

dy:元の矩形の調整に使用するy座標値を指定します。
挿入する矩形を生成するには、正の値を指定します。
大きく囲む矩形を生成するには、負の値を指定します。


setNeedsDisplayInRect:

- (void)setNeedsDisplayInRect:(CGRect)invalidRect

再描画が必要であるとして、レシーバの指定された矩形をマークします。

ビューのコンテンツを再描画する必要がある場合に、システムに通知するためにこのメソッドまたはsetNeedsDisplayを使用することができます。

このメソッドは、無効な矩形を示すビューの現在のリストに指定された矩形を追加すると、すぐにコードに制御を戻します。

ビューは次の再描画サイクルまでは実際に再描画されず、全て無効化された時点でビューが更新されます。

:ビューがCAEAGLLayerオブジェクトによって背景になっている場合、このメソッドは効果がありません。
これは(UIKitやCore Graphicsなど)ネイティブの描画技術を使用してコンテンツを描画したビューのみを意図しています。

ビューのコンテンツや外観を変更した時のみ、ビューの再描画を要求するためにこのメソッドを使用する必要があります。

単にビューのジオメトリを変更する場合は、通常ビューを再描画しません。

代わりにビューのcontentModeプロパティの値に基づいて、既存のコンテンツが調整されます。

再描画する必要の無いコンテンツの変更を避けることによって、既存のコンテンツの再表示のパフォーマンスを向上させることができます。


9)touchesMoved:withEvent:メソッドの実装

touchesMoved:withEvent:メソッドは、ビュー上でタッチしている指が移動している際の処理を記述するメソッドで、ここでは移動中の指の座標を検出して仮の終点として設定し、再描画領域の拡張と再描画の要請を行います。

前述のtouchesEnded:withEvent:と同様に、ドラッグ中の座標を仮の終点lastTouchとして設定し、再描画領域redrawRectの拡張を行います。

画像を貼り付けるImageが選択されていた場合に画像全体の領域をredrawRectとする部分は変わりませんが、touchesEnded:withEvent:の場合と異なり、(else文に書かれていないので)それ以外の場合と区別無くCGRectUnion関数でcurrentRectと合成されています。

後程サンプルコードの差異でも触れますが、ここでtouchesEnded:withEvent:での処理と異なる処理を行う必然性は特になく、移動中だからといって画像を選択した場合にもcurrentRectとの合成を行う必要はありません。
(else文でCGRectUnionによる合成を行っても見た目の動作は変わりません)

むしろtouchesEnded:withEvent:に記述されていた、CGRectInset関数による線幅の補正が削られていることにより、Image以外の図形の描画の際は移動中の図形が線幅分小さく描画され、指を離した時に線幅分大きくなる症状が出ます。
(Lineも影響は見え難いですが、線分の端が影響されています)

これは補正文をコピー&ペーストすることで解決します。


touchesMoved:withEvent:

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event

ビューまたはウィンドウ内で、一本以上の指に関連付けされたイベントが移動した時にレシーバに伝えます。

このメソッドはデフォルトでは何も実装しません。

しかしUIResponderはUIKitの直接のサブクラスであり、特にUIViewのレスポンダチェーンにメッセージを転送します。

次のレスポンダにメッセージを転送するには、以下のようにsuper(スーパークラスの実装)にメッセージを送信し、次のレスポンダに直接メッセージを送信しないでください。

[super touchesMoved:touches withEvent:event];

マルチタッチはデフォルトでは無効です。

マルチタッチイベントを受け取れるようにするには、同じビューインスタンスのmultipleTouchEnabledプロパティをYESに設定する必要があります。

このメソッドを(一般的な使用パターンである)superの呼び出しをせずにオーバーライドする場合は、タッチイベント処理のため他のメソッドをスタブとして実装し、オーバーライドする必要があります。

touches:eventによって表された、イベント中の移動タッチ操作を表す、UITouchインスタンスのセットを指定します。

event:タッチ操作が属するイベントを表すオブジェクトを指定します。


10)deallocメソッドの編集

deallocメソッドでは、Objective-Cのオブジェクトの2つのプロパティcurrentColorとdrawImageの解放を行っています。


●実行

touchesMoved:withEvent:メソッドのところで述べたように、ドラッグ中と後では図形表示にズレが生じます。

ドラッグ中)
7513

ドラッグ後)
7514

静止画で比較すると分かり難いですが、実際にシミュレータなどで操作するとすぐ気付きます。



参考文献

はじめてのiPhone3プログラミングはじめてのiPhone3プログラミング
(2009/12/17)
Dave Mark、Jeff LaMarche 他

商品詳細を見る

Beginning Ios 6 Development: Exploring the Ios SdkBeginning Ios 6 Development: Exploring the Ios Sdk
(2012/12/26)
David Mark、Jack Nutting 他

商品詳細を見る

QuartzFun(5)~ビューの編集(2)

2011. 08. 27
6)drawRect:メソッドの実装

drawRect:メソッドは、現在選択されている描画色と描画図形、ならびにドラッグで指定された領域を元にグラフィックスコンテキストを作成します。

旧版の本書では『長方形と楕円を描く』の項目で、太字で強調されていはいませんがcurrentColorが設定されていない場合にredColorを指定する処理がこっそり追加されています。

ただ今回は先のinitWithCoder:メソッド内でcurrentColorを初期化しているので追加しませんでした。
(気になる方は追加してください)

最初にUIGraphicsGetCurrentContext関数で現在のグラフィックスコンテキストを取得します。

次にCGContextSetLineWidthで線幅を、CGContextSetStrokeColorWithColorで線の色、CGContextSetFillColorWithColorで塗りつぶし色を設定します。

色の指定では、現在選択されている色を示すcurrentColorがUIColorオブジェクトなので、CGColorプロパティでCGColorRefに型変換しています。

続いて描画図形の判別を行います。

ビューコントローラQuartzFunViewControllerのアクションメソッドchangeShapeで設定されたプロパティshapeTypeを使用して、switch文で分岐します。

Line)
直線の描画は、CGContextMoveToPointで始点(firstTouch)に移動し、CGContextAddLineToPointで終点(lastTouch)までの直線を追加、最後にCGContextStrokePathで線を描画します。

Rect)
矩形の描画は、CGContextAddRectでcurrentRect矩形のパスを追加し、CGContextDrawPathで、指定された描画モード(kCGPathFillStroke:非ゼロワインディング規則による塗りつぶしと線の描画)でパスを描画します。

※ 非ゼロワインディング規則や偶奇規則についての詳細は『Quartz 2D Programming Guide/Paths/Painting a Path/Filling a Path』や『Adobe/ActionScript 3.0 開発ガイド/表示/描画 API の使用/描画 API の高度な使用/ワインディング規則の定義』などを参照してください。

Ellipse)
楕円の描画は、CGContextAddEllipseInRectでcurrentRect矩形に内接する楕円のパスを追加し、CGContextDrawPathで指定された描画モード(『Rect』と同じkCGPathFillStroke)でパスを描画します。

Image)
画像の描画にはdrawAtPoint:メソッドを用いていますが、引数として指定する座標を原点として右下に画像が貼られます。
今回は指定する座標(ドラッグの始点または終点)を中心に画像を貼るため、画像のsizeプロパティで幅と高さを取得して2で割り、CGPointMake関数で指定された座標からオフセット分として引くことで画像の貼り付け座標を補正しています。
switch文でこの項目を{ }で囲んでいるのは、『二流プログラマの三流な日常/switch文の途中で宣言する』によると、switch文の途中で変数を宣言することができないからで、これを回避するにはswitch文の前に宣言するか、case:ラベルを{ }で囲む必要があるためです。


UIGraphicsGetCurrentContext

CGContextRef UIGraphicsGetCurrentContext (
    void
);

戻り値は、現在のグラフィックスコンテキストです。

デフォルトでは、現在のグラフィックスコンテキストはnilです。

drawRect:メソッドの呼び出しより前に、ビューオブジェクトはスタックに有効なコンテキストをプッシュし、最新のものとします。

描画でUIViewオブジェクトを使用しない場合は、UIGraphicsPushContext関数を使用して、手動で有効なコンテキストをスタックにプッシュする必要があります。

この関数は、アプリケーションのメインスレッドからのみ呼び出す必要があります。


CGContextSetLineWidth

void CGContextSetLineWidth (
    CGContextRef c,
    CGFloat width
);

グラフィックスコンテキストの線幅を設定します。

デフォルトの線幅は1単位です。

ストロークする時、線は幅が半分ずつになるようにパスを跨いでいます。

c:変更するグラフィックスコンテキストを指定します。

width:ユーザ空間の単位で使用する新規の線幅を指定します。
値は0より大きい必要があります。


CGContextSetStrokeColorWithColor

void CGContextSetStrokeColorWithColor (
    CGContextRef c,
    CGColorRef color
);

Quartzのカラーを使用して、コンテキスト内の現在のストローク色を設定します。

c:変更するグラフィックスコンテキストを指定します。

color:新しいストローク色を指定します。


CGContextSetFillColorWithColor

void CGContextSetFillColorWithColor (
    CGContextRef c,
    CGColorRef color
);

Quartzのカラーを使用して、グラフィックスコンテキスト内の現在の塗りつぶし色を設定します。

c:塗りつぶし色を設定するグラフィックスコンテキストを指定します。

color:新しい塗りつぶし色を指定します。


CGColor

@property(nonatomic,readonly) CGColorRef CGColor

レシーバの色に対応するQuartzの色の参照です。(読み込みのみ)


CGContextMoveToPoint

void CGContextMoveToPoint (
    CGContextRef c,
    CGFloat x,
    CGFloat y
);

指定したポイントから新しいサブパスを開始します。

現在のポイントはこの開始地点に設定されます。

c:グラフィックスコンテキストを指定します。

x:ユーザ空間座標内でのポイントのx値を指定します。

y:ユーザ空間座標内でのポイントのy値を指定します。


CGContextAddLineToPoint

void CGContextAddLineToPoint (
    CGContextRef c,
    CGFloat x,
    CGFloat y
);

現在のポイントから指定したポイントまでの直線を追加します。

線分を追加した後で、現在のポイントは線分の終点に設定されます。

c:現在のパスが空ではないグラフィックスコンテキストを指定します。

x:ユーザ空間内での線分の終点となるx値を指定します。

y:ユーザ空間内での線分の終点となるy値を指定します。


CGContextStrokePath

void CGContextStrokePath (
    CGContextRef c
);

現在のパスの沿った線を描画します。

Quartzはパスを描画するために、グラフィックス状態の線幅とストローク色を使用します。

この関数を呼び出したときの副作用として、Quartzは現在のパスをクリアします。

c:グラフィックスコンテキストを指定します。


CGContextAddRect

void CGContextAddRect (
    CGContextRef c,
    CGRect rect
);

現在のパスに矩形のパスを追加します。

これは以下のような一連の工程で、パスへ矩形を追加する便利な関数です。

// start at origin
CGContextMoveToPoint (c, CGRectGetMinX(rect), CGRectGetMinY(rect));

// add bottom edge
CGContextAddLineToPoint (c, CGRectGetMaxX(rect), CGRectGetMinY(rect));

// add right edge
CGContextAddLineToPoint (c, CGRectGetMaxX(rect), CGRectGetMaxY(rect);

// add top edge
CGContextAddLineToPoint (c, CGRectGetMinX(rect), CGRectGetMaxY(rect));

// add left edge and close
CGContextClosePath (c);

c:グラフィックスコンテキストを指定します。

rect:ユーザ空間座標内での矩形を指定します。


CGContextDrawPath

void CGContextDrawPath (
    CGContextRef c,
    CGPathDrawingMode mode
);

指定された描画モードを使用して、現在のパスを描画します。

c:描画するパスを含むグラフィックスコンテキストを指定します。

mode:パスの描画モード定数(kCGPathFillkCGPathEOFillkCGPathStrokekCGPathFillStrokekCGPathEOFillStroke)を指定します。
定数の詳細については『CGPath』を参照してください。


CGPathDrawingMode

enum CGPathDrawingMode {
    kCGPathFill,
    kCGPathEOFill,
    kCGPathStroke,
    kCGPathFillStroke,
    kCGPathEOFillStroke
};
typedef enum CGPathDrawingMode CGPathDrawingMode;

関数CGContextDrawPathにパスの描画モードの定数を渡して、どのようにQuartzがグラフィックスコンテキストの現在のパスを描画するかを指定します。

kCGPathFill

非ゼロワインディング規則を使用して、パス内に含まれている領域を描画します。

kCGPathEOFill

偶奇規則を使用してパス内の領域を描画します。

kCGPathStroke

パスに沿って線を描画します。

kCGPathFillStroke

First fill and then stroke the path, using the nonzero winding number rule.
非ゼロワインディング規則を使用して、塗りつぶした後にパスの線を描きます。

kCGPathEOFillStroke

偶奇規則を使用して、塗りつぶした後にパスの線を描きます。


CGContextAddEllipseInRect

void CGContextAddEllipseInRect (
    CGContextRef context,
    CGRect rect
);

指定した矩形に内接する楕円を追加します。

楕円はベジェ曲線の連続によって近似されます。

その中心はrect変数によって定義された矩形の中点です。

矩形が正方形の場合、楕円は矩形の幅(または高さ)の半分と等しい半径の円となります。

rect変数が長方形の形状を指定した場合は、楕円の長軸と短軸は長方形の幅と高さによって定義されます。

楕円はパスの完全なサブパスで形成されています。

正確に言うと、楕円の描画は移動操作から始まり、全ての移動方向は右回りで、サブパスを閉じる操作で終了します。

context:グラフィックスコンテキストを指定します。

rect:楕円が内接する領域を定義する矩形を指定します。


drawAtPoint:

- (void)drawAtPoint:(CGPoint)point

現在のコンテキスト内の指定した点に画像を描画します。

このメソッドは、画像の向きの設定を尊重し、現在のグラフィックスコンテキスト内に画像全体を描画します。

デフォルトの座標系では、画像は指定された点の右下に配置されます。

このメソッドは現在のグラフィックスコンテキストに適用されている任意の変換を尊重します。

このメソッドはkCGBlendModeNormalブレンドモードを使用して、完全に不透明で画像を描画します。

point:画像を描画する左上角の点を指定します。


CGPointMake

CGPoint CGPointMake (
    CGFloat x,
    CGFloat y
);

指定した座標の点を返します。

x:構築する点のx座標を指定します。

y:構築する点のy座標を指定します。



参考文献

iOS Viewプログラミングガイド

二流プログラマの三流な日常/switch文の途中で宣言する

はじめてのiPhone3プログラミングはじめてのiPhone3プログラミング
(2009/12/17)
Dave Mark、Jeff LaMarche 他

商品詳細を見る

Beginning Ios 6 Development: Exploring the Ios SdkBeginning Ios 6 Development: Exploring the Ios Sdk
(2012/12/26)
David Mark、Jack Nutting 他

商品詳細を見る

QuartzFun(4)~ビューの編集(1)

2011. 08. 26
●画像ファイルの追加

画像描画に必要な画像は自前で用意するか、サンプルコード(iOS 3.x版は『Beginning iPhone 3 Development Exploring the iPhone SDK』の『12 QuartzFun』フォルダ、iOS 4.x版は『Beginning iPhone 4 Development Exploring the iOS SDK』の『14 - QuartzFun』フォルダ)にあるiphone.pngを自身のQuartzFunプロジェクトにコピーし、Resources下に追加します。
(サンプルコードの版によって画像の内容とサイズが異なりますが、ソースコードに影響しないのでどちらでもかまいません)


●QuartzFunView.hヘッダファイルの編集

QuartzFunViewクラスは、カスタム描画を行うビューのクラスです。

描画に必要なドラッグの始点と終点、描画色、描画図形、画像、ランダム色かどうかの判定、そして再描画する領域を保持するインスタンス変数をプロパティとして宣言します。
(太字が追加した部分)

#import <UIKit/UIKit.h>
#import "Constants.h"

@interface QuartzFunView : UIView {
    CGPoint firstTouch;
    CGPoint lastTouch;
    UIColor *currentColor;
    ShapeType shapeType;
    UIImage *drawImage;
    BOOL useRandomColor;
    CGRect redrawRect;

}

@property CGPoint firstTouch;
@property CGPoint lastTouch;
@property (nonatomic, retain) UIColor *currentColor;
@property ShapeType shapeType;
@property (nonatomic, retain) UIImage *drawImage;
@property BOOL useRandomColor;
@property (readonly) CGRect currentRect;
@property CGRect redrawRect;


@end

7501

最初にツールバー上のセグメンテッドコントロールで選択された描画図形を判別するために、Constants.hをインポートします。

各プロパティの示す内容は以下の通りです。

firstTouch:図形を描画する領域の始点をCGPoint型の座標として保持します。

lastTouch:図形を描画する領域の終点をCGPoint型の座標として保持します。

currentColor:QuartzFunViewControllerで判別・設定された描画色をUIColorオブジェクトとして保持します。

shapeType:QuartzFunViewControllerで判別された描画図形を列挙型のShapeTypeとして保持します。

drawImage:描画する画像(ここではiphone.png)をUIImageオブジェクトとして保持します。

useRandomColor:ランダム色が選択されているかどうかを判別するBOOL値を保持します。

currentRect:図形を描画する領域をCGRect型の領域として保持します。

redrawRect:図形を再描画する領域をCGRect型の領域として保持します。


●QuartzFunView.mソースファイルの編集

ソースファイルではヘッダファイルで宣言したプロパティの実装と、nibファイルからのビューの読み込み、ビューへのカスタム描画を行います。
(太字が追加・修正した部分)

#import "QuartzFunView.h"
#import "UIColor-Random.h"

@implementation QuartzFunView

@synthesize firstTouch;
@synthesize lastTouch;
@synthesize currentColor;
@synthesize shapeType;
@synthesize drawImage;
@synthesize useRandomColor;
@synthesize redrawRect;
@dynamic currentRect;

- (id)initWithCoder:(NSCoder *)coder {
    if ((self = [super initWithCoder:coder])) {
        self.currentColor = [UIColor redColor];
        self.useRandomColor = NO;
        if (drawImage == nil)
            self.drawImage = [UIImage imageNamed:@"iphone.png"];
    }
    return self;
}


- (id)initWithFrame:(CGRect)frame {

    self = [super initWithFrame:frame];
    if (self) {
        // Initialization code.
    }
    return self;
}

- (CGRect)currentRect {
    return CGRectMake(
            (firstTouch.x > lastTouch.x) ? lastTouch.x : firstTouch.x,
            (firstTouch.y > lastTouch.y) ? lastTouch.y : firstTouch.y,
            fabsf(firstTouch.x - lastTouch.x),
            fabsf(firstTouch.y - lastTouch.y));
}


// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.

- (void)drawRect:(CGRect)rect {
    // Drawing code.
    CGContextRef context = UIGraphicsGetCurrentContext();
    CGContextSetLineWidth(context, 2.0);
    CGContextSetStrokeColorWithColor(context, currentColor.CGColor);
    CGContextSetFillColorWithColor(context, currentColor.CGColor);

    switch (shapeType) {
        case kLineShape:
            CGContextMoveToPoint(context, firstTouch.x, firstTouch.y);
            CGContextAddLineToPoint(context, lastTouch.x, lastTouch.y);
            CGContextStrokePath(context);
            break;
        case kRectShape:
            CGContextAddRect(context, self.currentRect);
            CGContextDrawPath(context, kCGPathFillStroke);
            break;
        case kEllipseShape:
            CGContextAddEllipseInRect(context, self.currentRect);
            CGContextDrawPath(context, kCGPathFillStroke);
            break;
        case kImageShape: {
            CGFloat horizontalOffset = drawImage.size.width / 2;
            CGFloat verticalOffset = drawImage.size.height / 2;
            CGPoint drawPoint = CGPointMake(lastTouch.x - horizontalOffset,
                        lastTouch.y - verticalOffset);
            [drawImage drawAtPoint:drawPoint];
            break;
        }
        default:
            break;
    }

}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    if (useRandomColor)
        self.currentColor = [UIColor randomColor];
    UITouch *touch = [touches anyObject];
    firstTouch = [touch locationInView:self];
    lastTouch = [touch locationInView:self];

    if (shapeType == kImageShape) {
        CGFloat horizontalOffset = drawImage.size.width / 2;
        CGFloat verticalOffset = drawImage.size.height / 2;
        redrawRect = CGRectMake(
                    firstTouch.x - horizontalOffset,
                    firstTouch.y - verticalOffset,
                    drawImage.size.width, drawImage.size.height);
    }
    else
        redrawRect = CGRectMake(firstTouch.x, firstTouch.y, 0, 0);

    [self setNeedsDisplay];
}

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
    UITouch *touch = [touches anyObject];
    lastTouch = [touch locationInView:self];

    if (shapeType == kImageShape) {
        CGFloat horizontalOffset = drawImage.size.width / 2;
        CGFloat verticalOffset = drawImage.size.height /2;
        redrawRect = CGRectUnion(redrawRect, CGRectMake(
                    lastTouch.x - horizontalOffset,
                    lastTouch.y - verticalOffset,
                    drawImage.size.width, drawImage.size.height));
    }
    else
        redrawRect = CGRectUnion(redrawRect, self.currentRect);

    redrawRect = CGRectInset(redrawRect, -2.0, -2.0);
    [self setNeedsDisplayInRect:redrawRect];
}

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
    UITouch *touch = [touches anyObject];
    lastTouch = [touch locationInView:self];

    if (shapeType == kImageShape) {
        CGFloat horizontalOffset = drawImage.size.width / 2;
        CGFloat verticalOffset = drawImage.size.height /2;
        redrawRect = CGRectUnion(redrawRect, CGRectMake(
                    lastTouch.x - horizontalOffset,
                    lastTouch.y - verticalOffset,
                    drawImage.size.width, drawImage.size.height));
    }

    redrawRect = CGRectUnion(redrawRect, self.currentRect);
    [self setNeedsDisplayInRect:redrawRect];
}


- (void)dealloc {
    [currentColor release];
    [drawImage release];

    [super dealloc];
}

@end

7502


1)カテゴリUIColor-Random.hのインポート

ナビゲーションバーのセグメンテッドコントロールでランダム色を選択した時、描画を行う度にrandomColorメソッドを呼び出すため、UIColor-Random.hをインポートします。


2)宣言プロパティの実装

QuartzFunView.hヘッダファイルで宣言した8つのプロパティの内、currentRectを除く7つを@synthesizeで実装します。

currentRectのみ@dynamicというコンパイラ指示子になっています。

@dynamicは『詳解 Objective-C 2.0』によると『対応するアクセサメソッドをクラス内で定義する』あるいは『定義せずに実行中に関数を割り当てる』場合に使用するものとあります。

今回のcurrentRectの場合は前者にあたり、クラス内で直接メソッドを実装しています。


3)nibファイルからのビューの読み込み

nibファイルで作成したビューを読み込むにはNSCodingプロトコルinitWithCoder:メソッドを使用し、ファイルにアーカイブされているビューオブジェクト読み込んで初期化します。

この際、ビューの見た目として描画色はRed、描画図形はLineが選択されています。
(Interface Builderで構築した際のセグメンテッドコントロールのAttributesにおけるデフォルト設定)

しかしアプリケーション起動直後のセグメンテッドコントロールがタップされていない(アクションメソッドが呼び出されていない)状態では、UIColorオブジェクトである描画色currentColorやUIImageオブジェクトである画像drawImageが未設定なので、これらの初期化も行います。
(初期化を行わない場合、描画色は黒になり、画像は何も表示されません)

この初期化ルーチンでは描画図形shapeTypeが設定されていませんが、実際にはkLineShapeになっています。

これは初期化ルーチンに含まれているランダム色を判定するBOOL値useRandomColorを含め、Cオブジェクトの変数は暗黙的に0(浮動小数点値は0.0、BOOL値はNO)で初期化されているためです。

したがってInterface Builderでセグメンテッドコントロールの選択状態をLine以外に設定した場合でも、shapeTypeは0(=kLineShape)のままで線が描画されることになるので、本来はこのルーチン内で明示的にshapeTypeも初期化するべきと思われます。


initWithCoder:

- (id)initWithCoder:(NSCoder *)decoder

指定したアンアーカイバの中のデータから、初期化したオブジェクトを返します。

decoder:アンアーカイバオブジェクトを指定します。


4)initWithFrame:について

initWithFrame:メソッドは、UIViewのサブクラスとしてファイルを作成した際に自動生成されるメソッドの一つで、新規に生成するビューの初期化を行うメソッドです。

initWithFrame:メソッドの詳細説明や『iOS Viewプログラミングガイド』の『カスタムビューの初期化』で説明されているように、今回のようなビューのインスタンスをnibファイルから読み込む場合はinitWithFrame:メソッドを使用せず、initWithCoder:メソッドを使用するとありますので不要な気もします。

しかし(iOS 3.x版とiOS 4.x版ともに)サンプルコードではinitWithFrame:の空実装が残っているので必要なのかもしれません。
(コメントアウトしても正常に動いていますが)

ちなみに旧版の本書では、initWithCoder:で初期化していたコードが後半で何の説明も無くinitWithFrame:メソッドに置き換わっていますが、上記の通りinitWithCoder:が無ければ初期化できないので、単なる記載漏れと思われます。


initWithFrame:

- (id)initWithFrame:(CGRect)aRect

指定したフレーム矩形のビューオブジェクトを新規に割り当て、初期化して返します。

戻り値は初期化したビューオブジェクト、オブジェクトが生成できなかった場合はnilを返します。

新しいビューオブジェクトは、使用する前にウィンドウのビュー階層に入っている必要があります。

プログラムでビューオブジェクトを生成した場合、このメソッドはUIViewクラスのイニシャライザを指定します。

インターフェイスの設計にIntereface Builderを使う場合は、nibファイルからビューオブジェクトを読み込んだ後で、このメソッドを呼び出さないでください。

nibファイル内のオブジェクトを復元し初期化するにはinitWithCoder:メソッドを使用し、nibファイル内の属性とビューの属性が一致するように修正してください。

nibファイルからのビューの読み込みに関する詳細情報は、『Resources Programming Guide』を参照してください。

aRect:フレーム矩形のビューオブジェクトを生成します。
フレームの原点はスーパービューの座標になります。
このプロパティの設定は、centerboundsプロパティの値によって変わります。


5)currentRectメソッドの実装

currentRectメソッドは、矩形(Rect)や楕円(Ellipse)を描画する矩形領域を作成するメソッドで、CGRectMake()関数を使用して原点となるx、y座標と幅と高さを指定します。

ユーザがドラッグして描画領域を指定する際に、必ずしもUIKit座標系の正の向き(左上から右下方向)になるとは限らないので、ドラッグの始点と終点の座標を判別して補正しています。

具体的には、原点座標は始点と終点のx、y座標それぞれを比較してより数値の小さい方を選択、幅と高さは始点と終点の差の絶対値を指定します。



参考文献

iOS Viewプログラミングガイド

C言語/C言語の演算子について

C言語関数辞典/fabs, fabsf, fabsl

詳解 Objective-C 2.0 第3版詳解 Objective-C 2.0 第3版
(2011/12/28)
荻原 剛志

商品詳細を見る

はじめてのiPhone3プログラミングはじめてのiPhone3プログラミング
(2009/12/17)
Dave Mark、Jeff LaMarche 他

商品詳細を見る

Beginning Ios 6 Development: Exploring the Ios SdkBeginning Ios 6 Development: Exploring the Ios Sdk
(2012/12/26)
David Mark、Jack Nutting 他

商品詳細を見る

QuartzFun(3)~nibファイルの編集

2011. 08. 17
●QuartzFunViewController.xibの編集

アプリケーションのユーザインターフェイスを構築するため、nibファイルQuartzFunViewController.xibを編集します。

画面上部にセグメンテッドコントロールを持ったナビゲーションバーを、画面下部にセグメンテッドコントロールを持ったツールバーを配し、ビューコントローラQuartzFunViewControllerで設定したプロパティcolorControlと、2つのアクションメソッドchangeColor:とchangeShape:を接続します。

まずQuartzFunViewController.xibをダブルクリックしてInterface Builderで開きます。

最初に、ビューはカスタム描画をQuartzFunViewクラスで行うため、Viewのクラスを変更します。

DocumentウィンドウでViewを選択し、InspectorウィンドウのIdentityタブを開き、『Class Identity』の『Class』をUIViewからQuartzFunViewに変更します。

7488

次に画面上部にナビゲーションバーと描画色を選択するセグメンテッドコントロールを設置します。

LibraryウィンドウでNavigation Barを選択し、ドラッグ&ドロップでViewウィンドウの上端に設置します。

7489

LibraryウィンドウでSegmented Controlを選択しドラッグ&ドロップでNavigation Barの上に乗せます。

7490

Segmented Controlの左右どちらかの端を掴み、画面の端までドラッグして広げます。

7491

InspectorウィンドウのAttributesタブを開き、『Segmented Control』の『Segments』を2から5に変更します。

7492

Viewウィンドウで各セグメントの中央をダブルクリックし、セグメントのタイトルをRed、Blue、Yellow、Green、Randomにします。

7493

次に画面下部にツールバーと描画図形を選択するセグメンテッドコントロールを設置します。

LibraryウィンドウでToolbarを選択し、ドラッグ&ドロップでViewウィンドウの下端に設置します。

7494

左端にあるItemボタンは不要なので、ボタンを選択してdeleteキーで削除します。

7495

LibraryウィンドウでSegmented Controlを選択し、ドラッグ&ドロップでToolbarの上に乗せます。

しかしナビゲーションバーと異なり、ツールバーの場合は自動で中央揃えされませんので、セグメンテッドコントロールの両側にFlexible Space Bar Buttton Itemを置きます。

7496

その上でセグメンテッドコントロールを選択し、左右どちらかの端を掴んで幅を310程に広げます。

7497

InspectorウィンドウのAttributesタブを開き、『Segmented Control』の『Segments』を2から4に変更します。

7498

Viewウィンドウで各セグメントの中央をダブルクリックし、セグメントのタイトルをLine、Rect、Ellipse、Imageにします。

7499

これでレイアウトの設定が終わりましたので、プロパティとアクションメソッドの接続を行います。

DocumentウィンドウでFile's Ownerを選択し、InspectorウィンドウのConnectionタブを開きます。

『Outlets』にあるcolorControlは、Viewウィンドウのナビゲーションバーにあるセグメンテッドコントロールに接続します。

『Received Actions』にあるchangeColor:も、Viewウィンドウのナビゲーションバーにあるセグメンテッドコントロールに接続し、Value Changedを選択します。

同様にchangeShape:は、Viewウィンドウのツールバーにあるセグメンテッドコントロールに接続し、Value Changedを選択します。

7500

接続が終わったらファイルを保存します。



参考文献

はじめてのiPhone3プログラミングはじめてのiPhone3プログラミング
(2009/12/17)
Dave Mark、Jeff LaMarche 他

商品詳細を見る

Beginning Ios 6 Development: Exploring the Ios SdkBeginning Ios 6 Development: Exploring the Ios Sdk
(2012/12/26)
David Mark、Jack Nutting 他

商品詳細を見る

QuartzFun(2)~ビューコントローラの編集

2011. 08. 16
●QuartzFunViewController.hヘッダファイルの編集

ビューコントローラQuartzFunViewControllerは、画面上部のナビゲーションバーにある描画色を選択するセグメンテッドコントロールと、画面下部のツールバーにある描画図形を選択するセグメンテッドコントロールにおける、項目選択時の動作を設定します。

ヘッダファイルでは、描画図形でImage(画像)を選択した際に描画色のセグメンテッドコントロールを非表示にするためのアウトレットと、描画色と描画図形のセグメンテッドコントロールにおける選択時の動作を表すアクションメソッドを宣言します。
(太字が追加した部分)

#import <UIKit/UIKit.h>

@interface QuartzFunViewController : UIViewController {
    UISegmentedControl *colorControl;
}

@property (nonatomic, retain) IBOutlet UISegmentedControl *colorControl;

- (IBAction)changeColor:(id)sender;
- (IBAction)changeShape:(id)sender;


@end

7486


●QuartzFunViewController.mソースファイルの編集

ソースファイルでは、描画色のセグメンテッドコントロールを表すプロパティcolorControlと、2つのアクションメソッドを実装します。
(太字が追加・修正した部分)

#import "QuartzFunViewController.h"
#import "QuartzFunView.h"
#import "UIColor-Random.h"
#import "Constants.h"


@implementation QuartzFunViewController

@synthesize colorControl;

- (IBAction)changeColor:(id)sender {
    UISegmentedControl *control = sender;
    NSInteger index = [control selectedSegmentIndex];

    QuartzFunView *quartzView = (QuartzFunView *)self.view;

    switch (index) {
        case kRedColorTab:
            quartzView.currentColor = [UIColor redColor];
            quartzView.useRandomColor = NO;
            break;
        case kBlueColorTab:
            quartzView.currentColor = [UIColor blueColor];
            quartzView.useRandomColor = NO;
            break;
        case kYellowColorTab:
            quartzView.currentColor = [UIColor yellowColor];
            quartzView.useRandomColor = NO;
            break;
        case kGreenColorTab:
            quartzView.currentColor = [UIColor greenColor];
            quartzView.useRandomColor = NO;
            break;
        case kRandomColorTab:
            quartzView.useRandomColor = YES;
            break;
        default:
            break;
    }
}

- (IBAction)changeShape:(id)sender {
    UISegmentedControl *control = sender;
    [(QuartzFunView *)self.view setShapeType:[control selectedSegmentIndex]];

    if ([control selectedSegmentIndex] == kImageShape)
        colorControl.hidden = YES;
    else
        colorControl.hidden = NO;
}


// Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
- (void)viewDidLoad {
    [super viewDidLoad];
}


// Override to allow orientations other than the default portrait orientation.
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
    // Return YES for supported orientations
    return (interfaceOrientation == UIInterfaceOrientationPortrait);
}


- (void)didReceiveMemoryWarning {
    // Releases the view if it doesn't have a superview.
    [super didReceiveMemoryWarning];

    // Release any cached data, images, etc that aren't in use.

}

- (void)viewDidUnload {
    // Release any retained subviews of the main view.
    // e.g. self.myOutlet = nil;

    [super viewDidUnload];
    self.colorControl = nil;

}

- (void)dealloc {
    [colorControl release];
    [super dealloc];
}

@end

7487

1)ヘッダファイルのインポート

カスタム描画を行うビューのクラスQuartzFunViewと、ランダム色を生成するカテゴリUIColor-Random、セグメンテッドコントロールの選択肢を識別する定数を納めたConstantsの各ヘッダファイルをインポートします。

2)プロパティの実装

描画色のセグメンテッドコントロールのプロパティcolorControlを実装します。

3)アクションメソッドchangeColor:の追加

アクションメソッドchangeColor:は、ナビゲーションバーに設置したセグメンテッドコントロールで選択された描画色を設定します。

セグメンテッドコントロールの5つのボタン(Red、Blue、Yellow、Green、Random)は左から順に0から始まる整数が割り振られており、selectedSegmentIndexプロパティで取得できます。

取得したインデックス番号を元に、カスタム描画を行うビューのインスタンスquartzViewの、描画色を表すプロパティcurrentColorと、ランダム色かどうかを判別するプロパティuseRandomColorをswitch文で設定します。
(プロパティcurrentColorとuseRandomColorについては、後述するQuartzFunViewクラスで説明します)

Red、Blue、Yellow、Greenの4色はUIColorクラスでプリセットされているカラーオブジェクトを、それぞれredColorblueColoryellowColorgreenColorメソッドで設定しています。

4)アクションメソッドchangeShape:の追加

アクションメソッドchangeShape:は、ツールバーに設置したセグメンテッドコントロールで選択された描画図形を設定します。

セグメンテッドコントロールの4つのボタン(Line、Rect、Ellipse、Image)は左から順に0から始まる整数が割り振られており、selectedSegmentIndexプロパティで取得できます。

取得したインデックス番号を元に、カスタム描画を行うビューのインスタンスquartzViewの、描画図形を表すプロパティshapeType(のセッタsetShapeType:)を設定します。
(ここでは図形判別用のプロパティshapeTypeを設定するだけで、実際の図形描画の設定はビューのカスタム描画を行うQuartzFunViewクラスのdrawRect:メソッド内で行われます)

選択されたセグメンテッドコントロールが画像(kImagesShape)だった場合、(描画色の選択は無意味になるので)描画色のセグメンテッドコントロールcolorControlを非表示にします。

5)自動生成されたメソッドの削除

View-based Applicationテンプレートで自動生成されるメソッドの内、initWithNibName:bundle:loadViewは使用しないので削除します。

6)コメントアウトされているメソッドの実装

自動生成されているメソッドで、コメントアウトされているviewDidLoadshouldAutorotateToInterfaceOrientation:のコメントアウトを解除して実装します。
(編集はしていません)

7)viewDidUnloadの編集

旧版の本書には記述されていませんが、viewDidUnloadメソッドでプロパティcolorControlの所有権放棄を追加しています。

8)deallocの編集

deallocメソッドにプロパティcolorControlの解放を追加します。



参考文献

はじめてのiPhone3プログラミングはじめてのiPhone3プログラミング
(2009/12/17)
Dave Mark、Jeff LaMarche 他

商品詳細を見る

Beginning Ios 6 Development: Exploring the Ios SdkBeginning Ios 6 Development: Exploring the Ios Sdk
(2012/12/26)
David Mark、Jack Nutting 他

商品詳細を見る

QuartzFun(1)

2011. 08. 07
iOSアプリケーションでの描画には、これまでのようにUIKitによって予め提供されている部品を組み合わせて使用する方法の他に、一からビューを構築できるQuartz 2DとOpenGL ESの2つの方法があります。

Quartzは2D専用で、OpenGL ESは2D/3Dの両方をサポートしています。

本書では同じ内容の描画アプリケーションを、Quartz版(QuartzFun)とOpenGL ES版(GLFun)で作成し、それぞれの描画方法とその違いを勉強します。


●描画アプリケーションの概要

7452

画面上部のナビゲーションバーには描画色を選択するセグメンテッドコントロールが、画面下部のツールバーには描画図形を選択するセグメンテッドコントロールがあり、中央のグレー部分が描画するキャンバスとなっています。

7453

7454

7455

描画色はRed、Blue、Yellow、Green、Randomから選択でき、Randomの場合は描画する毎に不特定の色に変わります。

描画図形はLine、Rect、Ellipse、Imageから選択できます。

Image以外は、グレーの描画領域をドラッグすることによって、ドラッグした始点と終点を結ぶ直線、または矩形、矩形に内接する楕円が描画されます。

7456

Imageを選択した場合はナビゲーションバーのセグメンテッドコントロールが非表示になり、タップするとiPhoneの小さな図形が現れ、ドラッグすると追従して動き、話すとその場に固定されます。

描画後に再度ドラッグすると、前の描画図形は消去され、新たな図形が描画されます。


●プロジェクトの作成

View-based Applicationテンプレートで、プロジェクト名を『QuartzFun』とします。

7458


●ファイルの追加

View-basedテンプレートで作成したQuartzFunプロジェクトには、アプリケーションデリゲートのQuartzFunAppDelegateクラスと、ビューコントローラのQuartzFunViewControllerクラスが予め組み込まれています。

7459

今回は上記のアプリケーションの概要で述べたようなカスタム描画を行うので、以下のファイルを追加します。

1)指定した色と図形でカスタム描画を行うビュー(QuartzFunViewクラス)
2)色や図形を識別する定数(Constants.hヘッダファイル)
3)ランダムな色を生成するメソッド(UIColor-Randomカテゴリ)

1)QuartzFunViewクラスファイルの追加

カスタム描画を行うビューの処理なので、UIViewのサブクラスとしてヘッダおよびソースファイルを生成します。

XcodeのClassesを右クリックしコンテキストメニューから『追加』→『新規ファイル...』を選択します。

7460

『iOS』の『Cocoa Touch Class』グループにある『Objective-C class』を選択し、『Subclass of』を『UIView』として、ファイル名を『QuartzFunView』とします。

2)Constants.hヘッダファイルの追加

ナビゲーションバー(描画色)とツールバー(描画図形)のセグメンテッドコントロールの識別に使用する定数を定義するファイルなので、ヘッダファイルのみとなります。

XcodeのClassesを右クリックしコンテキストメニューから『追加』→『新規ファイル...』を選択します。

7461

『Mac OS X』の『C and C++』グループにある『Header File』を選択し、ファイル名を『Constants』とします。

3)UIColor-Randomカテゴリファイルの追加

描画色の選択肢にあるランダム色を生成するメソッドをUIColorクラスのカテゴリとして追加します。

カテゴリのテンプレートはありませんが、クラスファイルと似ていますし、UIColorはUIViewと同じくUIKitフレームワークのクラスなので、UIViewサブクラスのテンプレートを使用します。

XcodeのClassesを右クリックしコンテキストメニューから『追加』→『新規ファイル...』を選択します。

7460

『iOS』の『Cocoa Touch Class』グループにある『Objective-C class』を選択し、『Subclass of』を『UIView』として、ファイル名を『UIColor-Random』とします。


●UIColor-Randomカテゴリファイルの編集

図形の描画色をランダムで決定するメソッドは、ビュークラス内で実装するのではなく、UIColorのカテゴリとして実装します。
(カテゴリについての詳細は『Sections(2)~カテゴリ』や『詳解 Objective-C 2.0』を参照してください)

UIColor-Random.hの編集)

UIColorクラスにカテゴリ名を『Random』として、randomColorメソッドを宣言します。
(太字が追加・修正した部分)

#import <UIKit/UIKit.h>

@interface UIColor (Random)

+ (UIColor *)randomColor;


@end

7462

UIColor-Random.mの編集)

UIViewのテンプレートを使用したので、initWithFrame:やdrawRect:、deallocメソッドが自動生成されていますが全て削除し、randomColorメソッドを実装します。
(太字が追加・修正した部分)

#import "UIColor-Random.h"

@implementation UIColor (Random)

+ (UIColor *)randomColor {
    static BOOL seeded = NO;
    if (!seeded) {
        seeded = YES;
        srandom(time(NULL));
    }
    CGFloat red = (CGFloat)random() / (CGFloat)RAND_MAX;
    CGFloat blue = (CGFloat)random() / (CGFloat)RAND_MAX;
    CGFloat green = (CGFloat)random() / (CGFloat)RAND_MAX;
    return [UIColor colorWithRed:red green:green blue:blue alpha:1.0f];
}


@end

7463

randomColorはRGB各成分を乱数で決定し、colorWithRed:green:blue:alpha:メソッドでUIColorオブジェクトを生成して返します。

乱数はstdlibの関数random()を使用しますが乱数発生用のシードを現在時刻で初回のみ定義しています。

これは描画色を『Random』に設定した場合、図形を描画する度にrandomColorメソッドが呼び出されるので、毎回シードを生成する必要が無いように処理をしています。

その判別子seededはstaticで宣言されているため、randomColorメソッドが終了しても値を保持しており、2回目以降は初期化処理がスルーされるので、初回のみシードを作ることになっています。
(staticの変数についての詳細は『C言語入門講座/4.記憶クラス』を参照してください)

colorWithRed:green:blue:alpha:メソッドの成分値は0.0~1.0の値で指定する必要がありますが、生成される乱数値をRAND_MAX(乱数の最大値で、0x7fffffffと定義されています)で割ることで、範囲内に収まるようにしています。

colorWithRed:green:blue:alpha:

+ (UIColor *)colorWithRed:(CGFloat)red green:(CGFloat)green blue:(CGFloat)blue alpha:(CGFloat)alpha

指定した不透明度とRGB成分の値を使用してカラーオブジェクトを生成して返します。

戻り値はカラーオブジェクトです。

このオブジェクトによって表される色情報は、デバイスのRGB色空間です。

0.0未満の値は0.0として、1.0より上の値は1.0として解釈されます。

red:カラーオブジェクトの赤成分で、0.0から1.0の値で指定します。

green:カラーオブジェクトの緑成分で、0.0から1.0の値で指定します。

blue:カラーオブジェクトの青成分で、0.0から1.0の値で指定します。

alpha:カラーオブジェクトの不透明度で、0.0から1.0の値で指定します。


●Constants.hヘッダファイルの編集

ナビゲーションバーとツールバーに設置するセグメンテッドコントロールを識別する定数を定義します。

typedef enum {
    kLineShape = 0,
    kRectShape,
    kEllipseShape,
    kImageShape
} ShapeType;

typedef enum {
    kRedColorTab = 0,
    kBlueColorTab,
    kYellowColorTab,
    kGreenColorTab,
    kRandomColorTab
} ColorTabIndex;

#define degreesToRadian(x) (M_PI * (x) / 180.0)

7464

描画図形を示す列挙型ShapeTypeと、描画色を示す列挙型ColorTabIndexを定義しています。

degreesToRadian(x)の定義は、旧版では円周率を数値で記述していますが、ここでは新版以降と同様に数学関数math.hのマクロM_PIに置き換えています。
(degreesToRadian(x)は、このQuartzFunでは使用せず、OpenGL ES版のGLFunでのみ使用されます)



参考文献

C言語入門講座/4.記憶クラス

iOSアプリケーションプログラミングガイド

詳解 Objective-C 2.0 第3版詳解 Objective-C 2.0 第3版
(2011/12/28)
荻原 剛志

商品詳細を見る

はじめてのiPhone3プログラミングはじめてのiPhone3プログラミング
(2009/12/17)
Dave Mark、Jeff LaMarche 他

商品詳細を見る

Beginning Ios 6 Development: Exploring the Ios SdkBeginning Ios 6 Development: Exploring the Ios Sdk
(2012/12/26)
David Mark、Jack Nutting 他

商品詳細を見る






Lifestyle 650 home entertainment system
Calendar
07 | 2011/08 | 09
Sun Mon Tue Wed Thu Fri Sat
- 1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 31 - - -
Recent Articles
iTunes


Swift
Categories
Tips
Profile

水月杏香

Author:水月杏香
永遠の初心者プログラマ。

Wish List
WACOM


ARC
Technical Q&A
情報プロパティリストキー
Start Developing iOS Apps Today
BOSE

Lifestyle 650 home entertainment system
Reference
NSApplicationDelegateプロトコル
NSArrayクラス
NSAutoreleasePoolクラス
NSBundleクラス
NSBundle UIKit追加分
NSCalendarクラス
NSCoderクラス
NSCodingプロトコル
NSCopyingプロトコル
NSDataクラス
NSDateクラス
NSDateFormatterクラス
NSDictionaryクラス
NSEntityDescriptionクラス
NSEnumeratorクラス
NSErrorクラス
NSExceptionクラス
NSFetchRequestクラス
NSFileHandleクラス
NSFileManagerクラス
NSIndexPathクラス
NSIndexPath UIKit追加分
NSKeyedArchiverクラス
NSKeyedUnarchiverクラス
NSKeyValueCodingプロトコル
NSLocaleクラス
NSManagedObjectクラス
NSManagedObjectContextクラス
NSManagedObjectModelクラス
NSMutableArrayクラス
NSMutableCopyingプロトコル
NSMutableDictionaryクラス
NSMutableSetクラス
NSNotificationクラス
NSNotificationCenterクラス
NSNullクラス
NSNumberクラス
NSObjectクラス
NSObject UIKit追加分
NSObjectプロトコル
NSPersistentStoreクラス
NSPersistentStoreCoordinatorクラス
NSPredicateクラス
NSPropertyListSerializationクラス
NSRunLoopクラス
NSSetクラス
NSStringクラス
NSString UIKit追加分
NSTimerクラス
NSTimeZoneクラス
NSURLクラス
NSURLProtectionSpaceクラス
NSURLRequestクラス
NSUserDefaultsクラス
NSValueクラス

UIActionSheetクラス
UIActionSheetDelegateプロトコル
UIActivityIndicatorViewクラス
UIAlertViewクラス
UIAlertViewDelegateプロトコル
UIApplicationクラス
UIApplicationDelegateプロトコル
UIBarButtonItemクラス
UIBarItemクラス
UIButtonクラス
UIColorクラス
UIControlクラス
UIDatePickerクラス
UIDeviceクラス
UIEventクラス
UIFontクラス
UIGestureRecognizerクラス
UIImageクラス
UIImageViewクラス
UIKit Function
UILabelクラス
UINavigationControllerクラス
UINavigationItemクラス
UIPickerViewクラス
UIPickerViewDataSourceプロトコル
UIPickerViewDelegateプロトコル
UIPinchGestureRecognizerクラス
UIResponderクラス
UIScreenクラス
UIScrollViewクラス
UISearchBarクラス
UISearchBarDelegateプロトコル
UISegmentedControlクラス
UISliderクラス
UISwipeGestureRecognizerクラス
UISwitchクラス
UITableViewクラス
UITableViewCellクラス
UITableViewControllerクラス
UITableViewDataSourceプロトコル
UITableViewDelegateプロトコル
UITapGestureRecognizerクラス
UITextFieldクラス
UITextFieldDelegateプロトコル
UITextInputTraitsプロトコル
UITextViewクラス
UITextViewDelegateプロトコル
UIToolbarクラス
UITouchクラス
UIViewクラス
UIViewControllerクラス
UIWebViewクラス
UIWebViewDelegateプロトコル
UIWindowクラス

AVAudioPlayerクラス
AVAudioPlayerDelegateプロトコル

CADisplayLinkクラス
CAEAGLLayerクラス
CALayerクラス

CGAffineTransform
CGBitmapContext
CGColor
CGColorSpace
CGContext
CGGeometry
CGImage
CGPath

EAGLContextクラス
EAGLDrawableプロトコル

Foundation Constants
Foundation Data Types
Foundation Functions

MPMediaItemクラス
MPMediaItemArtworkクラス
MPMediaPlaylistクラス
MPMediaPropertyPredicateクラス
MPMediaQueryクラス
MPMusicPlayerControllerクラス

Randomization Services

System Sound Services
Amazon


OpenGL ES
SQLite
Monthly Archives
Recent Comments
Recent TrackBacks
RSS Link
Visitors
QR Code
QR