34: NSDocument with NSTextView window example of reading and writing RTFD, RTF and Plain text files


Useful Links


i. Create a document based application and set up the Document Types

In XCode do File->New Project. Choose Cocoa application and check the Create document-based application check box. Name the project 034-NSDocument-NSTextView.

Now set the Universal Type Identifier (UTI) for the types of files to be read and written. In the Groups & Files collumn, open the Targets Group, select the 034-NSDocument-NSTextView target and click on the Info button to bring up the Info panel.

Choose the Properties tab. Remove the first line of the Document Types table (which starts with DocumentType) by clicking the Minus button.

Click the Plus button to obtain a new row in the Document Types table. In the Names Collumn enter "Rich Text With Attachments (RTFD)". In the UTI collumn enter "com.apple.rtfd". Set the Class to "MyDocument", Store Type to "Binary" and Role to "Editor". Leave the Package check box unticked.

Click the Plus button again, set the Names Collumn to "Rich Text (RTF)" and the UTI entry to "public.rtf". Set the other collumn entries to the same values as before.

Now make the final entry. Set Name to "Plain Text" and UTI to "public.plain-text" and the other entries as before.

Set the Universal Type Identifiers in Document Types to RTFD, RTF and Plain text

Set the Universal Type Identifiers in Document Types to RTFD, RTF and Plain text (insets show the details)



ii. Declare the MyDocument variables

In MyDocument.h declare an NSTextView object nsTextViewObj as an IBOutlet. Declare the NSAttributedString object nsAttribStringObj. Make them both properties respectively to be assigned and copied. Create the asociated synthesise declarations in the .m file.

Save.

// MyDocument.h #import @interface MyDocument : NSDocument { IBOutlet NSTextView * nsTextViewObj; NSAttributedString * nsAttribStringObj; } @property (assign) IBOutlet NSTextView *nsTextViewObj; @property (copy) NSAttributedString * nsAttribStringObj; @end // MyDocument.m #import "MyDocument.h" @implementation MyDocument @synthesize nsTextViewObj; @synthesize nsAttribStringObj;



iii. Edit the MyDocument.xib in Interface Builder

Open the Resources group in the Groups & Files collumn and double click the MyDocument.xib entry to bring up Interface Builder.

Drag a NSTextView in a NSScrollView onto the nib window. Adjust it appropriately. Select the NSTextView. In the Connections pane set the delegate to File's Owner (drag from the delegate outlet to the File's Owner icon). In the Text View Attributes pane check the Graphics check box: this will allow you to add Graphics to the text. Set the class of the File's Owner to MyDocument. In the Connections pane drag the NSTextView outlet onto the NSTextView.

Save.

Set the NSTextView delegate to MyDocument

Set the NSTextView delegate to MyDocument

Check the Graphics check box of NSTextView

Check the Graphics check box of NSTextView

Set the File's Owner to MyDocument

Set the File's Owner to MyDocument

Set the File's Owner outlet to NSTextView

Set the File's Owner outlet to NSTextView



iv. Finish coding MyDocument

Return to XCode and add the NSTextView delegate method textDidChange.

- (void) textDidChange: (NSNotification *) notification { self.nsAttribStringObj = [self.nsTextViewObj textStorage]; }

Edit the init method so as to initialise the nsAttribStringObj.

- (id)init { if ( ! (self = [super init]) ) { NSLog(@"ERROR: MyDocument init"); return self; } self.nsAttribStringObj=[[NSAttributedString alloc]initWithString:@""]; return self; }

Replace the dataOfType and readFromData methods by fileWrapperOfType and readFromFileWrapper respectively. Inside each insert a sequence of if statements which call methods specific to each file type.

- (NSFileWrapper *)fileWrapperOfType:(NSString *)pTypeName error:(NSError **)pOutError { if ([pTypeName compare:@"com.apple.rtfd"] == NSOrderedSame) { return [self rtfdFileWrapperWithError:pOutError]; } if ([pTypeName compare:@"public.rtf"] == NSOrderedSame) { return [self rtfFileWrapperWithError:pOutError]; } if ([pTypeName compare:@"public.plain-text"] == NSOrderedSame) { return [self txtFileWrapperWithError:pOutError]; } // see Error Domains in the Error Handling Programming Guide *pOutError = [NSError errorWithDomain:@"my.ErrrorDomain" code:1 userInfo:NULL]; return nil; } - (BOOL)readFromFileWrapper:(NSFileWrapper *)pFileWrapper ofType:(NSString *)pTypeName error:(NSError **)pOutError { if ([pFileWrapper isRegularFile] && ([pTypeName compare:@"public.plain-text"] == NSOrderedSame)) { return [self txtReadFileWrapper:pFileWrapper error:pOutError]; } if ([pFileWrapper isRegularFile] && ([pTypeName compare:@"public.rtf"] == NSOrderedSame)) { return [self rtfReadFileWrapper:pFileWrapper error:pOutError]; } if ([pFileWrapper isDirectory] && ([pTypeName compare:@"com.apple.rtfd"] == NSOrderedSame)) { return [self rtfdReadFileWrapper:pFileWrapper error:pOutError]; } *pOutError = [NSError errorWithDomain:@"my.ErrrorDomain" code:2 userInfo:NULL]; return NO; }

The easiest to start with are the read methods. These are as follows.

- (BOOL)txtReadFileWrapper:(NSFileWrapper *)pFileWrapper error:(NSError **)pOutError{ NSData * zData = [pFileWrapper regularFileContents]; if(! zData) { *pOutError = [NSError errorWithDomain:@"my.ErrrorDomain" code:40 userInfo:NULL]; return NO; } NSString *zString = [[NSString alloc]initWithData:zData encoding:NSASCIIStringEncoding]; self.nsAttribStringObj= [[NSAttributedString alloc]initWithString:zString]; return YES; } - (BOOL)rtfReadFileWrapper:(NSFileWrapper *)pFileWrapper error:(NSError **)pOutError{ NSData * zData = [pFileWrapper regularFileContents]; if(! zData) { *pOutError = [NSError errorWithDomain:@"my.ErrrorDomain" code:50 userInfo:NULL]; return NO; } NSMutableAttributedString * zMutAtribString = [[NSMutableAttributedString alloc]initWithRTF:zData documentAttributes:NULL]; if(! zMutAtribString) { *pOutError = [NSError errorWithDomain:@"my.ErrrorDomain" code:60 userInfo:NULL]; return NO; } self.nsAttribStringObj = zMutAtribString; return YES; } - (BOOL)rtfdReadFileWrapper:(NSFileWrapper *)pFileWrapper error:(NSError **)pOutError{ NSMutableAttributedString * zMutAtribString = [[NSMutableAttributedString alloc] initWithRTFDFileWrapper:pFileWrapper documentAttributes:NULL]; if(! zMutAtribString) { return NO; } self.nsAttribStringObj = zMutAtribString; return YES; }

The file writing methods are complicated in the case of rtf and plain text files by the need to reduce information. In the case of rtf files this consists of any attachments owned by the rtfd representation. In the case of the plain text files this consists both of the attachments and the text formating information of the rtf representation.

My convertToASCII method is shown below. From the literature it looked as if one could use the NSText class to strip out all of the attributes and pointers to attachments. However I found it difficult to remove the pointers by such means. For this reason I've taken what feels like a less elegant approach.

- (NSString *)convertToASCII { self.nsAttribStringObj = [self.nsTextViewObj textStorage]; NSString * zStr = [NSString stringWithString: [self.nsAttribStringObj string]]; if ([zStr canBeConvertedToEncoding:NSASCIIStringEncoding]) { return zStr; } // remove the offending characters NSRange zRangeAsciiCharsComplete = NSMakeRange(0,127); NSCharacterSet *zAllowedSet = [NSCharacterSet characterSetWithRange:zRangeAsciiCharsComplete]; NSCharacterSet *zDisAllowedSet = [zAllowedSet invertedSet]; NSArray * zAryOfStrings = [zStr componentsSeparatedByCharactersInSet:zDisAllowedSet]; NSLog(@"zAryOfStrings count=%d",[zAryOfStrings count]); // concatanate the strings NSString *zAsciiString = [[NSString alloc]init]; for(NSString * zString in zAryOfStrings) { if( ! zString ) { NSLog(@"Nil String !!!"); continue; } if([zString length] > 0) { zAsciiString = [zAsciiString stringByAppendingString:zString]; } } NSLog(@"\nzAsciiString=\n%@\n",zAsciiString); return zAsciiString; }

Here is the method that calls the convertion method and writes the plain text.

- (NSFileWrapper *)txtFileWrapperWithError:(NSError **)pOutError { // The text must be in a form suitable for writing as plain text. // If part of an rtfd document then could contain attachment pointers. // If copied from web page then could be unicode. // Get what ASCII text may be obtained NSString * zASCIIString = [self convertToASCII]; NSAttributedString * zWritableString = [[NSAttributedString alloc]initWithString:zASCIIString]; NSDictionary * zDict = [NSDictionary dictionaryWithObjectsAndKeys: NSPlainTextDocumentType, NSDocumentTypeDocumentAttribute, nil]; NSRange zRange = NSMakeRange(0, [zWritableString length]); NSData * zData = [zWritableString dataFromRange:zRange documentAttributes:zDict error:pOutError]; if (! zData ) { return NULL; } // end if NSFileWrapper * zNSFileWrapperObj = [[NSFileWrapper alloc]initRegularFileWithContents:zData]; return zNSFileWrapperObj; }

The rtf write method is much less complicated. It only requires the use of the NSText method RTFFromRange to strip out the attachments data.

- (NSFileWrapper *)rtfFileWrapperWithError:(NSError **)pOutError { // If part of an rtfd document then could contain attachment info. // Use NSText to strip out the attachments NSRange zRange = NSMakeRange(0, [self.nsAttribStringObj length]); NSData * zData = [self.nsTextViewObj RTFFromRange:zRange]; NSAttributedString * zAttribString = [[NSAttributedString alloc]initWithData:zData options:NULL documentAttributes:NULL error:pOutError ]; if(!zAttribString && pOutError) { *pOutError = [NSError errorWithDomain:NSCocoaErrorDomain code:NSFileWriteUnknownError userInfo:nil]; return NULL; } zRange = NSMakeRange(0, [zAttribString length]); NSDictionary * zDict = [NSDictionary dictionaryWithObjectsAndKeys: NSRTFTextDocumentType, NSDocumentTypeDocumentAttribute, nil]; NSFileWrapper * zNSFileWrapperObj = [zAttribString fileWrapperFromRange:zRange documentAttributes:zDict error:pOutError]; if (!zNSFileWrapperObj && pOutError) { *pOutError = [NSError errorWithDomain:NSCocoaErrorDomain code:NSFileWriteUnknownError userInfo:nil]; return NULL; } return zNSFileWrapperObj; }

Finally the rtfd write method.

- (NSFileWrapper *)rtfdFileWrapperWithError:(NSError **)pOutError { self.nsAttribStringObj = [self.nsTextViewObj textStorage]; NSDictionary * zDict = [NSDictionary dictionaryWithObjectsAndKeys: NSRTFDTextDocumentType, NSDocumentTypeDocumentAttribute, nil]; NSRange zRange = NSMakeRange(0, [self.nsAttribStringObj length]); NSFileWrapper * zNSFileWrapperObj = [self.nsAttribStringObj fileWrapperFromRange:zRange documentAttributes:zDict error:pOutError]; if (!zNSFileWrapperObj && pOutError) { *pOutError = [NSError errorWithDomain:NSCocoaErrorDomain code:NSFileWriteUnknownError userInfo:nil]; return NULL; } // end if return zNSFileWrapperObj; }


If you want to download the code

Click the Download Link to obtain 034-NSDocument-NSTextView.zip file of this OS X 10.6 Snow Leopard program.

Download 034-NSDocument-NSTextView.zip (2.4 MB)



Please send me your comments

If you include your e-mail I may reply!  

Page last modified: 18:58 Sunday 12th. May 2013

Julius Guzy

Paintings & Drawings

  • Link to View at Bogay near Derry (river Foyle in distance)

animatedPaint