ラベル Core Graphics の投稿を表示しています。 すべての投稿を表示
ラベル Core Graphics の投稿を表示しています。 すべての投稿を表示

CAGradientLayer を使ったグラデーション付きボタンの試作

2011年10月22日土曜日 | Published in | 4 コメント

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

CAGradientLayer を使った描画がしたくて調べている。試しにボタンを作ってみた。


仕組み


UIControl をベースのクラスに使い、その上にレイヤーを重ねて作ってある。
基本の色は UIControl.backgroundColor で決める。

その上に CAGradientLayer をかぶせてグラデーションをかける。グラデーションは白色をアルファ値を変えて表現する。
self.gradientLayer = [CAGradientLayer layer];
    self.gradientLayer.frame = self.bounds;
    self.gradientLayer.locations = [NSArray arrayWithObjects:
                                    [NSNumber numberWithFloat:0.0],
                                    [NSNumber numberWithFloat:0.5],
                                    [NSNumber numberWithFloat:0.5],
                                    [NSNumber numberWithFloat:1.0],
                                    nil]
     self.gradientLayer.colors =
        [NSArray arrayWithObjects:
             (id)[UIColor colorWithWhite:1.0 alpha:0.7].CGColor,
             (id)[UIColor colorWithWhite:1.0 alpha:0.4].CGColor,
             (id)[UIColor colorWithWhite:1.0 alpha:0.3].CGColor,
             (id)[UIColor colorWithWhite:1.0 alpha:0.0].CGColor,
             nil];
    [self.layer addSublayer:self.gradientLayer];
こうするとベースの色(UIControl.backgroundColor)は単純に基本の色を指定するだけで良い。色を加工したり、あらかじめ色のグラーでションを用意する必要が無いので使い勝手がいい(と思う)。

その上にボタンの文字を CATextLayer に描く。
self.textLayer = [CATextLayer layer];
    self.textLayer.string = self.text;
    self.textLayer.font = CGFontCreateWithFontName((CFStringRef)[self _font].fontName);
    self.textLayer.fontSize = [self _font].pointSize;
    self.textLayer.truncationMode = kCATruncationEnd;
    self.textLayer.alignmentMode = kCAAlignmentCenter;
    self.textLayer.shadowColor = [UIColor blackColor].CGColor;
    self.textLayer.shadowRadius = 0.5;
    self.textLayer.shadowOffset =CGSizeMake(-0.5, -0.5);
    self.textLayer.shadowOpacity = 0.5;
    self.textLayer.foregroundColor = [UIColor whiteColor];
    [self.layer addSublayer:self.textLayer];

その上のレイヤーはボタンに立体感を出すためのハイライトライン。
これを描くために CALayer のサブクラス HighlightEdgeLayer を作り自前で線を描画している。
#define CORNER_RADIUS   5.0

@implementation HighlightEdgeLayer
- (void)drawInContext:(CGContextRef)context
{
    CGMutablePathRef path = CGPathCreateMutable();
    CGPoint p = CGPointMake(CORNER_RADIUS, CORNER_RADIUS);

    CGPathAddArc(path, NULL,
                 p.x, p.y,
                 CORNER_RADIUS,
                 2.0*M_PI/2.0,
                 3.0*M_PI/2.0,
                 false);

    p.x = self.bounds.size.width - CORNER_RADIUS;
    CGPathAddArc(path, NULL,
                 p.x, p.y,
                 CORNER_RADIUS,
                 3.0*M_PI/2.0,
                 4.0*M_PI/2.0,
                 false);

    CGContextAddPath(context, path);
    CGPathRelease(path);
    CGContextSetStrokeColorWithColor(context, [UIColor colorWithWhite:1.0 alpha:0.5].CGColor);
    CGContextSetLineWidth(context, 1.0);
    CGContextDrawPath(context, kCGPathStroke);

}
@end
一番上のレイヤーは UIControl に最初からついている CALayer。これで黒い境界線を引き、角を丸くしている。
self.layer.cornerRadius = CORNER_RADIUS;
    self.layer.masksToBounds = YES;
    self.layer.borderColor = [UIColor colorWithWhite:0.0 alpha:0.75].CGColor;
    self.layer.borderWidth = 1.0;

ボタンが押された時には -[UIControl setHighlighted:] が呼ばれるのでここでグラデーションの色を変えてやる。
- (void)setHighlighted:(BOOL)highlighted
{
    [super setHighlighted:highlighted];
    [self _setState:highlighted ? GradientButtonStateHighlighted : GradientButtonStateNormal];
}
サンプルでは3つの状態を定義しておいて、それぞれでグラデーションの色と文字色を変えている。
typedef enum {
    GradientButtonStateNormal = 0,
    GradientButtonStateHighlighted,
    GradientButtonStateDisabled
} GradientButtonState;

- (void)_setState:(GradientButtonState)state
{
    switch (state) {
        case GradientButtonStateNormal:
            self.gradientLayer.colors =
            [NSArray arrayWithObjects:
             (id)[UIColor colorWithWhite:1.0 alpha:0.7].CGColor,
             (id)[UIColor colorWithWhite:1.0 alpha:0.4].CGColor,
             (id)[UIColor colorWithWhite:1.0 alpha:0.3].CGColor,
             (id)[UIColor colorWithWhite:1.0 alpha:0.0].CGColor,
             nil];
            self.textLayer.foregroundColor = self.textColor.CGColor;
            break;
            
        case GradientButtonStateHighlighted:
            self.gradientLayer.colors =
            [NSArray arrayWithObjects:
             (id)[UIColor colorWithWhite:1.0 alpha:0.5].CGColor,
             (id)[UIColor colorWithWhite:1.0 alpha:0.2].CGColor,
             (id)[UIColor colorWithWhite:0.0 alpha:0.05].CGColor,
             (id)[UIColor colorWithWhite:0.0 alpha:0.1].CGColor,
             nil];
            self.textLayer.foregroundColor = self.textColor.CGColor;
            break;
            
        case GradientButtonStateDisabled:
            self.gradientLayer.colors =
            [NSArray arrayWithObjects:
             (id)[UIColor colorWithWhite:1.0 alpha:0.7].CGColor,
             (id)[UIColor colorWithWhite:1.0 alpha:0.4].CGColor,
             (id)[UIColor colorWithWhite:1.0 alpha:0.3].CGColor,
             (id)[UIColor colorWithWhite:1.0 alpha:0.0].CGColor,
             nil];
            self.textLayer.foregroundColor = [UIColor lightGrayColor].CGColor;
            break;
    }
}
押す前

押した後
ディゼーブル


サンプル


何色かのボタンを並べたサンプルを作ってみた。最後のボタンは Disable状態。
ベースの色は階調が見やすいように濃いめの色にした方がいい。これはグラデーションの表現を白で表現している為。標準で UIColor に用意されている greenColor や yellowColor は明るすぎて階調が出づらい。

Xib はこんな感じ。UIView を配置して大きさを調整した後、クラスに GradientButton を指定する。


利用上の注意点としては Xib 上で IBAction を指定すると Value Changed のアクションがデフォルトで選択される。これではボタン押下時のイベントが取れないので Touch up inside を使う。


ソースコード


GitHub からどうぞ。
GradientButton at 2011-10-22 from xcatsan/iOS-Sample-Code - GitHub


参考情報


テン*シー*シー - 【iPhoneアプリ開発ドリル】Aqua風ボタンを作る

CAGradientLayer の使い方はこのサイトがとても参考になった。UIControl をボタンとして実装するアイディアはここから拝借した。情報をどうも!

iPhoneアプリ開発、その(17) 文字だって回転|テン*シー*シー

CATextLayer の使い方もここ。

iphone - How can i convert UIFont to CGFontRef?I causes warning and not working - Stack Overflow

UIFont から CGFontRef を取る方法。
CGFontRef cgFont = CGFontCreateWithFontName((CFStringRef)uiFont.fontName);

サンプルコードのボタンのグラデーション(ハイライト)は iOS のポップアップメニューを参考にした。


なので通常のボタンと見た目がちょっと違う。


関連情報


CATextLayer Class Reference
CAGradientLayer Class Reference


CALayer を使ってビューの内側に影を落とす

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

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

ビューの上の縁に影を落としたい。こんな感じ。

簡単に出来る方法はないか。

CALayer


CALayer を使うと簡単にビューに影を落とすことができる。
CALayer* layer = self.imageView1.layer;
    layer.shadowOffset = CGSizeMake(2.5, 2.5);
    layer.shadowColor = [[UIColor blackColor] CGColor];
    layer.shadowOpacity = 0.5;


ただこの方法はビューの外側に影を落とせても、ビューの内部には影を落とせない。
どうするか。

CALayer のプロパティを眺めていていると shadowPath に気がついた。このプロパティには CGPathRef を渡すことができる。
@property CGPathRef shadowPath;
もしかしてこれを使って任意の場所や形で影が落とせないか。

試しにこんな矩形のパスを作って渡してみた。

CALayer* subLayer = imageView.layer;
    UIBezierPath* path = [UIBezierPath bezierPathWithRect:
            CGRectMake(-10.0, -10.0, subLayer.bounds.size.width+10.0, 10.0)];
    subLayer.shadowOffset = CGSizeMake(2.5, 2.5);
    subLayer.shadowColor = [[UIColor blackColor] CGColor];
    subLayer.shadowOpacity = 0.5;
    subLayer.shadowPath = [path CGPath];
すると影が落ちた(わかりやすいように画像を縮小してある)。

でも画像の下側だ。影はコンテンツの下に来るものだから当たり前といえば当たり前。コンテンツの上に影をかぶせるにはどうしたらいいか。

サブレイヤーを追加してそこへ影を落としてはどうか?やってみよう。
CALayer* subLayer = [CALayer layer];
    subLayer.frame = imageView.bounds;
    [imageView.layer addSublayer:subLayer];
    subLayer.masksToBounds = YES;
    UIBezierPath* path = [UIBezierPath bezierPathWithRect:
            CGRectMake(-10.0, -10.0, subLayer.bounds.size.width+10.0, 10.0)];
    subLayer.shadowOffset = CGSizeMake(2.5, 2.5);
    subLayer.shadowColor = [[UIColor blackColor] CGColor];
    subLayer.shadowOpacity = 0.5;
    subLayer.shadowPath = [path CGPath];

出た。いい感じだ。

どんなビューでも CALayer がサポートされているので例えば MKMapView でも簡単に影を落とせる。


バリエーション


上だけでなく左にも影を落としてみた。ついでに角も丸くした。


逆L字型の図形を左上に用意してその影を落とせばいい。

逆L字型図形は CGMutablePath を使って地道に描く。
- (void)_addDropShadowToView2:(UIView*)toView
{
    CALayer* subLayer = [CALayer layer];
    subLayer.frame = toView.bounds;
    [toView.layer addSublayer:subLayer];
    subLayer.masksToBounds = YES;

    CGSize size = subLayer.bounds.size;
    CGFloat x = -10.0;
    CGFloat y = -10.0;
    CGMutablePathRef pathRef = CGPathCreateMutable();
    CGPathMoveToPoint(pathRef, NULL, x, y);
    x += size.width + 10.0;
    CGPathAddLineToPoint(pathRef, NULL, x, y);
    y += 10.0;
    CGPathAddLineToPoint(pathRef, NULL, x, y);
    x -= size.width;
    CGPathAddLineToPoint(pathRef, NULL, x, y);
    y += size.height;
    CGPathAddLineToPoint(pathRef, NULL, x, y);
    x -= 5.0;   // (*)10
    CGPathAddLineToPoint(pathRef, NULL, x, y);
    y -= size.height;   // (*)size.height+10
    CGPathAddLineToPoint(pathRef, NULL, x, y);
    CGPathCloseSubpath(pathRef);
   
    subLayer.shadowOffset = CGSizeMake(2.5, 2.5);
    subLayer.shadowColor = [[UIColor blackColor] CGColor];
    subLayer.shadowOpacity = 0.5;
    subLayer.shadowPath = pathRef;
   
    CGPathRelease(pathRef);
   
}
実は逆L字型は少し歪んだ形をしている。(*)のついている2行はコメントに記載した値が正しいのだが、これを使うと下図の様にコンテンツ全体に薄い影がかかってしまった。

パスはきちんと閉じていると思うのだが、どうも思った形で影が落ちないようだ。上記値は試行錯誤で見つけた値。逆L字型が正確な形ではないが、見た目は意図通りの影が落ちているのでこれでよしとする。原因を知っている方(バグを見つけた方)がいたら是非教えて下さい。


ソースコード


GitHub からどうぞ。
LayerShadowSample at 2011-08-08 from xcatsan/iOS-Sample-Code - GitHub


参考情報


Fun shadow effects using custom CALayer shadowPaths | iOS/Web Developer's Life in Beta
shadowPath プロパティを使った様々な形の影の落とし方。参考になった。


Invitation to CoreAnimation - NIT-Universe
サブレイヤーを使うアイディアはここからヒントを得た。

関連情報


Cocoaの日々: Bezelボタンを作る[03]矩形の内側に影を落とす
以前紹介したビューの内側へ影を落とす方法。マスクを作ったりと結構面倒。今回のCALayerを使う方が簡単。

[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 ならこっちも使える。

トランジション[3] CATransition を使う(その2)

2010年10月2日土曜日 | Published in | 0 コメント

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

[前回] Cocoaの日々: トランジション[2] CATransition を使う

前回のコードに手を入れていろいろなトランジションを試してみよう。


サンプル


まずは実行画面から。4つのタイプと4つのサブタイプが選べる。

Fade-right でトランジション実行



実装


UISegmentedControl を2つ配置して、これらで CATransition.type, subType を指定する。追加したのはそれだけ。
@property (nonatomic, retain) IBOutlet UISegmentedControl* mainType;
@property (nonatomic, retain) IBOutlet UISegmentedControl* subType;

// transition
 CATransition* transition = [CATransition animation];
 transition.duration = self.slider.value;
 transition.timingFunction = [CAMediaTimingFunction
           functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
 
 NSString *maintypes[4] = {kCATransitionMoveIn, kCATransitionPush,
                                  kCATransitionReveal, kCATransitionFade};
 NSString *subtypes[4] = {kCATransitionFromLeft, kCATransitionFromRight,
                                   kCATransitionFromTop, kCATransitionFromBottom};

 transition.type = maintypes[mainType.selectedSegmentIndex];
 if (mainType.selectedSegmentIndex < 3) {
  transition.subtype = subtypes[subType.selectedSegmentIndex];
 }
UISegmentedControlは、本当はこんな使い方しないんだろうけれど ;;


備考


アニメーションを行うビューを UIViewController のビューにした場合、画面全体がトランジション対象になる。
[self.view.layer addAnimation:transition forKey:nil];

 画面の一部だけをトランジションする場合、アニメーション用にベースとなるビューを用意してそこでアニメーションさせる。

[self.baseView.layer addAnimation:transition forKey:nil];

なお Fade と MoveIn の場合はベースのビューの外側もアニメーションで使われる。以下は MoveIn+Top とした場合の例。


ソースコード


GitHub からどうぞ。 xcatsan's iOS-Sample-Code at 2010-10-02 - GitHub

トランジション[2] CATransition を使う

2010年10月1日金曜日 | Published in | 0 コメント

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

[前回] Cocoaの日々: UIView - トランジション[1] 標準のもの

今回は CATransitionを使ったトランジションを試してみる。


iOS Reference Library 提供のサンプル


"Transition"ボタンを押すとランダムに選ばれたトランジション効果が適用される。
ViewTransitions



トランジションの種類は4種類。さらにサブタイプ(方向を決める)を持つものがある。
CAAnimation.h より
CA_EXTERN NSString * const kCATransitionFade
    __OSX_AVAILABLE_STARTING (__MAC_10_5, __IPHONE_2_0);
CA_EXTERN NSString * const kCATransitionMoveIn
    __OSX_AVAILABLE_STARTING (__MAC_10_5, __IPHONE_2_0);
CA_EXTERN NSString * const kCATransitionPush
    __OSX_AVAILABLE_STARTING (__MAC_10_5, __IPHONE_2_0);
CA_EXTERN NSString * const kCATransitionReveal
    __OSX_AVAILABLE_STARTING (__MAC_10_5, __IPHONE_2_0);

これを参考にサンプルを作ってみよう。


ソースコード


とりあえず凝ったことはしないで一番シンプルな作りにした。

2つの UIImageView を予めInterfaceBuilderで用意しておく。
@interface TransitionSample2ViewController : UIViewController {

 UIImageView* imageView1;
 UIImageView* imageView2;

 UISlider* slider;
 UILabel* duration;

 NSMutableArray* images;
 NSInteger imageIndex;

}
@property (nonatomic, retain) IBOutlet UIImageView* imageView1;
@property (nonatomic, retain) IBOutlet UIImageView* imageView2;

@property (nonatomic, retain) IBOutlet  UISlider* slider;
@property (nonatomic, retain) IBOutlet  UILabel* duration;

@property (nonatomic, retain) NSMutableArray* images;

-(IBAction)didChangeDuration:(id)sender;
-(IBAction)doTransition:(id)sender;

@end

トランジション処理。CATransition を使うのと、2つの UIImageViewを切り替えるのがミソ。
-(IBAction)doTransition:(id)sender;
{
 // setup next image
 imageIndex = (imageIndex+1)%8;
 imageView2.image = [images objectAtIndex:imageIndex];
 
 CATransition* transition = [CATransition animation];
 transition.duration = self.slider.value;
 transition.timingFunction = [CAMediaTimingFunction
   functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
 transition.type = kCATransitionFade;
 
 [self.view.layer addAnimation:transition forKey:nil];
 
 imageView1.hidden = YES;
 imageView2.hidden = NO;
 
 UIImageView* tmp = imageView2;
 imageView2 = imageView1;
 imageView1 = tmp;
}

このあたりは Mac OS X と変わらない。以下は過去 Mac OS X でやったときのブログ。
(旧) Cocoaの日々: スライドトランジション
(旧) Cocoaの日々: 波紋(その1)

Mac OS X の場合は Core Image をアニメーションエフェクトに使えるのでさらに派手な効果が出せる。

なお CATransition 利用には QuartzCore.framework を追加し、QuartzCore.hを読み込んでおく必要がある。

#import <QuartzCore/QuartzCore.h>

自作サンプル


実行してみよう。

ボタンを押すと次の画像にジワジワと切り替わる。



ソースコードは GitHub からどうぞ。
TransitionSample2 at 2010-10-01 from xcatsan's iOS-Sample-Code - GitHub



- - - - -
CATransition は他にもあるので試してみよう。

UISearchDisplayController と NSFetchedResultContoller を組み合わせる (2) バグ修正

2010年7月20日火曜日 | Published in | 0 コメント

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

[前回] Cocoaの日々: UISearchDisplayController と NSFetchedResultContoller を組み合わせる

前回のコードに問題があることがわかった。

バグ


画面からはみ出る程度に件数を増やすと次のケースでクラッシュすることがわかった。

(1) 初期表示


(2) 検索実行


(3) キャンセルで元の一覧戻り、下へスクロール

⇒ クラッシュ


原因


検索時の NSFetchedResultController が、検索後に元の画面へ戻った時に使用されていた。どういうことかというと、元々10件のデータがあったにもかかわらず、検索で絞り込まれて1件となった後、元の画面に戻ってもその1件のままとなっていた。その状態で下へスクロールして見えていなかったデータを表示しようとした時に -tableView:cellForRowAtIndexPath: が呼び出され、そこで NSFetchedResultController に存在しない Indexを指定してエラーとなっていた(例:結果が1件にもかかわらずスクロールによって現れた 10件目のデータへアクセスしようとしていた)。

元の画面に戻った時に NSFetchedResultController の検索条件を元に(全件)戻し、フェッチをやり直す必要がある。


修正


検索完了時に NSFetchedResultController の検索条件を元に戻し、再フェッチする。UISearchBarの「キャンセル」ボタンが押された時を検索完了とすると UISearchBarDelegate のメソッドにこれらの処理を記述できる。
- (void)searchBarCancelButtonClicked:(UISearchBar *)searchBar
{
 [self.fetchedResultsController.fetchRequest setPredicate:nil];
 [self reloadFetchedResultsController];
}
条件をクリア(nil)した上で再フェッチを行う。

-(void)reloadFetchedResultsController {
 NSError *error = nil;
 [NSFetchedResultsController deleteCacheWithName:@"UserSearch"];
    if (![self.fetchedResultsController performFetch:&error]) {
        NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
        abort();
    }
}


ソースコード


SearchSample at 2010-07-20 from xcatsan's iOS-Sample-Code - GitHub

UIImage から縮小画像を生成する (2) iPhone4 高解像度対応

2010年7月1日木曜日 | Published in | 2 コメント

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

[前回] Cocoaの日々: UIImage から縮小画像を生成する

前回作成したメソッドに手を入れて iPhone4向け高解像度対応を行う。

デバイス毎の解像度に対応した縮小


例えば 960x300 の縮小画像を作成する場合、iPhone 3G/3GS で横幅ぴったりに表示できる画像のサイズは 320 x 100 となる。一方、iPhone4の場合は 640 x 200 が望ましい。どちらか一方のデバイスをターゲットにするアプリの場合は特に問題ないが、両方のデバイスをターゲットとする場合はデバイスの解像度に合わせた縮小率を適用する必要がある。


UIGraphicsBeginImageContextWithOptions


iOS4 からは UIGraphicsBeginImageContextWithOptions() が導入されて、これを使うとデバイス毎の解像度の違いを吸収することができる。
UIKit Function Reference - UIGraphicsBeginImageContextWithOptions

この関数ではビットマップに適用する倍率を指定することができる。
(例)UIGraphicsBeginImageContextWithOptions(CGRectMake(100, 100), NO, 2.0);

この場合、実際に作成されるビットマップのサイズは 200x200(ピクセル)となる。この倍率(scale)は 0 を指定することができて、その場合はデバイスに適した倍率が自動的い採用される。現在のところ iPhone3G/3GS は 1.0、iPhone4 は 2.0 となる。


高解像度対応版 縮小処理


前回のコードに手を入れて UIGraphicsBeginImageContext() の代わりに UIGraphicsBeginImageContextWithOptions()を使うようにする。
#import <CoreGraphics/CoreGraphics.h>

@implementation UIImage (extension)

- (UIImage*)imageByShrinkingWithSize:(CGSize)size
{
 CGFloat widthRatio  = size.width  / self.size.width;
 CGFloat heightRatio = size.height / self.size.height;
 
 CGFloat ratio = (widthRatio < heightRatio) ? widthRatio : heightRatio;

 if (ratio >= 1.0) {
  return self;
 }

 CGRect rect = CGRectMake(0, 0,
        self.size.width  * ratio,
        self.size.height * ratio);
 
 UIGraphicsBeginImageContextWithOptions(rect.size, NO, 0.0); // 変更

 [self drawInRect:rect];

 UIImage* shrinkedImage = UIGraphicsGetImageFromCurrentImageContext();
 
 UIGraphicsEndImageContext();
 
 return shrinkedImage;
}


実行例


次の画像を縮小してみた。サイズ 320x480 ピクセル。
縮小後のサイズは iPhone3G/3GS で 80x120 ピクセルとする。

生成されるビットマップ画像のサイズがそれぞれのデバイスで異なることをiPhoneシミュレータを使って確認してみる。

検証に使ったコードの一部
UIImage* image = [UIImage imageNamed:@"sample1.png"];
 CGSize size = CGSizeMake(80, 120);
 
 UIImage* shrinkedImage = [image imageByShrinkingWithSize:size];
 NSLog(@"image.scale: %f, size:[%f, %f]",
    shrinkedImage.scale,
    shrinkedImage.size.width,
    shrinkedImage.size.height);
 
 NSLog(@"cgimage.size:[%d, %d]",
    CGImageGetWidth(shrinkedImage.CGImage),
    CGImageGetHeight(shrinkedImage.CGImage));
UIImage.size と CGImageGetWidth/Height をコンソールへ出力して見比べてみた。

iPhone3の結果(iPhoneシミュレータ)

image.scale: 1.000000, size:[80.000000, 120.000000]
cgimage.size:[80, 120]

iPhone4の結果(iPhoneシミュレータ)

image.scale: 2.000000, size:[80.000000, 120.000000]
cgimage.size:[160, 240]

シミュレータでは見た目の違いはわからないが、コンソールの出力からは予想通りiPhone4では scaleが 2.0 となっており、ビットマップのサイズも iPhone3の縦横2倍となっているのがわかる。一方で、UIImage.size は両者とも同じになっている。実は iOS4から UIImage.sizeの定義が変わっている。リファレンスによると iOS4 からはピクセルではなく、ポイントを返すようになっている。
In iPhone OS 4.0 and later, this value reflects the logical size of the image and is measured in points. In iPhone OS 3.x and earlier, this value always reflects the dimensions of the image measured in pixels.

UIImage Class Reference

単位が変わるなんてちょっとドラスティックな変更な気もするが、従来のコードの修正を最小限に抑えるという意味ではうまい方法かもしれない。

参考情報


iPhone Application Programming Guide: Supporting High-Resolution Screens
アプリを高解像度対応させる方法についての解説

UIImage から縮小画像を生成する

2010年6月30日水曜日 | Published in | 2 コメント

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

UIImage からサムネイル用途で使用する縮小画像を作る。

縮小処理


こんな感じ。
@implementation UIImage (extension)

- (UIImage*)imageByShrinkingWithSize:(CGSize)size
{
 CGFloat widthRatio  = size.width  / self.size.width;
 CGFloat heightRatio = size.height / self.size.height;
 
 CGFloat ratio = (widthRatio < heightRatio) ? widthRatio : heightRatio;

 if (ratio >= 1.0) {
  return self;
 }

 CGRect rect = CGRectMake(0, 0,
     self.size.width  * ratio,
     self.size.height * ratio);
 
 UIGraphicsBeginImageContext(rect.size);

 CGContextRef context = UIGraphicsGetCurrentContext();
 [self drawInRect:rect];

 UIImage* shrinkedImage = UIGraphicsGetImageFromCurrentImageContext();
 
 UIGraphicsEndImageContext();
 
 return shrinkedImage;
}
UIImage のカテゴリとして実装した。縦横比は保持したまま、与えられたサイズ内に収まる最大の画像を返す。


参考情報

補間方法を指定してUIImageを縮小する(1) - tosh-tの日記
画像を縮小する時の補完方法について

Bezelボタンを作る[12]画像をグレースケールで表示する(その2)

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

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

[前回]

以前紹介した画像をグレースケール変換して表示する方法が、Alphaチャネル(半透明マスク)に対応していないことがわかった。

透明部分が黒くなる


以前は背景が白い画像を使っていたので気がつかなかったのだが、背景が透明な画像をグレースケール変換するとその部分が黒くなってしまうことがわかった。

元画像の例。図ではわからないが周りの部分が透明になっている。
これを前回の方法でグレースケール変換するとこうなる。
周りの透明部分が黒くなってしまっている。これはグレースケール変換用に作成した CGBitmapContextCreate() の引数で kCGImageAlphaNone を渡していたため。この引数は Alphaチャネルを持たないことを示している。
CGContextRef context = CGBitmapContextCreate(
             nil, image.size.width, image.size.height, 8, 0,
             colorSpace, kCGImageAlphaNone);



Alphaチャネルの合成


これを解決するには透明部分、すなわちAlphaチャネルのみを取り出した画像を作り、これをマスク情報として前回の画像に合成してやれば良い。

Alphaチャネルの画像は CGBitmapContextCreate() の引数に kCGImageAlphaOnly を渡すと作ることができる。
先の例の画像の場合、Alphaチャネルはこんな感じになる。
白い部分が透明で、黒い部分が不透明をな領域を表している。

合成には CGImageCreateWithMask() が使える。

最終的なソースコードはこんな感じ
-(UIImage*)convertGrayScaleImage:(UIImage*)image
{
 CGRect rect = CGRectMake(0.0, 0.0, image.size.width, image.size.height);
 
 CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceGray();
 
 // (1) create alpha chanel
 CGContextRef alphaContext = CGBitmapContextCreate(
   nil, image.size.width, image.size.height, 8, 0,
   colorSpace, kCGImageAlphaOnly);
 CGContextDrawImage(alphaContext, rect, [image CGImage]);
 CGImageRef alphaImage = CGBitmapContextCreateImage(alphaContext);
 CGContextRelease(alphaContext);
 
 // (2) create gray scale image
 CGContextRef context = CGBitmapContextCreate(
   nil, image.size.width, image.size.height, 8, 0,
   colorSpace, kCGImageAlphaNone);
 CGContextDrawImage(context, rect, [image CGImage]);
 CGImageRef grayScaleImage = CGBitmapContextCreateImage(context);
 CGContextRelease(context);
 
 // (3) composite images
 UIImage* grayScaleUIImage = [UIImage imageWithCGImage:
          CGImageCreateWithMask(grayScaleImage, alphaImage)];
 
 CGImageRelease(grayScaleImage);
 CGImageRelease(alphaImage);
 CGColorSpaceRelease(colorSpace);
 
 return grayScaleUIImage;
}


さて実行してみよう。
背景が透明になった。


参考情報


iPhoneアプリ開発、その(185) 出るもんが出たか…|テン*シー*シー
kCGImageAlphaOnly と CGImageCreateWithMask() について参考になった。

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