问题
Based on the accepted answer to this question I wrote the following code:
NSData* somedata;
somedata=[NSKeyedArchiver archivedDataWithRootObject:ts];
where ts is an NSAttributedString that is populated with some text and some attributes (colors, in this case).
When I execute this code, I receive this error:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSCFType encodeWithCoder:]: unrecognized selector sent to instance 0x6eb5b90'
I'm new to the NSCoder arena, but the answer to the aforementioned question made it seem like this is all I have to do. Is it? Did I miss something?
EDIT:
The unrecognized selector in this case is being sent to a color attribute in the NSAttributedString. When I initialize the string like so:
NSAttributedString *ts = [[NSAttributedString alloc] initWithString:text attributes:self.currentAttributeDictionary];
The dictionary is built like so:
self.currentAttributeDictionary=[NSDictionary dictionaryWithObjectsAndKeys:
[self.currentColor CGColor],(NSString*)kCTForegroundColorAttributeName,
nil];
And an NSLog of the dictionary yields this:
New dictionary is: {
CTForegroundColor = "<CGColor 0x6eb5b90> [<CGColorSpace 0x6e968c0> (kCGColorSpaceDeviceRGB)] ( 1 1 0 1 )";}
The address of the CGColor, above, matches the address in the error message.
回答1:
While UIColor
conforms to NSCoding
, it is (unlike most such classes) not toll-free bridged to CGColorRef
. Your dictionary is attempting to encode its contents, and CGColorRef
doesn't know how to encode itself.
Presuming that you don't want to encode a UIColor
instead (since these sound like Core Text attributes), you are going to have to handle serialization of the CGColorRef
yourself. See, for example, this question for some useful thoughts.
It should be noted, btw, since I don't know where the archived data is going, that if you want to unarchive the data on OS X that colors again become a headache at the AppKit/UIKit level: NSColor
and UIColor
are not directly compatible, so you would still need to go via CGColorRef
, stashing the color space information as appropriate.
回答2:
As requested, here's the code I used to accomplish what i needed to accomplish. It's been a year since I looked at this code, and it was written more to understand what was going on than for great coding practices or for any sort of efficiency. However, it did work, and it worked great!
I defined a category of NSAttributedString code is below.
Example use:
-(void)code:(id)sender {
self.testData=[textView.attributedString customEncode];
NSLog(@"%@",self.testData);
}
-(void)recover:(id)sender {
NSAttributedString* tString=[NSMutableAttributedString customDecode:self.testData];
NSLog(@"Recover pressed: %@",tString);
textView.attributedString=tString;
}
And here's the underlying code:
#import "NSAttributedString+Extras.h"
#import <CoreText/CoreText.h>
@implementation NSAttributedString (Extras)
-(NSData*)customEncode {
__block NSMutableArray* archivableAttributes=[[NSMutableArray alloc]init];
[self enumerateAttributesInRange:NSMakeRange(0, [self length]) options:0 usingBlock:^(NSDictionary *attrs, NSRange range, BOOL *stop) {
NSLog(@"range: %d %d",range.location, range.length);
NSLog(@"dict: %@",attrs);
NSLog(@"keys: %@", [attrs allKeys]);
NSLog(@"values: %@", [attrs allValues]);
NSMutableDictionary* tDict=[[NSMutableDictionary alloc]init];
[tDict setObject:[NSNumber numberWithInt:range.location] forKey:@"location"];
[tDict setObject:[NSNumber numberWithInt:range.length] forKey:@"length"];
for (NSString* tKey in [attrs allKeys]) {
if ([tKey isEqualToString:@"CTUnderlineColor"]) {
[tDict setObject:[NSAttributedString arrayFromCGColorComponents:((CGColorRef)[attrs objectForKey:@"CTUnderlineColor"])] forKey:@"CTUnderlineColor"];
}
if ([tKey isEqualToString:@"NSUnderline"]) {
NSNumber* underline=[attrs objectForKey:@"NSUnderline"];
[tDict setObject:underline forKey:@"NSUnderline"];
}
if ([tKey isEqualToString:@"CTForegroundColor"]) {
[tDict setObject:[NSAttributedString arrayFromCGColorComponents:((CGColorRef)[attrs objectForKey:@"CTForegroundColor"])] forKey:@"CTForegroundColor"];
}
if ([tKey isEqualToString:@"NSFont"]) {
CTFontRef font=((CTFontRef)[attrs objectForKey:@"NSFont"]);
NSDictionary* fontDict=[NSDictionary
dictionaryWithObjects:
[NSArray arrayWithObjects:(NSString*)CTFontCopyPostScriptName(font),[NSNumber numberWithFloat:CTFontGetSize(font)], nil]
forKeys:
[NSArray arrayWithObjects:@"fontName", @"fontSize", nil]];
[tDict setObject:fontDict forKey:@"NSFont"];
}
}
[archivableAttributes addObject:tDict];
}];
NSMutableDictionary* archiveNSMString=[NSMutableDictionary
dictionaryWithObjects: [NSArray arrayWithObjects:[self string],archivableAttributes,nil]
forKeys:[NSArray arrayWithObjects:@"string",@"attributes",nil]];
NSLog(@"archivableAttributes array: %@",archiveNSMString);
NSData* tData=[NSKeyedArchiver archivedDataWithRootObject:archiveNSMString];
NSLog(@"tdata: %@",tData);
return tData;
}
+(NSAttributedString*)customDecode:(NSData *)data {
NSMutableAttributedString* tString;
NSMutableDictionary* tDict=[NSKeyedUnarchiver unarchiveObjectWithData:data];
NSArray* attrs;
CTFontRef font=NULL;
CGColorRef color=NULL;
NSNumber* underlineProp=[NSNumber numberWithInt:0];
CGColorRef underlineColor=NULL;
NSLog(@"decoded dictionary: %@",tDict);
if ([[tDict allKeys]containsObject:@"string"]) {
tString=[[NSMutableAttributedString alloc]initWithString:((NSString*)[tDict objectForKey:@"string"])];
}
else {
tString=[[NSMutableAttributedString alloc]initWithString:@""];
}
if ([[tDict allKeys]containsObject:@"attributes"]) {
attrs=[tDict objectForKey:@"attributes"];
}
else {
attrs=nil;
}
for (NSDictionary* attDict in attrs) {
int location=-1;
int length=-1;
NSRange insertRange=NSMakeRange(-1, 0);
if ([[attDict allKeys]containsObject:@"location"]) {
location=[[attDict objectForKey:@"location"]intValue];
}
if ([[attDict allKeys]containsObject:@"length"]) {
length=[[attDict objectForKey:@"length"]intValue];
}
if (location!=-1&&length!=-1) {
insertRange=NSMakeRange(location, length);
}
if ([[attDict allKeys]containsObject:@"NSUnderline"]) {
underlineProp=[attDict objectForKey:@"NSUnderline"];
}
if ([[attDict allKeys]containsObject:@"CTUnderlineColor"]) {
underlineColor=[NSAttributedString cgColorRefFromArray:[attDict objectForKey:@"CTUnderlineColor"]];
}
if ([[attDict allKeys]containsObject:@"CTForegroundColor"]) {
color=[NSAttributedString cgColorRefFromArray:[attDict objectForKey:@"CTForegroundColor"]];
}
if ([[attDict allKeys]containsObject:@"NSFont"]) {
NSString* name=nil;
float size=-1;
NSDictionary* fontDict=[attDict objectForKey:@"NSFont"];
if ([[fontDict allKeys]containsObject:@"fontName"]) {
name=[fontDict objectForKey:@"fontName"];
}
if ([[fontDict allKeys]containsObject:@"fontSize"]) {
size=[[fontDict objectForKey:@"fontSize"]floatValue];
}
if (name!=nil&&size!=-1) {
font=CTFontCreateWithName((CFStringRef)name, size, NULL);
}
}
if (insertRange.location!=-1) {
if (color!=NULL) {
[tString addAttribute:(NSString*)kCTForegroundColorAttributeName value:(id)color range:insertRange];
}
if (font!=NULL) {
[tString addAttribute:(NSString*)kCTFontAttributeName value:(id)font range:insertRange];
}
if ([underlineProp intValue]!=0&&underlineColor!=NULL) {
[tString addAttribute:(NSString*)kCTUnderlineColorAttributeName value:(id)underlineColor range:insertRange];
[tString addAttribute:(NSString*)kCTUnderlineStyleAttributeName value:(id)underlineProp range:insertRange];
}
}
}
[tString enumerateAttributesInRange:NSMakeRange(0, [tString length]) options:0 usingBlock:^(NSDictionary *attrs, NSRange range, BOOL *stop) {
NSLog(@"range: %d %d",range.location, range.length);
NSLog(@"dict: %@",attrs);
NSLog(@"keys: %@", [attrs allKeys]);
NSLog(@"values: %@", [attrs allValues]);
}];
return [[NSAttributedString alloc]initWithAttributedString:tString];
}
+(NSArray*)arrayFromCGColorComponents:(CGColorRef)color {
int numComponents=CGColorGetNumberOfComponents(color);
CGFloat* components=CGColorGetComponents(color);
NSMutableArray* retval=[[NSMutableArray alloc]init];
for(int i=0;i<numComponents;i++) {
[retval addObject:[NSNumber numberWithFloat:components[i]]];
}
return [NSArray arrayWithArray:retval];
}
+(CGColorRef)cgColorRefFromArray:(NSArray*)theArray {
CGFloat* array=malloc(sizeof(CGFloat)*[theArray count]);
for (int i=0; i<[theArray count]; i++) {
array[i]=[[theArray objectAtIndex:i]floatValue];
}
CGColorSpaceRef theSpace;
if ([theArray count]==2) {
theSpace=CGColorSpaceCreateDeviceGray();
}
else {
theSpace=CGColorSpaceCreateDeviceRGB();
}
return CGColorCreate(theSpace, array);
}
@end
来源:https://stackoverflow.com/questions/10558619/encoding-nsattributedstring-throws-error