iOS:NSAttributedString

http://iphonedevelopment.blogspot.com/2011/03/attributed-strings-in-ios.html

 

十个月以前,苹果发布了iPad和iOS3.2。iOS开发者终于可以使用NSAttributedString和NSMutableAttributedString了。它们(这两个对象)可以将字符串和相关字体、段落格式及格式化信息保存在一起。我们不需要使用“重量级”的UIWebView或复杂的CoreGraphics API来绘制文本。

在Mac上,NSAttributedString和NSMutableAttributedString出现在Foundation框架中已经有一些历史了。此后不久,App Kit对这两个类进行了类别化,增加了一系列方法——即所谓的Application Kit Additions。
这些类别可以从各种格式的文本文档(RTF、HTML)来创建格式化字符串,通过指定各种标准属性、扩展属性绘制格式文本,还可以计算格式文本的绘制尺寸。

事实上,这两个类真正有用的方法都在App Kit类别中,而不是在基类中。不幸的是,iOSSDK没有这些类别,以及它们的简化版本。我们只有这两个基本的类。也就是说,我们只能使用NSAttributedString的13个方法和NSMutableAttributedString的13个方法。
Cocoa中有“豪华版”的“属性化”字符串;而Cocoa touch只有普通的字符串。

更为古怪的是,NSattributedString有一个初始化方法,它有一个字符串属性字典的参数,但在iOS公开的头文件和文档中找不到相关的key常量。在文档的概述中,关于这个方法中使用到的属性key常量的描述,只存在于MacOSX文档,而不存在于iOS文档。
换句话说,你不能用initWithString:attributes方法创建NSAttributedString或NSMutableAttributedString,因为你根本无法使用那些用于指定字符串格式化属性的常量。当然,你可以使用CoreText中相对应的常量,例如用Core Text中的 kCTForegroundColorAttributeName 来取代原本的NSForegroundColorAttributeName。但这不是官方文档所推荐的,而且NS常量和CT常量并不是百分之百的能对应上(虽然很接近)。

有种情况很奇怪。苹果致力于提供复杂文本绘制的底层接口,而不提供更高级别的类,以至于有许多功能我们没有更雅致的方式来处理。在 Mac OSX 狮子中,开放了所有的 CoreText 和
CoreGraphics 函数(但不包括 Core Image)。但是,哪怕是用 attributed string 实现一个最简单的文本编辑程序,我们仍然得写大量的底层CoreText 和 CoreGraphics 代码。

幸运的是,NSAttributedString/NSMutableAttributedString和 CFAttributedStringRef/CFMutableAttributedStringRef 之间有免费桥接。也就是说,你创建了一个CFAttributedStringRef,可以将它简单地转换为一个 NSAttributedString 指针,然后调用NSAttributedString 对象支持的所有方法。
注意,在iOS 中,UIFont 和 CTFont 并不是免费桥接的关系,尽管在 Mac 它们下是。你不能将一个 UIFont 当成CTFont 传递给函数,反之亦然。

要想将 UIFont 转换为 CTFont,你需要:

CTFontRefCTFontCreateFromUIFont(UIFont *font)
{
    CTFontRef ctFont =CTFontCreateWithName((CFStringRef)font.fontName,
                                           font.pointSize,
                                           NULL);
    return ctFont;
}


注意函数 CTFontCreateWithName 名中带有 create 字样,表明返回结果 CTFont 将是被 retain 过的,你应该负责调用CFRelease 去释放它以免内存泄露。
将 CTFont 转换为 UIFont 要麻烦点。下面有一个 UIFont 的类别方法,用于从 CTFontRef 指针创建一个UIFont 实例。


@implementationUIFont(MCUtilities)
+ (id)fontWithCTFont:(CTFontRef)ctFont
{
    CFStringRef fontName= CTFontCopyFullName(ctFont);
    CGFloat fontSize =CTFontGetSize(ctFont);
   
    UIFont *ret = [UIFontfontWithName:(NSString *)fontName size:fontSize];
    CFRelease(fontName);
    return ret;
}
@end

只要知道如何在 CTFont 和UIFont 之间进行转换,创建 attributed string 其实是一件简单的事情。下面是一个 NSMutableAttributedString 的类别方法,用指定的文字、字体、字体大小、以及文字属性来创建NSMutableAttributedString。结果返回一个 autorelease 的 attributed string(将文字属性应用到整个字符串)。

+ (id)mutableAttributedStringWithString:(NSString*)string font:(UIFont *)font color:(UIColor *)coloralignment:(CTTextAlignment)alignment

{
   CFMutableAttributedStringRef attrString =CFAttributedStringCreateMutable(kCFAllocatorDefault, 0);
   
    if (string != nil)
       CFAttributedStringReplaceString (attrString, CFRangeMake(0, 0),(CFStringRef)string);
   
   CFAttributedStringSetAttribute(attrString, CFRangeMake(0,CFAttributedStringGetLength(attrString)), kCTForegroundColorAttributeName,color.CGColor);
    CTFontRef theFont =CTFontCreateFromUIFont(font);
   CFAttributedStringSetAttribute(attrString, CFRangeMake(0,CFAttributedStringGetLength(attrString)), kCTFontAttributeName, theFont);
    CFRelease(theFont);
   
    CTParagraphStyleSetting settings[] ={kCTParagraphStyleSpecifierAlignment, sizeof(alignment), &alignment};
    CTParagraphStyleRefparagraphStyle = CTParagraphStyleCreate(settings, sizeof(settings) /sizeof(settings[0]));
   CFAttributedStringSetAttribute(attrString, CFRangeMake(0,CFAttributedStringGetLength(attrString)), kCTParagraphStyleAttributeName,paragraphStyle);   
   CFRelease(paragraphStyle);

   
   NSMutableAttributedString *ret = (NSMutableAttributedString*)attrString;
   
    return [retautorelease];
}


真烦,为什么绘制 attributable  string 时需要计算所需的空间?但这是必须的。NSAttributedString 有两个类别方法,用于告诉你假设指定绘制的宽度,attributed string 将占据多高的空间。或者指定绘制的高度,将占据多宽的空间。这在对文本进行布局时相当有用:
NB(1): 这是精简后的代码,同时修正了原来的一个错误。
NB(2): Twiter 上曾有人建议在CTFrameSetterRef 中保存高或者宽的计算结果。由于 framesetter 缓存了计算结果,因此我们可以重用它。如果使用新的framesetter,则不要仅多创建一个对象,而且需要计算两次 size。我准备在讨论如何绘制 attributed string 时再根据这些建议重构代码。

-(CGFloat)boundingWidthForHeight:(CGFloat)inHeight
{
    CTFramesetterRefframesetter = CTFramesetterCreateWithAttributedString((CFMutableAttributedStringRef) self);
    CGSize suggestedSize= CTFramesetterSuggestFrameSizeWithConstraints(framesetter, CFRangeMake(0, 0),NULL, CGSizeMake(CGFLOAT_MAX, inHeight), NULL);
   CFRelease(framesetter);
    returnsuggestedSize.width;  
}
- (CGFloat)boundingHeightForWidth:(CGFloat)inWidth
{
    CTFramesetterRefframesetter = CTFramesetterCreateWithAttributedString((CFMutableAttributedStringRef) self);
    CGSize suggestedSize= CTFramesetterSuggestFrameSizeWithConstraints(framesetter, CFRangeMake(0, 0),NULL, CGSizeMake(inWidth, CGFLOAT_MAX), NULL);
   CFRelease(framesetter);
    returnsuggestedSize.height;
}


我估计苹果迟早会提供NSAttributedString UIKit Additions 类别或其他类似的高级 API(译者注:实际上前者已经在iOS6 中提供了)。同时,你必须和 attributed string 打交道,必要时可能要用到 CoreText 或 CoreGraphics(苹果的编程指南中对于如何使用这两个框架完成大部分常见任务进行了详细介绍),当然你也可以将这些代码封装为NSAttributedString 或 NSMutableAttributedString 的类别方法。

你可能感兴趣的:(iPhone开发)