Challenge: Saving and Loading issues


#1

Hi,

I am having a heck of a time getting the archiving and unarchiving to work. I can get the NSView to archive and save to a file, and I can then read that same file back in just fine. Following the path of events, the MyDocument class reads in the file and all of the information is present, however when it is finally displayed the NSView is reinitialized and all of the data is lost. I know that I am missing something simple and it is rather aggravating so if anyone can lend a hand I would greatly appreciate it.

I only have the 2 classes.

MyDocument:

#import <Cocoa/Cocoa.h>
@class Canvas;

@interface MyDocument : NSDocument
{
	/*IBOutlet Canvas *canvas;
	Canvas *tempCanvas;*/
	
	Canvas *drawingSerface;
}

- (void)setCanvas:(Canvas *)aCanvas;
@end

#import "MyDocument.h"
#import "Canvas.h"

@implementation MyDocument

- (id)init
{
    self = [super init];
    if (self) {
    
        NSLog(@"init: MyDocument");
    
    }
    return self;
}

- (void)setCanvas:(Canvas *)aCanvas {
	if (aCanvas == drawingSerface)
		return;
	
	[drawingSerface release];
	[aCanvas retain];
	drawingSerface = aCanvas;
	//[drawingSerface setNeedsDisplay:YES];
}

- (NSString *)windowNibName
{
    // Override returning the nib file name of the document
    // If you need to use a subclass of NSWindowController or if your document supports multiple NSWindowControllers, you should remove this method and override -makeWindowControllers instead.
    return @"MyDocument";
}

- (void)windowControllerDidLoadNib:(NSWindowController *) aController
{
    [super windowControllerDidLoadNib:aController];
    NSLog(@"Window loaded");
	[drawingSerface setNeedsDisplay:YES];
}

- (NSData *)dataOfType:(NSString *)typeName error:(NSError **)outError
{
	return [NSKeyedArchiver archivedDataWithRootObject:drawingSerface];
	
    if ( outError != NULL ) {
		*outError = [NSError errorWithDomain:NSOSStatusErrorDomain code:unimpErr userInfo:NULL];
	}
	return nil;
}

- (BOOL)readFromData:(NSData *)data ofType:(NSString *)typeName error:(NSError **)outError
{
    NSLog(@"About to read data of type %@", typeName);
	Canvas *newCanvas = nil;
	
	@try {
		newCanvas = [NSKeyedUnarchiver unarchiveObjectWithData:data];
		NSLog(@"Loaded Canvas: %@", newCanvas);
	}
	@catch (NSException * e) {
		if (outError) {
			NSDictionary *d = [NSDictionary dictionaryWithObject:@"The data is corruptted." 
														  forKey:NSLocalizedFailureReasonErrorKey];
			*outError = [NSError errorWithDomain:NSOSStatusErrorDomain 
											code:unimpErr 
										userInfo:d];
		}
		return NO;
	}
    
	[self setCanvas:newCanvas];
	//tempCanvas = [newCanvas retain];
    return YES;
}

@end

Canvas (NSView subclass):

#import <Cocoa/Cocoa.h>


@interface Canvas : NSView <NSCoding> {
	NSBezierPath *drawingPath;
	
	NSPoint downPoint;
	NSPoint currentPoint;
	
	NSMutableArray *ovalArray;
}

- (NSRect)currentRect;
- (NSRect)makeRectFrom:(NSPoint)point andAnother:(NSPoint)another;

- (void)updateLastEntry:(NSPoint)point;

@end

#import "Canvas.h"


@implementation Canvas

#pragma mark Init & Dealloc
- (id)initWithFrame:(NSRect)frame {
    self = [super initWithFrame:frame];
    if (self) {
        ovalArray = [[NSMutableArray alloc] init];
		NSLog(@"initWithFrame: Canvas initialized");
    }
    return self;
}



- (void)dealloc {
	[ovalArray release];
	[super dealloc];
}

- (NSString *)description {
	return [NSString stringWithFormat:@"Oval Count: %d", [ovalArray count]];
}


#pragma mark Drawing
- (void)drawRect:(NSRect)dirtyRect {
	
	for(NSDictionary *dictionary in ovalArray) {
		NSLog(@"dictionary: %@ has count: %d", dictionary, [ovalArray count]);
		
		NSPoint down = NSPointFromString([dictionary objectForKey:@"down"]);
		NSPoint current = NSPointFromString([dictionary objectForKey:@"current"]);		
		NSRect drawing = [self makeRectFrom:down andAnother:current];
		
		drawingPath = [NSBezierPath bezierPathWithOvalInRect:drawing];
		[drawingPath setLineWidth:3.0];
		[drawingPath stroke];
	}
}

- (NSRect)currentRect {
	float minX = MIN(downPoint.x, currentPoint.x);
	float maxX = MAX(downPoint.x, currentPoint.x);
	float minY = MIN(downPoint.y, currentPoint.y);
	float maxY = MAX(downPoint.y, currentPoint.y);
	
	return NSMakeRect(minX, minY, maxX - minX, maxY - minY);
}

- (NSRect)makeRectFrom:(NSPoint)point andAnother:(NSPoint)another {
	float minX = MIN(point.x, another.x);
	float maxX = MAX(point.x, another.x);
	float minY = MIN(point.y, another.y);
	float maxY = MAX(point.y, another.y);
	
	return NSMakeRect(minX, minY, maxX - minX, maxY - minY);
}

- (void)updateLastEntry:(NSPoint)point {
	currentPoint = [self convertPoint:point fromView: nil];
	NSDictionary *rect = [NSDictionary dictionaryWithObjectsAndKeys: NSStringFromPoint(downPoint), @"down", 
						  NSStringFromPoint(currentPoint), @"current", nil];
	
	int index = [ovalArray count];
	[ovalArray replaceObjectAtIndex:(index - 1) withObject:rect];
	NSLog(@"Oval Count: %d", [ovalArray count]);
	[self setNeedsDisplay:YES];
}

#pragma mark Mouse Events
- (void)mouseDown:(NSEvent *)event {
	NSPoint p = [event locationInWindow];
	downPoint = [self convertPoint:p fromView: nil];
	currentPoint = downPoint;
	
	NSDictionary *rect = [NSDictionary dictionaryWithObjectsAndKeys: NSStringFromPoint(downPoint), @"down", 
						  NSStringFromPoint(currentPoint), @"current", nil];
	
	[ovalArray addObject:rect];
	
	[self setNeedsDisplay:YES];
}

- (void)mouseDragged:(NSEvent *)event {
	NSPoint p = [event locationInWindow];
	[self autoscroll:event];
	[self updateLastEntry:p];
}

- (void)mouseUp:(NSEvent *)event {
	NSPoint p = [event locationInWindow];
	[self updateLastEntry:p];
}

#pragma mark Archiving
- (id)initWithCoder:(NSCoder *)aDecoder {
	self = [super init];
    if (self) {
        ovalArray = [[aDecoder decodeObjectForKey:@"ovalArray"] retain];
		NSLog(@"initWithCoder: Encoded Count: %d", [ovalArray count]);
    }
    return self;
}

- (void)encodeWithCoder:(NSCoder *)aCoder {
	NSLog(@"Encoding Canvas: %d", [ovalArray count]);
	[aCoder encodeObject:ovalArray forKey:@"ovalArray"];
}
@end

Thanks,
Patrick


#2

Patrick,
Not sure if you still need help, but here is what I’ve done (with both Loading/Saving and Undo working). I kind of followed what you did with the one major difference: I created a new class (RectanglePoints) to hold the 2 points (downPoint and currentPoint) so that I can be able to use the NSCoder protocol. I used this instead of using NSDictionary as you did. I also created 1 outlet in MyDocument and 1 outlet in my Custom View (OvalView) that point to each other. I also added an ArrayController (using the array I used, “ovals”) in Interface Builder, but, ultimately, I don’t think it’s really needed.

MyDocument:

#import <Cocoa/Cocoa.h>
@class RectanglePoints;

@interface MyDocument : NSDocument
{
	NSMutableArray *ovals;
	IBOutlet NSView *ovalView;
}

- (NSArray *)getOvals;
- (int)ovalscount;
- (void) addEntry:(NSPoint)point andAnotherPoint:(NSPoint)thisPoint replaceLastObject:(BOOL)lastObject isMouseDown:(BOOL)mouseDown;
- (void) removeObjectFromOvalsAtIndex:(int)index;
- (void) insertObject:(RectanglePoints *)rp inOvalsAtIndex:(int)index;

@end

#import "MyDocument.h"
#import "RectanglePoints.h"

@implementation MyDocument

- (id)init
{
    self = [super init];
    if (self) {
    
        // Add your subclass-specific initialization here.
        // If an error occurs here, send a [self release] message and return nil.
		ovals = [[NSMutableArray alloc] init];
    }
    return self;
}

- (NSString *)windowNibName
{
    // Override returning the nib file name of the document
    // If you need to use a subclass of NSWindowController or if your document supports multiple NSWindowControllers, you should remove this method and override -makeWindowControllers instead.
    return @"MyDocument";
}

- (void)windowControllerDidLoadNib:(NSWindowController *) aController
{
    [super windowControllerDidLoadNib:aController];
    // Add any code here that needs to be executed once the windowController has loaded the document's window.
}

-(void)setOvals:(NSMutableArray *)a
{
	//This is an unusual setter method. We are going to add a lot 
	//of smarts to it in the next chapter.
	if (a == ovals)
		return;
	
	[a retain];
	[ovals release];
	ovals = a;
	
}


- (BOOL)readFromData:(NSData *)data ofType:(NSString *)typeName error:(NSError **)outError
{
    NSLog(@"About to read data of type %@", typeName);
	NSMutableArray *newArray = nil;
	@try{
		newArray = [NSKeyedUnarchiver unarchiveObjectWithData:data];
	}
	@catch (NSException *e)
	{
		if(outError)
		{
			NSDictionary *d = [NSDictionary
							   dictionaryWithObject:@"The data is corrupted."
							   forKey:NSLocalizedFailureReasonErrorKey];
			*outError = [NSError errorWithDomain:NSOSStatusErrorDomain
											code:unimpErr 
										userInfo:d];
		}
		return NO;
	}
	[self setOvals];
	return YES;
	
}

- (NSArray *)getOvals 
{
    return [NSArray arrayWithArray:ovals];
}

- (int)ovalscount 
{
	return [ovals count];
}

- (void) insertObject:(RectanglePoints *)rp inOvalsAtIndex:(int)index
{
	NSLog(@"adding oval at index %d", index);
	NSUndoManager *undo = [self undoManager];
	[[undo prepareWithInvocationTarget:self]
	 removeObjectFromOvalsAtIndex:index];
	if (![undo isUndoing])
	{
		[undo setActionName:@"Insert Oval"];
	}
	
	[ovals insertObject:rp atIndex:index];
	[ovalView setNeedsDisplay:YES];
}

- (void) removeObjectFromOvalsAtIndex:(int)index
{
	NSLog(@"Removing oval at index %d", index);
	RectanglePoints *p = [ovals objectAtIndex:index];
	
	
	NSUndoManager *undo = [self undoManager];
	[[undo prepareWithInvocationTarget:self] insertObject:p
										   inOvalsAtIndex:index];
	if(![undo isUndoing])
	{
		[undo setActionName:@"Delete Oval"];
	}
	
	
	[ovals removeObjectAtIndex:index];
	[ovalView setNeedsDisplay:YES];
}

- (void) addEntry:(NSPoint)point andAnotherPoint:(NSPoint)thisPoint replaceLastObject:(BOOL)lastObject isMouseDown:(BOOL)mouseDown
{
	RectanglePoints *points = [[RectanglePoints alloc] init];
	points.downPoint = point;
	points.currentPoint = thisPoint;
	
	int index = 0;
	
	if(mouseDown)
	{
		index = [ovals count] - 1;
	
	
		NSUndoManager *undo = [self undoManager];
		[[undo prepareWithInvocationTarget:self] removeObjectFromOvalsAtIndex:index];

		if(![undo isUndoing])
		{
			[undo setActionName:@"Reinsert Oval"];
		}
	}
	
	if (lastObject)
		[ovals replaceObjectAtIndex:([ovals count] - 1)
						 withObject:points];
	else
		[ovals addObject:points];

	
	[points release];
	
}

- (NSData *)dataOfType:(NSString *)typeName error:(NSError **)outError
{
	return [NSKeyedArchiver archivedDataWithRootObject:ovals];
}

@end

OvalView (Custom View):

#import <Cocoa/Cocoa.h>

@class MyDocument;

@interface OvalView : NSView {
	NSBezierPath *ovalPath;
	NSPoint downPoint;
	NSPoint currentPoint;
	IBOutlet MyDocument *document;
}

- (NSRect)currentRect;
- (void) refreshView;

@end

#import "OvalView.h"
#import "RectanglePoints.h"
#import "MyDocument.h"


@implementation OvalView

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

- (NSRect)currentRect
{
	float minX = MIN(downPoint.x, currentPoint.x);
	float maxX = MAX(downPoint.x, currentPoint.x);
	float minY = MIN(downPoint.y, currentPoint.y);
	float maxY = MAX(downPoint.y, currentPoint.y);
	
	return NSMakeRect(minX, minY, maxX-minX, maxY-minY);
}

- (NSRect)makeRectWith:(NSPoint)thisPoint and:(NSPoint)anotherPoint
{
	float minX = MIN(thisPoint.x, anotherPoint.x);
	float maxX = MAX(thisPoint.x, anotherPoint.x);
	float minY = MIN(thisPoint.y, anotherPoint.y);
	float maxY = MAX(thisPoint.y, anotherPoint.y);
	
	return NSMakeRect(minX, minY, maxX-minX, maxY-minY);
}

- (void)drawRect:(NSRect)dirtyRect {
    // Drawing code here.
	NSRect bounds = [self bounds];
	[[NSColor greenColor] set];
	[NSBezierPath fillRect:bounds];
	

	for (RectanglePoints *points in [document getOvals])
	{
		NSLog(@"points:%@ has count: %d", points, [document ovalscount]);

		NSRect drawing = [self makeRectWith:points.downPoint and:points.currentPoint];
		ovalPath = [NSBezierPath bezierPathWithOvalInRect:drawing];
		[[NSColor whiteColor] set];
		[ovalPath setLineWidth:3.0];
		[ovalPath stroke];
	}
	
}

- (void)refreshView
{
	[self setNeedsDisplay:YES];
}

#pragma mark Events

-(void)mouseDown:(NSEvent *)event
{
	NSLog(@"mouseDown: %d", [event clickCount]);
	NSPoint p = [event locationInWindow];
	downPoint = [self convertPoint:p fromView:nil];
	currentPoint = downPoint;

	[document addEntry:downPoint andAnotherPoint:currentPoint replaceLastObject:NO isMouseDown:NO];

	NSLog(@"after add");
	[self setNeedsDisplay:YES];
}

-(void)mouseDragged:(NSEvent *)event
{
	NSPoint p = [event locationInWindow];
	NSLog(@"mouseDragged:%@", NSStringFromPoint(p));
	currentPoint = [self convertPoint:p fromView:nil];

	[document addEntry:downPoint andAnotherPoint:currentPoint replaceLastObject:YES isMouseDown:NO];
	[self setNeedsDisplay:YES];
}

-(void)mouseUp:(NSEvent *)event
{
	NSLog(@"mouseUp:");
	NSPoint p = [event locationInWindow];
	currentPoint = [self convertPoint:p fromView:nil];

	[document addEntry:downPoint andAnotherPoint:currentPoint replaceLastObject:YES isMouseDown:YES];
	[self setNeedsDisplay:YES];
}

#pragma mark Accessors




- (void)dealloc
{

	[super dealloc];
}

@end

RectanglePoints:

#import <Cocoa/Cocoa.h>


@interface RectanglePoints : NSObject <NSCoding> {
	NSPoint downPoint;
	NSPoint currentPoint;
}

@property (readwrite) NSPoint downPoint;
@property (readwrite) NSPoint currentPoint;

@end

#import "RectanglePoints.h"


@implementation RectanglePoints

@synthesize downPoint;
@synthesize currentPoint;

- (id)init
{
	[super init];
	return self;
}

- (void) dealloc
{
	[super dealloc];
}

- (void)encodeWithCoder:(NSCoder *)coder
{
	NSString *firstPoint = NSStringFromPoint(downPoint);
	NSString *secondPoint = NSStringFromPoint(currentPoint);
	
	[coder encodeObject:firstPoint forKey:@"downPoint"];
	[coder encodeObject:secondPoint forKey:@"currentPoint"];
}

- (id)initWithCoder:(NSCoder *)coder
{
	[super init];
	NSString *firstPoint = [coder decodeObjectForKey:@"downPoint"];
	NSString *secondPoint = [coder decodeObjectForKey:@"currentPoint"];
	downPoint = NSPointFromString(firstPoint);
	currentPoint = NSPointFromString(secondPoint);
	
	return self;
}

@end

-Alfred


#3

alfredog1976 Can u post the files?
I tried your approach but can’t make the ovals to appear :confused: