taoru's memo

Objective-c,cocos2d,など開発についてのメモ(andoroidも少しだけ)

UITableViewのCellを、タッチ中(highlight)だけ色を変える(半透明にしたい)

UITableViewのcellは、defaultのままだとタッチすると青くなる。

単純なセルの色変更なら、UITableViewDelegateのdidSelectRowAtIndexPath中でcellを取得した後に、

cell.selectionStyle = UITableViewCellSelectionStyleGlay;

などセットすれば色を変えられる。

Frameworkで提供されているのは以下の三種類だけ

typedef NS_ENUM(NSInteger, UITableViewCellSelectionStyle) {
    UITableViewCellSelectionStyleNone,    // 無色(無反応に見える)
    UITableViewCellSelectionStyleBlue,      // 青色
    UITableViewCellSelectionStyleGray       // 灰色
};


だが、これだとセル全体に色がついてしまう。

やりたいのは、ハイライト中にview部分だけ半透明にしたい。


そこで、UITableViewCellのクラスにあるsetHighlightedをオーバーライドする。

- (void)setHighlighted:(BOOL)highlighted animated:(BOOL)animated
{
    [super setHighlighted:highlighted animated:animated];
    if (highlighted) {
        self.alpha = 0.5f;
    }else{
        self.alpha = 1.0f;
    }
}

これと合わせて UITableViewCellSelectionStyleNone にセットしておけば、タッチされている間だけ半透明になる。

highlight状態が変わる度にこのメソッドが呼ばれるので、応用がききそう。

UIViewをUIImageに変換する

拡張子が.pngの場合、UIImageに渡すファイル名では拡張子を省略できる(iOS 4以降)

UIImage Class Reference
On iOS 4 and later, if the file is in PNG format, it is not necessary to specify the .PNG filename extension

UIImage *image = [UIImage imageNamed:@"hoge"];

UIViewをUIImageに変換する

- (UIImage *)convertToUIImage
{
    UIImage *image;
     
    // UIViewのサイズの 画像コンテキストを開始
    UIGraphicsBeginImageContextWithOptions(self.frame.size, NO, [UIScreen mainScreen].scale);
    
    CGContextRef context = UIGraphicsGetCurrentContext();
    [self.layer renderInContext:context];
    image = UIGraphicsGetImageFromCurrentImageContext();
    
    // 画像コンテキストを終了
    UIGraphicsEndImageContext();
    
    return image;
}

Xcodeプロジェクトのgitignore設定とgitattributes設定

Xcodeプロジェクトをgit管理するとき、ユーザーデータやスワップファイルは無視させる。
こちはら使っていればすぐ.gitignore設定しなきゃ!って気がつくので問題があまりない。

しかし、これだけでは複数人開発をしたときに .pbxproj という拡張子のついたファイルがconflictを多発させる。
中身はJSONで記述されたXcodeのファイル管理情報で、1つでもファイルを追加したりXcode上で移動させればmodifiedとなるの。そのままcommitすれば、mergeやrebaseの際にconflictする。
この解決策は、.gitattributesを設定して.pbxprojファイルをbinaryとして扱うようにすればいいらしい。


.gitignore設定

Xcode and git: bridging the gap
上記サイトを参考に、git管理下のルートディレクトリに .gitignoreファイルを作成し記述する。

.gitignore

# OS X Finder
.DS_Store

# Xcode per-user config
*.mode1
*.mode1v3
*.mode2v3
*.perspective
*.perspectivev3
*.pbxuser
*.xcworkspace
xcuserdata

# Build products
build/
*.o
*.LinkFileList
*.hmap

# Automatic backup files
*~.nib/
*.swp
*~
*.dat
*.dep

.gitattributes設定

gitattiributesを設定して、.pbxprojファイルをバイナリとして扱うようにする。
そうすることで、conflictせずにautomergeされる(たぶん)
git管理下のルートディレクトリに、 .gitattributes というファイルを作成する。

.gitattributes

*.pbxproj binary -merge

binaryは Git 1.6系で定義されているマクロだそうで、

*.pbxproj -crlf -diff

と同義

"-crlf" は、Windows系の改行コードCRLFが混在していてもLFにconvertしたりして解決してくれるとかなんとか…(うろ覚え)

gitattributes(5) Manual Page
読み解くのに時間がかかる…

  • 追記1

ファイル削除したときなどに、やっぱりpbxprojがconflictするしconflictメッセージも記述されなくて逆に不便になった…
引き続き調査を続行します。

How to use Git properly with XCode?

*.pbxproj text -crlf -diff -merge=union

上記サイトではこう記述していた。

  • 追記2

しかしdelete情報があるとやはりconflict!と怒られる。
addばかりの時はよかったんだけど、renameすら正常にできなくて逆に効率落ちたので.gitattributesを削除することに…

何か良い解決策はないだろうか。

ファイル名に自由にsuffixをつけるNSString+Suffixを作った

suffixといえば、retina用画像なら hoge@2x.png のように@2xをつけるのだけれど、
オリジナルのsuffixを使いたい場合があったので、やり方を調べた。

が、サクッとやるメソッドは用意されていないっぽい…?
ので簡単なExtentionを作った。

要件

  • 拡張子の前にsuffixをつけたい
  • suffixは引数で指定
  • hoge.png -> hoge-suffix.png のようになればよい

NSString+Suffix.h

#import <Foundation/Foundation.h>

@interface NSString (Suffix)

/**
 *  拡張子の前にsuffixをつける
 */
- (NSString *)stringByAppendingSuffix:(NSString *)suffix;

@end

NSString+Suffix.m

#import "NSString+Suffix.h"

@implementation NSString (Suffix)
- (NSString *)stringByAppendingSuffix:(NSString *)suffix
{
    if (suffix) {
        NSString *fileNameWithoutExtension = [self stringByDeletingPathExtension];
        NSString *extention                = [self pathExtension];
        NSString *suffixedString           = [NSString stringWithFormat:@"%@%@",fileNameWithoutExtension,suffix];
        return [suffixedString stringByAppendingPathExtension:extention];
    }else{
        return self;
    }
}

@end


ほんとにこんなの必要なのか…?もっと1行とかでやる方法がありそう…

CCNodeのカテゴリで、他のCCNodeをゆったり追うSoftFollowというのを作ってみた その2 - 追記2/27

CCNodeのカテゴリで、他のCCNodeをゆったり追うSoftFollowというのを作ってみた - taoru's memo という記事を書いたんだけど、
kmVec2でベクトル計算するのがかっこく見えたので使って書きなおしてみた。

CCNode+SoftFollow.h

#import "CCNode.h"

@interface CCNode (SoftFollow)

/**
 *  toNode:   targetNode
 *  strength: 引きよせる強さ。最大値は"1"
 *  distance: targetNodeと一定の距離を保ちたい場合に指定
 */
- (void)followTo:(CCNode *)targetNode strength:(float)strength;
- (void)followTo:(CCNode *)targetNode strength:(float)strength distance:(float)distance;

- (void)stopFollow;

@end

CCNode+SoftFollow.m

#import "CCNode+SoftFollow.h"
#import "CCActionInstant.h"
#import "CCActionInterval.h"
#import "CGPointExtension.h"

#define ACTIONTAG_FOLLOWNODE 314159265351234

@implementation CCNode (SoftFollow)

- (void)followTo:(CCNode *)targetNode strength:(float)strength
{
    [self followTo:targetNode strength:strength distance:0.0f];
}

- (void)followTo:(CCNode *)targetNode strength:(float)strength distance:(float)distance
{
    CCCallBlockN *follow = [CCCallBlockN actionWithBlock:^(CCNode *node) {
        float distX = targetNode.position.x - node.position.x;
        float distY = targetNode.position.y - node.position.y;
        kmVec2 distVec = (kmVec2){distX,distY};
        if (kmVec2Length(&distVec) > distance) {            
            // 距離distanceのベクトル
            kmVec2 scaledVec;
            kmVec2Scale(&scaledVec, &distVec, distance/kmVec2Length(&distVec));
            
            // [targetNodeまでの距離 - distance]のベクトル
            kmVec2 subVec;
            kmVec2Subtract(&subVec, &distVec, &scaledVec);
            
            // 単位ベクトル
            kmVec2 unitVec;
            kmVec2Fill(&unitVec, subVec.x/kmVec2Length(&subVec), subVec.y/kmVec2Length(&subVec));
            
            // 単位ベクトル分だけ必ず進むようにする
            kmVec2 resultVec;
            kmVec2Add(&resultVec, &subVec, &unitVec);
            
            node.position = ccpAdd(self.position, ccp(resultVec.x*min(strength,1), resultVec.y*min(strength,1)));
        }
    }];
    
    CCSequence *seq         = [CCSequence actions:[CCDelayTime actionWithDuration:0.0f],follow, nil];
    CCRepeatForever *repeat = [CCRepeatForever actionWithAction:seq];
    repeat.tag = ACTIONTAG_FOLLOWNODE;
    
    [self runAction:repeat];
}

- (void)stopFollow
{
    [self stopActionByTag:ACTIONTAG_FOLLOWNODE];
}

@end

ここち良い感じに動く!
作法的にどうなのかってのは分からないけど、概ね満足している。
何か問題がありそうな箇所を発見した優しい方、ぜひコメントで教えてください。

追記 2013/02/25

kmVec2の関数を勉強したらもっともっと簡単に書けそうだった。単位ベクトルとか用意されてるんですね。
kmVec2でのベクトル計算 - taoru's memo

気が向いた時に書きなおしてみよう。

追記 2013/02/26

ということでkmVec2の動きも理解できたし書きなおす。
まず上記コードはblocks内でtargetNodeをcopyしちゃってるからまずいっぽい。

で、書きなおしたのが以下になります。

CCNode+SoftFollow.m

#import "CCNode+SoftFollow.h"
#import "CCActionInstant.h"
#import "CCActionInterval.h"
#import "CGPointExtension.h"

#define ACTIONTAG_FOLLOWNODE 314159265351234

@implementation CCNode (SoftFollow)

- (void)followTo:(CCNode *)targetNode strength:(float)strength
{
    [self followTo:targetNode strength:strength distance:0.0f];
}

- (void)followTo:(CCNode *)targetNode strength:(float)strength distance:(float)distance
{
    __block CCNode *target = targetNode;
    
    CCCallBlockN *follow = [CCCallBlockN actionWithBlock:^(CCNode *node) {
        float distX = target.position.x - node.position.x;
        float distY = target.position.y - node.position.y;
        kmVec2 nodeToTargetVec = (kmVec2){distX,distY};
        
        if (kmVec2Length(&nodeToTargetVec) > distance) {
            // ベクトルをdistanceの大きさまで縮める
            kmVec2 goalToTargetVec;
            kmVec2Scale(&goalToTargetVec, &nodeToTargetVec, distance/kmVec2Length(&nodeToTargetVec));
            
            // nodeからgoalまでのベクトル
            kmVec2 nodeToGoalVec;
            kmVec2Subtract(&nodeToGoalVec, &nodeToTargetVec, &goalToTargetVec);
            
            // strengthを乗算
            kmVec2 resultVec;
            kmVec2Scale(&resultVec, &nodeToGoalVec, min(strength,1));
            
            // 単位ベクトル分だけ必ず進むようにする
            if (kmVec2Length(&resultVec) < 1) {
                kmVec2Normalize(&resultVec, &resultVec);
            }
            
            node.position = ccpAdd(node.position, ccp(resultVec.x, resultVec.y));
        }
    }];
    
    CCSequence      *seq    = [CCSequence actions:[CCDelayTime actionWithDuration:0.0f],follow, nil];
    CCRepeatForever *repeat = [CCRepeatForever actionWithAction:seq];
    repeat.tag = ACTIONTAG_FOLLOWNODE;
    
    [self runAction:repeat];
}

- (void)stopFollow
{
    [self stopActionByTag:ACTIONTAG_FOLLOWNODE];
}

オリジナルカラーをドラッグで動かして、それを追わせている。楽しい。

動くには動く。理想通りに綺麗に動いてくれる。
しかしこのコードには問題があって、targetNodeがdeallocされるとクラッシュしてしまう。

if(targetNode == nil) とかも取れないし、色々試したけど解決できなかった。
__block修飾子をつけたオブジェクトがdeallocされているかどうかって、どうやって判定すればいいの…

普通に考えてCCAction継承して、CCFollowとかを真似して作ればいいんじゃないかと思った。
次回、CCActionのsubclass版を作ってみよう。

CCNodeのカテゴリで、他のCCNodeをゆったり追うSoftFollowというのを作ってみた

要件

  • CCNodeが、CCNodeを追って欲しい
  • 自動追尾だけど、自然な感じで追って欲しい
  • 指定したい項目は、対象Node、 引っ張られる強さ、対象との距離
  • 気軽に使いたい

ということで、CCNodeのカテゴリで CCNode+SoftFolloというのを作った。
CCDelayを継承してActionとして作ってもよかったかも?
あんまり綺麗にかけなかったので、改善案求む。

CCNode+SoftFollow.h

#import "CCNode.h"

@interface CCNode (SoftFollow)

- (void)followTo:(CCNode *)targetNode strength:(float)strength;
- (void)followTo:(CCNode *)tarNodgete strength:(float)strength distance:(float)distance;

@end

CCNode+SoftFollow.m

#import "CCNode+SoftFollow.h"
#import "CCActionInstant.h"
#import "CCActionInterval.h"
#import "CGPointExtension.h"

@implementation CCNode (SoftFollow)

- (void)followTo:(CCNode *)targetNode strength:(float)strength
{
    [self followTo:targetNode strength:strength distance:0.0f];
}

- (void)followTo:(CCNode *)targetNode strength:(float)strength distance:(float)distance
{
    CCCallBlockN *follow = [CCCallBlockN actionWithBlock:^(CCNode *node) {
        float distX = targetNode.position.x - node.position.x;
        float distY = targetNode.position.y - node.position.y;
        float dist  = sqrtf(distX*distX + distY*distY);
        if (dist > distance) {
            node.position = ccpAdd(self.position, ccp(distX*strength,distY*strength));
        }
    }];
    
    CCSequence *seq         = [CCSequence actions:[CCDelayTime actionWithDuration:0.0f],follow, nil];
    CCRepeatForever *repeat = [CCRepeatForever actionWithAction:seq];
    
    [self runAction:repeat];
}

scheduleにするのがなんとなく嫌で、CCRepeatForeverするための苦肉の策が、
CCDelayTimeを挟み込んだSequenceとすること…うーん。
問題は止められないということ

とりあえず, repeatにタグつけて、 [self stopActionByTag: タグ] ってやる関数を作れば止まるけどダサい。

追記 2012/11/27 19:33

kmVec2を使って書きなおしてみました。ついでにstopFollowも仮実装。
CCNodeのカテゴリで、他のCCNodeをゆったり追うSoftFollowというのを作ってみた その2 - 追記2/26 - taoru's memo

NSInvocationを組み立てるのが面倒くさかったので1メソッド呼び出しにしてみた

引数付きでSelectorを保持したいと思い、調べてみたらNSInvocationが良いらしいということがわかった。

が、いちいちSignature作って、setTargetして、引数セットして…というのが面倒なのでビルドメソッドを作った。
一応動いてはいるけど、これが安全なのか検証はしていません。

悩むのは、どこにこのメソッドを記述しておくかということ。
NSInvocationのカテゴリにでもするか?

  • -

追記1 2012年11月19日 12:04
NSObjectのカテゴリとして、selfを省略する形に変更。

NSObject + BuildInvocation.h

- (NSInvocation *)invocationWithSelector:(SEL)selector args:(void *)arg1, ... NS_REQUIRES_NIL_TERMINATION;

NSObject + BuildInvocation.m

- (NSInvocation *)invocationWithSelector:(SEL)selector args:(void *)arg1, ...
{
    va_list params;
    va_start(params, arg1);
    
    void *arg = arg1;
    
    NSMethodSignature *signature = [self methodSignatureForSelector:selector];
    NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
    [invocation retainArguments];
    [invocation setTarget:self];
    [invocation setSelector:selector];
    
    for (int i=2; arg != nil; i++) {
        [invocation setArgument:arg atIndex:i];
        arg = va_arg(params, void*);
    }
    va_end(params);
    return invocation;
}
    • -
QLOOKアクセス解析