[iOS] カスタムプログレスバー公開

2011年3月30日水曜日 | Published in | 3 コメント

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

カスタムプログレスバーのソースコードを GitHub にて公開しました。
dev5tec/FBProgressView - GitHub

iOS版 Twitterアプリや iDisk などで使われているあの表現↓



インストール


GitHubからプロジェクトをダウンロードし、その中から FBProgressView.h と FBProgressView.m を自分のプロジェクトへコピーして追加する。


使い方


FBProgressView のインスタンスを作成し progress プロパティへ 0.0〜1.0 の値を渡すだけ。以下はコードでインスタンスを生成する例。
FBProgressView* progressView =
  [[FBProgressView alloc] initWithFrame:CGRectMake(100, 100, 200, 40)];
[self.view addSubView:progressView];
Interface Builder で Custom View を貼りつけ、そのクラスに FBProgressView を指定しても良い(付属のサンプルではこの方法を使っている)。

後は進捗率(0.0〜1.0)を progress プロパティへ渡すだけで良い。
progreeView.progress = 0.5;  // 50%


カスタマイズ


その他、カスタマイズ用にいくつかプロパティを用意している。
progressViewStyle    // default: FBProgressViewStyleDefault
lineWidth            // default: 5.0
hidesUntilStart      // default: YES

typedef enum {
    FBProgressViewStyleDefault = 0,
    FBProgressViewStyleGray,
    FBProgressViewStyleWhite
} FBProgressViewStyle;


ソース解説


UIBezierPath を使ってベタに描画してる。外枠のパス作成はこんな感じ。
- (void)_createOutlinePath
{
    [outlinePath_ release];
    outlinePath_ = [[UIBezierPath bezierPath] retain];

    CGSize size = self.bounds.size;
    CGFloat unit = size.height/2.0 - self.lineWidth;

    CGPoint c1 = CGPointMake(unit+self.lineWidth, unit+self.lineWidth);
    [outlinePath_ addArcWithCenter:c1
                            radius:unit
                        startAngle:3*M_PI/2 endAngle:M_PI/2
                         clockwise:NO];
    
    [outlinePath_ addLineToPoint:CGPointMake(size.width - c1.x,
                                            size.height - self.lineWidth)];
    CGPoint c2 = CGPointMake(size.width - unit - self.lineWidth,
                             unit+self.lineWidth);
    [outlinePath_ addArcWithCenter:c2
                           radius:unit
                       startAngle:M_PI/2 endAngle:-M_PI/2
                        clockwise:NO];
    
    [outlinePath_ addLineToPoint:CGPointMake(c1.x, self.lineWidth)];
    
    [outlinePath_ setLineWidth:self.lineWidth];
   
}
中のバーはこう
- (void)_drawProgressBar
{
    CGFloat margin = self.lineWidth + MARGIN_UNIT;
    CGSize size = self.bounds.size;
    size.width -= margin*2;
    size.height -= margin*2;
    CGFloat unit = size.height/2.0;
    
    CGFloat progressWidth = size.width * self.progress;
    if (progressWidth < unit*2) {
        progressWidth = unit*2;
    }
    
    CGRect barRect = CGRectMake(margin,
                                margin,
                                progressWidth,
                                size.height);
    UIBezierPath* path = [UIBezierPath bezierPathWithRoundedRect:barRect
                                                    cornerRadius:unit];
    [path fill];
iOS 4.0 から Mac OS X と同じように Bezier Path が使えるようになったのはかなり便利。

ライセンス


MIT ライセンスです。商用・非商用を問わず自由にご利用下さい。連絡も不要です(でもくれるとうれしい)。

- - - -
最近は2種類のアプリを開発中(iPad向け、iPhone向け)。4月には公開できそう。

[iOS][Mac] ISO 8601 相当の日付文字列を NSDateFormatter で変換する

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

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

ISO 8601日付文字列を NSDateに変換する


例えば
2010-12-01T21:35:43+0900
という文字列があった場合
NSDateFormatter* formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ssZ"];
NSDate* date = [formatter dateFromString:string];
[formatter release];
で NSDate へ変換できる。

ISO 8601 には多くのバリエーションがあって上記書式は Facebook API などで使われている ISO 8601 書式の一つ。

 参考:ISO 8601 - Wikipedia

これはどうか?時差が +09:00 となっている(先ほどは 0900)。
2010-12-01T21:35:43+09:00
上記書式は W3C が定義している書式。

 参考:日付の表記に関するノート

ISO 8601 のサブセットとして位置づけられている。

結果は×。

先ほどの setDateFormat:@"yyyy-MM-dd'T'HH:mm:ssZ" では変換できない。ZZ, ZZZ, ZZZZ, z も駄目。'+09:00' には対応していない。

この場合は +09:00 から : を無くして +0900 として NSDateFormatter へ渡してやる(ベタなやり方だが)。
string = [string stringByReplacingOccurrencesOfString:@":"
                                                          withString:@""
                                                             options:0
                                                               range:NSMakeRange(22, 1)];

NSDateFormatter の書式


NSDateFormatter で扱える書式は公式リファレンスには見つからず、独自に調査している人の情報が見つかった。

Stepcase » Blog Archive » Format String for the iPhone NSDateFormatter

これは参考になる。


ISO 8601 変換ライブラリ


ISO 8601 文字列から NSDate に変換するライブラリも存在する。

ISO 8601 parser and unparser

このライブラリは ISO 8601 の多くのバリエーションをサポートしているようだ。


参考情報


PHPにおける日付と時刻の混乱(1/4):CodeZine
PHPの情報だがさまざまな書式例が載っていて参考になる。

[iOS] ディレクトリ配下の総ファイルサイズを計算する 〜 BSDのftsを使う

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

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

ファイルキャッシュライブラリを作っていてキャッシュディレクトリ配下の総ファイルサイズを計算する必要が出てきた。今回はファイルサイズ計算にBSD由来のライブラリ fts を使ってみた。


fts


Apple のリファレンスを眺めているとディレクトリ走査でパフォーマンスを考慮するなら fts を使うのがいいと出ていた。
File-System Performance Guidelines: Iterating Directory Contents
上記内の Traversing Directories in BSD に記述あり。

調べてみたところ fts というのは BSD系OSで使われているディレクトリ走査用のライブラリのようだ。
fts_open - Linuxの手で行なうページ

(特徴)
・サブディレクトリを含むファイルとディレクトリの一覧を取得できる
・パス名の他、stats構造体を取得できる
・ソートが可能(比較関数を渡すことができる)

サブディレクトリも再帰的にリストアップできるので従来からある readdir系のライブラリよりも使い勝手が良い。また fts ではパス名だけでなく stats構造体も取得できるのでファイルサイズを計算する場合にわざわざ stats関数を呼び出す必要がない。

以下、1ファイル/ディレクトリ毎に取得できる構造体 FTSENT の内容:
typedef struct _ftsent {
             u_short fts_info;               /* flags for FTSENT structure */
             char *fts_accpath;              /* access path */
             char *fts_path;                 /* root path */
             u_short fts_pathlen;            /* strlen(fts_path) */
             char *fts_name;                 /* file name */
             u_short fts_namelen;            /* strlen(fts_name) */
             short fts_level;                /* depth (-1 to N) */
             int fts_errno;                  /* file errno */
             long fts_number;                /* local numeric value */
             void *fts_pointer;              /* local address value */
             struct ftsent *fts_parent;      /* parent directory */
             struct ftsent *fts_link;        /* next file structure */
             struct ftsent *fts_cycle;       /* cycle structure */
             struct stat *fts_statp;         /* stat(2) information */
     } FTSENT;
※ man fts(3) より転載



実装例


こんな感じ。
#include <sys/types.h>
#include <sys/stat.h>
#include <fts.h>

- (IBAction)fts
{
    int size = 0;
    FTS* fts;
    FTSENT *entry;
    char* paths[] = {
        [[self path] cStringUsingEncoding:NSUTF8StringEncoding], NULL
    };
    fts = fts_open(paths, 0, NULL);
    while ((entry = fts_read(fts))) {
        if (entry->fts_info & FTS_DP || entry->fts_level == 0) {
            // ignore post-order
            continue;
        }
        if (entry->fts_info & FTS_F) {
            size += entry->fts_statp->st_size;
        }
    }
    fts_close(fts);
}
再帰コードが要らないのでスッキリして簡単。fts_openで渡すパスは配列で渡す必要がある。また通常だと取得リスト内にディレクトリが2回現れる(pre-order と post-order)ので重複させたくない場合は上記コードのように2回目に出現するタイミング(post-order)を無視するようにしている(entry->fts_info & FTS_DP)。また fts_open の引数で渡したディレクトリ自身がリストに含まれないようにトップレベルのディレクトリ(etnry->fts_level==0 のケース)も無視している。ファイルサイズは FTSENT構造体の持つ fts_statp 経由で簡単に取得できる。

fts_open の第3引数にはソート用の比較関数を渡すことができる。
FTS *fts_open(char * const *path_argv, int options,
                     int (*compar)(const FTSENT **, const FTSENT **));
例えば名前順に一覧を取得したい場合は
fts = fts_open(paths, 0, cmpare);
として compare関数を用意してやる。
int cmpare(const FTSENT **a, const FTSENT **b)
{
    return (strcasecmp((*a)->fts_name, (*b)->fts_name));
}


他の方法との比較


ファイルサイズ計算の方法は fts 以外には readdir と stats の組み合わせの他、NSFileManager を使う方法がある。これらの方法と fts を使う場合の計3つの方法についてかかる時間の比較をやってみた。

条件


下記のテストデータ(ディレクトリ、ファイル)を作成し、rootディレクトリ配下のファイルサイズを計算するのにかかった時間を実機で計測した。
条件:ディレクトリ数 10x10x10、ファイル数 10x10x10、1ファイルのサイズ 1024バイト
実機: iPhone 3GS / iOS 4.2.1
(root)
  |--00
  |   |--00
  |   |   |--00
  |   |   |   |--file-00
  |   |   |   |--file-01
  |   |   |   :
  |   |   |   |--file-09
  |   |   |--01
  |   |   :
  |   |
  |   |--01
  :   :

ソース


readdir を使った場合のソース。
int countStdlib;
int sizeStdlib;
void countdir(const char* path) {
    DIR* dir = opendir(path);
    struct dirent* ent;
    struct stat buf;
    
    char newPath[4096];
    if (dir) {
        for(;;) {
            if ((ent = readdir(dir)) == NULL) {
                break;
            }
            strcpy(newPath, path);
            strcat(newPath, "/");
            strcat(newPath, ent->d_name);

            if (ent->d_type == DT_DIR) {
                if (strcmp(ent->d_name, ".") && strcmp(ent->d_name, "..")) {
                    countdir(newPath);
                }
            } else {
                // file
                stat(newPath, &buf);
                sizeStdlib += buf.st_size;
                countStdlib++;
            }
        }
    }
    closedir(dir);
}

- (IBAction)stdlib
{
    countStdlib = 0;
    sizeStdlib = 0;
    countdir([[self path] cStringUsingEncoding:NSUTF8StringEncoding]);
}

NSFIleManager を使った場合のソース。
- (IBAction)cocoa
{
    int count = 0;
    int size = 0;
    NSFileManager* fileManager = [NSFileManager defaultManager];
    NSError* error = nil;
    NSString* path = [self path];

    for (NSString* filename in [fileManager enumeratorAtPath:[self path]]) { 
        NSDictionary* attributes = [fileManager
            attributesOfItemAtPath:[path stringByAppendingPathComponent:filename]
                                    error:&error];
        if ([[attributes objectForKey:NSFileType] isEqualToString:NSFileTypeRegular]) {
            count++;
            size += [[attributes objectForKey:NSFileSize] intValue];
        }
    }
}

結果


fts            6.6秒
readdir        8.5秒
NSFileManager 40.6秒
fts が一番速いが readdirとの差はそれほど大きくない。readdir の遅れ 2秒近くは関数の再帰処理と stats関数で呼び出し回数が増えている分のオーバヘッドと思われる。NSFileManager はオブジェクトの生成によるオーバヘッドが大きく予想通り他の2つと比べてかなり遅い。

ファイルサイズの取得処理を除いて単純な一覧取得だけに絞って実行してみると次のようになる。
fts            1.3秒 (FTS_NOSTAT オプション)
readdir        1.1秒
NSFileManager 11.0秒
全体的にかなり時間が短縮できることから stats実行の処理が比較的重いことが分かる。

考察


fts は readdir とほぼ同じパフォーマンスを持ちながら、readdirよりも簡潔にコードを記述できる。また標準で stats情報の取得やソート処理が行えるなど高機能である。fts が使える処理系(iOS含む)であればこちらを利用する方が何かと便利だろう。NSFileManager は他の2つに比べると速度性能でかなり劣る。ただ cocoaで記述できるメリットもあるので対象データ量が少ない場合であればこれを使うのも悪くはない。

なお性能が同程度となるともう一つ気になるのはメモリのフットプリント。Instrumentsを使いシミュレータ上でのメモリの変化を調べてみたところ次のようになった。

(1) fts
770KB→934KB (+164KB)

(2) readdir
762KB→934KB(+172KB)

(3) NSFileManager
1MB → 27MB(+26MB)

fts と readdir はほとんど変わらず、両方共にメモリの利用量は多く無い。一方 NSFileManager の場合は NSString の生成コストが大きくかなりのメモリを消費している。

おまけ:fts ソート時の性能


先のケースではソートを行っていなかった。パス名でのソート処理を入れた時の時間を計測してみた。
:
fts = fts_open(paths, 0, cmpare);
  :
}
int cmpare(const FTSENT **a, const FTSENT **b)
{
    return (-strcasecmp((*a)->fts_name, (*b)->fts_name));  // パス名降順
}

結果は 6.6秒とソート未指定時と変わらない。念のためパス内容を NSLog で出して確認してみたがちゃんとパス名降順で一覧が取得できてる。これは速いな。



サンプル


GitHub からどうぞ。
ftsSample at 2011-03-07 from xcatsan/iOS-Sample-Code - GitHub

実行するとボタンの載った画面が現れる。
"setup directories" をタップするとテスト用のディレクトリ/ファイルが生成される。これは数十秒かかるがインストール後の最初の一回だけの実行で良い。

 BSD fts .... fts によるファイルサイズ計算
 stdlib ... readdir を使ったファイルサイズ計算
 Cocoa ... NSFileManager を使ったファイルサイズ計算


参考情報


fts_open

ftsでファイル階層を取得する。: Xo式 実験室(labo.xo-ox.net)

FreeBSDのlsを読む fts(3)でlsを作ってみよう~。 - ボクノス

Recursive read directory - verzeichnis rekursiv auslesen - dirent - GIDForums

Manpage of READDIR

dirent

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