22、项目开发:添加框架、创建控制器与管理对象类

项目开发:添加框架、创建控制器与管理对象类

在项目开发过程中,我们需要完成多个关键步骤,包括添加框架、创建窗口控制器、视图控制器以及管理对象类。下面将详细介绍这些步骤的操作方法和代码实现。

1. 添加 Quartz 框架

本项目会使用 Mac OS X 的 ImageKit 框架中的 IKImageBrowserView 类。在使用该类之前,需要先将 ImageKit 框架添加到项目中,而 ImageKit 框架实际上是更大的 Quartz 框架的子框架。

操作步骤
  1. 在项目中右键点击(或按住 Control 点击) Frameworks → Linked Frameworks 组。
  2. 选择 Add → Existing Frameworks
  3. 从列表中选择 Quartz.framework 并点击 Add

添加完成后,在任何头文件或实现文件中包含 Quartz.h 文件,就可以使用 Quartz 框架中的任何类(包括 ImageKit 中的类)。

2. 创建窗口控制器

窗口控制器类将管理项目的所有视图控制器,每个视图控制器将包含用户界面的不同部分。这里会创建一个窗口控制器类及其对应的 XIB 文件,以及三个视图控制器类,每个类都有自己的 XIB 文件。

操作步骤
  1. 创建一个新的 Cocoa 类,超类为 NSWindowController ,命名为 CBMainWindow.m
  2. 替换 CBMainWindow.h 头文件的内容为以下代码:
#import <Cocoa/Cocoa.h>
#import <Quartz/Quartz.h>
@interface CBMainWindow : NSWindowController
// outlets.
@property (retain) IBOutlet NSSegmentedControl*  viewSelectionControl;
// view management properties.
@property (retain) NSMutableDictionary* viewControllers;
@property (assign) NSViewController*    currentViewController;
@property (copy)   NSArray*             controllerNamesByIndex;
// view management methods.
- (IBAction) viewSelectionDidChange:(id)sender;
- (void) activateViewController: (NSViewController*)controller;
- (NSViewController*) viewControllerForName: (NSString*)name;
@end
  1. 打开 CBMainWindow.m 实现文件,输入以下代码:
#import "CBMainWindow.h"
// the 'static' means these are only visible in this file.
static const NSInteger BrowserViewIndex = 0;
static const NSInteger EditorViewIndex  = 1;
static const NSInteger ListViewIndex    = 2;
// names for each view.
static NSString* const CBBrowserViewName = @"CBBrowserView";
static NSString* const CBEditorViewName  = @"CBEditorView";
static NSString* const CBListViewName    = @"CBListView";
@implementation CBMainWindow
// view modes.
@synthesize viewSelectionControl;
@synthesize viewControllers;
@synthesize currentViewController;
@synthesize controllerNamesByIndex;
- (void) loadWindow {
    [super loadWindow];
    self.viewControllers = [NSMutableDictionary dictionary];        
    // match up indexes to names.
    NSMutableArray* names = [NSMutableArray array];
    [names insertObject:CBBrowserViewName atIndex:BrowserViewIndex];
    [names insertObject:CBEditorViewName  atIndex:EditorViewIndex];
    [names insertObject:CBListViewName    atIndex:ListViewIndex];
    self.controllerNamesByIndex = names;

    // start on browser mode.
    NSViewController* initial;
    initial = [self viewControllerForName:CBBrowserViewName];
    [self activateViewController:initial];
}
- (IBAction) viewSelectionDidChange:(id)sender {
    // find requested view controller.
    NSInteger selection       = [sender selectedSegment];
    NSArray*  names           = self.controllerNamesByIndex;
    NSString* controllerName  = [names objectAtIndex:selection];
    // load view controller.
    NSViewController* controller;
    controller = [self viewControllerForName:controllerName];    
    [self activateViewController:controller];
}
- (void) activateViewController: (NSViewController*)controller {
    NSArray*  names       = self.controllerNamesByIndex;    
    NSInteger segment     = self.viewSelectionControl.selectedSegment;

    NSString* targetName  = [controller className];
    NSInteger targetIndex = [names indexOfObject:targetName];

    // update segmented control.
    if ( segment != targetIndex )
        [self.viewSelectionControl setSelectedSegment:targetIndex];  

    // remove current view.
    [self.currentViewController.view removeFromSuperview];

    // set up new view controller.
    self.currentViewController = controller;
    [[self.window contentView] addSubview:controller.view];

    // adjust for window margin.
    NSWindow* window = self.window;
    CGFloat padding = [window contentBorderThicknessForEdge:NSMinYEdge];    
    NSRect frame    = [window.contentView frame];
    frame.size.height -= padding;
    frame.origin.y += padding;
    controller.view.frame = frame;
}
- (NSViewController*) viewControllerForName: (NSString*)name {
    // see if this view already exists.
    NSMutableDictionary* allControllers = self.viewControllers;
    NSViewController* controller = [allControllers objectForKey:name];
    if ( controller ) return controller;

    // create a new instance of the view.
    Class controllerClass = NSClassFromString( name );
    controller = [[controllerClass alloc] initWithNibName:name bundle:nil];
    [allControllers setObject:controller forKey:name];

    // use key-value coding to avoid compiler warnings.
    [controller setValue:self forKey:@"mainWindowController"];
    return [controller autorelease];
}
- (void) dealloc {
    self.viewSelectionControl   = nil;
    self.viewControllers        = nil;
    self.controllerNamesByIndex = nil;

    [super dealloc];
}
@end
  1. 在 Xcode 侧边栏中选择 Resources 组,选择 File → New File
  2. 在模板选择窗口的 Mac OS X 部分选择 “User Interface” 组,然后选择 Window XIB 并点击 Next ,命名文件为 CBMainWindow.xib
  3. 更新 Gallery_AppDelegate.m 以使用新的窗口控制器,添加以下 -applicationDidFinishLaunching: 方法的实现:
- (void)applicationDidFinishLaunching:(NSNotification *)note {
    CBMainWindow* windowController;
    windowController = [[CBMainWindow alloc] initWithWindowNibName:@"CBMainWindow"];
    [windowController showWindow:nil];
    self.mainWindowController = windowController;
    [windowController release];
}

2. 创建视图控制器

接下来创建定义应用程序用户界面的三个视图,每个视图都有单独的 XIB 文件和对应的视图控制器。

2.1 浏览器视图控制器

浏览器视图是应用程序启动时用户看到的第一个界面,左侧显示相册列表,右侧主照片网格显示所选相册中的所有照片。照片网格视图是 IKImageBrowserView 的实例,用户可以将图像从 Finder 拖放到浏览器视图中,图像将自动添加到 Core Data 存储中。

操作步骤
  1. 创建一个新的 Cocoa 类,超类为 NSObject ,命名为 CBBrowserView.m
  2. 替换 CBBrowserView.h 头文件的内容为以下代码:
#import <Cocoa/Cocoa.h>
#import <Quartz/Quartz.h>
@class CBMainWindow;
@interface CBBrowserView : NSViewController <NSTableViewDelegate>
// parent window.
@property (assign) CBMainWindow* mainWindowController;
// xib items.
@property (retain) IBOutlet IKImageBrowserView* imageBrowser;
@property (retain) IBOutlet NSTableView*        albumsTable;
@property (retain) IBOutlet NSArrayController*  albumsArrayController;
@property (retain) IBOutlet NSArrayController*  imagesArrayController;
// additional values.
@property (retain) NSArray* imagesSortDescriptors;
@property (assign) CGFloat  thumbnailScale;
@end
  1. 打开 CBBrowserView.m 实现文件,输入以下代码:
#import "CBBrowserView.h"
#import "CBPhoto.h"
#import "CBAlbum.h"
#import "CBEditorView.h"
#import "CBMainWindow.h"
@interface CBBrowserView (Private)
- (void) setupImageBrowser;
- (void) updateSortOrderForObjects:(NSArray*)items;
@end
@implementation CBBrowserView
@synthesize mainWindowController;
@synthesize imageBrowser;
@synthesize albumsTable;
@synthesize albumsArrayController;
@synthesize imagesArrayController;
@synthesize imagesSortDescriptors;
@synthesize thumbnailScale;
- (void) loadView {
    [super loadView];

    NSSortDescriptor* sort;
    sort = [NSSortDescriptor sortDescriptorWithKey: @"orderIndex"
                                         ascending: YES];

    self.imagesSortDescriptors = [NSArray arrayWithObject:sort];    
    self.albumsTable.delegate  = self;
    [self setupImageBrowser];
}
- (void)tableViewSelectionDidChange:(NSNotification *)notification {
    NSTableView* table     = [notification object];
    NSInteger    selection = table.selectedRow;
    NSArray*     albums    = [self.albumsArrayController arrangedObjects];
    CBAlbum* album = [albums objectAtIndex:selection];
    [[NSApp delegate] setValue:album forKey:@"selectedAlbum"];
}
#pragma mark -
#pragma mark Image Browser
- (void) imageBrowser: (IKImageBrowserView *)browser
         cellWasDoubleClickedAtIndex: (NSUInteger)index {
    NSArray* visiblePhotos = [self.imagesArrayController arrangedObjects];
    CBPhoto* photo         = [visiblePhotos objectAtIndex:index];

    CBMainWindow* window = self.mainWindowController;
    id editor = [window viewControllerForName:@"CBEditorView"];

    if ( [editor isKindOfClass:[CBEditorView class]] )
        [(CBEditorView*)editor editPhoto:photo];
}
- (BOOL)performDragOperation:(id <NSDraggingInfo>)sender {
    IKImageBrowserView* browser = self.imageBrowser;

    NSPasteboard* pboard    = [sender draggingPasteboard];
    NSUInteger    dropIndex = [browser indexAtLocationOfDroppedItem];
    NSArray*      photos    = self.imagesArrayController.arrangedObjects;

    // indexes to place photos.
    NSMutableIndexSet* indexSet = [NSMutableIndexSet indexSet];
    [indexSet addIndex:dropIndex];

    // the move might be within the view.
    if ( [sender draggingSource] == browser ) {
        NSIndexSet*     selected        = browser.selectionIndexes;
        NSArray*        draggingItems   = [photos objectsAtIndexes:selected];
        NSMutableArray* reorderedItems  = [photos mutableCopy];

        [reorderedItems removeObjectsInArray:draggingItems];

        NSUInteger newDropIndex = dropIndex;        
        NSUInteger index        = 0;
        NSUInteger firstIndex   = selected.firstIndex;

        for ( index = firstIndex; index != NSNotFound;
                index = [selected indexGreaterThanIndex:index] ) {
            if ( index < dropIndex )
                newDropIndex -= 1;
            else
                break;
        }

        NSRange     dropRange   = NSMakeRange( newDropIndex, draggingItems.count );
        NSIndexSet* dropIndexes = [NSIndexSet indexSetWithIndexesInRange:dropRange];

        [reorderedItems insertObjects:draggingItems atIndexes:dropIndexes];
        [self updateSortOrderForObjects:reorderedItems];
        [reorderedItems release];
        return YES;
    }

    NSMutableArray* newItems = [NSMutableArray array];
    CBAlbum* album = [[NSApp delegate] valueForKey:@"selectedAlbum"];

    // get list of files.
    NSArray*  fileNames  = [pboard propertyListForType:NSFilenamesPboardType];
    NSInteger indexCount = 0;    
    if ( fileNames.count < 1 ) return NO;    

    for ( NSString* file in fileNames ) {
        CBPhoto* newItem  = [CBPhoto photoInDefaultContext];
        newItem.filePath  = file;
        [newItems addObject:newItem];
        [indexSet addIndex: dropIndex+indexCount];
        newItem.album = album;
        indexCount++;
    }

    NSMutableArray* array = [photos mutableCopy];
    [array insertObjects:newItems atIndexes:indexSet];    
    [self updateSortOrderForObjects:array];
    [array release];

    return YES;
}
- (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender {
    return NSDragOperationCopy;
}
- (NSDragOperation)draggingUpdated:(id <NSDraggingInfo>)sender {
    return NSDragOperationCopy;
}
#pragma mark -
#pragma mark Private
- (void) setupImageBrowser {
    IKImageBrowserView* browser = self.imageBrowser;
    browser.draggingDestinationDelegate = self;
    browser.delegate       = self;
    browser.cellsStyleMask = (IKCellsStyleShadowed|IKCellsStyleTitled);
    browser.zoomValue      = 0.55;

    // base attributes.
    NSFont*  font         = [NSFont systemFontOfSize:11];
    NSColor* textColor    = [NSColor colorWithCalibratedWhite:0.8 alpha:1.0];
    NSColor* textColorAlt = [NSColor colorWithCalibratedWhite:1.0 alpha:1.0];
    NSColor* background   = [NSColor colorWithCalibratedWhite:0.2 alpha:1.0];

    // text attributes.
    NSMutableDictionary* attr;
    attr = [NSMutableDictionary dictionary];
    [attr setObject:textColor forKey:NSForegroundColorAttributeName];
    [attr setObject:font forKey:NSFontAttributeName];

    // selected text attributes.
    NSMutableDictionary* attrAlt;
    attrAlt = [NSMutableDictionary dictionary];
    [attrAlt setObject:textColorAlt forKey:NSForegroundColorAttributeName];
    [attrAlt setObject:font forKey:NSFontAttributeName];

    // set text attributes.
    [browser setValue: attr
               forKey: IKImageBrowserCellsTitleAttributesKey];
    [browser setValue: attrAlt
               forKey: IKImageBrowserCellsHighlightedTitleAttributesKey];
    [browser setValue: background
               forKey: IKImageBrowserBackgroundColorKey];
}
- (void) updateSortOrderForObjects:(NSArray*)items {
    NSMutableArray* arrangedItems = [NSMutableArray array];
    NSInteger       orderIndex    = 0;

    for ( CBPhoto* item in items ) {        
        // only do each item once.
        if ( [arrangedItems containsObject:item] ) continue;        
        item.orderIndex = [NSNumber numberWithInteger:orderIndex];
        [arrangedItems addObject:item];
        orderIndex++;
    }

    // reload the array controller.
    [self.imagesArrayController rearrangeObjects];
}
- (void) dealloc {
    self.imageBrowser           = nil;
    self.albumsTable            = nil;
    self.albumsArrayController  = nil;
    self.imagesArrayController  = nil;
    self.imagesSortDescriptors  = nil;

    [super dealloc];
}
@end
  1. 在 Xcode 侧边栏中选择 Resources 组,选择 File → New File
  2. 在模板选择窗口的 Mac OS X 部分选择 “User Interface” 组,然后选择 View XIB 并点击 Next ,命名文件为 CBBrowserView.xib

2.2 编辑器视图控制器

编辑器视图使用 IKImageView 类,允许用户查看和编辑图像。双击编辑器视图中的图像可以调出调整面板。由于本项目仅用于演示,编辑视图中的更改不会保存回文件,但可以看到其功能。

操作步骤
  1. 创建一个新的 Cocoa 类,超类为 NSObject ,命名为 CBEditorView.m
  2. 替换 CBEditorView.h 头文件的内容为以下代码:
#import <Cocoa/Cocoa.h>
#import <Quartz/Quartz.h>
@class CBMainWindow;
@class CBPhoto;
@interface CBEditorView : NSViewController
@property (assign) CBMainWindow* mainWindowController;
@property (retain) IBOutlet IKImageView* imageView;
- (void) editPhoto: (CBPhoto*)photo;
@end
  1. 打开 CBEditorView.m 实现文件,输入以下代码:
#import "CBEditorView.h"
#import "CBPhoto.h"
#import "CBMainWindow.h"
@implementation CBEditorView
@synthesize mainWindowController;
@synthesize imageView;
- (void) loadView {
    [super loadView];
    [self.imageView setImageWithURL:nil];
}
- (void) editPhoto: (CBPhoto*)photo {
    if ( self.view == nil ) [self loadView];
    NSURL* url = [NSURL fileURLWithPath:photo.filePath];
    [self.imageView setImageWithURL:url];
    [self.mainWindowController activateViewController:self];
}
- (void) dealloc {
    self.imageView = nil;
    [super dealloc];
}
@end
  1. 在 Xcode 侧边栏中选择 Resources 组,选择 File → New File
  2. 在模板选择窗口的 Mac OS X 部分选择 “User Interface” 组,然后选择 View XIB 并点击 Next ,命名文件为 CBEditorView.xib

2.3 列表视图控制器

列表视图是最后一个视图,以表格视图形式显示所有相册中的照片列表。双击照片可在编辑器视图中打开。

操作步骤
  1. 创建一个新的 Cocoa 类,超类为 NSObject ,命名为 CBListView.m
  2. 替换 CBListView.h 头文件的内容为以下代码:
#import <Cocoa/Cocoa.h>
@class CBMainWindow;
@interface CBListView : NSViewController
@property (assign) CBMainWindow* mainWindowController;
@property (retain) IBOutlet NSTableView* imagesTable;
@property (retain) IBOutlet NSArrayController* imagesArrayController;
- (IBAction) tableViewItemDoubleClicked:(id)sender;
@end
  1. 打开 CBListView.m 实现文件,输入以下代码:
#import "CBListView.h"
#import "CBMainWindow.h"
#import "CBPhoto.h"
#import "CBEditorView.h"
@implementation CBListView
@synthesize mainWindowController;
@synthesize imagesTable;
@synthesize imagesArrayController;
- (void) loadView {
    [super loadView];
    self.imagesTable.target       = self;
    self.imagesTable.doubleAction = @selector(tableViewItemDoubleClicked:);
}
- (IBAction) tableViewItemDoubleClicked:(id)sender {
    NSInteger row           = self.imagesTable.clickedRow;
    NSArray*  visiblePhotos = [self.imagesArrayController arrangedObjects];
    CBPhoto*  photo         = [visiblePhotos objectAtIndex:row];

    CBMainWindow* window = self.mainWindowController;
    id editor = [window viewControllerForName:@"CBEditorView"];

    if ( [editor isKindOfClass:[CBEditorView class]] )
        [(CBEditorView*)editor editPhoto:photo];
}
- (void) dealloc {
    self.imagesTable           = nil;
    self.imagesArrayController = nil;
    [super dealloc];
}
@end
  1. 在 Xcode 侧边栏中选择 Resources 组,选择 File → New File
  2. 在模板选择窗口的 Mac OS X 部分选择 “User Interface” 组,然后选择 View XIB 并点击 Next ,命名文件为 CBListView.xib

3. 创建管理对象类

在项目中,我们可以直接使用 NSManagedObject 实例,因为与其关联的实体包含了所有可存储数据的信息。但是,使用通用的 NSManagedObject 类时,不能直接调用像 -firstName -setFirstName: 这样的 getter 和 setter 方法来更改其值,而是需要使用通用的键值编码方法。

3.1 键值编码方法示例

NSManagedObjectContext* context = self.managedObjectContext; 
NSManagedObject* photo;
photo = [NSEntityDescription insertNewObjectForEntityForName: @"Photo" 
                                      inManagedObjectContext: context];
[photo setValue: @"/Library/Desktop Pictures/Nature/Earth.jpg" 
         forKey: @"filePath"];
NSLog( @"photo filePath: %@", [photo valueForKey:@"filePath"] ); 

上述代码的执行结果在控制台显示为:

photo filePath: /Library/Desktop Pictures/Nature/Earth.jpg

3.2 代码解释

  1. 获取应用程序委托的 NSManagedObjectContext 实例,用于获取现有管理对象、创建新的管理对象或保存数据更改。
  2. 使用名为 Photo 的实体创建一个新的 NSManagedObject ,即使使用的是通用的 NSManagedObject 类,其可存储的数据由实体定义。
  3. 使用键值编码的 -setValue:forKey: 方法设置管理对象的值,键为 filePath ,该属性在图形模型编辑器中定义。
  4. 使用键值编码的 -valueForKey: 方法检索值,键同样为 filePath

3.3 创建自定义子类

为了更方便地操作管理对象,我们可以为每个实体类型创建 NSManagedObject 的自定义子类。这样可以声明属性、直接调用方法,并添加支持特定功能的方法,同时编译器可以进行类型检查,确保不会将 NSNumber 传递给定义为字符串的属性。

3.3.1 实现照片类

创建两个新的 Objective-C 类,作为 NSObject 的子类,命名为 CBPhoto CBAlbum

操作步骤
  1. 替换 CBPhoto.h 头文件的内容为以下代码:
#import <Cocoa/Cocoa.h>
@class CBAlbum;
@interface CBPhoto : NSManagedObject
// attributes.
@property (retain) NSString* filePath;
@property (retain) NSString* uniqueID;
@property (retain) NSNumber* orderIndex;
// relationships.
@property (retain) CBAlbum*  album;
// non-modeled properties.
@property (readonly) NSImage* largeThumbnail;
// methods.
+ (id) photoInDefaultContext;
@end
  1. 替换 CBPhoto.m 实现文件的内容为以下代码:
#import "CBPhoto.h"
#import <Quartz/Quartz.h>
@interface CBPhoto ()
@property (retain) NSImage* thumbnail;
- (void) generateUniqueID;
@end
@implementation CBPhoto
// use 'dynamic' for Core Data properties.
@dynamic filePath;
@dynamic uniqueID;
@dynamic orderIndex;
@dynamic album;
// use 'synthesize' for normal properties.
@synthesize thumbnail;
+ (id) photoInDefaultContext {
    NSManagedObjectContext* context = [[NSApp delegate] managedObjectContext];

    CBPhoto* newItem;    
    newItem = [NSEntityDescription insertNewObjectForEntityForName:@"Photo"
                                            inManagedObjectContext:context];

    newItem.filePath = nil;

    return newItem;
}
- (NSImage*) largeThumbnail {
    // 'largeThumbnail' is used by the list view.

    if ( self.thumbnail ) return self.thumbnail;

    NSSize      size  = NSMakeSize( 250, 250 );
    CFStringRef path  = (CFStringRef) self.filePath;
    CFURLRef    url   = 
      CFURLCreateWithFileSystemPath( NULL, path, kCFURLPOSIXPathStyle, NO);

    // use QuickLook to generate a thumbnail of the image.
    CGImageRef  thumb = QLThumbnailImageCreate( NULL, url, size, nil );
    NSImage*    image = [[NSImage alloc] initWithCGImage:thumb size:size];
    self.thumbnail = image;
    CFRelease( url );
    CFRelease( thumb );
    [image release];

    return image;
}
#pragma mark -
#pragma mark Core Data Methods
- (void) awakeFromInsert {
    // called when the object is first created.
    [self generateUniqueID];
}
#pragma mark -
#pragma mark 'IKImageBrowserItem' Protocol Methods
-(NSString *) imageTitle {
    NSString* fullFileName = self.filePath.lastPathComponent;    
    return [fullFileName stringByDeletingPathExtension];
}
- (NSString*) imageUID {
    // return uniqueID if it exists.
    NSString* uniqueID = self.uniqueID;
    if ( uniqueID ) return uniqueID;
    [self generateUniqueID];
    return self.uniqueID;
}
- (NSString *) imageRepresentationType {
    return IKImageBrowserPathRepresentationType;
}
- (id) imageRepresentation {
    return self.filePath;
}
#pragma mark -
#pragma mark Private
- (void) generateUniqueID {
    NSString* uniqueID = self.uniqueID;
    if ( uniqueID != nil ) return;
    self.uniqueID = [[NSProcessInfo processInfo] globallyUniqueString];
}
- (void) dealloc {
    // Core Data properties automatically managed.
    // Only release sythesized properties.
    self.thumbnail = nil;
    [super dealloc];
}
@end

CBPhoto 实现了 IKImageBrowserView 中的 IKImageBrowserItem 协议,该协议用于支持项目的显示,而不是提供需要实现的特定类。这是因为我们可能希望任何对象都能成为浏览器视图中的项目,而不仅仅是图像。例如,可以显示带有波形预览图标的声音文件。

3.3.2 IKImageBrowserItem 协议方法

@interface NSObject (IKImageBrowserItem)
- (NSString *) imageUID;                /* required */
- (NSString *) imageRepresentationType; /* required */
- (id)         imageRepresentation;     /* required */
- (NSUInteger) imageVersion;
- (NSString *) imageTitle;
- (NSString *) imageSubtitle;
- (BOOL)       isSelectable;
@end

3.3.3 实现相册类

操作步骤
  1. 替换 CBAlbum.h 头文件的内容为以下代码:
#import <Cocoa/Cocoa.h>
@interface CBAlbum : NSManagedObject
@property (retain) NSString* title;
@property (retain) NSSet*    photos;
+ (id) defaultAlbum;
+ (id) albumInDefaultContext;
@end
  1. 替换 CBAlbum.m 实现文件的内容为以下代码:
#import "CBAlbum.h"
@implementation CBAlbum
// use 'dynamic' for Core Data properties.
@dynamic title;
@dynamic photos;
+ (id) albumInDefaultContext {
    NSManagedObjectContext* context = 
      [[NSApp delegate] managedObjectContext];

    CBAlbum* newItem;    
    newItem = [NSEntityDescription insertNewObjectForEntityForName:@"Album"
                                            inManagedObjectContext:context];

    return newItem;
}
+ (id) defaultAlbum {
    NSManagedObjectContext* context = 
      [[NSApp delegate] managedObjectContext];
    NSEntityDescription* entity  
       = [NSEntityDescription entityForName: @"Album"
                     inManagedObjectContext: context];

    // create a fetch request to find 'Default' album.
    NSFetchRequest* fetch   = [[NSFetchRequest alloc] init];
    fetch.entity            = entity;
    fetch.predicate         
      = [NSPredicate predicateWithFormat:@"title == 'Default'"];

    // run fetch and make sure it succeeded.
    NSError* error = nil;
    NSArray* results = [context executeFetchRequest:fetch error:&error];
    [fetch release];
    if ( error ) {
        NSLog( @"error: %@", error );
        return nil;
    }

    // create the album if it doesn't exist.
    CBAlbum* album = nil;
    if ( results.count > 0 ) {
        album = [results objectAtIndex:0];
    } else {
        album = [self albumInDefaultContext];
    }
    return album;
}
@end

通过以上步骤,我们完成了项目开发中的关键部分,包括添加框架、创建控制器和管理对象类,为项目的进一步开发奠定了基础。在实际开发过程中,我们可以根据具体需求对代码进行调整和扩展,以实现更丰富的功能。

项目开发流程总结

步骤 操作内容
添加 Quartz 框架 右键点击 Frameworks → Linked Frameworks 组,选择 Add → Existing Frameworks ,添加 Quartz.framework
创建窗口控制器 创建 CBMainWindow.m 类,替换 CBMainWindow.h CBMainWindow.m 内容,创建 CBMainWindow.xib 文件,更新 Gallery_AppDelegate.m
创建视图控制器 分别创建浏览器、编辑器和列表视图控制器及其对应的 XIB 文件,替换相应的头文件和实现文件内容
创建管理对象类 创建 CBPhoto CBAlbum 类,替换相应的头文件和实现文件内容

项目开发流程图

graph LR
    A[添加 Quartz 框架] --> B[创建窗口控制器]
    B --> C[创建视图控制器]
    C --> D[创建管理对象类]
    C1[浏览器视图控制器] --> C
    C2[编辑器视图控制器] --> C
    C3[列表视图控制器] --> C
    D1[照片类] --> D
    D2[相册类] --> D

通过以上的操作步骤和代码实现,我们可以逐步完成项目的开发,确保各个部分的功能正常运行。在实际开发中,还可以根据具体需求对代码进行优化和扩展,以提升项目的性能和用户体验。

详细功能分析

窗口控制器功能

窗口控制器 CBMainWindow 在项目中起着管理视图控制器的核心作用。它通过 viewSelectionControl 来切换不同的视图控制器,实现了用户界面的动态切换。以下是其主要功能和操作流程:
1. 视图控制器初始化 :在 loadWindow 方法中,初始化了 viewControllers 字典,并将视图索引与视图名称进行匹配。默认情况下,启动时激活浏览器视图。

- (void) loadWindow {
    [super loadWindow];
    self.viewControllers = [NSMutableDictionary dictionary];        
    NSMutableArray* names = [NSMutableArray array];
    [names insertObject:CBBrowserViewName atIndex:BrowserViewIndex];
    [names insertObject:CBEditorViewName  atIndex:EditorViewIndex];
    [names insertObject:CBListViewName    atIndex:ListViewIndex];
    self.controllerNamesByIndex = names;

    NSViewController* initial;
    initial = [self viewControllerForName:CBBrowserViewName];
    [self activateViewController:initial];
}
  1. 视图切换 :当用户通过 viewSelectionControl 选择不同的视图时, viewSelectionDidChange 方法会根据用户的选择加载相应的视图控制器,并调用 activateViewController 方法来激活该视图。
- (IBAction) viewSelectionDidChange:(id)sender {
    NSInteger selection       = [sender selectedSegment];
    NSArray*  names           = self.controllerNamesByIndex;
    NSString* controllerName  = [names objectAtIndex:selection];
    NSViewController* controller;
    controller = [self viewControllerForName:controllerName];    
    [self activateViewController:controller];
}
  1. 激活视图控制器 activateViewController 方法负责更新 viewSelectionControl 的选中状态,移除当前视图,添加新的视图控制器的视图,并调整视图的位置和大小。
- (void) activateViewController: (NSViewController*)controller {
    NSArray*  names       = self.controllerNamesByIndex;    
    NSInteger segment     = self.viewSelectionControl.selectedSegment;

    NSString* targetName  = [controller className];
    NSInteger targetIndex = [names indexOfObject:targetName];

    if ( segment != targetIndex )
        [self.viewSelectionControl setSelectedSegment:targetIndex];  

    [self.currentViewController.view removeFromSuperview];

    self.currentViewController = controller;
    [[self.window contentView] addSubview:controller.view];

    NSWindow* window = self.window;
    CGFloat padding = [window contentBorderThicknessForEdge:NSMinYEdge];    
    NSRect frame    = [window.contentView frame];
    frame.size.height -= padding;
    frame.origin.y += padding;
    controller.view.frame = frame;
}
视图控制器功能
浏览器视图控制器

浏览器视图控制器 CBBrowserView 主要负责显示相册列表和照片网格,支持用户拖放图片到视图中,并将其添加到 Core Data 存储中。以下是其主要功能和操作流程:
1. 视图加载 :在 loadView 方法中,设置了照片排序描述符,并初始化了 imageBrowser

- (void) loadView {
    [super loadView];

    NSSortDescriptor* sort;
    sort = [NSSortDescriptor sortDescriptorWithKey: @"orderIndex"
                                         ascending: YES];

    self.imagesSortDescriptors = [NSArray arrayWithObject:sort];    
    self.albumsTable.delegate  = self;
    [self setupImageBrowser];
}
  1. 相册选择 :当用户选择不同的相册时, tableViewSelectionDidChange 方法会更新选中的相册。
- (void)tableViewSelectionDidChange:(NSNotification *)notification {
    NSTableView* table     = [notification object];
    NSInteger    selection = table.selectedRow;
    NSArray*     albums    = [self.albumsArrayController arrangedObjects];
    CBAlbum* album = [albums objectAtIndex:selection];
    [[NSApp delegate] setValue:album forKey:@"selectedAlbum"];
}
  1. 图片拖放 performDragOperation 方法处理用户拖放图片的操作,根据拖放的来源和目标位置,更新照片的排序和存储。
- (BOOL)performDragOperation:(id <NSDraggingInfo>)sender {
    IKImageBrowserView* browser = self.imageBrowser;

    NSPasteboard* pboard    = [sender draggingPasteboard];
    NSUInteger    dropIndex = [browser indexAtLocationOfDroppedItem];
    NSArray*      photos    = self.imagesArrayController.arrangedObjects;

    NSMutableIndexSet* indexSet = [NSMutableIndexSet indexSet];
    [indexSet addIndex:dropIndex];

    if ( [sender draggingSource] == browser ) {
        NSIndexSet*     selected        = browser.selectionIndexes;
        NSArray*        draggingItems   = [photos objectsAtIndexes:selected];
        NSMutableArray* reorderedItems  = [photos mutableCopy];

        [reorderedItems removeObjectsInArray:draggingItems];

        NSUInteger newDropIndex = dropIndex;        
        NSUInteger index        = 0;
        NSUInteger firstIndex   = selected.firstIndex;

        for ( index = firstIndex; index != NSNotFound;
                index = [selected indexGreaterThanIndex:index] ) {
            if ( index < dropIndex )
                newDropIndex -= 1;
            else
                break;
        }

        NSRange     dropRange   = NSMakeRange( newDropIndex, draggingItems.count );
        NSIndexSet* dropIndexes = [NSIndexSet indexSetWithIndexesInRange:dropRange];

        [reorderedItems insertObjects:draggingItems atIndexes:dropIndexes];
        [self updateSortOrderForObjects:reorderedItems];
        [reorderedItems release];
        return YES;
    }

    NSMutableArray* newItems = [NSMutableArray array];
    CBAlbum* album = [[NSApp delegate] valueForKey:@"selectedAlbum"];

    NSArray*  fileNames  = [pboard propertyListForType:NSFilenamesPboardType];
    NSInteger indexCount = 0;    
    if ( fileNames.count < 1 ) return NO;    

    for ( NSString* file in fileNames ) {
        CBPhoto* newItem  = [CBPhoto photoInDefaultContext];
        newItem.filePath  = file;
        [newItems addObject:newItem];
        [indexSet addIndex: dropIndex+indexCount];
        newItem.album = album;
        indexCount++;
    }

    NSMutableArray* array = [photos mutableCopy];
    [array insertObjects:newItems atIndexes:indexSet];    
    [self updateSortOrderForObjects:array];
    [array release];

    return YES;
}
编辑器视图控制器

编辑器视图控制器 CBEditorView 主要用于查看和编辑图片。通过 IKImageView 实现了图片的显示和基本编辑功能。
1. 视图加载 :在 loadView 方法中,初始化 imageView

- (void) loadView {
    [super loadView];
    [self.imageView setImageWithURL:nil];
}
  1. 图片编辑 editPhoto 方法根据传入的 CBPhoto 对象,加载相应的图片并激活该视图。
- (void) editPhoto: (CBPhoto*)photo {
    if ( self.view == nil ) [self loadView];
    NSURL* url = [NSURL fileURLWithPath:photo.filePath];
    [self.imageView setImageWithURL:url];
    [self.mainWindowController activateViewController:self];
}
列表视图控制器

列表视图控制器 CBListView 以表格形式显示所有相册中的照片列表。双击照片可以在编辑器视图中打开。
1. 视图加载 :在 loadView 方法中,设置 imagesTable 的双击事件。

- (void) loadView {
    [super loadView];
    self.imagesTable.target       = self;
    self.imagesTable.doubleAction = @selector(tableViewItemDoubleClicked:);
}
  1. 双击事件处理 tableViewItemDoubleClicked 方法处理用户双击照片的事件,根据点击的行索引获取相应的照片,并在编辑器视图中打开。
- (IBAction) tableViewItemDoubleClicked:(id)sender {
    NSInteger row           = self.imagesTable.clickedRow;
    NSArray*  visiblePhotos = [self.imagesArrayController arrangedObjects];
    CBPhoto*  photo         = [visiblePhotos objectAtIndex:row];

    CBMainWindow* window = self.mainWindowController;
    id editor = [window viewControllerForName:@"CBEditorView"];

    if ( [editor isKindOfClass:[CBEditorView class]] )
        [(CBEditorView*)editor editPhoto:photo];
}
管理对象类功能
照片类 CBPhoto

照片类 CBPhoto 继承自 NSManagedObject ,实现了 IKImageBrowserItem 协议,用于在浏览器视图中显示照片。
1. 初始化 photoInDefaultContext 方法用于在默认上下文环境中创建新的照片对象。

+ (id) photoInDefaultContext {
    NSManagedObjectContext* context = [[NSApp delegate] managedObjectContext];

    CBPhoto* newItem;    
    newItem = [NSEntityDescription insertNewObjectForEntityForName:@"Photo"
                                            inManagedObjectContext:context];

    newItem.filePath = nil;

    return newItem;
}
  1. 缩略图生成 largeThumbnail 方法使用 QuickLook 生成照片的缩略图。
- (NSImage*) largeThumbnail {
    if ( self.thumbnail ) return self.thumbnail;

    NSSize      size  = NSMakeSize( 250, 250 );
    CFStringRef path  = (CFStringRef) self.filePath;
    CFURLRef    url   = 
      CFURLCreateWithFileSystemPath( NULL, path, kCFURLPOSIXPathStyle, NO);

    CGImageRef  thumb = QLThumbnailImageCreate( NULL, url, size, nil );
    NSImage*    image = [[NSImage alloc] initWithCGImage:thumb size:size];
    self.thumbnail = image;
    CFRelease( url );
    CFRelease( thumb );
    [image release];

    return image;
}
  1. 协议方法实现 :实现了 IKImageBrowserItem 协议的必要方法,用于在浏览器视图中显示照片。
-(NSString *) imageTitle {
    NSString* fullFileName = self.filePath.lastPathComponent;    
    return [fullFileName stringByDeletingPathExtension];
}
- (NSString*) imageUID {
    NSString* uniqueID = self.uniqueID;
    if ( uniqueID ) return uniqueID;
    [self generateUniqueID];
    return self.uniqueID;
}
- (NSString *) imageRepresentationType {
    return IKImageBrowserPathRepresentationType;
}
- (id) imageRepresentation {
    return self.filePath;
}
相册类 CBAlbum

相册类 CBAlbum 继承自 NSManagedObject ,提供了创建相册和获取默认相册的方法。
1. 创建相册 albumInDefaultContext 方法用于在默认上下文环境中创建新的相册对象。

+ (id) albumInDefaultContext {
    NSManagedObjectContext* context = 
      [[NSApp delegate] managedObjectContext];

    CBAlbum* newItem;    
    newItem = [NSEntityDescription insertNewObjectForEntityForName:@"Album"
                                            inManagedObjectContext:context];

    return newItem;
}
  1. 获取默认相册 defaultAlbum 方法用于获取默认相册,如果默认相册不存在则创建一个。
+ (id) defaultAlbum {
    NSManagedObjectContext* context = 
      [[NSApp delegate] managedObjectContext];
    NSEntityDescription* entity  
       = [NSEntityDescription entityForName: @"Album"
                     inManagedObjectContext: context];

    NSFetchRequest* fetch   = [[NSFetchRequest alloc] init];
    fetch.entity            = entity;
    fetch.predicate         
      = [NSPredicate predicateWithFormat:@"title == 'Default'"];

    NSError* error = nil;
    NSArray* results = [context executeFetchRequest:fetch error:&error];
    [fetch release];
    if ( error ) {
        NSLog( @"error: %@", error );
        return nil;
    }

    CBAlbum* album = nil;
    if ( results.count > 0 ) {
        album = [results objectAtIndex:0];
    } else {
        album = [self albumInDefaultContext];
    }
    return album;
}

总结与展望

通过以上的步骤和代码实现,我们完成了一个具有基本功能的项目,包括添加框架、创建控制器和管理对象类。在实际开发中,我们可以根据具体需求对代码进行进一步的优化和扩展。例如:
1. 数据持久化 :可以添加数据保存和加载的功能,确保用户的操作能够持久化存储。
2. 用户交互优化 :可以增加更多的用户交互功能,如图片的删除、重命名等。
3. 性能优化 :对缩略图生成和数据加载进行优化,提高应用的性能。

关键技术点总结

技术点 描述
Quartz 框架 提供了 ImageKit 框架,用于实现图片浏览器和编辑器等功能
视图控制器 管理不同的视图,实现用户界面的切换和交互
管理对象类 用于管理数据模型,实现数据的存储和操作
IKImageBrowserItem 协议 用于在浏览器视图中显示自定义对象

项目扩展流程图

graph LR
    A[现有项目] --> B[数据持久化]
    A --> C[用户交互优化]
    A --> D[性能优化]
    B --> B1[数据保存]
    B --> B2[数据加载]
    C --> C1[图片删除]
    C --> C2[图片重命名]
    D --> D1[缩略图优化]
    D --> D2[数据加载优化]

通过以上的分析和总结,我们对项目的整体架构和功能有了更深入的理解。在后续的开发中,我们可以根据这些知识对项目进行进一步的完善和扩展,以满足不同的需求。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值