Write/Read NSMutableAttributedString to Plist - objective-c

OK, here we are :
I've got an NSTextView
I'm getting it's NSMutableAttributedString content
I'm unable to read/write it to plist
By using Rob's code ( Saving custom attributes in NSAttributedString ), I've made some progress (I'm managing to write the data to disk), but I cannot recover it (= NSKeyedUnarchiver returns nil).
Encoding :
// where MAS --> NSMutableAttributedString
NSData* stringData = [NSKeyedArchiver archivedDataWithRootObject:MAS];
Decoding :
NSMutableAttributedString* mas = (NSMutableAttributedString*)[NSKeyedUnarchiver unarchiveObjectWithData:dat];
Any ideas? Any possible workaround (even if not with NSCoder, which I doubt it works with RTFs...) would be welcome!

And here's how it was solved.
NSAttributedString -> NSData :
NSData* j = [(NSMutableAttributedString*)MAS RTFDFromRange:NSMakeRange(0,[[MAS string] length])
documentAttributes:nil];
NSData -> NSAttributedString
NSMutableAttributedString* mas = [[NSMutableAttributedString alloc] initWithRTFD:dat
documentAttributes:nil];
Simple as that.

Related

Writing attributed strings line-by-line to an RTF file

I'm trying to write a series of attributed strings to an RTF file, line-by-line at various points in my application sit is running (you can think of this as log data, only with attributes). The file is created just fine and it would appear that all the data is being written to the file, but when I open the RTF file only the first line written to the file appears. I suspect that there's something written to the file in the first write that essentially end the RTF file, but I'm not quite sure what that is.
- (void) writeLineWithSizeAndStyle: (NSString *) line : (CGFloat)fontSize : (NSFontTraitMask) traits {
NSMutableAttributedString *attrString = [[NSMutableAttributedString alloc] initWithString:line];
NSFileHandle *fileHandle = [NSFileHandle fileHandleForUpdatingAtPath:filePath];
NSFontManager *fm = [NSFontManager sharedFontManager];
NSRange range = [line rangeOfString:line];
NSFont *font = [NSFont systemFontOfSize:fontSize];
font = [fm convertFont:font toHaveTrait:traits];
[attrString addAttribute: NSFontAttributeName value:font range:range];
NSData *rtfData = [attrString RTFFromRange:NSMakeRange(0, attrString.length) documentAttributes:#{}];
// Go the the end of the file, write the data, and close the file
[fileHandle seekToEndOfFile];
[fileHandle writeData: rtfData];
[fileHandle closeFile];
}
Is there something I need to to in converting each attributed string to NSData that effectively tells RFT not to terminate the file upon writing?
Thanks!
Update:
Here's the code for solution 2 I proposed in the comments section. This operates on an instance variable in the class object (playByPlay) that simply appends each new line onto the entire "log". This works great, but again is only really a solution for sufficiently small files.
- (void) writeLineWithSizeAndStyle: (NSString *) line : (CGFloat)fontSize : (NSFontTraitMask) traits {
NSFileHandle *fileHandle = [NSFileHandle fileHandleForUpdatingAtPath:filePath];
NSMutableAttributedString *attrString = [[NSMutableAttributedString alloc] initWithString:line];
NSRange range = [line rangeOfString:line];
NSFontManager *fm = [NSFontManager sharedFontManager];
NSFont *font = [NSFont systemFontOfSize:fontSize];
font = [fm convertFont:font toHaveTrait:traits];
[attrString addAttribute: NSFontAttributeName value:font range:range];
[playByPlayText appendAttributedString:attrString];
NSData *rtfData = [playByPlayText RTFFromRange:NSMakeRange(0, playByPlayText.length) documentAttributes:#{}];
[fileHandle writeData: rtfData];
[fileHandle closeFile];
}

Add hyperlinks to NSString iOS

I have a simple NSString, for example :
NSString *text = #"Stackoverflow is amazing!"
and I'd like to turn the word Stackoverflow into a hyperlink pointing to https://stackoverflow.com/ and still be able to output the string as a variable. Is this possible?
A string only contains letters - no formatting what so ever.
To make a part into a link, you have to Attributed Text and assign the first word a NSLink attribute with the url:
NSURL *url = [NSURL URLWithString:#"http://www.google.de"];
NSAttributedString *my = [NSAttributedString attributedStringWithString:#"my"
attributes:#{NSForegroundColorAttributeName:[UIColor blackColor], NSFontAttributeName:[UIFont systemFontWithSize:16]}];
NSAttributedString *link = [NSAttributedString attributedStringWithString:#"Link"
attributes:#{NSForegroundColorAttributeName:[UIColor blue], NSFontAttributeName:[UIFont systemFontWithSize:16], NSLinkAttributeName:url}];
NSMutableAttributedString *attr = [my mutableCopy];
[attr appendAttributedString:link];
show it in a textview, a UILabel doesn't support clicking AFAIK

How to save (and retrieve) NSAttributedString in a NSDictionary

I am trying to save and retrieve XML data to a file.
For this purpose I am using NSDictionary's writeToFile:
The question is, how to write and retrieve an attributed string to and from a file on disk using NSDictionary's writeToFile:?
If you need something more portable than archived object consider using RTF representation of NSAttributedString:
NSAttributedString* attrString = [[NSAttributedString alloc] initWithString:#"Attributed String"
attributes:#{ NSForegroundColorAttributeName: [NSColor redColor]}];
NSData* rtfData = [attrString RTFFromRange:NSMakeRange(0, attrString.length) documentAttributes:nil];
[rtfData writeToFile:#"string.rtf" atomically:YES];
You can read it back:
NSData* rtfData = [NSData dataWithContentsOfFile:#"string.rtf"];
NSAttributedString* attrString = [[NSAttributedString alloc] initWithRTF:rtfData documentAttributes:nil];
Also since RTF is a simple text format you can convert RTF data to NSString and store it as plain text in a dictionary.
NSAttributedString is not a property list object, so writeToFile: won't work.
It does, however, conform to NSCoding, so you can write it to an archive with your dictionary as the root object (assuming that all the other objects in the dictionary also conform).

modifying json data in a local file using SBjson

I have recently started Application development on MAC OS 10.6, I am trying to modify a "key/value" pair in a local JSON file on my MAC machine using SBJSON. I have successfully read the value of a key, but I am not able to get that how to modify the value of a key and synchronize this to the JSON file. Lets suppose, I have a following JSON Data int o a local file:
{
"name": {
"fName":"John",
"lName":"Doe"
}
}
And i want to change the value of "fName" to something else, like Robert.
I have tried alot searching about it, but got no clue... Can anyone help me.
I am using SBJSON Framework!
Code:
NSString *filePath = #"/Users/dev/Desktop/SQLiteFile/myJSON2.json";
NSData *myData = [NSData dataWithContentsOfFile:filePath];
NSString *responseString = [[NSString alloc] initWithData:myData encoding:NSUTF8StringEncoding];
NSLog(#"FILE CONTENT : %#", responseString);
SBJsonParser *jsonParser = [[SBJsonParser alloc] init];
NSDictionary * dictionary = (NSDictionary*)[jsonParser objectWithString:responseString error:NULL];
[dictionary setObject:#"Robert" forKey:#"fName"];
//
// Code for writing this change into the file, which i needed.
//
[jsonParser release];
You want a mutable deep copy of your dictionary. Then you'll be able to modify it.

Write to file not working

I'm trying to combine images in my app into one file and write it to disk.
NSMutableArray *array = [NSMutableArray arrayWithObjects:
[NSData dataWithContentsOfFile:#"0.png"],
[NSData dataWithContentsOfFile:#"1.png"],
[NSData dataWithContentsOfFile:#"2.png"],
nil];
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:array];
NSError *error = nil;
NSString *path=#"/Users/myusername/Desktop/_stuff.dat";
[data writeToFile:path options:NSDataWritingAtomic error:&error];
or
NSArray *array = [NSArray arrayWithObjects:
[NSImage imageNamed:#"0"],
[NSImage imageNamed:#"1"],
[NSImage imageNamed:#"2"],
nil];
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:array];
NSError *error = nil;
NSString *path=#"/Users/myusername/Desktop/_stuff.dat";
[data writeToFile:path options:NSDataWritingAtomic error:&error];
But both produce a file that is 4KB (empty). If I NSLog the error it is (null). Am I making the data the wrong way?
Edit: If I open the resulting file with a text editor, it looks like this:
I wrote a quick example:
Missing: memory management / error handling / proper file handling
// Archive
NSMutableArray *array = [[NSMutableArray alloc] init];
NSString * input = #"/Users/Anne/Desktop/1.png";
[array addObject:[NSData dataWithContentsOfFile:input]];
[array addObject:[NSData dataWithContentsOfFile:input]];
[array addObject:[NSData dataWithContentsOfFile:input]];
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:array];
NSString *path = #"/Users/Anne/Desktop/archive.dat";
[data writeToFile:path options:NSDataWritingAtomic error:nil];
// Unarchive
NSMutableArray *archive = [NSKeyedUnarchiver unarchiveObjectWithFile:path];
NSData * firstObject = [archive objectAtIndex:0];
NSString * output = #"/Users/Anne/Desktop/2.png";
NSURL *fileURL = [[NSURL alloc] initFileURLWithPath:output];
[firstObject writeToURL:fileURL atomically:YES];
You can also add NSImages to the NSMutableArray:
NSString * input = #"/Users/Anne/Desktop/1.png";
NSImage *image = [[NSImage alloc] initWithContentsOfFile: input];
[array addObject:image];
But that will significantly increase the file size.
Response to the following comment:
So if I only need to access an image at runtime (in the archive), is there a way to access that image at an index without unarchiving the whole thing? Seems like unnecessary overhead to me.
I assume you're still struggling with this problem?
Hiding (or encrypting) app resources?
Like i mentioned earlier, combining all files into one big file does the trick.
Just make sure you remember the file-length of each file and file-order.
Then you can extract any specific file you like without reading the whole file.
This might be a more sufficient way if you only need to extract one file at the time.
Quick 'dirty' sample:
// Two sample files
NSData *fileOne = [NSData dataWithContentsOfFile:#"/Users/Anne/Desktop/1.png"];
NSData *fileTwo = [NSData dataWithContentsOfFile:#"/Users/Anne/Desktop/2.png"];
// Get file length
int fileOneLength = [fileOne length];
int fileTwoLength = [fileTwo length];
// Combine files into one container
NSMutableData * container = [[NSMutableData alloc] init];
[container appendData:fileOne];
[container appendData:fileTwo];
// Write container to disk
[container writeToFile:#"/Users/Anne/Desktop/container.data" atomically:YES];
// Read data and extract sample files again
NSData *containerFile = [NSData dataWithContentsOfFile:#"/Users/Anne/Desktop/container.data"];
NSData *containerFileOne =[containerFile subdataWithRange:NSMakeRange(0, fileOneLength)];
NSData *containerFileTwo =[containerFile subdataWithRange:NSMakeRange(fileOneLength, fileTwoLength)];
// Write extracted files to disk (will be exactly the same)
[containerFileOne writeToFile:#"/Users/Anne/Desktop/1_extracted.png" atomically:YES];
[containerFileTwo writeToFile:#"/Users/Anne/Desktop/2_extracted.png" atomically:YES];
// Only extract one file from the container
NSString * containerPath = #"/Users/Anne/Desktop/container.data";
NSData * oneFileOnly = [[NSFileHandle fileHandleForReadingAtPath:containerPath] readDataOfLength:fileOneLength];
// Write result to disk
[oneFileOnly writeToFile:#"/Users/Anne/Desktop/1_one_file.png" atomically:YES];
Tip:
You can also save the 'index' inside the container file.
For example: The first 500 bytes contain the required information.
When you need a specific file: Read the index, get the file position and extract it.
You are archiving a NSMutable array of NSImage. This two classes conform to the NSCoding protocol required by NSKeyedArchiver, so I don't see where would be your problem.
So, here are many ideas to test.
First, are you sure that the data you think you have are valid? In your first code snippet, you write [NSData dataWithContentsOfFile:#"0.png"]. This method expects an absolute file path.
Assuming the problem is not in your code, just in your question, let's continue:
Do you have something different than nil in the variable data after your archiving? Ie, after the assignement to data, can you add this code. If the assertion fail, you will get an exception at runtime:
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:array];
NSAssert(nil != data, #"My object data is nil after archiving");
If the problem was not here, what is the return of the line [data writeToFile:path options:NSDataWritingAtomic error:&error];
(Not the variable error, but the return value of the call to the method - writeToFile: options: error:)
What happens if you simplify your code and just do this:
result = [NSKeyedArchiver archiveRootObject:data
toFile:archivePath];
If everything was ok, have you tried to unarchive your file with NSKeyedUnarchiver?
The problem is that [NSData dataWithContentsOfFile:#"0.png"] looks for the file "0.png" in the current directory, but what the application thinks of as the current directory is probably not the place you're expecting. For graphical apps, you should always either use an absolute path or a path relative to some place that you can get the absolute path of (e.g. your app bundle, the application support directory, some user-selected location).
For command-line tools, using the current directory is more common. But I doubt that's the case here.
Another thing I noticed on Mavericks and up is that the folders in the path must be in existence. Meaning you must create the folder structure prior to saving into that folder. If you try to write to a folder on the desktop or elsewhere, even with sandboxing off, it will fail if the folder does not exist. I know this has been answered already, but I found that my issue continued regardless, but once I make sure that the folder structure was in place, I could do my writing to that folder.
On a side note: I'm sure that you could do this from NSFileManager, and I'll be doing that myself once I finalize my app structure, but hope this helps someone else lost in the sauce.