月別アーカイブ: 2013年4月

RetinaImageShrinker ver1.0.3

レティナ用イメージから非レティナ用イメージを作成するフリーウエア「RetinaImageShrinker」を更新しました。
まぁ不具合の修正なんですけどね。。

変更内容
・高さ、幅が1ピクセルのイメージを縮小した時に不正なイメージが生成される不具合を修正

ダウンロードはこちらから

UIImageの一部を切り抜く方法

画像の一部分を切り抜いたイメージを作成する方法です。
画像のトリミングや画像を複数枚に切り出す時などに使用して下さい。


// image : 元の画像
// rect  : 切り抜く範囲
-(UIImage*)clipImage:(UIImage*)image rect:(CGRect)rect
{
    // イメージの解像度に従いrectも換算
    float scale = image.scale;
    CGRect cliprect = CGRectMake(rect.origin.x * scale, rect.origin.y * scale,
                                 rect.size.width * scale, rect.size.height * scale);
    
    // ソース画像からCGImageRefを取り出す
    CGImageRef srcImgRef = [image CGImage];
    
    // 指定された範囲を切り抜いたCGImageRefを生成しUIImageとする
    CGImageRef imgRef = CGImageCreateWithImageInRect(srcImgRef, cliprect);
    UIImage* resultImage = [UIImage imageWithCGImage:imgRef scale:scale orientation:image.imageOrientation];
    
    // 後片付け
    CGImageRelease(imgRef);
        
    return resultImage;
}


@2xなどのratina用イメージにも対応してます。

カテゴリーでインスタンス変数的なものを追加する

objective-cのカテゴリーって便利ですね。

最近はついついなんでもカテゴリーで実装しちゃいますが(ダメ!)、この機能には弱点があります。
それは、インスタンス変数が追加できないことです。
(C#の拡張メソッドもそうですよね。)

まぁ継承して使えばなにも問題は無いのですが、継承するほどの事でもないな、って時はこんな方法で解決できます。

ソースを見たらすぐに理解できると思います。

#import <objc/runtime.h>

//宣言
@interface UIView (Title)

@property (nonatomic) NSString* title;

@end

//実装
@implementation UIView (Title)

static NSString* title_key = @"title-key";

-(void)setTitle:(NSString*) title
{
    objc_setAssociatedObject(self, title_key, title, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

-(NSString*) title
{
    return objc_getAssociatedObject(self, title_key);
}

@end


objc_setAssociatedObject、objc_getAssociatedObjectを使うと保存出来る事が分かるのではないでしょうか。

ちょっと補足すると、これは関連参照という機能を使っており、オブジェクトとオブジェクトとを特定のキーで紐付けすることで保持する機能です。
この例ではインスタンス変数なのでselfに対してtitleを関連付けて保存しているのです。

また、上記ソースを見て、「プロパティの破棄してないやんけ!(激怒)」と思いますよね。
これも関連参照の便利な所なのですが、対象のオブジェクトが破棄された時に自動的に関連付けたオブジェクトも破棄してくれます。カシコイですね。

Objective-cでBASE64変換

(2014-04-10追記)
この記事は古いです。
どうやらiOS7からNSDataにbase64対応メソッドが追加されています。
コチラ
—-

Amazon Web Serviceを使うのにBase64が必要となったんですけど、cocoaには含まれてないんですね。

今までライブラリに含まれている環境ばかりで育ったゆとりおじさんなもんで、ちょっとショックだったんですけど、
wikiで調べてみるとそんなに難しい変換ではなさそう。

Base64変換の手順を以下に挙げる。
  元データを6bitずつに分割。(6bitに満たない分は0を追加して6bitにする)
  各6bitの値を変換表を使って4文字ずつ変換。(4文字に満たない分は = 記号を追加して4文字にする)

変換例
1. 元データ
   文字列: “ABCDEFG”
   16進表現: 41, 42, 43, 44, 45, 46, 47
   2進表現: 0100 0001, 0100 0010, 0100 0011, 0100 0100, 0100 0101, 0100 0110, 0100 0111
2. 6bitずつに分割
   010000 010100 001001 000011 010001 000100 010101 000110 010001 11
3. 2bit余るので、4bit分0を追加して6bitにする
   010000 010100 001001 000011 010001 000100 010101 000110 010001 110000
4. 変換表により、4文字ずつ変換
   “QUJD”,”REVG”,”Rw”
5. 2文字余るので、2文字分 = 記号を追加して4文字にする
   “QUJD”,”REVG”,”Rw==”
6. Base64文字列
   “QUJDREVGRw==”
   http://ja.wikipedia.org/wiki/Base64

元データを6bitずつに分割して、そこから得られた値を変換表を使って対応する文字を取得すればいいんですな。
4文字ずつ変換ってことは6×4=24bit ・・3バイトずつ処理すれば良いってことで、
図解するとこんな感じ?
  ■■■■■■■■
 ■■■■
■■■■
 ■■
■■■■■■
 :1文字目 :2文字目 :3文字目 :4文字目

せっかくなのでNSDataのカテゴリにしてみました。

インターフェース部


@interface NSData (Base64)

//BASE64でエンコードされた文字列をデコードする
+(NSData*)dataWithBase64String:(NSString*)string;

//レシーバーの内容をBASE64でエンコードします
-(NSString*)stringByBase64Encode;

@end

実装側はこんな感じ



@implementation NSData (Base64)

//変換用テーブル
const static char base64table[] =
    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";

/**
 * BASE64でエンコードされた文字列をデコードする
 */
+(NSData*)dataWithBase64String:(NSString*)string
{
    if (!string || string.length == 0) return nil;
    
    unsigned char idx[4];
    unsigned char val1,val2,val3;
    char buf;

    unsigned long datalength = string.length;
    NSMutableData* result = [NSMutableData data];
    
    //ポインタで処理するためにchar*へ変換
    const char* src = [string cStringUsingEncoding:NSASCIIStringEncoding];
    
    for(int i=0;i<datalength;i += 4)
    {
        //base64の先頭からのインデックスを求める(4文字ずつ処理する)
        for (int j=0; j < 4; j++)
        {
            buf = *(src + i + j);
            idx[j] = (buf == '=') ? 0 : strchr(base64table, buf) - base64table;
        }
        
        //求めたインデックスからバイト値を求める(6bit×4 から 8bit×3を生成)
        val1 = ((idx[0] & 0x3F) << 2 | (idx[1] & 0x30) >> 4 );
        val2 = ((idx[1] & 0x0F) << 4 | (idx[2] & 0x3C) >> 2 );
        val3 = ((idx[2] & 0x03) << 6 | (idx[3] & 0x3F) >> 0 );
        
        //結果を保存
        [result appendBytes:&val1 length:1];
        [result appendBytes:&val2 length:1];
        [result appendBytes:&val3 length:1];
    }
    
    return result;
}


/**
 * レシーバーの内容をBASE64でエンコードします
 */
-(NSString*) stringByBase64Encode
{
    unsigned char buf1,buf2,buf3;
    unsigned char idx[4];
    char bufchar[2];
    bufchar[1] = '\0';
    
    NSMutableString *result = [NSMutableString string];
    
    unsigned long datalength = self.length;
    const unsigned char* data = [self bytes];

    for(int i = 0 ; i < datalength ; i += 3 )
    {
        //2バイト目、3バイト目が存在するかチェック
        bool flg2 = ((i+1) < datalength);
        bool flg3 = ((i+2) < datalength);
        
        
        //3バイト(24bit)ずつ処理
        buf1 = data[i];
        buf2 = flg2 ? data[i+1]: '\0';
        buf3 = flg3 ? data[i+2]: '\0';
        
        //24bitを6bitに分け、それぞれに対応する文字へのインデックスを求める
        idx[0] = (buf1  >> 2);                        //buf1の6bit
        idx[1] = (buf1 & 0x03) << 4 | (buf2 >> 4);    //buf1の2bitとbuf2の4bit
        idx[2] = (buf2 & 0x0F) << 2 | (buf3 >> 6);    //buf2の4bitとbuf3の2bit
        idx[3] = (buf3 & 0x3F);                       //buf3の6bit
        
        //結果へ格納
        for (int j=0; j<4; j++)
        {
            if ((j == 2 && !flg2) || (j == 3 && !flg3))
            {
                [result appendString:@"="];                
            }
            else
            {
                bufchar[0] = base64table[idx[j]];
                [result appendString:[NSString stringWithCString:bufchar encoding:NSASCIIStringEncoding]];
            }
        }        
    }
    
    return result;
}

@end

実行速度は計っていないですが、デコード側でテーブルの検索を行っているので結構遅いのではないかと思います。
とはいえ、自分が使う分には十分に動作しているのでまぁいいや。

UIViewを超簡単にフリップアニメーションさせる方法

ビューをフリップアニメーション(Y軸を中心にくるっと回るアレ)させる方法です。

以前CAAnimationを使って同様の処理を投稿しましたがこんな簡単な方法でイケルみたいです。


UIView* targetView = 対象のビュー;

//UIViewをクルリンパ
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:0.4];
[UIView setAnimationTransition:UIViewAnimationTransitionFlipFromRight forView:targetView cache:YES];

[UIView commitAnimations];

こりゃラクチン

CIFilterを使って画像の輝度を調整する

イメージの明るさをCIFilterを使って調整するサンプルです。


//brightness -1.0〜1.0
-(UIImage*)adjustImage:(UIImage*)image brightness:(float)brightness
{
    brightness = MAX(brightness, -1.0);
    brightness = MIN(brightness, 1.0);

    // UIImageをCIImageに変換

    CIImage *ciImage = [[[CIImage alloc] initWithImage:image]autorelease];
    
    // フィルタの作成
    CIFilter *ciFilter = [CIFilter filterWithName:@"CIColorControls"
                          keysAndValues:kCIInputImageKey, ciImage,
                            @"inputBrightness", [NSNumber numberWithFloat:brightness]
                         ,nil];
    // 結果画像の取り出し
    CIImage* filterdImage = [ciFilter outputImage];


    // CIImageからUIImageに変換
    CIContext *ciContext = [CIContext contextWithOptions:nil];    
    CGImageRef imgRef = [ciContext createCGImage:filterdImage fromRect:[filterdImage extent]];    
    UIImage* resultImage = [UIImage imageWithCGImage:imgRef scale:1.0f orientation:UIImageOrientationUp];
    CGImageRelease(imgRef);

    return resultImage;    
}

以上

最後のウィンドウが閉じた時にアプリケーションを終了させる

MAC特有の動きで、ウィンドウを全て閉じてもアプリは残っている事がありますよね。
大体のアプリはそうなってるような。

でも、単機能でさくっと表示してさくっと終了させたいアプリの場合、この機能はイラっとしますよね。
なので、自作のアプリくらいは行儀よく終了するようにしましょう。

やり方は簡単で、AppDelegateに下記を追加するだけ。


/**
 * 最後のウィンドウが閉じた時にアプリケーションを終了させる
 */
-(BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)sender
{
    return YES;
}

あー、スッキリ

FMDBを組み込むとエラーが発生している人へズバリ!

sqliteを便利に使える神ライブラリFMDBですが、これをXCODEのプロジェクトに加えてビルドするとなぜかエラーが発生している人いませんか?
ちゃんとlibsqlite3.0.dylibもリンクしたのに。。。

そんなあなた、ずばり!
fmdb.m を加えてしまっているでしょう!

fmdb.mにもmain関数が存在しているので、ビルド時に起こられます。
このファイルはプロジェクトから除外して下さい。

え、違いました!?!?

EditableをNOにしたNSTextViewに文字列を追加する方法

EditableがTrueであればinsertTextとかでプログラムから文字列を追記できるんですけど、ログの表示とかでreadonlyにしようとEditableをFalseにしたとたん、insertTextしても無視されます。

ドキュメントを読むと、どうやらinsertTextはユーザからの入力と同じ扱いになるようで、そのためEditableな状態じゃないとダメみたいです。

こんなときは、NSTextViewのデータを保持しているNSTextStrageを操作することで対応できます。


//NSTextView* textView;

-(void)appendText:(NSString*)text;
{
  //描画を一時的に止める
  [textView.textStorage beginEditing];

  //テキストを追加
  NSAttributedString* atrstr = [[[NSAttributedString alloc] initWithString:text] autorelease];
  [textView.textStorage appendAttributedString: atrstr];

  //描画再開
  [textView.textStorage endEditing];

  //最終行へスクロール
  [textView autoscroll:nil];
}