[iOS][Mac] Core Graphics - グラデーション

2011年6月28日火曜日 | Published in | 0 コメント

このエントリーをはてなブックマークに追加

CoreGraphics の グラデーションについての覚書き。



Linear Gradient


2つの点を指定してその間でグラデーションを表現する方式。

CGGradientRef() で定義し、CGContextDrawLinearGradient() で描画する。あらかじめ描画したい形(パス)を登録しておく。コードはこんな感じ。
- (void)drawRect:(CGRect)rect
{
    CGContextRef context = UIGraphicsGetCurrentContext();   
    CGContextSaveGState(context);
   
    CGContextAddRect(context, self.frame);
   
    CGColorSpaceRef colorSpaceRef = CGColorSpaceCreateDeviceRGB();
    CGFloat components[] = {
        1.0f, 1.0f, 1.0f, 0.5f,     // R, G, B, Alpha
        0.0f, 0.0f, 0.0f, 0.5f
    };
    CGFloat locations[] = { 0.0f, 1.0f };

    size_t count = sizeof(components)/ (sizeof(CGFloat)* 4);
   
    CGRect frame = self.bounds;
    CGPoint startPoint = frame.origin;
    CGPoint endPoint = frame.origin;
    endPoint.y = frame.origin.y + frame.size.height;
   
    CGGradientRef gradientRef =
        CGGradientCreateWithColorComponents(colorSpaceRef, components, locations, count);
   
    CGContextDrawLinearGradient(context,
                                gradientRef,
                                startPoint,
                                endPoint,
                                kCGGradientDrawsAftersEndLocation);
   
    CGGradientRelease(gradientRef);
    CGColorSpaceRelease(colorSpaceRef);
   
    CGContextRestoreGState(context);
}

startPoint, endPoint はグラデーションの開始位置、終了位置を決める。例えば左上から左下にした場合はこう。

左上から右上。

左上から右下。

例では2色間のグラデーションを使っているが複数色間のグラデーションを作ることもできる。その場合は上記コードの components に色情報を増やす。
CGFloat components[] = {
        1.0f, 1.0f, 1.0f, 0.5f,
        1.0f, 0.0f, 0.0f, 0.5f,
                  :
        0.0f, 0.0f, 0.0f, 0.5f
    };
components を増やした場合は locations の要素もそれに合わせて増やす。locations は各色の割合を表していて 0〜1 の数値を取る。例えば
CGFloat locations[] = { 0.0f, 0.2f, 1.0f };
とすると全体の描画対象の 0〜20% が1色目と2色目のグラデーション、20%〜100%の領域が2色目と3色目のグラデーションになる。等間隔にしたい場合は CGGradientCreateWithColorComponents() の locations に NULLを渡すこともできる。


Radial Gradient


放射状のグラデーションを表現する。

こちらも2点を指定しその間のグラデーションを表現するが、Linearと違うのは各点から放射状にグラデーションがかかること。コードはこんな感じ。
- (void)drawRect:(CGRect)rect
{
    CGContextRef context = UIGraphicsGetCurrentContext();   
    CGContextSaveGState(context);
   
    CGContextAddEllipseInRect(context, self.frame);
   
    CGColorSpaceRef colorSpaceRef = CGColorSpaceCreateDeviceRGB();
    CGFloat components[] = {
        1.0f, 1.0f, 1.0f, 1.0f,
        0.5f, 0.5f, 0.5f, 1.0f,
        0.25f, 0.25f, 0.25f, 1.0f,
    };   
    CGFloat locations[] = { 0.0, 0.5, 1.0 };
   
    size_t count = sizeof(components)/ (sizeof(CGFloat)* 4);
    CGGradientRef gradientRef =
        CGGradientCreateWithColorComponents(colorSpaceRef, components, locations, count);
       
    CGRect frame = self.bounds;
    CGFloat radius = frame.size.height/2.0*0.8;

    CGPoint startCenter = frame.origin;
    startCenter.x += frame.size.width/2.0;
    startCenter.y += frame.size.height/2.0;
    CGPoint endCenter = startCenter;
   
    CGFloat startRadius = 0;
    CGFloat endRadius = radius;
   
    CGContextDrawRadialGradient(context,
                                gradientRef,
                                startCenter,
                                startRadius,
                                endCenter,
                                endRadius,
                                kCGGradientDrawsAfterEndLocation);
   
    CGGradientRelease(gradientRef);
    CGColorSpaceRelease(colorSpaceRef);
   
    CGContextRestoreGState(context);
}
CGGradientRef の作り方は Linear と同じ。ポイントは2つの点とそれらの点を中心として描かれる円の半径を指定するところ。例では startCenter と endCenter を同じにしているので中心から放射状にグラデーションがかかっているように見える。startRadius は 0。

次は startCenter を左上に動かした例。
擬似 3D ぽい画像になる(見かたを変えると遠くでライトを光らせているようにも見える)。


サンプル


GitHub からどうぞ。

GradientSample at 2011-06-28 from xcatsan/iOS-Sample-Code - GitHub

なお背景には標準で付いていた "Scroll View Textured Background Color" を使っている(これ自体を描画しているわけではない)



参考情報


Gradients
Gradientsの解説はマニュアルが詳しい。


A-Liaison BLOG: CGGradientを用いてUITableViewCellを描画し、テーブルをカッコよく見せる方法
UIColor 元にグラデーションを作るアイディアが面白い。

(旧) Cocoaの日々: NSGradiation
Mac OS X ならこっちも使える。

[iOS] Responder Chain と UIViewController

2011年6月26日日曜日 | Published in | 0 コメント

このエントリーをはてなブックマークに追加

@novi_ さんの指摘をきっかけに UIViewController が UIResponder のサブクラスで Responder Chain に参加していることを知った(今更だが。。)。

Responder Chain


Responder Chain とは UIResponderオブジェクトの連なり(リンク)のことで、イベント処理の順番を決定するのに使われる。イベントが発生すると、システムは Redponder Chain の順番にしたがって UIResponderオブジェクトを評価する。もし最初の UIResponderオブジェクトでイベント処理が行われない場合は Responder Chain の次の UIResponderオブジェクト へと移り再び評価を行う。イベント処理を行う UIResponderオブジェクトが見つかるまで Responder Chain 上のリンクを辿って評価を続けていく。一旦どこかの UIResponderオブジェクトがイベントを処理するとイベントの伝搬はそこで終わる。Responder Chain 上のどの UIResponderオブジェクトも処理を行わない場合はやがては最終的に UIApplication へ到達し、そこでも処理されない場合はそのイベントは処理されず無視される。

例えばボタンがタップされた場合、タップイベントを最初に処理するのは当該ボタンだが、そこでイベント処理が行われない場合、そのボタンが配置されている superview が評価される。もし superview にイベント処理コードが存在すれば、そこで処理が実行されてイベントの伝搬は終わる。もしイベント処理コードが superview上に無ければ、その次の UIWindow, UIApplication と Responder Chain が次々と辿られる。
なお明示的に Responder Chain を利用する場合はイベント登録時のターゲットに nil を渡す。
- (void)viewDidLoad
{
    [super viewDidLoad];
    [self.button addTarget:nil 
                    action:@selector(touchMe:)
          forControlEvents:UIControlEventTouchUpInside];
}
これを nilターゲットと呼ぶ(動作も含めてたしか「nilターゲットアクション」と呼んだりもした)。この仕組は Mac OS X 由来で昔からある(Next由来か)。


Responder Chain と UIViewController


UIViewController は紐づいている UIView が参加している Responder Chain に割り込んで参加している。
UIResponder のサブクラスなのはこの Responder Chain に参加させる為だと思われる。nilターゲットをうまく使うことで紐づいている UIView 上のイベントをすべて UIViewController で集約的に処理することが可能になる。IBActionを宣言してXib上で接続する手間もない(そっちはそっちで明示的にターゲット・アクションの関係がわかりやすいくなるので良い面もあるが)。


※上の図は下記ページより借用
Event Handling Guide for iOS: Event Types and Delivery


サンプル


Responder Chain の確認と nilターゲットアクションが確認できるサンプルプログラムを作ってみた。
"display" ボタンを押すと、そのボタンが参加している Responder Chain をログに出力する。
- (void)_displayResponderChain:(UIResponder*)responder
{
    if (responder != nil) {
        NSLog(@"%@", responder);
        [self _displayResponderChain:[responder nextResponder]];
    }
}
- (IBAction)display:(id)sender {
    [self _displayResponderChain:sender];
    
}
再帰的に Responder Chain を辿って表示させる。結果はこんな感じ。
<UIRoundedRectButton: 0x4e13: 0x4e13c70>
 <UIView: 0x4e12f40;>
 <ResponderChainStudyViewController>
 <UIWindow: 0x4b44b30>
 <UIApplication: 0x4e012b0>
イベントの発生源であるボタンを起点に→superview→UIViewController→UIWindow→UIApplication と連なっているのがわかる。UIViewController は UIView と UIWindow の間に入り込んでいる。

次に nilターゲットアクション。
- (void)viewDidLoad
{
    [super viewDidLoad];
    [self.button2 addTarget:nil 
                 action:@selector(touchMe:)
       forControlEvents:UIControlEventTouchUpInside];
}
としておき、UIViewController にイベントハンドラを書いておく。
- (void)touchMe:(id)sender
{
    NSLog(@"%s|%@", __PRETTY_FUNCTION__, sender);
    
}
すると "touch me !"ボタンが押されたときにそのイベントハンドラが呼び出され、Responder Chain が辿られていることがわかる。


ソースコード


GitHub からどうぞ。
ResponderChainStudy at 2011-06-26 from xcatsan/iOS-Sample-Code - GitHub


参考情報


Event Handling Guide for iOS: Event Types and Delivery

Responder Chain の解説ならびに UIViewController との関係が説明されている。

[iOS] UINib を使ったカスタム UITableViewCell の作り方(その3)ボタンの処理[改良版]

| Published in | 0 コメント

このエントリーをはてなブックマークに追加

[前回] Cocoaの日々: [iOS] UINib を使ったカスタム UITableViewCell の作り方(その2)ボタンの処理

前回のコードで何番目のセルがタップされたかどうかを判断するのに View階層上で特定のクラスに所属するインスタンスを検索する処理をしていた。
- (IBAction)didTouchDoitButton:(id)sender
{
    id cell = sender;
    while (![cell isKindOfClass:[CustomCell class]]) {
        cell = [cell superview];
    }
    NSIndexPath* indexPath = [self.tableView indexPathForCell:cell];
    NSLog(@"%@", indexPath);
}

その後、UITableView を見ていると特定のポイント(CGPoint)から何番目のセルを指しているかどうかを取得するメソッドがあるのに気がついた。
UITableView Class Reference - indexPathForRowAtPoint:
- (NSIndexPath *)indexPathForRowAtPoint:(CGPoint)point
これを使うとクラスの検索は不要でもっと汎用的に書ける。こんな感じ。
- (IBAction)didTouchDoitButton:(id)sender event:(UIEvent*)event
{
    UITouch* touch = [[event allTouches] anyObject];
    CGPoint p = [touch locationInView:self.tableView];
    NSIndexPath* indexPath = [self.tableView indexPathForRowAtPoint:p];
    NSLog(@"%@", indexPath);
}
こっちの方が良さそうだ。

なおボタンのイベントハンドラは引数が0〜2個のいずれかを取るメソッドとして実装が可能で、必要に応じて使い分けられる。
-(IBAction)didTouch;
-(IBAction)didTouch:(id)sender;
-(IBAction)didTouch:(id)sender (UIEvent*)event;
今回はタッチ位置を取得する必要があったので引数に UIEvent を含む方を使った。

[iOS] UINib を使ったカスタム UITableViewCell の作り方(その2)ボタンの処理

2011年6月25日土曜日 | Published in | 0 コメント

このエントリーをはてなブックマークに追加

[参考] Cocoaの日々: [iOS] UINib を使ったカスタム UITableViewCell の作り方

カスタムセルにボタンを貼りつけた時の処理パターンの紹介。

処理パターン


ボタンのタップイベントをどこで受け取るべきか。カスタムセルで受け取るのも手だが単なるビューなのでモデルとのつながりが無い。モデルとの接点があるのは ViewController なのでここでイベントを受け取るのが順当だろう。


実装


まず ViewController に IBAction を追加する。
@interface RootViewController : UITableViewController {
    CGFloat cellHeight_;
}
// cell events
- (IBAction)didTouchDoitButton:(id)sender;
@end

続いて Xib へボタンを追加する。

ここで Xib の File's Owner に RootViewController を指定する。

そしてタップアクションのターゲットとして先ほど用意した IBAction へ接続する。


次にカスタムセルの生成コードに手を入れる。前回は -[UINib instantiateWithOwner:options:] の owner引数に nil を渡していた。これを self(=RootViewController)とする。
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    CustomCell* cell = (CustomCell*)[tableView dequeueReusableCellWithIdentifier:CUSTOM_CELL_NIB];
    if (cell == nil) {
        UINib* nib = [UINib nibWithNibName:CUSTOM_CELL_NIB bundle:nil];
//      NSArray* array = [nib instantiateWithOwner:self options:nil];   // 前回
        NSArray* array = [nib instantiateWithOwner:nil  options:nil];   // 今回
          :
    }
       :
こうすることで Xib がインスタンス化された時に File's Owner が RootViewController のインスタンスとなり、ボタンがタップされた時に所定のメソッドが呼び出されるようになる。

最後にボタンタップ時の処理。
- (IBAction)didTouchDoitButton:(id)sender
{
    id cell = sender;
    while (![cell isKindOfClass:[CustomCell class]]) {
        cell = [cell superview];
    }
    NSIndexPath* indexPath = [self.tableView indexPathForCell:cell];
    NSLog(@"%@", indexPath);
}
タップされたセルがどのモデルと紐づくかを知るには NSIndexPath が必要になるので -[UITableView indexPathForCell:] を使う。ただ、ボタンタップ時に呼び出されるこのメソッドの引数 sender には UIButton のインスタンスが入っているだけなので、ここからCustomCell のインスタンスを手に入れる必要がある。UIButton はカスタムセルの子ビュー(sub view) なので、逆に UIButton のビュー階層を上に辿っていって CustomCellクラスのインスタンスを探せば良い。

(2011-06-27追記)クラス検索は使わずに、タッチ位置から NSIndexPathを取得する方法が見つかった。そちらの方が汎用性があり実用的なので参照されたい。
Cocoaの日々: [iOS] UINib を使ったカスタム UITableViewCell の作り方(その3)ボタンの処理[改良版]

ソースコード


GitHub からどうぞ。
CustomCellSample at 2011-06-23 from xcatsan/iOS-Sample-Code - GitHub


参考情報

なし

[iOS][Mac] Xcode4 : カスタムなコードスニペットの作成

2011年6月9日木曜日 | Published in | 0 コメント

このエントリーをはてなブックマークに追加

やってみたらとても簡単だった。コードを選択して Code Snippet Library へドロップするだけ。

"My Code Snippet" が追加される。

ダブルクリックすると内容の確認ができる。

Edit を押すと内容の編集が可能。編集画面ではタイトルや補完ショートカットなどの設定ができる。

またプラットフォーム(iOS, Mac OS X)や言語(Objective-C, HTML, Perl, ...)、スコープ(宣言部、実装部、メソッド内、コメント内、...)などが選べる。以下はスコープの選択肢。
Title, Summary を対象に検索ができるので自分のものはそれとわかるキーワードを入れておくと探しやすいかも。


"Completion Shortcut" には補完する時にマッチさせる文字列を入れておく。

するとコード上で文字列の一部を入れると選択できるようになる。

置換したい箇所は <#文字列#> を埋め込んでおく。

すると該当箇所が置換を促すように反転表示となる(タブキーでその箇所へ移動可能)。


参考情報


Source Editor Help: Creating a Custom Code Snippet

Source Editor Help: Determining the Shortcut for a Code Snippet


- - - -
こりゃ便利。Evernote で管理していたスニペットを全部 Xcodeへ持ってこられそう。

[iOS][Mac] Xcode4 : ドラッグ&ドロップで IBOutlet コードを生成する

2011年6月8日水曜日 | Published in | 0 コメント

このエントリーをはてなブックマークに追加

知ってはいたが試してなかった。こりゃ便利。
#しこしこ手で書いていた今まではいったいなんだったのか。。

以下、手順

まず Xib を開いた後、右上の Editor から真ん中の Assistant Editor を開く。

次に IBOutlet を作りたいコントロールを選択し、Controlキーを押しながらコードを挿入したい位置で離す。

するとタイプや変数名を聞いてくるので入力する。
これでおしまい。
プロパティだけでなくメンバ変数の宣言も。

もちろん接続も済んでいる。
さらに @synthesize も書いてくれる。

さらにさらに viewDidUnload まで。
ちゃんと dealloc も(抜かり無い)

必要なものはすべてお膳立てしてくれるようだ。こりゃ便利。


IBAction も同様。ヘッダへ落とせば宣言に加え、実装ファイル(*.m)にメソッドの雛形も書いてくれる。





参考情報


Xcode 4 User Guide: Designing User Interfaces in Xcode

[iOS] AES128暗号化ライブラリ FBEncryptor 公開

2011年6月3日金曜日 | Published in | 0 コメント

このエントリーをはてなブックマークに追加

AESで暗号化・復号化できる CCCrypt の簡易ラッパー FBEncryptor を作りました。結果(バイナリ値)を Base64 で受け取ることもできます。
dev5tec/FBEncryptor - GitHub

暗号化方式
アルゴリズム:AES 128
モード:CBC
鍵長:256ビット
パディング:PKCS7
これ一種類だけ

インストール


GitHubからプロジェクトをダウンロードした後 Xcode 4 で開く。その中から下記のファイルを自分のプロジェクトへコピーして追加する。

FBEncryptorAES.h
FBEncryptorAES.m
NSData+Base64.h
NSData+Base64.m


使い方


NSDataベースの暗号化・復号化


Data系メソッドを使う。
+ (NSData*)encryptData:(NSData*)data key:(NSData*)key iv:(NSData*)iv;
+ (NSData*)decryptData:(NSData*)data key:(NSData*)key iv:(NSData*)iv;

こんな感じ。
NSData* encryptedData = [FBEncryptorAES encryptData:data
            keyData:key
             iv:iv];

NSData* decryptedData = [FBEncryptorAES decryptData:encryptData
             keyData:key
              iv:iv];
iv (Initialization Vector) は nil指定可能。

ランダムな iv を生成するユーティリティメソッドも用意した。
NSData* iv = [FBEncryptorAES generateIv];
iv として利用可能な 16バイトのランダムなバイナリ値を返す。
バイナリ値を16進数文字列に変換するメソッドも付けてある。
SString* hexString = [FBEncryptorAES hexStringForData:iv];
 (例) @"b20cd8d972e65762824cc3190040388c"

NSData* bin = [FBEncryptorAES dataForHexString:hexString]; // 逆変換

文字列を暗号化したい場合は後述の Base64ベースのメソッドを使うか -[NSString dataUsingEncoding:] を使えば良い。こんな感じ。
data = [FBEncryptorAES encryptData:[str dataUsingEncoding:NSUTF8StringEncoding]
  key:[keyStr dataUsingEncoding:NSUTF8StringEncoding]  iv:nil];


Base64ベースの暗号化・復号化


NSDataの代わりに NSString を引数に取る。暗号化の結果は Base64エンコードされる。復号時はこの Base64エンコードされた文字列を引数に取る。iv は指定できない。
+ (NSString*)encryptBase64String:(NSString*)string
 keyString:(NSString*)keyString separateLines:(BOOL)separateLines;
+ (NSString*)decryptBase64String:(NSString*)encryptedBase64String
 keyString:(NSString*)keyString;

使い方はこんな感じ。
NSString* encrypted = [FBEncryptorAES encryptBase64String:@"Hello"
                  keyString:@"somekey"
          separateLines:NO];
NSString* decrypted = [FBEncryptorAES decryptBase64String:encrypted
                  keyString:@"somekey"];
結果例
暗号化前文字列:@"Hello"
暗号化後文字列:@"gT2IUF9Jzmn7wglXk3XC3w=="
最後の separateLines: オプションは結果の Base64 を改行で区切るかどうかを指定する。YES の場合、64バイト毎に CRLFを挿入した文字列を返す。


カスタマイズ


FBEncryptor.h 内の定数を変更するとコンパイル時に鍵の長さを変更できる。
#define FBENCRYPT_KEY_SIZE      kCCKeySizeAES256
例えば kCCKeySizeAES128 とすれば鍵長を 128ビットにできる。※ただしテストケースは 256ビット用に書かれているのでここを変更するとテストに失敗する。

また CBCモードを持つ暗号化アルゴリズムであれば同じく FBEncryptor.h 内の定数の書き換えでコンパイルできるかもしれない(試していないので動作するか不明)。
#define FBENCRYPT_ALGORITHM     kCCAlgorithmAES128
#define FBENCRYPT_BLOCK_SIZE    kCCBlockSizeAES128
#define FBENCRYPT_KEY_SIZE      kCCKeySizeAES256
指定可能な定数は CommonCryptor.h 辺りを参照のこと。


サンプル


暗号化・復号化が試せるサンプルアプリが付属する。


ソース解説


単純な CCCrypt のラッパーなので特別なことはやっていないので解説は割愛。

なお Base64エンコード・デコードのライブラリに Matt Gallagher 氏のソースコードを借用している(base64EncodedStringWithSeparateLines関数の追加あり)。
Cocoa with Love: Base64 encoding options on the Mac and iPhone

この為、FBEncryptor ライブラリを使う場合、一部のソースコードライセンスは MIT ではなく、Gallagpherさんの方のライセンスに従うので注意。



ライセンス


今回は借用ソースがあるので2つのライセンスが混在する。

FBEncryptor.m/FBEncryptor.h

MIT ライセンス。商用・非商用を問わず利用可能。カスタマイズしての再配布も自由。連絡も不要。

NSData+Base64.m/NSData+Base64.h

Created by Matt Gallagher on 2009/06/03.
Copyright 2009 Matt Gallagher. All rights reserved.

This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software. Permission is granted to anyone to
use this software for any purpose, including commercial applications, and to
alter it and redistribute it freely, subject to the following restrictions:

1. The origin of this software must not be misrepresented; you must not
   claim that you wrote the original software. If you use this software 
   in a product, an acknowledgment in the product documentation would be
   appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
   misrepresented as being the original software.
3. This notice may not be removed or altered from any source
   distribution.


関連情報


人気の投稿(過去 30日間)