Сохранение пользовательских атрибутов в NSAttributedString

Мне нужно добавить настраиваемый атрибут к выбранному тексту в NSTextView. Поэтому я могу сделать это, получив атрибутную строку для выделения, добавив к ней настраиваемый атрибут и затем заменив выделение на мою новую атрибутную строку.

Итак, теперь я получаю текст, обозначенный как строка NSData, и записываю его в файл. Позже, когда я открою этот файл и верну его в текстовое представление, мои пользовательские атрибуты исчезли! После разработки всей схемы для моего пользовательского атрибута я обнаружил, что пользовательские атрибуты не сохраняются для вас. Посмотрите на ВАЖНУЮ заметку здесь: http://developer.apple.com/mac/library/DOCUMENTATION/Cocoa/Conceptual/AttributedStrings/Tasks/RTFAndAttrStrings.html

Поэтому я не знаю, как сохранить и восстановить мои документы с помощью этого настраиваемого атрибута. Любая помощь?

Ответ 1

Обычным способом сохранения NSAttributedString является использование RTF, а данные RTF - это способ -dataFromRange:documentAttributes:error: NSAttributedString.

Однако формат RTF не поддерживает пользовательские атрибуты. Вместо этого вы должны использовать протокол NSCoding для архивирования вашей атрибутной строки, которая сохранит пользовательские атрибуты:

//asssume attributedString is your NSAttributedString
//encode the string as NSData
NSData* stringData = [NSKeyedArchiver archivedDataWithRootObject:attributedString];
[stringData writeToFile:pathToFile atomically:YES];

//read the data back in and decode the string
NSData* newStringData = [NSData dataWithContentsOfFile:pathToFile];
NSAttributedString* newString = [NSKeyedUnarchiver unarchiveObjectWithData:newStringData];

Ответ 2

Существует способ сохранения пользовательских атрибутов в RTF с помощью Cocoa. Он полагается на то, что RTF является текстовым форматом, поэтому его можно манипулировать как строку, даже если вы не знаете все правила RTF и не имеете пользовательского RTF-ридера/записи. Процедура, описанная ниже, обрабатывает RTF как при записи, так и при чтении, и я использовал эту технику лично. Одно очень важно, чтобы текст, который вы вставляете в RTF, использует только 7-битный ASCII и не имеет неэкранированных управляющих символов, которые включают "\ {}".

Здесь вы можете кодировать свои данные:

NSData *GetRtfFromAttributedString(NSAttributedString *text)
{
    NSData *rtfData = nil;
    NSMutableString *rtfString = nil;
    NSString *customData = nil, *encodedData = nil;
    NSRange range;
    NSUInteger dataLocation;

// Convert the attributed string to RTF
    if ((rtfData = [text RTFFromRange:NSMakeRange(0, [text length]) documentAttributes:nil]) == nil)
        return(nil);

// Find and encode your custom attributes here. In this example the data is a string and there at most one of them
    if ((customData = [text attribute:@"MyCustomData" atIndex:0 effectiveRange:&range]) == nil)
        return(rtfData); // No custom data, return RTF as is
    dataLocation = range.location;

// Get a string representation of the RTF
    rtfString = [[NSMutableString alloc] initWithData:rtfData encoding:NSASCIIStringEncoding];

// Find the anchor where we'll put our data, namely just before the first paragraph property reset
    range = [rtfString rangeOfString:@"\\pard" options:NSLiteralSearch];
    if (range.location == NSNotFound)
        {
        NSLog(@"Custom data dropped; RTF has no paragraph properties");
        [rtfString release];
        return(rtfData);
        }

// Insert the starred group containing the custom data and its location
    encodedData = [NSString stringWithFormat:@"{\\*\\my_custom_keyword %d,%@}\n", dataLocation, customData];
    [rtfString insertString:encodedData atIndex:range.location];

// Convert the amended RTF back to a data object    
    rtfData = [rtfString dataUsingEncoding:NSASCIIStringEncoding];
    [rtfString release];
    return(rtfData);
}

Этот метод работает, потому что все совместимые читатели RTF будут игнорировать "звездные группы", ключевое слово которых они не распознают. Поэтому вы хотите быть уверенным, что ваше контрольное слово не будет распознано каким-либо другим читателем, поэтому используйте что-то, что может быть уникальным, например префикс вашей компании или название продукта. Если ваши данные сложны или бинарны или могут содержать незаконные символы RTF, которые вы не хотите убегать, закодируйте его в base64. Убедитесь, что вы поместили пробел после ключевого слова.

Аналогично, при чтении RTF вы ищете свое управляющее слово, извлекаете данные и восстанавливаете атрибут. Эта процедура принимает в качестве аргументов атрибутивную строку и RTF, из которых она была создана.

void RestoreCustomAttributes(NSMutableAttributedString *text, NSData *rtfData)
{
    NSString *rtfString = [[NSString alloc] initWithData:rtfData encoding:NSASCIIStringEncoding];
    NSArray *components = nil;
    NSRange range, endRange;

// Find the custom data and its end
    range = [rtfString rangeOfString:@"{\\*\\my_custom_keyword " options:NSLiteralSearch];
    if (range.location == NSNotFound)
        {
        [rtfString release];
        return;
        }
    range.location += range.length;

    endRange = [rtfString rangeOfString:@"}" options:NSLiteralSearch
        range:NSMakeRange(range.location, [rtfString length] - endRange.location)];
    if (endRange.location == NSNotFound)
        {
        [rtfString release];
        return;
        }

// Get the location and the string data, which are separated by a comma
    range.length = endRange.location - range.location;
    components = [[rtfString substringWithRange:range] componentsSeparatedByString:@","];
    [rtfString release];

// Assign the custom data back to the attributed string. You should do range checking here (omitted for clarity)
    [text addAttribute:@"MyCustomData" value:[components objectAtIndex:1]
        range:NSMakeRange([[components objectAtIndex:0] integerValue], 1)];
}