ラベル ライブラリ開発 の投稿を表示しています。 すべての投稿を表示
ラベル ライブラリ開発 の投稿を表示しています。 すべての投稿を表示

バッジ描画ライブラリを公開

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

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

バッジ描画ライブラリを GitHub にて公開しました。

lakesoft/LKbadgeView - GitHub

バッジって何かというとメールの未読件数などで使われているアレ。

数字だけでなく文字列も表示できる。また背景色や文字色を変えたり、アウトライン(輪郭線)を付けられるようにした。


インストール


GitHubからプロジェクトをダウンロードし、その中から下記のファイルを自分のプロジェクトへコピーして追加する。
LKBadgeView.m
LKBadgeView.h


使い方


UIView のサブクラスなので生成してビューへ貼り付けるだけで使える。
(コード例)
LKBadgeView* badgeView =
    [[[LKBadgeView alloc] initWithFrame:CGRectMake(100, 200, 50, 20)] autorelease];
[self.view addSubview:badgeView];
badgeView.text = @"23";
XIB に配置してそれを使ってもいい。サンプルアプリではこの方法を使っている。
@property (nonatomic, retain) IBOutlet LKBadgeView* badgeView;
 :
self.badgeView.text = @"23";
この場合はインスペクタのクラス指定を LKBadgeView にすることを忘れずに。


カスタマイズ


色の変更の他、アウトラインの描画や水平位置の調整などが行える。

水平位置

LKBadgeView のビューとしての大きさとは別に角丸のバッジがその中に配置される。この時の水平位置を指定することができる。
@property (nonatomic, assign) LKBadgeViewHorizontalAlignment horizontalAlignment;
左寄せ、中央揃え、右寄せの3種類を指定することができる。デフォルトは中央揃え(LKBadgeViewHorizontalAlignmentCenter)。


 ※上の図の灰色の背景色は説明の都合上、意図的に付けてある。

最小サイズ

横幅の最小サイズは2種類設定することができる。
@property (nonatomic, assign) LKBadgeViewWidthMode widthMode;

・LKBadgeViewWidthModeSmall にした場合は1文字だと丸になる。
・LKBadgeViewWidthModeStandard の場合は2文字までは同じ横幅となる。3文字以上はそれより拡がる。

デフォルトは LKBadgeViewWidthModeStandard。

文字色とバッジの背景色を指定できる(デフォルトでは文字色=白、背景色=灰色)。
@property (nonatomic, retain) UIColor* textColor;
@property (nonatomic, retain) UIColor* badgeColor;

アウトライン

バッジの周りに輪郭線を描くことができる。こんな感じ。

アウトラインを描くには outline プロパティを YESにする。NOだと描画しない。色と線の太さを指定することができる(デフォルトは色=灰色、太さ=2.0)。
@property (nonatomic, retain) UIColor* outlineColor;
@property (nonatomic, assign) CGFloat outlineWidth;
@property (nonatomic, assign) BOOL outline;
もちろんバッジの背景色と組み合わせることもできる。


その他


切り捨て表示

ビューのサイズに対して文字数が多い場合は切り捨て表示( ...表示)になる。

※上の図の灰色の背景色は説明の都合上、意図的に付けてある。

バッジの高さ

バッジの高さは 20ピクセル固定で変更はできない。この値は定数として参照出来る他、実行時に取得できるようクラスメソッドを用意してある。
#define LK_BADGE_VIEW_STANDARD_HEIGHT       20.0
 :
+ (CGFloat)badgeHeight;
LKBadgeView の高さはこの値と同じか大きくする必要がある。大きい場合の縦位置は縦中寄せとなる。通常ビュー自体の高さはこの値と同じにしておくと良い。

サンプル


付属のサンプルを実行するとカスタマイズされた表示をひと通り確認できる。

右上のスイッチをONにするとアウトライン描画に切り替わる。



ソース解説


バッジの表示は UIBezierPath を使って drawRect: で描画している。文字は UILabel を使わず直接描画した。詳細はソースコードを参照のこと。


ライセンス


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


数字キーボードビュー(ライブラリ)を公開

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

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

数字キーボードもどきを作ってみました。

キートップをタップすると標準キーボードのようにポップアップします。
CoreGraphicsの関数を使って全部自力で描いてます。

lakesoft/LKNumberPad - GitHub



インストール


1. GitHubからプロジェクトをダウンロードした後 Xcode 4 で開く。
2. その中から LKNumberPadView.h と LKNumberPadView.m を自分のプロジェクトへコピーして追加する。


使い方


Xib を使う場合は次の通り。
1. 表示したいビューを開き、UIView を貼りつけた後、クラスを LKNumberPadView に変更する。
推奨サイズは 横 320 x 縦 54。
2. 背景色(Background)プロパティを設定する。
これだけで表示できるようになる。

次にボタンがタップされた時の動作を記述する。
3. アウトレット delegate を設定する。※例では File's Owner に設定している
4. デリゲート LKNumberPadViewDelegate のメソッドを実装する
@protocol LKNumberPadViewDelegate 

@optional
- (void)didTouchNumberPadView:(LKNumberPadView*)numberPadView touchedString:(NSString*)string;
- (void)didTouchNumberPadView:(LKNumberPadView*)numberPadView withSequentialString:(NSString*)string;

@end

- didTouchNumberPadView:touchedString:
タップされる毎に呼び出され、押された数字が文字列として1文字だけ渡される。

- didTouchNumberPadView:withSequentialString:
タップされる毎に呼び出され、押された数字が文字列として渡される。一定期間に連続してボタンがタップされた場合は連続した数字が文字列として渡される。例えば1を押した直後にすぐに4を押すと @"14" が渡ってくる。


カスタマイズ


背景色などをプロパティで設定することができる。

BOOL startWithZero
YES だと0始まりになる(一番左が0で、一番右が9)。デフォルトは NOで標準のキーボードと同じ1始まり(一番左が1で、一番右が0)。

UIColor* keyboardColor
キーボードのキートップの背景色。
デフォルトは [UIColor colorWithWhite:0.95 alpha:1.0]。

UIColor* textColor
キーボードのキートップの文字色。
デフォルトは [UIColor blackColor]。

UIColor* disabledKeyboardColor
ディゼーブル時のキーボードのキートップの背景色。
デフォルトは [UIColor colorWithWhite:0.85 alpha:1.0]。

UIColor* disabledTextColor
ディゼーブル時のキーボードのキートップの文字色
デフォルトは [UIColor lightGrayColor]。

NSTimeInterval sequenceInterval
キー入力間隔(秒)。次のキーがこの間隔内にタップされた場合、連続したキー入力とみなす。
デフォルトは 0.75秒。

NSSet* enabledSet
キートップの有効・無効を指定する。有効にしたい数字(注意:インデックスではない)を NSNumber の値として入れておく。nil の場合は全て有効になる。
view.enabledSet = [NSSet setWithObjects:
                                     [NSNumber numberWithInt:1],
                                     [NSNumber numberWithInt:2], nil];


ソース解説


キートップは UIBezierPath などを使い描画している。
- (void)- (void)drawRect:(CGRect)rect
{
    CGContextRef context = UIGraphicsGetCurrentContext();   
    CGRect frame;
    CGColorRef shadowColorRef;
    UIColor* drawColor;

    // (1) draw keyboard
    frame = CGRectMake(0, 0,
                       self.bounds.size.width / LKNUMBERPADVIEW_KEYBOARD_NUM,
                       self.bounds.size.height);
    
   :
}

キーが押された時のポップアップ表示には CALayer を使っている。この描画を行う為に CALayer のサブクラス LKNumberPadLayer を LKNumberPadView.m 内で定義している。
enum {
    LKNumberPadViewImageLeft = 0,
    LKNumberPadViewImageInner,
    LKNumberPadViewImageRight,
    LKNumberPadViewImageMax
};

@interface LKNumberPadLayer : CALayer {
    CGImageRef keytopImages_[LKNumberPadViewImageMax];
}
@property (nonatomic, copy) NSString* character;
@property (nonatomic, assign) int imageKind;

- (CGImageRef)createKeytopImageWithKind:(int)kind;

@end
ポップアップは左はじ、右はじ、それ以外の3種類の表示が必要になる。あらかじめ3種類の画像を描画しておき、配列 keytopImages_[] に取っておく。

ポップアップの描画は CoreImage関数の CGPathAddArc() や CGPathAddCurveToPoint() を使ってベタに書いている。
- (CGImageRef)createKeytopImageWithKind:(int)kind
{
    CGMutablePathRef path = CGPathCreateMutable();
   
    CGPoint p = CGPointMake(LKNUMBERPADVIEW_PADDING_X, LKNUMBERPADVIEW_PADDING_Y);
    CGPoint p1 = CGPointZero;
    CGPoint p2 = CGPointZero;
   
    p.x += LKNUMBERPADVIEW_PAN_UPPER_RADIUS;
    CGPathMoveToPoint(path, NULL, p.x, p.y);
   
    p.x += LKNUMBERPADVIEW_PAN_UPPDER_WIDTH;
    CGPathAddLineToPoint(path, NULL, p.x, p.y);
   
    p.y += LKNUMBERPADVIEW_PAN_UPPER_RADIUS;
    CGPathAddArc(path, NULL,
                 p.x, p.y,
                 LKNUMBERPADVIEW_PAN_UPPER_RADIUS,
                 3.0*M_PI/2.0,
                 4.0*M_PI/2.0,
                 false);
          :

頂点のイメージ



キーがタップされたら適切なポップアップ画像を選び CALayer 上に表示する。
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    NSUInteger index = [self _indexWithEvent:event];
    [CATransaction begin];
    [CATransaction setValue:(id)kCFBooleanTrue
                     forKey:kCATransactionDisableActions];
    [self _updateWithIndex:index];
    [CATransaction commit];
}

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
    NSUInteger index = [self _indexWithEvent:event];
    if (index != self.touchedIndex) {
        [self _updateWithIndex:index];
    }
}

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
    self.touchedIndex = -1;
    self.numberPadLayer.opacity = 0.0;
    [self setNeedsDisplay];
}
- (void)_updateWithIndex:(NSUInteger)index
{
    self.touchedIndex = index;
    NSUInteger number = [self _numberWithIndex:index];
    self.touchedString = [NSString stringWithFormat:@"%d", number];
          :    
    [self setNeedsDisplay];
    [self.numberPadLayer setNeedsDisplay];    // ポップアップ表示
          :
}


ポップアップの描画は -[CALayer drawInContext:] 内で描画する。あらかじめ描いておいた画像を表示し、その上に数字を描画する。
- (void)drawInContext:(CGContextRef)context
{
    CGColorRef shadowColorRef = [[UIColor colorWithWhite:0.1 alpha:1.0] CGColor];
    CGContextSetShadowWithColor(context,
                                CGSizeMake(LKNUMBERPADVIEW_PAN_SHADOW_OFFSET_X,
                                           LKNUMBERPADVIEW_PAN_SHADOW_OFFSET_Y),
                                LKNUMBERPADVIEW_PAN_SHADOW_BLUR,
                                shadowColorRef
                                );

    CGImageRef imageRef = keytopImages_[self.imageKind];
    CGRect imageFrame = CGRectMake(0, 0,
                                   CGImageGetWidth(imageRef),
                                   CGImageGetHeight(imageRef));
    CGContextDrawImage(context, imageFrame, imageRef);

    // draw text
    CGContextSelectFont(context, "Helvetica Bold", 44, kCGEncodingMacRoman);
    CGContextSetTextDrawingMode(context, kCGTextFill);
    CGContextSetFillColorWithColor(context, [[UIColor blackColor] CGColor]);
    CGContextSetTextMatrix(context, CGAffineTransformMakeScale(1.0, -1.0));
   
    shadowColorRef = [[UIColor whiteColor] CGColor];
    CGContextSetShadowWithColor(context,
                                CGSizeMake(0.0,
                                           1.0),
                                1.0,
                                shadowColorRef
                                );
 
    CGContextShowTextAtPoint(context, 28, 55,
                             [self.character UTF8String],
                             [self.character length]);
   
}



ライセンス


MIT ライセンスです。商用・非商用を問わず利用可能。カスタマイズしての再配布も自由。連絡も不要(でもくれるとうれしい)。


関連情報


なし



備考


・アプリ審査の実績なし。もし使って通った方がいたら是非教えて下さい。
・ポップアップ表示の下の影の部分だけは本物同様にできなかった(本物は影のblur値が小さい)。これをやるとなるとそれなりに手間がかかるので、ここだけは手を抜いた。押した時にここは見えないし。




[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.


関連情報


[iOS] ネットワーク接続状況取得ライブラリを公開

2011年5月25日水曜日 | Published in | 2 コメント

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

ネットワークの接続状況を取得できるライブラリを公開しました。
dev5tec/FBNetworkReachability - GitHub


特徴


現在の接続状況(WiFi, 3Gなど)を取得することができる。

以下、使い方の例
FBNetworkReachability* network =
    [FBNetworkReachability networkReachabilityWithHostname:@"http://xcatsan.com/"];

FBNetworkReachabilityConnectionMode mode = network.connectionMode;
switch (mode) {
    case FBNetworkReachableNon:
        // 未接続
        break;

    case FBNetworkReachableWiFi:
        // WiFi接続
        break;

    case FBNetworkReachableWWAN:
        // 3G接続
        break;
}
if(network.reachable) {
    // 接続時処理
}

NSLog(@"ip: %@", network.ipaddress);  // IPアドレス

接続状況が変化した時には FBNetworkReachabilityDidChangeNotification をポストするので、この変化をトリガーにして非同期的な処理を書くこともできる。
[[NSNotificationCenter defaultCenter]
             addObserver:self
             selector:@selector(didChangeNetworkReachability:)
             name:FBNetworkReachabilityDidChangeNotification
             object:nil];

- (void)didChangeNetworkReachability:(NSNotification*)notification
{
    FBNetworkReachabiity* network = [notification object];
        :
}

その他の特徴は以下のとおり。

  • バックグラウンド動作は行わない。
  • バックグラウンドからフォアグラウンドに変わる間に変化があった場合は最新のイベントがポストされる。
  • 内部的にはURL毎にインスタンスが共有される(例えば URLを "http://xcatsan.com" として3回インスタンスを取得した場合、いずれも同じインスタンスが返る)。
  • 到達不可能なURLは使えない。URLは名前解決できてIP的に到達可能である必要がある。
  • スレッドセーフ



インストール


ソースコードの追加およびフレームワークの追加が必要。

ソースコード追加

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

フレームワーク追加

SystemConfiguration.framework を追加する。



ソース解説


解説は過去のブログが詳しいのでそちらを参照のこと。

Cocoaの日々: ネットワーク接続状況を知る

Cocoaの日々: ネットワーク接続状況を知る[2] SCNetworkReachabilityGetFlags はブロックする

以前ブログで紹介したバージョンではインスタンス作成直後に connectionModeを取得すると必ず未初期化状態(FBNetworkReachableUninitialization)となっていた。これだと実用上はいろいろと困るので、未初期化状態の場合は「0.5秒待つ」処理を加えた。こんな感じ。

- (FBNetworkReachabilityConnectionMode)connectionMode
{
    FBNetworkReachabilityConnectionMode mode;
    @synchronized (self) {
        mode = connectionMode_;
    }
    if (mode == FBNetworkReachableUninitialization) {
        [[NSRunLoop currentRunLoop]
         runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.5]];
    }
    return connectionMode_;
}
SCNetworkReachability における非同期処理(コールバック)はランループを使っている。接続状況変化のイベントはランループを介して SCNetworkRechability へ届くので、sleep でスレッドを止めるのではなく、runUntileDate: を使ってその場でランループの処理だけを回してやる。大抵はこの 0.5秒間の間にイベントが届き、コールバックが呼び出され、connectionMode が FBNetworkReachableUninitialization 以外になる。これによってインスタンス作成直後であっても有効な connectionMode が得られる。

(2011-11-14追記)
Cocoaの日々: [iOS] SCNetworkReachabilityGetFlags のブロックの件
上記で記事で解説したように遅延は解消されたので最新版では「0.5秒待つ」処理は入っていない。


動作確認用アプリケーション


GitHub のプロジェクトには動作確認用のアプリケーションが付属している。
接続方式に変化があるとテーブルが更新される。



カスタマイズ


特になし。強いて言えばソース解説で説明した初回の待ち時間(0.5秒)。これはソースコードを直接書き換える。



ライセンス


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



関連情報


なし

[iOS] 簡易 Keychain Services ラッパー LKKeychain を公開

2011年5月14日土曜日 | Published in | 0 コメント

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

Keychain Serivce の簡易ラッパー LKKeychain を公開しました。
lakesoft/LKKeychain - GitHub

パスワードの追加、更新、削除が行えます。追加はこんな感じ。
[LKKeychain updatePassowrd:@"hdsI3823Khdf"
                    account:@"hashiguchi@lakesoft.jp"
                    service:@"SampleService"];


インストール


GitHubからプロジェクトをダウンロードした後 Xcode で開く。

その中から LKKeychain.h と LKKeychain.m を自分のプロジェクトへコピーして追加する。


使い方


用意されているクラスメソッドを呼び出す。

追加・更新

[LKKeychain updatePassowrd:@"hdsI3823Khdf"
                   account:@"hashiguchi@lakesoft.jp"
                   service:@"SampleService"];
該当するアカウント情報がない場合は新規登録、既に存在する場合はパスワードが更新される。なお Keychain Services においてはサービス名(service)とアカウント(account)の組が検索時のキーとなる(2つが同じなら同じ情報とみなされる)。

削除

[LKKeychain deletePasswordWithAccount:@"hashiguchi@lakesoft.jp"
                              service:@"SampleService"];

デバッグ

NSArray* accounts = [LKKeychain getItemsWithServices:@"SampleService"];
指定したサービスに登録されているすべてのパスワードを配列で戻す。
(
{
    acct = "hashiguchi@lakesoft.jp";
    agrp = test;
    cdat = "0023-05-13 14:40:33 +0000";
    mdat = "0023-05-13 14:40:33 +0000";
    pdmn = ak;
    svce = SampleService;
    "v_Data" = <70617373 776f7264>;
},
:


カスタマイズ


なし。


サンプル


Github には簡単なサンプルアプリケーションが同梱されている。実行すると登録や削除、内容の確認が行える。


ソース解説


特別なことはやっていない。メソッドや定数の意味等は以前解説したのでそちらを参照のこと。
Cocoaの日々: [iOS] Keychain Services とは


ライセンス


MIT ライセンスです。商用・非商用を問わず利用可能。カスタマイズしての再配布も自由。連絡も不要(でもくれるとうれしい)。


関連情報


Cocoaの日々: [iOS] Keychain Services とは

Cocoaの日々: [iOS] 複数アプリケーション間でのデータ共有 〜 Keychain Services を使った第三の方法

(旧) Cocoaの日々: Keychain Services 調査 (1) 情報収集

[iOS] ユーザ名/パスワードの入力ができるカスタム UIAlertView ライブラリを公開

2011年5月11日水曜日 | Published in | 0 コメント

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

ユーザ名とパスワードの入力を促すカスタム UIAlertView を公開しました。
lakesoft/LKAccountPanel - GitHub



呼び出しコードはこんな感じ。
[LKAccountPanel showWithTitle:@"Test"
                       completion:^(BOOL result, NSString* username, NSString* password) {
                           NSLog(@"result: %d\nusername: %@\npassword: %@",
                                 result, username, password);
                       }];
結果を Blocks で処理できるようにしてみた。

[2011-05-17 追記] 非同期用のメソッドを追加しました。
BOOL result = [LKAccountPanel showWithTitle:@"Test2"
                                       username:&username
                                       password:&password];
    NSLog(@"done2");
    NSLog(@"result2: %d\nusername: %@\npassword: %@",
          result, username, password);

参考情報:Cocoaの日々: [iOS] 非同期処理を同期処理に変える



インストール


GitHubからプロジェクトをダウンロードした後 Xcode 4 で開く。

その中から LKAccountPanel.h と LKAccountPanel.m、そして LKAccountPanel.strings を自分のプロジェクトへコピーして追加する。


使い方


クラスメソッド showWithTitle:completion: を呼び出すだけ。
(例)[LKAccountPanel showWithTitle:@"タイトル"
                       completion:^(BOOL result, NSString* username, NSString* password) {
                                   if (result) {
                                       // OKボタンが押された場合の処理
                                   }
                       }];
OKボタンが押された場合は、result == YESとなる。この時入力値が username, password に格納されて渡される。いわゆる必須チェックは行っていないので未入力でもOKボタンが押せる。未入力やキャンセル時には username と password には nil が入る。


カスタマイズ


ボタンの文言や、UITextField のプレースホルダに表示する文字列は LKAccountPanel.strings で定義している。
"OK" = "OK";
"Cancel" = "キャンセル";
"Username" = "ユーザ名";
"Password" = "パスワード";
他の文言に変更したい場合はここを変える。現在は日本語と英語の文字列を用意しているが必要なら他の言語の追加もできる。


ソース解説


定義はこう。
@interface LKAccountPanel :
 NSObject  {

}
@property (nonatomic, retain) UITextField* usernameTextField;
@property (nonatomic, retain) UITextField* passwordTextField;

// API
+ (void)showWithTitle:(NSString*)title completion:(void(^)(
  BOOL result, NSString* username, NSString* password))completion;

@end
UIAlertViewDelegate を実装してこのクラスで UIAlertViewに関する処理を完結させている。

シングルトンパターンを使っていて、showWithTitle:completion: を呼び出すと内部的には1つのインスタンスを使い回し、このインスタンスで実際の処理を行わせる。
+ (void)showWithTitle:(NSString*)title
  completion:(void(^)(BOOL result, NSString* username, NSString* password))completion
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        accountPanel_ = [[LKAccountPanel alloc] init];
    });
    
    [accountPanel_ _showWithTitle:title completion:completion];
}
シングルトンの初期化には dispatch_once を使っている。
[参考] Cocoaの日々: [iOS][Mac] dispatch_once を使ったシングルトン

UIAlertView のカスタマイズと表示は下請けのメソッド _showWithTitle:completion: でやる。
- (void)_showWithTitle:(NSString*)title
  completion:(void(^)(BOOL result, NSString* username, NSString* password))completion;
{
    self.completionBlock = completion;
    self.alertView =
        [[[UIAlertView alloc] initWithTitle:title
                                    message:@"\n\n\n"
                                   delegate:self 
                          cancelButtonTitle:NSLocalizedStringFromTable(
                              @"Cancel", LOCALIZED_STRING_TABLE, nil)
                          otherButtonTitles:NSLocalizedStringFromTable(
                              @"OK", LOCALIZED_STRING_TABLE, nil), nil] autorelease];

    LKAccountPanelBackgroundView* backgroundView =
        [[[LKAccountPanelBackgroundView alloc]
          initWithFrame:CGRectMake(15.0, 47.0, 255.0, 65)] autorelease];
    [self.alertView addSubview:backgroundView];
    
    self.usernameTextField = [[[UITextField alloc] initWithFrame:
                               CGRectMake(20.0, 52.0, 245.0, 30.0)] autorelease];
    self.usernameTextField.placeholder =
         NSLocalizedStringFromTable(@"Username", LOCALIZED_STRING_TABLE, nil);
    self.usernameTextField.keyboardType = UIKeyboardTypeEmailAddress;
    self.usernameTextField.autocapitalizationType = UITextAutocapitalizationTypeNone;
    self.usernameTextField.returnKeyType = UIReturnKeyNext;
    self.usernameTextField.delegate = self;
    [self.alertView addSubview:self.usernameTextField];

    self.passwordTextField = [[[UITextField alloc] initWithFrame:
                               CGRectMake(20.0, 87.0, 245.0, 30.0)] autorelease];
    self.passwordTextField.placeholder =
       NSLocalizedStringFromTable(@"Password", LOCALIZED_STRING_TABLE, nil);
    self.passwordTextField.keyboardType = UIKeyboardTypeASCIICapable;
    self.passwordTextField.autocapitalizationType = UITextAutocapitalizationTypeNone;
    self.passwordTextField.secureTextEntry = YES;
    self.passwordTextField.returnKeyType = UIReturnKeyDone;
    self.passwordTextField.delegate = self;
    [self.alertView addSubview:self.passwordTextField];

    [self.alertView show];
    [self.usernameTextField becomeFirstResponder];
}
・UITextFiled を2つ作って UIAlertView へ貼りつけている。
・場所を確保するために改行を3つ入れている。
・ローカライズ文字列の取得には NSLocalizedStringFromTable() を使用している。
と、あんまりたいしたことはやってない。UIAlertView のインスタンスを保持しておくのはキーボードで "Done"が押された時に閉じたいから。
- (BOOL)textFieldShouldReturn:(UITextField *)textField
{
    if (textField == self.usernameTextField) {
        [self.passwordTextField becomeFirstResponder];
    } else if (textField == self.passwordTextField) {
        [self.alertView dismissWithClickedButtonIndex:1 animated:YES];
        [self alertView:self.alertView clickedButtonAtIndex:1];
    }
    return YES;
}
-[UIAlertView dismissWithClickedButtonIndex:animated:] は何故かデリゲートメソッド alertView:clickedButtonAtIndex: を呼び出してくれなかったので明示的に呼び出すようにした。

なお Blocks もプロパティとして定義することができる。
@property (nonatomic, retain copy)
   void(^completionBlock)(BOOL result, NSString* username, NSString* password);
こうやっておくと retain の手間が省けるし release も楽。

UITextFiled の背景用に専用の UIView を用意している。
@interface LKAccountPanelBackgroundView : UIView {
}
@end
drawRect: で UIBezierPath を使い角丸矩形と横線、そして白い背景を描く。
@implementation LKAccountPanelBackgroundView

- (id)initWithFrame:(CGRect)frame {
    self = [super initWithFrame:frame];
    if (self) {
        self.backgroundColor = [UIColor clearColor];
    }
    return self;
}

- (void)drawRect:(CGRect)rect
{
    UIBezierPath* path = [UIBezierPath bezierPathWithRoundedRect:self.bounds
                                                    cornerRadius:5.0];
    [path moveToPoint:CGPointMake(0, self.bounds.size.height/2.0)];
    [path addLineToPoint:CGPointMake(self.bounds.size.width-1.0,
                                     self.bounds.size.height/2.0)];
    [[UIColor whiteColor] set];
    [path fill];
    [[UIColor lightGrayColor] set];
    [path stroke];
}
@end
UIBezierPath が追加されてほんと楽だ。


ライセンス


MIT ライセンスです。商用・非商用を問わず利用可能。カスタマイズしての再配布も自由。連絡も不要(でもくれるとうれしい)。

[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月には公開できそう。

[Mac][iOS] Static Library (8) フレームワーク検索パス

2010年11月25日木曜日 | Published in | 0 コメント

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

[前回] Cocoaの日々: [Mac][iOS] Static Library (7) カテゴリを使う場合の注意点 "-ObjC" と "-all_load"

利用しているフレームワークのパスなどを変更した場合、次のエラーが出ることがある。

"Frameworks" ではちゃんと目的のフレームワークを指すように設定しているにもかかわらず出る。

これはビルド設定の「フレームワーク検索パス」が違っている場合がある。ターゲットの情報を開き「ビルド」タブの「フレームワーク検索パス」が正しいかどうかを確認する。
この設定は "Frameworks" へ登録した時に自動的に追加されるようだ。ただその後、"Frameworks"の方でパスを変更してもここには反映されない為に冒頭のエラーが出てている。

[Mac][iOS] Static Library (7) カテゴリを使う場合の注意点 "-ObjC" と "-all_load"

2010年11月24日水曜日 | Published in | 0 コメント

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

[前回] Cocoaの日々: [iOS] Static Library (6) OCUnitで単体テスト

カテゴリが定義されている Static Library を使う場合、リンカフラグに "-ObjC" と "all_load" を設定する必要がある。リンカフラグを設定しない場合 "selector not recognized" 例外が発生しクラッシュする。
[49296:207] -[NSCFString extString]: unrecognized selector sent to instance 0x3044
[49296:207] *** Terminating app due to uncaught exception 'NSInvalidArgumentException',
 reason: '-[NSCFString extString]: unrecognized selector sent to instance 0x3044'
*** Call stack at first throw:
このことは下記のQ&Aで解説されている。
この Q&A によればこの問題は UNIXの Static library実装 と Objective-C の動的な性質の違いによって引き起こされるとのこと。この問題を回避する為にリンカフラグに "-ObjC" オプションを追加する。

リンカフラグは、ターゲットの情報を開きビルドタブの「他のリンクフラグ」に設定する。
なお Mac OS X の64bit環境および iOS ではリンカにバグがあり、"-ObjC" フラグを指定してもカテゴリを含む Static Library のロードが働かない。これを回避する為に "-all_load" または "-force_load" フラグを使う。

"-all_load" フラグはリンカに全オブジェクトファイルのロードを指示する。"-force_load" は Xcode 3.2 から導入されたフラグで、オブジェクトファイル毎にロードを指示することができる。このフラグはオブジェクトファイルへのパスを引数に取る。


Duplicate Symbol 問題


[iPhone][Objective-C] staticライブラリにカテゴリを含む際に気をつけること によれば "-all_load" フラグも万能というわけではなく libtool のバグが原因で "Duplicate symbol" エラーを引き起こすケースがあるとのこと。この問題を回避するにはちょっとしたトリックを施すと良いらしい。以下、引用。
具体的には、以下のように関係ないクラスの宣言を.mの先頭の方に付加すればOK。「FIXCATEGORYBUG_NSSTRING_SAMPLE」は他と名前の衝突が無ければ何でもよいです。
  • NSString+Sample.m
@interface FIXCATEGORYBUG_NSSTRING_SAMPLE @end
@implementation FIXCATEGORYBUG_NSSTRING_SAMPLE @end

@implementation NSString (Sample)
 .
 .
 .
@end
この現象は確認できていないので今のところ何ともいえないがエラーが出た時に参考になりそうだ。


参考情報


IPHONE - Objective-C categories in static library - efreedom

use force_load - Three20 | Google グループ

[iOS] Static Library (5) Frameworkを作成する

2010年11月19日金曜日 | Published in | 0 コメント

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

[前回] Cocoaの日々: [iOS] Static Library (4) Universal Static Library

今回は iOS 向けに Framework を作成する。


Frameworkとは?


前回のエントリでまとめたのでそちらをどうぞ。
Cocoaの日々: [Mac][iOS] Frameworkとは?


サマリー


今回は SampleKit という Framework を作成する。

中身はこんな感じ。

Xcodeプロジェクトはこんな感じになる。

ターゲットを2つを用意する。 Frameworkを作成するターゲット(上図の SampleKit.framework ターゲット)を使いビルドを開始すると、最初に SampleKit ターゲットを呼び出して各アーキテクチャ毎のライブラリ(*.a)を作成する。

そしてこれらを lipoコマンドでユニバーサル化した後、Frameworkのディレクトリを構成する。最後に ZIP圧縮して配布できるようにする。


Frameworkで公開したいヘッダファイル(*.h)はHeaders に入れておく。これらがビルド時に Framework/Headers へコピーされる。公開したくないヘッダファイルはここへは入れない(Classesなどへ入れる)。

ビルド時に使用する Framework名称とバージョン番号(ZIPファイル名に使用)は Info.plist ファイル内の値を使う[*1]

[*1] ビルド設定(環境変数)は使わない


Frameworkを作成した後、動作確認の為にサンプルアプリケーションを作る。



Framework 作成手順


1. Xcode プロジェクト作成


まずは Xcode を開き、テンプレートから "Cocoa Touch Static Library" を選択する。名前は "SampleKit" とした。
こんなプロジェクトが生成される。

2. リソース準備


Resources グループを作成し、Info.plist ファイルを作成する。
Info.plist の中身はこんな感じ。

上記はMac OS X 用フレームワーク作成テンプレートのものをコピーして、それにドキュメントで推奨されていた項目を付け足した( Copyright, Get Info stringなど)。Bundle Versions string, short と Bundle name はこの後のシェルスクリプトで参照する。


3. ヘッダ用ディレクトリ準備


公開ヘッダ用のディレクトリを準備する。まず Xcodeで新規にグループを作成し "Headers" と名前をつける。

ただこれは Xcodeプロジェクト上だけで有効なもので自分でディレクトリを作成してひもづける必要がある。"Headers"の情報を開き、パスの「選択」を選ぶ。

ダイアログが開いたら左下の「新規フォルダ」ボタンを押して "Headers" というディレクトリを作成する。
このディレクトリを選択すると情報のパスの欄に "Headers" と表示される。

この Headersディレクトリに入っているヘッダファイル(*.h)を後ほど Frameworkディレクトリへコピーする。公開したくないヘッダファイルがある場合はここへ入れなければ良い。



4. ソースコード準備


動作確認用のクラスのソースコードファイルを Classes 配下へ追加する。

今回 SampleClass.h は公開したい(Frameworkに同梱)ので Headers 配下に配置しておく逆に公開したくない場合は Classesなどへ入れておく。

中身はこんな感じ。
@interface SampleClass : NSObject {

}
- (NSString*)helloString;

@end
@implementation SampleClass

- (NSString*)stringHello
{
 return @"Hello mac!";
}
@end


5. ターゲット準備


Framework作成用に新規ターゲットを作成する。ターゲットの上で右クリックして「追加」→「新規ターゲット」→ "Shell Script Target" を選択する。
ターゲット名を "SampleKit.framework" とする。

ターゲット内の「スクリプトを実行」を開き、ビルド用のシェルスクリプトを記述する。

ビルド用シェルスクリプト
#--------------------------------------------------------------------
echo "[0] Framework: Preparing ..."
#--------------------------------------------------------------------
FRAMEWORK_NAME=$(/usr/libexec/PlistBuddy -c "Print CFBundleName" Info.plist)
BUILD_TARGET_NAME=$FRAMEWORK_NAME
FRAMEWORK_VERSION_NUMBER=$(/usr/libexec/PlistBuddy -c "Print CFBundleShortVersionString" Info.plist)
FRAMEWORK_VERSION=A
FRAMEWORK_BUILD_PATH="build/${BUILD_STYLE}-framework"
FRAMEWORK_DIR="${FRAMEWORK_BUILD_PATH}/${FRAMEWORK_NAME}.framework"
PACKAGENAME="${FRAMEWORK_NAME}.${FRAMEWORK_VERSION_NUMBER}.zip"

#--------------------------------------------------------------------
echo "[1] Framework: Building libraries ..."
#--------------------------------------------------------------------
xcodebuild -configuration ${BUILD_STYLE} -target ${BUILD_TARGET_NAME} clean
xcodebuild -configuration ${BUILD_STYLE} -target ${BUILD_TARGET_NAME} \
  -sdk iphonesimulator${IPHONEOS_DEPLOYMENT_TARGET}
[ $? != 0 ] && exit 1

xcodebuild -configuration ${BUILD_STYLE} -target ${BUILD_TARGET_NAME} \
  -sdk iphoneos${IPHONEOS_DEPLOYMENT_TARGET}
[ $? != 0 ] && exit 1

echo "Framework: Cleaning framework..."
[ -d "${FRAMEWORK_BUILD_PATH}" ] && rm -rf "${FRAMEWORK_BUILD_PATH}"

#--------------------------------------------------------------------
echo "[2] Framework: Setting up directories..."
#--------------------------------------------------------------------
mkdir -p ${FRAMEWORK_DIR}
mkdir -p ${FRAMEWORK_DIR}/Versions
mkdir -p ${FRAMEWORK_DIR}/Versions/${FRAMEWORK_VERSION}
mkdir -p ${FRAMEWORK_DIR}/Versions/${FRAMEWORK_VERSION}/Resources
mkdir -p ${FRAMEWORK_DIR}/Versions/${FRAMEWORK_VERSION}/Headers

#--------------------------------------------------------------------
echo "[3] Framework: Creating symlinks..."
#--------------------------------------------------------------------
ln -s ${FRAMEWORK_VERSION} ${FRAMEWORK_DIR}/Versions/Current
ln -s Versions/Current/Headers ${FRAMEWORK_DIR}/Headers
ln -s Versions/Current/Resources ${FRAMEWORK_DIR}/Resources
ln -s Versions/Current/${FRAMEWORK_NAME} ${FRAMEWORK_DIR}/${FRAMEWORK_NAME}

#--------------------------------------------------------------------
echo "[4] Framework: Creating library..."
#--------------------------------------------------------------------
lipo -create \
 build/${BUILD_STYLE}-iphoneos/lib${FRAMEWORK_NAME}.a \
 build/${BUILD_STYLE}-iphonesimulator/lib${FRAMEWORK_NAME}.a \
 -o "${FRAMEWORK_DIR}/Versions/Current/${FRAMEWORK_NAME}"

#--------------------------------------------------------------------
echo "[5] Framework: Copying assets into current version..."
#--------------------------------------------------------------------
cp Headers/*.h ${FRAMEWORK_DIR}/Headers/
cp Info.plist ${FRAMEWORK_DIR}/Resources/

#--------------------------------------------------------------------
echo "[6] Framework: Packaging framework..."
#--------------------------------------------------------------------
cd ${FRAMEWORK_BUILD_PATH}
zip -ry ${PACKAGENAME} $(basename $FRAMEWORK_DIR)
(2011-09-05追記)
Xcode 4.1 から環境変数 BUILD_STYLE が使われなくなった。代わりに CONFIGURATION を使う。上記スクリプト内の ${BUILD_STYLE} をすべて ${CONFIGURATION} で置換する。

(2011-10-21追記)
Xcode 4.2 から SDK 5.0 のみの提供となった。この為、ターゲットを 4.3 としてビルドするとエラーになる。その場合は xcodebuild コマンドの引数から ${IPHONEOS_DEPLOYMENT_TARGET} を取り除く。
(修正前)xcodebuild -configuration ${BUILD_STYLE} -target ${BUILD_TARGET_NAME} \
  -sdk iphoneos${IPHONEOS_DEPLOYMENT_TARGET}
(修正後)xcodebuild -configuration ${BUILD_STYLE} -target ${BUILD_TARGET_NAME} \
  -sdk iphoneos


(2011-10-21追記)
Xcode 4.2 でターゲットを 4.3 としてビルドするとエラーが出る場合がある。この場合はコンパイラを Apple LLVM compiler 3.0 から LLVM GCC 4.2 に切り替えると解消する(かもしれない)。


xcodebuild コマンドを使い実機(iphoneos)とシミュレータ(iphonesimulator)用のバイナリ(*.a) ファイルを作成する。その後、lipo コマンドで1つにまとめ(ユニバーサル化)Frameworkのディレクトリを構成する。最後はそのディレクトリを ZIP圧縮する。

6. ビルド


ビルドしてみよう。"SampleKit.framework" を選択しビルドを実行する。
※Device/Simulator は無視される。Debug/Release は有効。

フォルダを確認すると...
できた。

file コマンドでバイナリを確認してみる。
$ file SampleKit 
SampleKit: Mach-O universal binary with 3 architectures
SampleKit (for architecture armv6): current ar archive random library
SampleKit (for architecture armv7): current ar archive random library
SampleKit (for architecture i386): current ar archive random library
ちゃんと arm6/7、i386用のライブラリが入っている。


Frameworkを利用するサンプル


作成した Framework を使うサンプルを作ってみる。View-basedなプロジェクトを作成し、先ほど作成した Frameworkを追加する。
Frameworkとして認識された。
Xcodeのウィンドウの左ツリーで SampleKit.frameworkを選択すると右側には内包している SampleClass.h が表示される。

コントローラに下記コードを追加する。
#import <SampleKit/SampleClass.h>
  :
- (void)viewDidLoad {
    [super viewDidLoad];
 
 SampleClass* obj = [[[SampleClass alloc] init] autorelease];
 NSLog(@"%@", [obj stringHello]);
}
Framework 内のヘッダファイルは下記の形式でインポートする。
#import <フレームワーク名/ヘッダファイル名>

シミュレータで実行すると...
SampleKitClient[19559:207] Hello mac!
デバッグコンソールに出た。その後、実機でも動作確認ができた。良さそうだ。


考察


"Frameworkとは?" の記事で触れた様に iOS の場合は Framework は静的ライブラリのプレースホルダとして使うのが一般的と思われる。


運用イメージ

この為、もし自作した Framework がリソース(nib,画像など)を含む場合は、利用側のプロジェクトのビルドフェーズでそれらをアプリケーションバンドル内へコピーして一緒に配布する必要がある。

なお仕組み自体は動的ライブラリの格納も可能なので、"Frameworkとは?"で取り上げた embed形態を取ることで実行時にリンクさせることもできる。この場合だと実行時に必要なライブラリしか読み込まれない為、フットプリント(メモリ消費)を減らせる可能性がある。



トラブルシュート


SDKが見つからないエラーが出た場合



スクリプト内のPATH環境変数設定で古い SDKのディレクトリを指定している場合など。
export PATH=/Developer/usr/bin:"$PATH"
明示的に指定する必要がなければ、この PATH設定を削除しておく。すると現在実行中の Xcodeの SDKのバージョンが使用される

ビルド中にエラー


ターゲットが見つからない場合などは、スクリプト内の xcodebuild コマンドで指定しているターゲットが正しく設定されていない可能性がある。

スクリプト内の xcodebuildコマンドのターゲットに指定しているのは、最初にテンプレートで作成されたターゲットである "SampleKit" である。このターゲット名を用意するのに Info.plist 内の "Bundle Name" を使用している。この名前とライブラリ(*.a)作成用のターゲット名が一致している必要がある。
シェルスクリプト内の該当箇所は下記になる。
#--------------------------------------------------------------------
echo "[0] Framework: Preparing ..."
#--------------------------------------------------------------------
FRAMEWORK_NAME=$(/usr/libexec/PlistBuddy -c "Print CFBundleName" Info.plist)  // ←これ
BUILD_TARGET_NAME=$FRAMEWORK_NAME  // ←

ヘッダが無いというエラーが出る


Headers ディレクトリにファイルが無いとこのエラーが出る。例えば追加したヘッダファイルのパスが間違っていた場合など。
この場合は該当するファイルが入っているフォルダを開く。
そしてファイルを Headers へコピーする。
すると Xcode 上でファイルが赤くなる。これは指定パス位置にファイルが無いことを示している。
そこで情報を開き、パスの「選択」ボタンを押して目的のファイルを選択する。
これでビルドが通る様になる。


ソースコード


GitHubからどうぞ。

Framework作成プロジェクト
SampleKit at 2010-11-19c from xcatsan's iOS-Sample-Code - GitHub

Frameworkを使うサンプル
SampleKitClient at 2010-11-19 from xcatsan's iOS-Sample-Code - GitHub

※ SampleKitClient 内で参照している SampleKit へのパスは私のPCのローカル環境になっています。ビルドする際には SampleKit を別途ダウンロードして、参照先をそちらに切り替えて下さい。


備考:コマンド


lipo


lipo(1) Mac OS X Manual Page

複数のアーキテクチャ向けのライブラリやアプリケーションバイナリを1つにまとめることができるツール。

[参考情報]
Undocumented Mac OS X:第13回 Universal Binary【後編】 (2/4) - ITmedia エンタープライズ
yebo blog: ユニバーサルバイナリ化するツール lipo

PlistBuddy(8) Mac OS X Manual Page


PlistBuddy


*.plist ファイルの値を設定したり取得したりできるコマンド。/usr/libexec/配下に存在する。
# CFBundleName 取得例
FRAMEWORK_NAME=$(/usr/libexec/PlistBuddy -c "Print CFBundleName" Info.plist)

PlistBuddy を使ってバージョン番号を自動的にインクリメントするアイディアも公開されている。
Incrementing Build Numbers in Xcode | Dave DeLong


備考


今回 PlistBuddy を使い、シェルスクリプトから Info.plist 内の情報を取り出してビルドに使用した。本来であればビルド設定の値(環境変数に設定される)を使うべきで、一般的な Xcodeプロジェクトではそのようになっている。Info.plist 自体にも環境変数が埋めこまれていてビルド時にこれが置換される。下記は Mac OS X 用 Framework テンプレートで生成された Info.plist。
${EXECUTABLE_NAME} など環境変数が使われているのがわかる。Info.plist 内の環境変数の置換はビルド時に行われている。以下は Mac OS X 用Framework をビルドしている時のメッセージ。
メッセージに表示されている builtin-infoPlistUtility は外部コマンドとして存在するものではなくて、Xcode内部の機能として存在するようだ。先頭にある "ProcessInfoPlistFile" はビルドのルール設定項目と思われるが見つからなかった(図にあるように CopyPlistFile というルールは設定項目にある)。
また今回使用しているターゲットタイプ(Shell Script)ではルールが編集できない。
仕方ないので
Info.plist ==(参照)==> 環境変数
シェルスクリプト ==(参照)==> 環境変数
をやめて
Info.plist をオリジナル
シェルスクリプト ==(参照)==> Info.plist
とした。


Framework 参考情報


iOS向けの Framework に関しては Appleからドキュメントが提供されていない。この為、多くの人が自分たちで試行錯誤して Framework作成を試みている。そのおかげもあって Framework作成はだいたいすんなり行うことができた。情報提供してくれた方々にはこの場を借りてお礼を申し上げます。

iPhone アプリ開発で使える Framework の作り方 | KRAY Inc
Framework の作成方法が図入りで丁寧に解説されている。とても参考になった。

[Mac][iPhone][develop] iPhone OS用のほぼFrameworkの作り方 - Ni chicha, ni limona -平均から抜けられない僕-
ライブラリファイル(*.a)を lipo を使ってユニバーサル化する方法の紹介。

Mac♪Mac♪Mac♪ - 第11回 フレームワークを作成する
Mac OS X でフレームワークを作成する解説。

How to (almost) create your own iPhone OS framework

Making Your Own iPhone Frameworks @ Cocoanetics

“Making Your Own iPhone Frameworks”

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