项目开发:添加框架、创建控制器与管理对象类
在项目开发过程中,我们需要完成多个关键步骤,包括添加框架、创建窗口控制器、视图控制器以及管理对象类。下面将详细介绍这些步骤的操作方法和代码实现。
1. 添加 Quartz 框架
本项目会使用 Mac OS X 的 ImageKit 框架中的
IKImageBrowserView
类。在使用该类之前,需要先将 ImageKit 框架添加到项目中,而 ImageKit 框架实际上是更大的 Quartz 框架的子框架。
操作步骤
-
在项目中右键点击(或按住 Control 点击)
Frameworks → Linked Frameworks组。 -
选择
Add → Existing Frameworks。 -
从列表中选择
Quartz.framework并点击Add。
添加完成后,在任何头文件或实现文件中包含
Quartz.h
文件,就可以使用 Quartz 框架中的任何类(包括 ImageKit 中的类)。
2. 创建窗口控制器
窗口控制器类将管理项目的所有视图控制器,每个视图控制器将包含用户界面的不同部分。这里会创建一个窗口控制器类及其对应的 XIB 文件,以及三个视图控制器类,每个类都有自己的 XIB 文件。
操作步骤
-
创建一个新的 Cocoa 类,超类为
NSWindowController,命名为CBMainWindow.m。 -
替换
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
-
打开
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
-
在 Xcode 侧边栏中选择
Resources组,选择File → New File。 -
在模板选择窗口的 Mac OS X 部分选择 “User Interface” 组,然后选择
Window XIB并点击Next,命名文件为CBMainWindow.xib。 -
更新
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 存储中。
操作步骤
-
创建一个新的 Cocoa 类,超类为
NSObject,命名为CBBrowserView.m。 -
替换
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
-
打开
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
-
在 Xcode 侧边栏中选择
Resources组,选择File → New File。 -
在模板选择窗口的 Mac OS X 部分选择 “User Interface” 组,然后选择
View XIB并点击Next,命名文件为CBBrowserView.xib。
2.2 编辑器视图控制器
编辑器视图使用
IKImageView
类,允许用户查看和编辑图像。双击编辑器视图中的图像可以调出调整面板。由于本项目仅用于演示,编辑视图中的更改不会保存回文件,但可以看到其功能。
操作步骤
-
创建一个新的 Cocoa 类,超类为
NSObject,命名为CBEditorView.m。 -
替换
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
-
打开
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
-
在 Xcode 侧边栏中选择
Resources组,选择File → New File。 -
在模板选择窗口的 Mac OS X 部分选择 “User Interface” 组,然后选择
View XIB并点击Next,命名文件为CBEditorView.xib。
2.3 列表视图控制器
列表视图是最后一个视图,以表格视图形式显示所有相册中的照片列表。双击照片可在编辑器视图中打开。
操作步骤
-
创建一个新的 Cocoa 类,超类为
NSObject,命名为CBListView.m。 -
替换
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
-
打开
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
-
在 Xcode 侧边栏中选择
Resources组,选择File → New File。 -
在模板选择窗口的 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 代码解释
-
获取应用程序委托的
NSManagedObjectContext实例,用于获取现有管理对象、创建新的管理对象或保存数据更改。 -
使用名为
Photo的实体创建一个新的NSManagedObject,即使使用的是通用的NSManagedObject类,其可存储的数据由实体定义。 -
使用键值编码的
-setValue:forKey:方法设置管理对象的值,键为filePath,该属性在图形模型编辑器中定义。 -
使用键值编码的
-valueForKey:方法检索值,键同样为filePath。
3.3 创建自定义子类
为了更方便地操作管理对象,我们可以为每个实体类型创建
NSManagedObject
的自定义子类。这样可以声明属性、直接调用方法,并添加支持特定功能的方法,同时编译器可以进行类型检查,确保不会将
NSNumber
传递给定义为字符串的属性。
3.3.1 实现照片类
创建两个新的 Objective-C 类,作为
NSObject
的子类,命名为
CBPhoto
和
CBAlbum
。
操作步骤
-
替换
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
-
替换
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 实现相册类
操作步骤
-
替换
CBAlbum.h头文件的内容为以下代码:
#import <Cocoa/Cocoa.h>
@interface CBAlbum : NSManagedObject
@property (retain) NSString* title;
@property (retain) NSSet* photos;
+ (id) defaultAlbum;
+ (id) albumInDefaultContext;
@end
-
替换
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];
}
-
视图切换
:当用户通过
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];
}
-
激活视图控制器
:
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];
}
-
相册选择
:当用户选择不同的相册时,
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"];
}
-
图片拖放
:
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];
}
-
图片编辑
:
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:);
}
-
双击事件处理
:
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;
}
-
缩略图生成
:
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;
}
-
协议方法实现
:实现了
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;
}
-
获取默认相册
:
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[数据加载优化]
通过以上的分析和总结,我们对项目的整体架构和功能有了更深入的理解。在后续的开发中,我们可以根据这些知识对项目进行进一步的完善和扩展,以满足不同的需求。
超级会员免费看

被折叠的 条评论
为什么被折叠?



