at_yasu's blog

ロード的なことを

CocoaBindingを使って画像管理ソフトを作成

ちと必要になったのと、復習がてらCoreData + CocoaBindingを使って、画像管理ソフトを作りましたのでそのメモ。

ざっくりしたいこと

まんまですが、本タイトルを入力し、既存のファイルをドラッグアンドドロップすると画像が登録されるというシンプルな物です。

大量に入れた時は止まったようになったり、終了が何か遅かったりしますが気にせず。一日やっつけでやった物ですし。

新規作成

Xcode で新規プロジェクトを作ります。「use Core Data for storage」にチェックは必ずして下さい。プロジェクト名前は任意で(^^;

ちなみに私は、「ImagePending」という名前にしました。


Core Data モデル

まずCoreDataのモデル作成。全部そこで一元管理。


Books エンティティが大本になり、Imagesエンティティが画像一覧テーブルになります。

プロパティ タイプ
Books.name String
Images.image Blob

Window設計

ちとややこしい部分です。かなりおおざっぱに解説してます。

CoreDataのモデルを、Optキーを押しながらWindow上に持って行けばそれに対応するパーツを自動で作ってくれます。今回はそれを使います。

ただし、一度に二つとも一度にすると面倒な事になるので、一つずつします。

Booksのパーツ作成

一覧だけが欲しいので、BooksだけOptキー押しながら、Window上へ。文字列の一覧だけなので、「Master/Detail View」を選択して作成します。Boxパーツは不要だと思っている性分なので、中のテーブルだけ引っこ抜いて、Boxは消しました。

この時に、Add/removeボタンを付けておくように。これは、手動で付けてもいいですし、「Master/Detail View」を選択する時にあるチェックボックスにチェックを入れて作成してもかまいません。

画像一覧パーツ作成

これも同様にOptキーを押しながら、Window上へ。これは画像一覧なので、「Collection View」を選択。同様に、必要な部分だけを引っこ抜き、Boxパーツは私は消しました。

そうすると、xibの一覧には、「Images Array Controller」や「View」や「Collection View Item」や「Books Array Controller」など色々増えました。

さてこれで期待通りに動くかと言ったら動きません。事実上、HMDT Third Editionのp270(6-5複雑なユーザインターフェースの作成 - 3ペインインターフェース)の応用です。




現在、二つのArray Controller がそれぞれ、ManagedObjectContextを見にいっており、連結していない状態です。ですので、どっちかがどっちかのデータソースを見るようにしたらいい訳です。


結論的には、Booksパーツで選んだアイテム一覧を、Imagesパーツへ表示するようにしたらいいのです。

上の一文は具体的には間違いで、『「Images」は「Books」で選ばれたアイテムのImageを表示するようにしたらいい』です。


→のスナップショットは、Images Array Controllerの設定です。参考にしてください。


ここまでやって出来る事。

さて、ここまでやって出来る事は、本のNameを追加したり削除する程度です。


次は、Finderからドラッグアンドドロップを受け付けて、画像をがさがさほおり込む部分を作成です。


カスタムNSViewを作成 - IPView

ドラッグアンドドロップに対応するカスタムNSViewを作ります。名前は、「IPView」としました。なお、managedObjectを持つAppDelegateのクラス名は「ImagePending_AppDelegate」としており、xibファイル内でリンクしている状態にしております。

この、IPViewの場所は悩みます。このViewにドラッグして来たアイテムを読み込むので、上のレイヤーにパーツがあると、うまく反応しません。

私は、私的利用で困らないだろうと思い、標準で付いてくるNSWindowの直下にあるNSViewにこのIPViewを割り当てました。この場所だと、Collectionなどに邪魔されますが、まぁ問題ないです*1(^^;;

ソースコードは長いので↓の方に割合。

データへ投入

IPView では足りない部分があり、実際にデータが投入されません。では、そのメソッドを、AppDelegateへ書き足します。

継ぎ足したメソッド
- (void) addImage:(NSImage*)image
          forBook:(Books*)object
{
    Images* img = [[Images alloc] initWithEntity:[[self.managedObjectModel
                                                   entitiesByName]
                                                  objectForKey:@"Images"]
                  insertIntoManagedObjectContext:self.managedObjectContext];
    
    img.image = [image TIFFRepresentation];
    img.book = object;
    [object addImagesObject:img];
    [img release];
}

以上完了

以上で終了です。

おそらく↓のような感じで動きます。



さてこのアプリにはいくつかの問題点や改良点があります。

  • 大量にデータを入れると、動作が遅くなる。
    • 手元のアプリで試した所、100枚近く入れただけで、500Mもメモリーを食い始めました。
  • フィールドが少なすぎる。増やしたい。
    • 日時などを入れたい場合がありますよね。
  • サムネイル表示と、元データの表示を切り分けたい。
    • 画像データが多すぎると、選択を切り替えた時のフェードがかなり遅いです。画質を落としたサムネイルを表示させておくと、おそらく速くなるかと。
  • 画像登録遅い
    • これは、Threadを使うべきでは?

などなどあります。それぞれの解決法はこの記事内にあります*2。改良などは頑張ってください。(^^;)

Source

IPView.h
//
//  IPView.h
//  ImagePending
//
//  Created by 安井 惇 on 10/03/09.
//  Copyright 2010 Apple Inc. All rights reserved.
//

#import <Cocoa/Cocoa.h>

@class ImagePending_AppDelegate;


@interface IPView : NSView
{
    IBOutlet NSArrayController* books;
    IBOutlet ImagePending_AppDelegate * appdelegate;
}

@end
IPView.m
//
//  IPView.m
//  ImagePending
//
//  Created by 安井 惇 on 10/03/09.
//  Copyright 2010 Apple Inc. All rights reserved.
//

#import "IPView.h"
#import "ImagePending_AppDelegate.h"

@implementation IPView

- (id)initWithFrame:(NSRect)frame {
    self = [super initWithFrame:frame];
    if (self) {
        // Initialization code here.
    }
    return self;
}

- (void)drawRect:(NSRect)dirtyRect {
    // Drawing code here.
    NSArray* array = [NSArray arrayWithObjects:
                      NSFilenamesPboardType,nil];
    [self registerForDraggedTypes:array];
}

// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
- (void) addFile:(NSString*)filepath
{
    NSImage* img = [[NSImage alloc] initWithContentsOfFile:filepath];
    if (img)
    {
        NSArray* selects = [books selectedObjects];
        if ([selects count])
        {
            NSLog(@"append file %@", img);
            Books* b = [selects objectAtIndex:0];
            [appdelegate addImage:img forBook:b];
        }
    }
}


// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

- (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
{
    NSPasteboard *pboard = [sender draggingPasteboard];
    NSLog(@"pboard: %@",pboard);
    if ( [[pboard types] containsObject:NSFilenamesPboardType] ) {
        NSArray *files = [pboard propertyListForType:NSFilenamesPboardType];
        NSLog(@"files: %@",files);
        
        for (NSString* str in files)
        {
            [self addFile:str];
        }
        
        // Perform operation using the list of files
        [appdelegate saveAction:self];
    }
    return YES;
}

- (NSDragOperation)draggingEntered:(id < NSDraggingInfo >)sender
{
    NSLog(@"draggingEntered");
    return NSDragOperationGeneric;
}

- (NSDragOperation)draggingUpdated:(id < NSDraggingInfo >)sender
{
    NSLog(@"draggingUpdated");
    return NSDragOperationGeneric;
}

@end

*1:なお、やるとしたら、透過レイヤーにしてwindow内一杯に広げるという方法があります。

*2:間接的表記有り