Rich Text Format Directory File to NSAttributedString - ios

Is it possible to convert an RFTD (Rich Text Format Directory) package to an NSAttributedString in iOS? This is a package that includes an RTF (Rich Text Format) file plus other files like images that are included in the rich text file.
I can convert a normal RTF file like this but I don't know how to convert an RFTD package to an NSData object. I also don't know if it's then possible to convert that NSData object to an NSAttributedString object.
NSString *path = [[NSBundle mainBundle] pathForResource:#"Name" ofType:#"rtf"];
NSData *data = [NSData dataWithContentsOfFile:path];
NSError *error = nil;
NSAttributedString *string = [[NSAttributedString alloc] initWithData:data options:#{NSDocumentTypeDocumentAttribute: NSRTFTextDocumentType} documentAttributes:nil error:&error];

It looks like all RTFD-related functions are deliberately cut off from iOS, but since RTFD is just a directory with a normal RTF file, you could try accessing it as such.
If you need attachments, based on the RTF docs here it seems that you can find a marker "NeXTGraphic" inside the RTF file string
{{\NeXTGraphic attachment \widthN \heightN} string}
where "attachment" will be a file name.
Similar question here: Read RTFD data in IOS

Related

trouble saving NSAttributedString, with image, to an RTF file

I have some output that is a very simple RTF file. When I generate this document, the user can email it. All this works fine. The document looks good. Once I have the NSAttributedString, I make an NSData block, and write it to a file, like this:
NSData* rtfData = [attrString dataFromRange:NSMakeRange(0, [attrString length]) documentAttributes:#{NSDocumentTypeDocumentAttribute: NSRTFTextDocumentType} error:&error];
This file can be emailed. When I check the email all is good.
Now, I'm tasked with adding a UIImage at the top of the document. Great, so I'm creating an attributed string like this:
NSTextAttachment *attachment = [[NSTextAttachment alloc] init];
UIImage* image = [UIImage imageNamed:#"logo"];
attachment.image = image;
attachment.bounds = CGRectMake(0.0f, 0.0f, image.size.width, image.size.height);
NSMutableAttributedString *imageAttrString = [[NSAttributedString attributedStringWithAttachment:attachment] mutableCopy];
// sets the paragraph styling of the text attachment
NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc] init] ;
[paragraphStyle setAlignment:NSTextAlignmentCenter]; // centers image horizontally
[paragraphStyle setParagraphSpacing:10.0f]; // adds some padding between the image and the following section
[imageAttrString addAttribute:NSParagraphStyleAttributeName value:paragraphStyle range:NSMakeRange(0, [imageAttrString length])];
[imageAttrString appendAttributedString:[[NSAttributedString alloc] initWithString:#"\n\n"]];
At this point, in Xcode, I can do a QuickLook on imageAttrString, and it draws just fine.
Once this string is built, I'm doing this:
[attrString appendAttributedString:imageAttrString];
And then adding in all the rest of the attributed text that I originally generated.
When I look at the file now, there is no image. QuickLook looks good in the debugger, but no image in the final output.
Thanks in advance for any help with this.
Although, RTF does support embedded images on Windows, apparently it doesn't on OS X. RTF was developed by Microsoft and they added embedded images in version 1.5 (http://en.wikipedia.org/wiki/Rich_Text_Format#Version_changes). I think that Apple took earlier version of the format and their solution to images in documents was RTFD. Here is what Apple documentation says about RTF:
Rich Text Format (RTF) is a text formatting language devised by Microsoft Corporation. You can represent character, paragraph, and document format attributes using plain text with interspersed RTF commands, groups, and escape sequences. RTF is widely used as a document interchange format to transfer documents with their formatting information across applications and computing platforms. Apple has extended RTF with custom commands, which are described in this chapter.
So no images are mentioned. Finally to prove that RTF doesn't support images on mac, download this RTF document - it will show photo in Windows WordPad and won't show it in OS X TextEdit.
So as Larme mentioned - you should choose RTFD file type when adding attachments. From Wikipedia:
Rich Text Format Directory, also known as RTFD (due to its extension .rtfd), or Rich Text Format with Attachments
Although you will be able to get NSData object that contains both the text and the image (judging by its size), via dataFromRange:documentAttributes:#{NSDocumentTypeDocumentAttribute: NSRTFDTextDocumentType} error:] you probably won't be able to save it so that it could be opened successfully. At least - I wasn't able to do that.
That's probably because actually RTFD is not a file format - it's a format of a bundle. To check it, you could use TextEdit on your mac to create a new document, add image and a text to it and save it as a file. Then right click on that file and choose Show Package Contents and you'll notice that the directory contains both your image and the text in RTF format.
However you will be able to save this document successfully with this code:
NSFileWrapper *fileWrapper = [imageAttrString fileWrapperFromRange:NSMakeRange(0, [imageAttrString length]) documentAttributes:#{NSDocumentTypeDocumentAttribute: NSRTFDTextDocumentType} error:&error];
[fileWrapper writeToURL:yourFileURL options:NSFileWrapperWritingAtomic originalContentsURL:nil error:&error];
Because apparently NSFileWrapper knows how to deal with RTFD documents while NSData has no clue of what it contains.
However the main problem still remains - how to send it in email? Because RTFD document is a directory not a file, I'd say it's not very well suited for sending by email, however you can zip it and send with an extension .rtfd.zip. The extension here is the crucial because it will tell Mail app how to display contents of the attachment when user taps on it. Actually it will work also in Gmail and probably other email apps on iOS because it's the UIWebView that knows how to display .rtfd.zip. Here is a technical note about it: https://developer.apple.com/library/ios/qa/qa1630/_index.html#//apple_ref/doc/uid/DTS40008749
So the bottom line is - it can be done but the RTFD document will be an attachment to the email not the email content itself. If you want to have it as an email content you should probably look into embedding your image into HTML and sending the mail as HTML.
As Andris mentioned, Apple RTF implementation does not support embedded images.
RTFD isn't a real alternative, as only a few OS X apps can open RTFD files. For example MS Office can't.
Creating a HTML file with embedded images might help in some cases, but - for example - most email clients don't support HTML with embedded images (Apple Mail does, Outlook however doesn't).
But fortunately there is a solution to create real RTF files with embedded images!
As the RTF format of course supports embedded images (only Apples implementation doesn't), images in a NSAttributedStrings (NSTextAttachments) can be (hand) coded into the RTF stream.
The following category does all the work needed:
/**
NSAttributedString (MMRTFWithImages)
*/
#interface NSAttributedString (MMRTFWithImages)
- (NSString *)encodeRTFWithImages;
#end
/**
NSAttributedString (MMRTFWithImages)
*/
#implementation NSAttributedString (MMRTFWithImages)
/*
encodeRTFWithImages
*/
- (NSString *)encodeRTFWithImages {
NSMutableAttributedString* stringToEncode = [[NSMutableAttributedString alloc] initWithAttributedString:self];
NSRange strRange = NSMakeRange(0, stringToEncode.length);
//
// Prepare the attributed string by removing the text attachments (images) and replacing them by
// references to the images dictionary
NSMutableDictionary* attachmentDictionary = [NSMutableDictionary dictionary];
while (strRange.length) {
// Get the next text attachment
NSRange effectiveRange;
NSTextAttachment* textAttachment = [stringToEncode attribute:NSAttachmentAttributeName
atIndex:strRange.location
effectiveRange:&effectiveRange];
strRange = NSMakeRange(NSMaxRange(effectiveRange), NSMaxRange(strRange) - NSMaxRange(effectiveRange));
if (textAttachment) {
// Text attachment found -> store image to image dictionary and remove the attachment
NSFileWrapper* fileWrapper = [textAttachment fileWrapper];
UIImage* image = [[UIImage alloc] initWithData:[fileWrapper regularFileContents]];
// Kepp image size
UIImage* scaledImage = [self imageFromImage:image
withSize:textAttachment.bounds.size];
NSString* imageKey = [NSString stringWithFormat:#"_MM_Encoded_Image#%zi_", [scaledImage hash]];
[attachmentDictionary setObject:scaledImage
forKey:imageKey];
[stringToEncode removeAttribute:NSAttachmentAttributeName
range:effectiveRange];
[stringToEncode replaceCharactersInRange:effectiveRange
withString:imageKey];
strRange.length += [imageKey length] - 1;
} // if
} // while
//
// Create the RTF stream; without images but including our references
NSData* rtfData = [stringToEncode dataFromRange:NSMakeRange(0, stringToEncode.length)
documentAttributes:#{
NSDocumentTypeDocumentAttribute: NSRTFTextDocumentType
}
error:NULL];
NSMutableString* rtfString = [[NSMutableString alloc] initWithData:rtfData
encoding:NSASCIIStringEncoding];
//
// Replace the image references with hex encoded image data
for (id key in attachmentDictionary) {
NSRange keyRange = [rtfString rangeOfString:(NSString*)key];
if (NSNotFound != keyRange.location) {
// Reference found -> replace with hex coded image data
UIImage* image = [attachmentDictionary objectForKey:key];
NSData* pngData = UIImagePNGRepresentation(image);
NSString* hexCodedString = [self hexadecimalRepresentation:pngData];
NSString* encodedImage = [NSString stringWithFormat:#"{\\*\\shppict {\\pict \\pngblip %#}}", hexCodedString];
[rtfString replaceCharactersInRange:keyRange withString:encodedImage];
}
}
return rtfString;
}
/*
imageFromImage:withSize:
Scales the input image to pSize
*/
- (UIImage *)imageFromImage:(UIImage *)pImage
withSize:(CGSize)pSize {
UIGraphicsBeginImageContextWithOptions(pSize, NO, 0.0);
[pImage drawInRect:CGRectMake(0, 0, pSize.width, pSize.height)];
UIImage* resultImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return resultImage;
}
/*
hexadecimalRepresentation:
Returns a hex codes string for all bytes in a NSData object
*/
- (NSString *) hexadecimalRepresentation:(NSData *)pData {
static const char* hexDigits = "0123456789ABCDEF";
NSString* result = nil;
size_t length = pData.length;
if (length) {
NSMutableData* tempData = [NSMutableData dataWithLength:(length << 1)]; // double length
if (tempData) {
const unsigned char* src = [pData bytes];
unsigned char* dst = [tempData mutableBytes];
if ((src) &&
(dst)) {
// encode nibbles
while (length--) {
*dst++ = hexDigits[(*src >> 4) & 0x0F];
*dst++ = hexDigits[(*src++ & 0x0F)];
} // while
result = [[NSString alloc] initWithData:tempData
encoding:NSASCIIStringEncoding];
} // if
} // if
} // if
return result;
}
#end
The basic idea was taken from this article.

Formatted text on UITextView

I struck with a small thing i think so, in my app UITextView plays important role. So i like to add formatting feature (Bold, Italics, underline) to it.
Once i tried using,
[NotesTxtView setAllowsEditingTextAttributes:YES];
it works fine but when i save the data to db the formatted texts change to normal. What can i do for that?
Is there any solution for my problem?
Helpers are appreciated,..
You need to save style information also. NSAttributedString's method dataFromRange:documentAttributes:error: will help:
Returns an data object that contains a text stream corresponding to the characters and attributes within the given range.
So you save and restore NSData object from db.
NSDictionary *attrs = #{NSDocumentTypeDocumentAttribute:NSRTFTextDocumentType};
// export data
NSData *data =
[self.textView.attributedText
dataFromRange:NSMakeRange(0, self.textView.text.length)
documentAttributes:attrs
error:nil];
...
// save data to db, fetch later
...
// restore
self.textView.attributedText =
[[NSAttributedString alloc]
initWithData:data
options:nil
documentAttributes:&attrs
error:nil];
Consider using other document types (all available from iOS 7):
NSString *NSPlainTextDocumentType;
NSString *NSRTFTextDocumentType;
NSString *NSRTFDTextDocumentType;
NSString *NSHTMLTextDocumentType;

iOS: read raw plist

I want to read the UserDefaults plist but not as Dictionary or Data. I want it as string like it is when you open it with an editor.
NSString* documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *homeDir = [documentsPath stringByReplacingOccurrencesOfString:#"Documents" withString:#""];
NSString *defaultsPath = [homeDir stringByAppendingString:[NSString stringWithFormat:#"Library/Preferences/%#.plist", [[NSBundle mainBundle] bundleIdentifier]]];
NSData *data = [NSData dataWithContentsOfFile:defaultsPath];
Already tried:
`NSString *contents = [NSString stringWithContentsOfFile:defaultsPath encoding:NSUTF8StringEncoding error:&error];
which ends up with
The operation couldn’t be completed. (Cocoa error 261.)
Property list formats can be either binary or text. Binary plists can't be loaded into an NSString because strings are for text, not arbitrary binary data. The error you're getting seems to suggest that the file cannot be interpreted as UTF-8, which either means it is encoded using another encoding or is not text at all.
If you are certain that the property list is a text property list, you can use:
NSStringEncoding enc;
NSString *contents = [NSString stringWithContentsOfFile:defaultsPath usedEncoding:&enc error:&error];
This will allow the framework to determine the encoding of the text plist for you. If it isn't a text plist, you can convert it to one using the plutil command line utility:
plutil -convert xml1 file.plist
Or, alternatively you can do this in code by loading the plist using the NSPropertyListSerialization class, obtaining the NSData from it that represents the plist as the XML format, and then convert that to a string.
An example would be [uncompiled and untested]:
// load the file as-is into a data object
NSData *data = [NSData dataWithContentsOfFile:defaultsPath];
// convert the plist data into actual property list structure
id plistFile = [NSPropertyListSerialization propertyListWithData:data
options:0
format:NULL
error:&error];
// get the XML representation of the property list
NSData *asXML = [NSPropertyListSerialization dataWithPropertyList:plistFile
format:NSPropertyListXMLFormat_v1_0
options:0
error:&error];
// convert the NSData object into an NSString object
NSString *asString = [[NSString alloc] initWithData:asXML encoding:NSUTF8StringEncoding];
This should work whether the original plist is in XML or binary format. In this example, I am assuming that the XML representation of the property list is in fact UTF-8 encoded, as this is the most common encoding for XML data.

How to display a .rtf file into a UITextView

I've created a T&C for my app in TextMate. Then pasted it to a .rtf file created by XCode.
The content of the file is shown but I can see back-slashes when there are line breaks. What am I missing that this is happening?
NSError *e = Nil;
NSString * tc = [NSString stringWithContentsOfFile:[[NSBundle mainBundle] pathForResource:#"termsAndConditions" ofType:#"rtf"] encoding:NSUTF8StringEncoding error:&e];
NSLog(#"%#",e);
tvView.text = tc;
.rtf file always contains Tags with itself for formating text color alignment and other properties, Try using a .txt file instead.
In TextEdit> Create a .txt file>Copy the contents from .rtf to .txt and use it.
You can use the same code by just renaming the file type.
NSError *e = Nil;
NSString * tc = [NSString stringWithContentsOfFile:[[NSBundle mainBundle] pathForResource:#"termsAndConditions" ofType:#"txt"] encoding:NSUTF8StringEncoding error:&e];
NSLog(#"%#",e);
tvView.text = tc;
Couldn't you just replace the backslashes with blank ?
Such as
tc = [tc stringByReplacingOccurrencesOfString:#"\" withString:#""];

How to convert formatted content of NSTextView to string

I need transfer content of NSTextView from Mac app to iOS app. I'm using XML as transfered file format.
So I need to save content of NSTextView (text, fonts, colors atd.) as a string. Is there any way how to do that?
One way to do this is to archive the NSAttributedString value. Outline sample code typed directly into answer:
NSTextView *myTextView;
NSString *myFilename;
...
[NSKeyedarchiver archiveRootObject:myTextStorage.textStorage
toFile:myFilename];
To read it back:
myTextView.textStorage.attributedString = [NSKeyedUnarchiver unarchiveObjectWithFile:myFilename];
That's all that is needed to create and read back a file. There are matching methods which create an NSData rather than a file, and you can convert an NSData into an NSString or just insert one into an NSDictionary and serialise that as a plist (XML), etc.
Your best bet is probably to store the text as RFTD and load it as such in the other text view via an NSAttributedString.
// Load
NSFileWrapper* filewrapper = [[NSFileWrapper alloc] initWithPath: path];
NSTextAttachment *attachment = [[NSTextAttachment alloc] initWithFileWrapper: filewrapper];
NSAttributedString* origFile = [NSAttributedString attributedStringWithAttachment: attachment];
// Save
NSData *data = [origFile RTFDFromRange: NSMakeRange(0, [origFile length]) documentAttributes: nil];
[[NSFileManager defaultManager] createFileAtPath: path contents: data attributes:nil];

Resources