トップ 最新 追記

Cocoa練習帳

iOS/iPhone/iPad/watchOS/tvOS/MacOSX/Android プログラミング, Objective-C, Cocoa, Swiftなど

2012|01|02|03|04|05|06|07|08|09|10|11|12|
2013|01|02|03|04|05|06|07|08|09|10|11|12|
2014|01|02|03|04|05|06|07|08|09|10|11|12|
2015|01|02|03|04|05|06|07|08|09|10|11|12|
2016|01|02|03|04|05|06|07|08|09|10|11|12|
2017|01|02|03|04|05|06|

2012-02-01 [Mac][Arduino]Getting Started

今回は、Arduinoだ。

まずは、Arduinoを入手しないと話にならない。私はスイッチサイエンスの製品をAmazonで購入した。

ArduinoとMacとは、USBケーブルで接続となるとなるがA-Bタイプを購入する事。A-miniBは別物だ!私はそれで失敗した。

画像の説明

製品が入手できたら以下のサイトの説明通りにすればいい。

ただ、私の場合だけの可能性があるが、入手したArduino UNO R3は、初期状態でExamples/1.Basics/Blinkがアップロードされている様で、この手順で試しても最初は変化が分からなかった。

_ 関連情報

Arduino - HomePage
本家のホームページ。
スイッチサイエンス
初心者のため、よく分かっていないが、ここで購入するのが楽そうだ。

2012-02-02 日本語のADCサイト情報

ADCの日本語情報は、以下のRSSから取得できる。
日本語のADCサイト情報


2012-02-03 [iOS]Local Notification

Notificationには、LocalとPushの二種類があって、前者は発生源が手元のアプリケーション、後者は発生源が外部という事のようだ。

Local Notificationの登録は、通知する日時を指定する方法と、直ぐに通知する方法の二通りがある。

通知する日時を指定する手順は以下のとおり。

- (IBAction)scheduleNotification:(id)sender
{
    DBGMSG(@"%s", __func__);
    NSDate  *today = [NSDate date];
    UILocalNotification *localNotif = [[UILocalNotification alloc] init];
    if (localNotif == nil)
        return;
    localNotif.fireDate = [today dateByAddingTimeInterval:10];
    localNotif.timeZone = [NSTimeZone defaultTimeZone];
    localNotif.alertBody = [NSString stringWithString:NSLocalizedString(@"Local Notify", nil)];
    localNotif.alertAction = NSLocalizedString(@"local notify", nil);
    localNotif.soundName = UILocalNotificationDefaultSoundName;
    localNotif.applicationIconBadgeNumber = localNotif.applicationIconBadgeNumber + 1;
    NSDictionary *infoDict = [NSDictionary dictionaryWithObject:@"local notify" forKey:@"Key"];
    localNotif.userInfo = infoDict;
    [[UIApplication sharedApplication] scheduleLocalNotification:localNotif];
}

UILocalNotificationのプロパティfireDateに、通知する日時を設定する。上記の例は、現在の時刻の10秒後だ。

UILocalNotificationのインスタンスをUIApplicationのscheduleLocalNotification:メソッドに設定すると、指定された日時に通知される。

直ぐに通知させたい場合は、presentLocalNotificationNow:メソッドを呼ぶ。

- (void)applicationDidEnterBackground:(UIApplication *)application
{
    DBGMSG(@"Application entered background state.");
    NSAssert(self.bgTask == UIBackgroundTaskInvalid, nil);
    self.bgTask = [application beginBackgroundTaskWithExpirationHandler: ^{
        dispatch_async(dispatch_get_main_queue(), ^{
            [application endBackgroundTask:self.bgTask];
            self.bgTask = UIBackgroundTaskInvalid;
        });
    }];
    dispatch_async(dispatch_get_main_queue(), ^{
        while ([application backgroundTimeRemaining] > 1.0) {
            UILocalNotification *localNotif = [[UILocalNotification alloc] init];
            if (localNotif) {
                localNotif.alertBody = [NSString stringWithString:NSLocalizedString(@"background", nil)];
                localNotif.alertAction = NSLocalizedString(@"action", nil);
                localNotif.soundName = UILocalNotificationDefaultSoundName;
                localNotif.applicationIconBadgeNumber = localNotif.applicationIconBadgeNumber + 1;
                [application presentLocalNotificationNow:localNotif];
                break;
            }
        }
        [application endBackgroundTask:self.bgTask];
        self.bgTask = UIBackgroundTaskInvalid;
    });
}

通知を受けてアプリケーションを呼び出す場合は、アプリケーション側で準備が必要となる。

アプリケーションの起動されていない場合は、アプリケーションのデリゲートのdidFinishLaunchingWithOptions:メソッドで通知からの呼び出しかどうかの判断を行う。

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    UILocalNotification *localNotif = [launchOptions objectForKey:UIApplicationLaunchOptionsLocalNotificationKey];
    if (localNotif) {
        NSString *value = [localNotif.userInfo objectForKey:@"Key"];
        DBGMSG(@"%s, Notify: %@", __func__, value);
        application.applicationIconBadgeNumber = localNotif.applicationIconBadgeNumber - 1;
    }
    
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    self.viewController = [[ViewController alloc] initWithNibName:@"ViewController" bundle:nil];
    self.window.rootViewController = self.viewController;
    [self.window makeKeyAndVisible];
    return YES;
}

起動済みの場合は、didReceiveLocalNotification:メソッドが呼ばれる。

- (void)application:(UIApplication *)app didReceiveLocalNotification:(UILocalNotification *)notif
{
    NSString *value = [notif.userInfo objectForKey:@"Key"];
    NSLog(@"%s, Notify: %@", __func__, value);
    app.applicationIconBadgeNumber = notif.applicationIconBadgeNumber - 1;
}

_ 関連情報

Local and Push Notification Programming Guide
iOS Developer Libraryの情報です。

2012-02-04 関東第50回Cocoa勉強会

_ 関東第50回Cocoa勉強会

日時: 2012/02/04(土) 13:00-18:00

会場:松戸シティホテルSENDAN-YA

今回は、USBパラレル変換モジュールをMacに接続してみる、ストーリーボードとセグウェ、スペルチェッカー、Local Notificationと通知センター、アクションシートのバグの発表があった。
画像の説明


2012-02-05 [Mac][Aruduino]Aruduinoハンズオンセミナー初級編

AppleStore銀座で開催されたAruduinoハンズオンセミナー初級編に参加してきた。

Aruduinoハンズオンセミナー初級編

今回は、その復習の結果だが、素人なので間違いがある可能性がある。その場合は指摘してくれると嬉しい!

オームの法則
I=(1/R)*V[A]
I:電流[A]
V:電圧[V]
1/R:比例定数。
R:電気抵抗[Ω]

発光ダイオード(LED)
Arduinoからの電源電圧は5V、LEDの順方向降下電圧は3.5V(予想)、セミナーで使用した抵抗は1,600Ω(だと思う)
電流 = 電圧 ÷ 抵抗 = (5V - 3.5V) ÷ 1.6kΩ ≒ 1mA

つまり、1mAの電流を流れる事を期待したということか。

プルアップ抵抗/プルダウン抵抗
回路にOn/Offするスイッチを入れた場合、Off時は断線した状態。つまり、電圧不定の状態になる。プルアップの場合、例えば、5Vの電源と間に10kΩの抵抗を入れてつなぐと、0.5mA(=5V÷10kΩ)と低い電流で安定することになる。そして、スイッチをOnにすると抵抗値が低い側に電流が流れるので、望みどおりとなる。
プルダウンの場合、スイッチがOffの際、マイコンの0Vの端子と接続させて安定させ、Onの際に5Vの端子と接続させるということか。まだ、説明していて自身がない。

今回は、基礎的な話なので、今後は、Arduinoとの関係について調べて説明していきたい。

_ 関連情報

Arduino
本家のホームページです。
Arduino 日本語リファレンス
助かります。ありがとうございます。
TETRASTYLE-dev-BLOG
Aruinoハンズオン(銀座)やりました
iOSとArduinoの情報が豊富です。

2012-02-06 [iOS]Storyboards

先日のCocoa勉強会の復習だ。

Empty Applicationを生成する。

Empty Application

Storyboardファイルを新規作成する。

Storyboardファイル

Main Storyboardに、さっき作成したStoryboardファイルを設定する。

Main Storyboard

ViewControllerを追加する。追加した物かどうか、分かりやすくする為、背景を黄色に設定。

ViewController

アプリケーション・デリゲートでwindowプロパティの生成をコメントアウトする。

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    /*
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    // Override point for customization after application launch.
    self.window.backgroundColor = [UIColor whiteColor];
    [self.window makeKeyAndVisible];
    */
    return YES;
}

実行する。

RUN

今回はここまで。

_ 関連情報

Converting to Storyboards Release Notes
Your Second iOS App: Storyboards
iOS Developer Libraryの情報です。

2012-02-07 [Android]hello, world

ソフトウェア開発者、それもアプリケーション開発者ならAndroid開発を学ばない理由はないだろう。
特に、複数のプラットフォームに接する事によって、設計思想的に得る物は多いはずだ。

開発環境の設定については、多くの情報が公開されているので、ここで説明する必要はないだろう。著者は普通にEclipse上で開発を行っている。
ただ、申し訳ないが、AndroidプロジェクトのどのパートをGitHubで公開するといいのか理解していない為、今回はソースコードの公開なしで許して欲しい。

と、ここまで書いて、開発環境の更新に手間取り、これ以上、進めなくなってしまった。
申し訳ないが、今回はここまでとさせて欲しい。

_ 関連情報

Android Developers
Android開発者サイトです。
本日のツッコミ(全1件) [ツッコミを入れる]

_ m_yukio [環境構築失敗の原因が分かった。 後で試す。]


2012-02-08 [Android]hello, world(その2)

トラブルの原因が分かった。debug.keystoreが期限切れだった。

~/.android/debug.keystoreを削除するなり、ファイル名を変更するなりして、再度、RUNを実行すればOK!
ということで、予定通り入門編をすすめる。

今回は、どこでもある、"hello, world"

新規Androidプロジェクトを作成する。


    Project name: HelloWorld
    Build Target: Android 2.3.3
    Application name: HelloWorld
    Package name: demo.hello
    Create Activity: HelloWorldActivity
    Min SDK Version: 10
NewProj CreateProj SelectBuildTarget ApplInfo

プロジェクトが作成された。

Android 4.0 (Ice Cream Sandwich)のSDKが出ているが、ベーシックに2.3.3とした。

Proj

実行。

Run

_ 関連情報

Android Developers
Android開発者サイトです。

2012-02-09 [iOS]Storyboards(その2)

前回作成したストーリーボードに追加したViewControllerを削除して、NavigationControllerを追加し、カスタムクラスでRootViewControllerとSubViewControllerを追加する。

Storyboard.storyboard

RootViewControllerにはボタンあって、ボタンとSubViewControllerをPushのSegueでつなぐ。

drag

push

line

シミュレータで動作確認。

RootViewController

SubViewController

以前、Documentクラスを紹介したが、ストーリーボードを使用する場合、各ビュー・コントローラが個別に存在してる感じなので、シングルトンでアクセスできるのは便利だと思う。状態がDocumentクラスに保持されていれば、このコードだけでそれなりの実装が出来ると思う。

_ 関連情報

Converting to Storyboards Release Notes
Your Second iOS App: Storyboards
iOS Developer Libraryの情報です。

2012-02-10 [iOS]Storyboards(その3)

もし、画面が遷移したタイミングを知りたいのなら、元のビュー・コントローラでUIViewControllerの- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)senderメソッドをオーバーライドする。

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
    DBGMSG(@"%s", __func__);
    DBGMSG(@"segue identifier:%@", [segue identifier]);
}

Segueに"segue01"というidをつけたので、それがデバッグ出力されるはずだ。
ここで画面遷移となった原因に対応した状態をDocumentに保存する等すれば、遷移理由に対応した処理が出来るはずだ。

_ 関連情報

Converting to Storyboards Release Notes
Your Second iOS App: Storyboards
iOS Developer Libraryの情報です。

2012-02-12 [iOS]項目を選択する

複数の表示項目(テキストのラベルや画像etc)がある場合、そのどれが有効で、どれが無効なのかを選択するUIはどう実現できるのだろうか?
iPhoneらしいのは、別の設定画面があって、そこで項目をスイッチでOn/Offする。
画面遷移が煩わしくないか。
表示画面そのもので、On/Offを選択する。
良さそうだ。だが、チェック的なモノがあるのも煩わしくないか。
今回は、項目の表示状態からOn/Offが選択できるUIを実験してみた。

項目が選択されたら表示を変更したいのなら、ユーザー操作、つまり、イベントを取得できるようにしないといけない。
今回は、画像とラベルの選択を実践してみたが、こららのビューを管理するビュー・コントローラでイベントを取得する方法と、個々のビューのサブクラスを作成して、そのサブクラスでイベントを取得する方法に二通りが考えられる。
ビュー・コントローラで対応する方法だと、新たにサブクラスを作成する手間が減るが、ユーザーの操作に対して、どのビューが対象なのかを判断しないといけないが、サブクラスを作成する方法は、その判断が不要となる。
そこで、今回の実験では、サブクラスを作成する方法を選択した。

プロジェクトにQuartzCore.frameworkを追加する。

QuartzCore.framework

画面のビュー・コントローラに画像とラベルを追加する(下図の赤丸)。

ViewController

そして、それぞれのクラスをカスタムクラス(MyImageViewとMyLabel)に変更する。

CustomClass

MyImageViewの選択処理のコードは以下のとおり。

- (void)awakeFromNib
{
    self.selected = NO;
     
    self.layer.masksToBounds = YES;
    self.layer.cornerRadius = 4.0f;
     
    self.layer.borderWidth = 3.0f;
    self.layer.borderColor = [[UIColor grayColor] CGColor];
}
 
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    [self toggleSwitch];
}
 
- (void)toggleSwitch
{
    if (self.selected) {
        self.selected = NO;
        self.layer.borderColor = [[UIColor grayColor] CGColor];
    }
    else {
        self.selected = YES;
        self.layer.borderColor = [[UIColor blueColor] CGColor];
    }
}

MyLabelの選択処理のコードは以下のとおり。

- (void)awakeFromNib
{
    self.selected = NO;
    self.textColor = [UIColor grayColor];
}
 
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    [self toggleSwitch];
}
 
- (void)toggleSwitch
{
    if (self.selected) {
        self.selected = NO;
        self.textColor = [UIColor grayColor];
    }
    else {
        self.selected = YES;
        self.textColor = [UIColor blueColor];
    }
}

実行。

Run

_ 関連情報

User Experience Coding How-To's
iOS Developer Libraryの情報です。
本日のツッコミ(全1件) [ツッコミを入れる]

_ eien [タッチする位置間違いもあるので、touchsBeganよりかtouchesEndedのほうが私は好きなんですが、どう..]


2012-02-13 [iOS]Image Masking

表示したい画像とマスクの画像を用意する。

画像の説明
画像の説明

それを以下のコードでマスクした画像を取得し、描画する。

- (void)awakeFromNib
{
    self.image = [UIImage imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"pict.png" ofType:nil]];
    self.mask = [UIImage imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"mask.png" ofType:nil]];
    self.imageMaskedWithImage = [self maskImage:self.image withMask:self.mask];
}
 
- (void)drawRect:(CGRect)rect
{
    [self.imageMaskedWithImage drawAtPoint:CGPointMake(10.0, 10.0)];
}
 
- (UIImage*)maskImage:(UIImage *)image withMask:(UIImage *)maskImage
{   
	CGImageRef maskRef = maskImage.CGImage; 
	CGImageRef mask = CGImageMaskCreate(CGImageGetWidth(maskRef),
                                        CGImageGetHeight(maskRef),
                                        CGImageGetBitsPerComponent(maskRef),
                                        CGImageGetBitsPerPixel(maskRef),
                                        CGImageGetBytesPerRow(maskRef),
                                        CGImageGetDataProvider(maskRef),
                                        NULL,
                                        false);
	CGImageRef masked = CGImageCreateWithMask([image CGImage], mask);
	CGImageRelease(mask);
	return [UIImage imageWithCGImage:masked];
}

実行。

画像の説明

画像のマスクについては、まだまだ、ほんの一部だと思っているので、機会があったら続きを試してみたい。

_ 関連情報

Programming with Quartz: 2D and PDF Graphics in Mac OS X
WWDCでも薦められていた書籍です。
How to Mask an Image

2012-02-14 [iOS]Image Masking(その2)

実は、本題の画像処理のコードより、Storyboardを使った、ターブル一覧の実装に時間がかかってしまったが、いい経験となった。Storyboardについては、機会があれば続きをと考えている。

画像を任意の形に切り抜いて表示する方法の一つにクリッピングがある。
今回は、クリッピングの基本的な内容で、画像の四隅を丸くする例だ。

- (void)drawRect:(CGRect)rect
{
    CGContextRef    context = UIGraphicsGetCurrentContext();
    CGSize  imageSize = self.image.size;
    CGRect  imageRect = {10.0, 10.0, imageSize.width, imageSize.height};
    float   radius = 10.0;
    CGFloat minX = CGRectGetMinX(imageRect);
    CGFloat midX = CGRectGetMidX(imageRect);
    CGFloat maxX = CGRectGetMaxX(imageRect);
    CGFloat minY = CGRectGetMinY(imageRect);
    CGFloat midY = CGRectGetMidY(imageRect);
    CGFloat maxY = CGRectGetMaxY(imageRect);
    
    /* 現状の描画環境を保存 */
    CGContextSaveGState(context);
    
    /* 四角形の辺に接する、半径radiusの円弧を四隅に追加 */
    CGContextMoveToPoint(context, minX, midY);
    CGContextAddArcToPoint(context, minX, minY, midX, minY, radius);
    CGContextAddArcToPoint(context, maxX, minY, maxX, midY, radius);
    CGContextAddArcToPoint(context, maxX, maxY, midX, maxY, radius);
    CGContextAddArcToPoint(context, minX, maxY, minX, midY, radius);
    CGContextClosePath(context);
    
    /* 先ほどのパスをクリップ領域として設定 */
    CGContextClip(context);
    
    /* 描画 */
    [self.image drawAtPoint:CGPointMake(10.0, 10.0)];
    
    /* 描画環境を先ほどの保存時点に戻す */
    CGContextRestoreGState(context);
}

実行。テーブルの「ClippingADrawing」を選択して欲しい。

テーブル
クリッピング

_ 関連情報

Programming with Quartz: 2D and PDF Graphics in Mac OS X
WWDCでも薦められいた書籍です。
iOS SDK Hacks (吉田 悠一、高山 征大、UICoderz 著)
「HACK #17 クリッピング」を参考にしました。

2012-02-15 [iOS]座標と描画

Mac OS Xでは、基本的に描画の座標は左下が原点だ。

iOSでは、UIKitとCoreAnimationでは左上が原点(便宜上、ULO(upper-left-origin)と呼ぶ)、Core Graphicsでは左下が原点(便宜上、LLO(lower-left-origin)と呼ぶ)だそうだ。試してみよう。

まず、Mac OS Xから。NSViewのサブクラスを作成して、以下の描画コードを追加する。

- (void)drawRect:(NSRect)dirtyRect
{
    /* LLO(lower-left-origin) */
    NSGraphicsContext   *nsctx = [NSGraphicsContext currentContext];
    CGContextRef    context = (CGContextRef)[nsctx graphicsPort];
    CGContextSetLineWidth(context, 4.0);
    CGContextBeginPath(context);
    CGContextMoveToPoint(context, 10.0, 30.0);
    CGContextAddLineToPoint(context, 10.0, 10.0);
    CGContextDrawPath(context, kCGPathStroke);
    CGContextMoveToPoint(context, 10.0, 10.0);
    CGContextAddLineToPoint(context, 30.0, 10.0);
    CGContextDrawPath(context, kCGPathStroke);
}

実行。確かに左下が原点だ。

Mac OS X

次にiPhoneで確認。画像を座標 (10.0, 10.0) に描画する。

- (void)drawRect:(CGRect)rect
{
    /* ULO(upper-left-origin) */
    [self.upperLeftOriginImage drawAtPoint:CGPointMake(10.0, 10.0)];
}

実行。向きも変えてみる。確かに左上に描画される。

ULO縦
ULO横

今度はパスを座標 (10.0, 10.0) に描画するコードを追加。

- (void)drawRect:(CGRect)rect
{
    /* ULO(upper-left-origin) */
    [self.upperLeftOriginImage drawAtPoint:CGPointMake(10.0, 10.0)];
 
    /* LLO(lower-left-origin) */
    CGContextRef    context = UIGraphicsGetCurrentContext();
    CGContextSetLineWidth(context, 4.0);
    CGContextBeginPath(context);
    CGContextMoveToPoint(context, 10.0, 30.0);
    CGContextAddLineToPoint(context, 10.0, 10.0);
    CGContextDrawPath(context, kCGPathStroke);
    CGContextMoveToPoint(context, 10.0, 10.0);
    CGContextAddLineToPoint(context, 30.0, 10.0);
    CGContextDrawPath(context, kCGPathStroke);
}

実行。赤丸の箇所に描画。あれ?左上に描画されている。

LLO縦
LLO横

何かを勘違いしているのだろうか?

_ 関連情報

Drawing and Printing Guide for iOS
「Default Coordinate Systems and Drawing in iOS」の章を参照。

2012-02-16 [iOS]座標と描画(その2)

前回の疑問、どのような動作をするのか考えれば、当たり前の結果だと分かった。
アドバイスしていただいたMOSAのMOSAdeBBの方々、cocoa-dev-japanの方々、ありがとうございます!

ただ、今回、これが確認できるサンプルを用意しようと考えたのだが、上手くいかなかったので、少し時間が欲しい。申し訳ない。

_ 関連情報

Drawing and Printing Guide for iOS
「Default Coordinate Systems and Drawing in iOS」の章を参照。

2012-02-17 [Mac][iOS]練習方法

今回は、自分のCocoa練習の場を紹介します。

■相互の学習の場。勉強になっています。
 Cocoa勉強会

■交流の場。
 特定非営利活動法人MOSA
MOSAdeBBという会員向け掲示板があるのですが、これが質問しやすく、直ぐに回答が得られて、助かります。

■Googleグループ
 cocoa-dev-japan

状況共有しやすい場として設立されました。


2012-02-18 [Mac][iOS]Xcode 4.3

Xcode 4.3 for Lionがリリースされた。

/Developerディレクトリでなく、/Applicationsディレクトリにインストールされるようになったり、バンドル内に各種ツールを置くようになったりと、OS X Mountain Lion時代を感じされる変化が興味深い。

そして、これが一番大きな事だが、バージョンアップに伴う様々が事で、本日は日記に新しい話題を投稿する事が出来なくなってしまった。残念!


2012-02-19 [iOS]座標と描画(その3)

デフォルト座標系の話、理由は分かったが、それを実際に試せないか、試行錯誤している。

Apple Developerサイトの文書でも、UIKitでの描画。つまり、UIViewサブクラスのdrawRect:メソッド内で描画する場合、デフォルト座標系は原点は左上で、座業は下と右方向に伸びるULO(upper-left-origin)となって、LLO(lower-left-origin)を選択する場合は独自の変換行列(CTM:Current Transformation Matrix)を適用すると説明されていた。

ということで、現在の描画環境以外にCoreGraphicsで描画すれば、LLOになると考え、ビットマップ・コンテキストに描画した物を表示してみた。

- (void)drawRect:(CGRect)rect
{
    DBGMSG(@"%s", __func__);
    CGContextRef    context = UIGraphicsGetCurrentContext();
 
    /* LLO(lower-left-origin) */
    size_t  witdh = rect.size.width;
    size_t  height = rect.size.height;
    size_t  bytesPerRow = witdh * 4;
    bytesPerRow = COMPUTE_BEST_BYTES_PER_ROW(bytesPerRow);
    unsigned char   *rasterData = calloc(1, bytesPerRow * height);
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    CGContextRef    bitmapContext = CGBitmapContextCreate(rasterData, witdh, height,
                                                8, bytesPerRow,
                                                colorSpace,
                                                kCGImageAlphaPremultipliedLast);
 
    CGContextSetRGBStrokeColor(bitmapContext, 1.0, 0.0, 0.0, 1.0);
    CGContextSetLineWidth(bitmapContext, 4.0);
    CGContextBeginPath(bitmapContext);
    CGContextMoveToPoint(bitmapContext, 5.0, 25.0);
    CGContextAddLineToPoint(bitmapContext, 5.0, 5.0);
    CGContextDrawPath(bitmapContext, kCGPathStroke);
    CGContextMoveToPoint(bitmapContext, 5.0, 5.0);
    CGContextAddLineToPoint(bitmapContext, 25.0, 5.0);
    CGContextDrawPath(bitmapContext, kCGPathStroke);
 
    CGImageRef  cgimage = CGBitmapContextCreateImage(bitmapContext);
    CGContextDrawImage(context, rect, cgimage);
    CGContextRelease(bitmapContext);
    free(rasterData);
    CGColorSpaceRelease(colorSpace);
 
    /* ULO(upper-left-origin) */
    [self.upperLeftOriginImage drawAtPoint:CGPointMake(20.0, 20.0)];
 
    CGContextSetLineWidth(context, 4.0);
    CGContextBeginPath(context);
    CGContextMoveToPoint(context, 20.0, 40.0);
    CGContextAddLineToPoint(context, 20.0, 20.0);
    CGContextDrawPath(context, kCGPathStroke);
    CGContextMoveToPoint(context, 20.0, 20.0);
    CGContextAddLineToPoint(context, 40.0, 20.0);
    CGContextDrawPath(context, kCGPathStroke);
}

あれ、ULOみたい。

RUN

現在の描画環境に表示する際にULOに変換されるのでは?と考え、ビットマップ・コンテキストの内容をファイルに保存してみた。

    UIImage *uiimage = [[UIImage alloc] initWithCGImage:cgimage];
    NSData  *data = UIImagePNGRepresentation(uiimage);
    NSString    *filePath = [NSString stringWithFormat:@"%@/demo.png" ,
                          [NSHomeDirectory() stringByAppendingPathComponent:@"Documents"]];
    NSLog(@"%@", filePath);
    [data writeToFile:filePath atomically:YES];

確かに、LLOになっている。

demo.png

_ 関連情報

Programming with Quartz: 2D and PDF Graphics in Mac OS X
Drawing and Printing Guide for iOS
結局は、これらの一次情報を読んでいくのがベストのようですね。

2012-02-20 [iOS]AdMob

Google AdMobの魅力は、容易に自分のアプリケーションに組み込む事が出来る事だ。

以前は、使用しているオープンソースのフレームワークも一緒に組み込む等、面倒な部分があったが、現在はアーカイブのライブラリとヘッダファイルを追加するだけでOK。組み込むソースコードも、AdMobサイトで丁寧に説明してある。なので、ここで組み込む手順を説明するまでもないので、自分が工夫していることについて説明する。

AdMobは使い始めたら直ぐに広告が表示されるようになる訳ではないのと、小数の同じ端末から何度も広告が表示されると、不正な操作と判断されるということからだと思うが、デバッグ時は明示的にそのことを設定する必要がある。

著者は、Supporting Filesの<アプリ名>-Prefix.pchで、デバッグ出力用のマクロの定義時に、AdMob試験用のマクロを定義している。

#ifdef	DEBUG
#define	DBGMSG(...)	NSLog(__VA_ARGS__)
#define	ADMOB_TESTDRIVE
#else	/* DEBUG */
#define	DBGMSG(...)
#endif	/* DEBUG */

ViewControllerにAdMobのプロパティを定義する。

@interface MyViewController : UIViewController 
@property (strong, nonatomic) GADBannerView             *bannerView;
@end

ViewControllerの-(void)viewDidLoadメソッドでインスタンスを生成する。

- (void)viewDidLoad
{
    [super viewDidLoad];
 
    self.bannerView = [[GADBannerView alloc]
                   initWithFrame:CGRectMake(0.0,
                                            self.view.frame.size.height -
                                            GAD_SIZE_320x50.height,
                                            GAD_SIZE_320x50.width,
                                            GAD_SIZE_320x50.height)];
    self.bannerView.adUnitID = @"自分のAdMobパグリッシャーID";
    self.bannerView.rootViewController = self;
    [self.view addSubview:self.bannerView];
#ifdef	ADMOB_TESTDRIVE
    GADRequest  *request = [GADRequest request];
    request.testDevices = [NSArray arrayWithObjects:
                           GAD_SIMULATOR_ID,
                           @"デバイスID(1)",
                           @"デバイスID(2)",
                           nil];
    [bannerView_ loadRequest:request];
#else   /* ADMOB_TESTDRIVE */
    [self.bannerView loadRequest:[GADRequest request]];
#endif  /* ADMOB_TESTDRIVE */
    self.bannerView.delegate = self;
}

その際、ADMOB_TESTDRIVEが定義されている。つまりデバッグ時は、デバッグで使用する自分のデバイスIDを上記のコードのように設定する。

広告が表示される場合と、表示されない場合で、画面レイアウトを変更する場合があると思が、以下のそれの例。

- (void)adViewDidReceiveAd:(GADBannerView *)bannerView
{
    [UIView beginAnimations:@"BannerSlideOn" context:nil];
    bannerView.frame = CGRectMake(0.0,
                                  self.view.frame.size.height - bannerView.frame.size.height,
                                  bannerView.frame.size.width,
                                  bannerView.frame.size.height);
    self.コントロール.frame = CGRectMake(self.コントロール.frame.origin.x,
                                      元のY座標の値 - bannerView.frame.size.height,
                                      self.コントロール.frame.size.width,
                                      self.コントロール.frame.size.height);
    [UIView commitAnimations];
}
 
- (void)adView:(GADBannerView *)bannerView
didFailToReceiveAdWithError:(GADRequestError *)error
{
    [UIView beginAnimations:@"BannerSlideOff" context:NULL];
    self.コントロール.frame = CGRectMake(self.コントロール.frame.origin.x,
                                      元のY座標の値,
                                      self.コントロール.frame.size.width,
                                      self.コントロール.frame.size.height);
     [UIView commitAnimations];
}

上記の例では、ボタン等の<コントロール>を広告の表示/非表示に合わせて位置を変更している。全てを計算で求めると、トラブルで順番が狂うとおかしくなるので、ハードコーディングしている。

_ ソースコード

AdMobが組み込まれていないプロジェクトだと、しょうがないので、今回はソースコードはなしだ。

_ 関連情報

Google AdMob
Google AdMob Ads iOS Fundamentals
Google AdMobのサイト。

2012-02-21 [iOS]設定バンドル Settings Bundle

どの様な物かは知っていたが、ユーザーの立場からアクセスがしづらいのでは?と思えて縁がなかった物の一つに設定バンドル(Settings Bundle)がある。

プロジェクトの新規ファイルとして、ResourceのSettings Bundleを選択する。

新規文書

Setting.bundleのRoot.plistに項目を設定する。
デフォルトでは、テキストフィールドNameとトグルスイッチ、スライダーの3項目が用意されている。

Root.plist

実装すると、この3項目が表示される。

Settings
SettingsBundle

設定バンドルの値は、以下のコードで取得できる

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    NSLog(@"%s", __func__);
    NSString    *name = [[NSUserDefaults standardUserDefaults] stringForKey:@"name_preference"];
    NSLog(@"Name:%@", name);
    BOOL    enabled = [[NSUserDefaults standardUserDefaults] boolForKey:@"enabled_preference"];
    NSLog(@"Enabled:%d", (int)enabled);
    float    slider = [[NSUserDefaults standardUserDefaults] floatForKey:@"slider_preference"];
    NSLog(@"Slider:%f", slider);
    return YES;
}
 
- (void)applicationDidBecomeActive:(UIApplication *)application
{
    NSLog(@"%s", __func__);
    NSString    *name = [[NSUserDefaults standardUserDefaults] stringForKey:@"name_preference"];
    NSLog(@"Name:%@", name);
    BOOL    enabled = [[NSUserDefaults standardUserDefaults] boolForKey:@"enabled_preference"];
    NSLog(@"Enabled:%d", (int)enabled);
    float    slider = [[NSUserDefaults standardUserDefaults] floatForKey:@"slider_preference"];
    NSLog(@"Slider:%f", slider);
}

_ 関連情報

Preferences and Settings Programming Guide
Apple Developerサイトの情報。

2012-02-22 [iOS]座標と描画(その4)

前回の例で、ビットマップ・コンテキストを利用していたが、Retina displayとなった現在では、以下の方法がお勧めだ。

前回では、CoreGraphicsの関数を使ってビットマップ・コンテキストを生成して、そこに描画していた。

- (void)drawRect:(CGRect)rect
{
    CGContextRef    context = UIGraphicsGetCurrentContext();
    
    /* LLO(lower-left-origin) */
    size_t  witdh = rect.size.width;
    size_t  height = rect.size.height;
    size_t  bytesPerRow = witdh * 4;
    bytesPerRow = COMPUTE_BEST_BYTES_PER_ROW(bytesPerRow);
    unsigned char   *rasterData = calloc(1, bytesPerRow * height);
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    CGContextRef    bitmapContext = CGBitmapContextCreate(rasterData, witdh, height,
                                                8, bytesPerRow,
                                                colorSpace,
                                                kCGImageAlphaPremultipliedLast);
    
    CGContextSetRGBStrokeColor(bitmapContext, 1.0, 0.0, 0.0, 1.0);
    CGContextSetLineWidth(bitmapContext, 4.0);
    CGContextBeginPath(bitmapContext);
    CGContextMoveToPoint(bitmapContext, 5.0, 25.0);
    CGContextAddLineToPoint(bitmapContext, 5.0, 5.0);
    CGContextDrawPath(bitmapContext, kCGPathStroke);
    CGContextMoveToPoint(bitmapContext, 5.0, 5.0);
    CGContextAddLineToPoint(bitmapContext, 25.0, 5.0);
    CGContextDrawPath(bitmapContext, kCGPathStroke);
CGImageRef cgimage = CGBitmapContextCreateImage(bitmapContext); CGContextDrawImage(context, rect, cgimage); CGContextRelease(bitmapContext); free(rasterData); CGColorSpaceRelease(colorSpace); }

iOS4からは、以下のように記述できる。

- (void)drawRect:(CGRect)rect
{
    CGContextRef    context = UIGraphicsGetCurrentContext();
    
    /* ULO(upper-left-origin) */
    UIGraphicsBeginImageContextWithOptions(rect.size, NO, 0.0);
    CGContextRef    bitmapContext = UIGraphicsGetCurrentContext();
    
    CGContextSetRGBStrokeColor(bitmapContext, 1.0, 0.0, 0.0, 1.0);
    CGContextSetLineWidth(bitmapContext, 4.0);
    CGContextBeginPath(bitmapContext);
    CGContextMoveToPoint(bitmapContext, 5.0, 25.0);
    CGContextAddLineToPoint(bitmapContext, 5.0, 5.0);
    CGContextDrawPath(bitmapContext, kCGPathStroke);
    CGContextMoveToPoint(bitmapContext, 5.0, 5.0);
    CGContextAddLineToPoint(bitmapContext, 25.0, 5.0);
    CGContextDrawPath(bitmapContext, kCGPathStroke);
 
    UIImage*    uiimage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    [uiimage drawAtPoint:CGPointMake(0.0, 0.0)];
}

この方法だと、- (void)drawRect:(CGRect)rectメソッドの描画環境と座標系が同じとなる。


2012-02-23 [iOS]Image Masking(その3)

前回のクリッピングではCoreGraphicsの関数を使ったが、今回はUIKitのUIBezierPathを使ったクリップングに挑戦だ。

前回のCoreGraphicsの関数を使ったコードだ。

- (void)drawRect:(CGRect)rect
{
    CGContextRef    context = UIGraphicsGetCurrentContext();
    CGSize  imageSize = self.image.size;
    CGRect  imageRect = {10.0, 10.0, imageSize.width, imageSize.height};
    float   radius = 10.0;
    CGFloat minX = CGRectGetMinX(imageRect);
    CGFloat midX = CGRectGetMidX(imageRect);
    CGFloat maxX = CGRectGetMaxX(imageRect);
    CGFloat minY = CGRectGetMinY(imageRect);
    CGFloat midY = CGRectGetMidY(imageRect);
    CGFloat maxY = CGRectGetMaxY(imageRect);
    
    /* 現状の描画環境を保存 */
    CGContextSaveGState(context);
    
    /* 四角形の辺に接する、半径radiusの円弧を四隅に追加 */
    CGContextMoveToPoint(context, minX, midY);
    CGContextAddArcToPoint(context, minX, minY, midX, minY, radius);
    CGContextAddArcToPoint(context, maxX, minY, maxX, midY, radius);
    CGContextAddArcToPoint(context, maxX, maxY, midX, maxY, radius);
    CGContextAddArcToPoint(context, minX, maxY, minX, midY, radius);
    CGContextClosePath(context);
    
    /* 先ほどのパスをクリップ領域として設定 */
    CGContextClip(context);
    
    /* 描画 */
    [self.image drawAtPoint:CGPointMake(10.0, 10.0)];
    
    /* 描画環境を先ほどの保存時点に戻す */
    CGContextRestoreGState(context);
}

UIBezierPathを使うとこうなる。ちょっと、四隅が丸い四角の例なので、簡単なコードになってしまったが。

- (void)drawRect:(CGRect)rect
{
    CGContextRef    context = UIGraphicsGetCurrentContext();
    CGSize  imageSize = self.image.size;
    CGRect  imageRect = {10.0, 10.0, imageSize.width, imageSize.height};
    float   radius = 10.0;
    
    /* 現状の描画環境を保存 */
    CGContextSaveGState(context);
    
    /* 四角形の四隅を半径radiusの円弧に */
    UIBezierPath*   aPath = [UIBezierPath bezierPathWithRoundedRect:imageRect cornerRadius:radius];
    
    /* パスをクリップ領域として設定 */
    [aPath addClip];
    
    /* 描画 */
    [self.image drawAtPoint:CGPointMake(10.0, 10.0)];
   
    /* 描画環境を先ほどの保存時点に戻す */
    CGContextRestoreGState(context);
}

_ 関連情報

Programming with Quartz: 2D and PDF Graphics in Mac OS X
CoreGraphicsを解説した書籍。
Drawing and Printing Guide for iOS
iOS Developer Libraryの情報。

2012-02-24 WWDCノススメ

この時期になってくると気になるのは、今年のWWDCについてだ。

今年は、どうしましょう。行くべきでしょうね。

Cocoa勉強会でも、WWDCについての情報を発信しています。


2012-02-25 [Mac]談話と特殊効果(Text-to-Speech)

WWDCに行くと、どんな方向を目指しているのか感じ取る事が出来る。また、その場で理解できなくても、これは聞いた事があるな?と、調査のヒントを得られる。今回は、WWDCが解決の糸口になった例だ。

OS Xでテキストの内容をスピーチされる事は簡単だ。

Cocoaでは、

NSSpeechSynthesizer *synthesizer = [[NSSpeechSynthesizer alloc] init];
[synthesizer setDelegate:self];
[synthesizer startSpeakingString:@"Hello, world."];
 
- (void)speehSynthesizer:(NSSpeechSynthesizer *)sender
    didFinishSpeaking:(BOOL)finishedpeaking
{
    ...
}

Core Foundationでは、

SpeechChannel *chan;
err = NewSpeechChannel(NULL, &chan);
CFNumber *callback = CFNumberCreate(NULL, kCFNumberLongType, HighlightSpokenWord);
err = SetSpeechProperty(chan, kSpeechWordCFCallBack, callback);
err = SpeakCFString(chan, CFSTR("Hello, world."), NULL);
 
void HighlightSpokenWord(SpeechChannel chan,
    SRefCo refCon,
    CFStringRef aString,
    CFRange wordRange)
{
    ....
}

でも、それにAudio Unitを使った効果を適用させるには、どうすればいいのか?

どうして、これが出来ると思ったのかは今となっては思い出せないが、多分、WWDCでのセッションの内容が頭の片隅にあったのだろう。それで、WWDCのセッションのビデオを見直しのが、WWDC2009のSession 129『Text-to-Speech: Adventures with Alex』だった。

一度、解決の糸口をつかむと、後は芋づる式。ヘッダーファイルのコメントで説明されていた。

SpeechSynthesis.h
 
/*------------------------------------------*/
/* AudioUnit constants - new in 10.5        */
/*------------------------------------------*/
enum {
  kAudioUnitSubType_SpeechSynthesis = 'ttsp', /* kAudioUnitType_Generator */
  kAudioUnitProperty_Voice      = 3330, /* Get/Set (VoiceSpec)      */
  kAudioUnitProperty_SpeechChannel = 3331 /* Get (SpeechChannel)      */
};

指定した文章をSpeech Synthesisで喋らせ、それをAudio Unitのコンポーネントでディレイさせて再生させる例が以下だ。

AUNode	inputNode, effectNode, outputNode;
	
NewAUGraph(&_auGraph);
    
AudioComponentDescription	cd;
cd.componentType = kAudioUnitType_Generator;
cd.componentSubType = kAudioUnitSubType_SpeechSynthesis;
cd.componentManufacturer = kAudioUnitManufacturer_Apple;
cd.componentFlags = 0;
cd.componentFlagsMask = 0;
	
AUGraphAddNode(_auGraph, &cd, &inputNode);
	
cd.componentType = kAudioUnitType_Effect;
cd.componentSubType = kAudioUnitSubType_Delay;
AUGraphAddNode(_auGraph, &cd, &effectNode);
	
cd.componentType = kAudioUnitType_Output;
cd.componentSubType = kAudioUnitSubType_DefaultOutput;
AUGraphAddNode(_auGraph, &cd, &outputNode);
	
AUGraphConnectNodeInput(_auGraph, inputNode, 0, effectNode, 0);
AUGraphConnectNodeInput(_auGraph, effectNode, 0, outputNode, 0);
	
AUGraphOpen(_auGraph);
AUGraphInitialize(_auGraph);
    
AudioUnit	generateAudioUnit;
AUGraphNodeInfo(_auGraph, inputNode, NULL, &generateAudioUnit);
SpeechChannel	channel;
UInt32	sz = sizeof(SpeechChannel);
AudioUnitGetProperty(generateAudioUnit, kAudioUnitProperty_SpeechChannel,
					 kAudioUnitScope_Global, 0, &channel, &sz);
	
AUGraphStart(_auGraph);
	
SpeakCFString(channel, CFSTR("Hello, world."), NULL);

2012-02-26 [iOS][Web]ネイティブWebアプリケーション

WWDCで、最初のiPhoneの開発環境の紹介があった際は、Webアプリケーションのみを認めるという説明だったが不要だった為、その後、ネイティブ・アプリケーションが開発できるSDKがリリースされた。

とは云うものの、Webアプリケーションでも対応できる分野はあると思われるし、WebアプリケーションとネイティブAPIを組み合わせる手法も出来ているので、今回は、ネイティブなWebアプリケーションの初歩の初歩に挑戦してみた。

Single View Applicationのプロジェクトを新規生成する。

新規プロジェクト

ViewをUIWebViewに置換する。

WebView

HTMLコンテンツを置くフォルダをグループとして登録し、"index.html"を新規ファイルとして生成する。

webapp

ViewControllerに、アプリケーション内部のHTMLコンテンツを表示するコードを追加する。

- (void)viewDidLoad
{
    [super viewDidLoad];
     
    UIWebView   *webView = (UIWebView *)self.view;
    NSString    *path = [[NSBundle mainBundle] pathForResource:@"index" ofType:@"html"];
    NSURL   *fileURL = [[NSURL alloc] initFileURLWithPath:path];
    NSURLRequest    *req = [NSURLRequest requestWithURL:fileURL];
    [webView loadRequest:req];
}

実行。

run

まだ、これは初歩の初歩で、HTMLコンテンツの置き場所はバンドル・リソースでいいのか?とか、ディレクトリ構造を保持したまま、実行時の環境にコピーするには?とか課題があるが、それは次回で。

_ ソースコード

GitHubからどうぞ。
https://github.com/murakami/workbook/tree/master/ios/WebApp - GitHub

_ 関連情報

User Experience Coding How-To's
Apple Developerサイトの情報。
iPhone Webアプリケーション
Webアプリケーションの見本として参考になる。

2012-02-27 [iOS][Web]ネイティブWebアプリケーション(その2)

DocumentsディレクトリやLibraryディレクトリ配下への配置を考えたが、課題があるということで、バンドル・リソース内に配置する方法を試してみた。

実際にWebアプリケーションを組み込む状況を想定して、前回、用意してHTMLコンテンツが置かれているwebappフォルダ配下に、infoとaboutフォルダを追加して、このディレクトリ構造が保たれたまま、アクセスできる事を確認したいと思う。

dir

infoとaboutフォルダをXcodeのプロジェクトにドラッグ&ドロップする。すると、以下のシートが表示されるが、Distinationのチェックは外し、Foldersは2番目の"Create folder references for any added folders"を選択する。

dragdrop

するとプロジェクトでの表示は、以下となる。

proj

"webapp/index.html"の内容は以下のとおり。

<html>
    <head>
        <title>My WebApp</title>
    </head>
    <body>
        <p>My WebApp</p>
        <a href="info/index.html">info</a><br />
        <a href="about/index.html">about</a>
    </body>
</html>

"webapp/info/index.html"の内容は以下のとおり。

<html>
    <head>
        <title>Info</title>
    </head>
    <body>
        <p>Info</p>
    </body>
</html>

"webapp/about/index.html"の内容は以下のとおり。

<html>
    <head>
        <title>About</title>
    </head>
    <body>
        <p>About</p>
    </body>
</html>

実行。infoとaboutのリンクが機能するので、バンドル・リソース内でもディレクトリ構造は保持されている事が分かる。

webapp/index.html
webapp/info/index.html

_ ソースコード

GitHubからどうぞ。
https://github.com/murakami/workbook/tree/master/ios/WebApp - GitHub

_ 関連情報

File System Programming Guide
iOS標準ディレクトリのファイル保存場所が説明されている。

2012-02-28 [Mac][iOS]ZipArchive ファイルの圧縮と解凍

iOSはストレージ容量もメモリも節約を求められる。また、データサイズが小さくなる事によって、操作性の向上も期待できる。
その為にキーとなる事の一つにデータの圧縮と解凍があるが、今回は、Cocoaでzipデータを扱う初歩的に事に挑戦する。

業務上パスワード付きzip圧縮ファイルを受け取る事が多いのだが、何時もこれを解凍するのをコマンドラインでおこなっていた。

$ unzip -P パスワード ファイル.zip

これをAppStore時代では存在が小さくなってしまったが、Dropletsとして実装してみよう。

Mac OS XのCocoa Applicationを新規作成し、TARGETSの設定のDocument TypesにZip文書を追加する。

Document Types

アプリケーションのデリゲートに、以下のコードを追加する。

- (BOOL)application:(NSApplication *)theApplication openFile:(NSString *)filename
{
    DBGMSG(@"%s, filename(%@)", __func__, filename);
    return NO;
}

これでドラッグ&ドロップされた一つのファイルのパスが取得できるようになった。

次に、ZipArchiveライブラリを以下のサイトから入手する。

入手したZipArchiveライブラリをプロジェクトに組み込み、ZipArchive.hでUIKit.hをインクルードしているのでコメントアウトし、

ZipArchive.h

ZipArchive.mmをARCの対象から外す。

ZipArchive.mm

そして、かなり手抜きをしているが、zipファイルが渡されると、末尾の"zip"サフィクスを削るコードを先ほどのメソッドに追加する。

#import "ZipArchive.h"
        :
- (BOOL)application:(NSApplication *)theApplication openFile:(NSString *)filename
{
    ZipArchive  *archiver = [[ZipArchive alloc] init];
    [archiver UnzipOpenFile:filename];
    NSString    *path = [filename stringByDeletingPathExtension];
    [archiver UnzipFileTo:path overWrite:NO];
    [archiver UnzipCloseFile];
    return YES;
}
        :

_ ソースコード

GitHubからどうぞ。
https://github.com/murakami/workbook/tree/master/mac/UnZip - GitHub

_ 関連情報

ZipArchive
An Objective C class for zip/unzip on iPhone and Mac OSX

2012-02-29 [Mac][iOS]minizip ファイルの圧縮と解凍

前回のZipArchiveライブラリが内部で利用していたminizipライブラリを直接利用する事に挑戦する。

minizipを直接利用する利点は、複数のファイルが固められて圧縮されていた場合、全てを解凍するのではなくて、必要なファイルのみにアクセスする事が可能になる事だ。

以下のMinizipのサイトからminizipライブラリ一式をダウンロードする。

minizip一式には、コマンドラインのプログラム用のソースファイルが含まれているので、ライブラリとして使用する場合に必要となる、以下のファイルのみをプロジェクトに追加する。

crypt.h
ioapi.c
ioapi.h
mztools.c
mztools.h
unzip.c
unzip.h
zip.c
zip.h

プロジェクトに以下のライブラリを追加する。

libz.dylib

文書オープンのダイアログで指定したzipファイルの中身を出力するコードを追加する。

@interface AppDelegate : NSObject 
        :
- (IBAction)openDocument:(id)sender;
@end
#import "unzip.h"
        :
@interface AppDelegate ()
- (void)unzip:(NSString *)path;
@end
 
@implementation AppDelegate
        :
- (IBAction)openDocument:(id)sender
{
    DBGMSG(@"%s", __func__);
    NSOpenPanel *panel = [NSOpenPanel openPanel];
    [panel beginSheetModalForWindow:self.window
                  completionHandler:^(NSInteger returnCode){
        NSURL       *pathToFile = nil;
        NSString    *path = nil;
         
        if (returnCode == NSOKButton) {
            pathToFile = [[panel URLs] objectAtIndex:0];
            path = [pathToFile path];
            DBGMSG(@"%@", pathToFile);
            DBGMSG(@"%@", path);
            dispatch_async(dispatch_get_main_queue(), ^{
                [self unzip:path];
            });
        }
    }];
}
 
- (void)unzip:(NSString *)path
{
    DBGMSG(@"%s, %@", __func__, path);
    int error = UNZ_OK;
    unzFile file = NULL;
    file = unzOpen([path UTF8String]);
    unzGoToFirstFile(file);
    while (error == UNZ_OK) {
        unz_file_info   fileInfo;
        char            filename[PATH_MAX];
        unzGetCurrentFileInfo(file, &fileInfo, filename, PATH_MAX, NULL, 0, NULL, 0);
        DBGMSG(@"%s", filename);
        error = unzGoToNextFile(file);
    }
    unzClose(file);
}
@end

以下のディレクトリ構造のフォルダをzipで圧縮する。


junk
 | file01.txt
 | file02.txt
 +- folder01
 |    file11.txt
 |    file12.txt
 +- folder02
      file21.txt
      file22.txt

これを先ほどのコードでディレクトリ構造をデバッグ出力した結果の抜粋が以下。

junk/
junk/file01.txt
junk/file02.txt
junk/folder01/
junk/folder01/file11.txt
junk/folder01/file12.txt
junk/folder02/
junk/folder02/file21.txt
junk/folder02/file22.txt

_ ソースコード

GitHubからどうぞ。
https://github.com/murakami/workbook/tree/master/mac/Zip

_ 関連情報

zlib
A Massively Spiffy Yet Delicately Unobtrusive Compression Library
Minizip
Zip and UnZIp additionnal library
ZipArchive
An Objective C class for zip/unzip on iPhone and Mac OSX
objective-zip
An iOS wrapper for ZLib and MiniZip
Objective-CでZIPアーカイブを読み取る
@marvelphさんのブログです。

トップ 最新 追記