NSAttributedString 详解

NSAttributedString可以让我们使一个字符串显示的多样化,但是目前到iOS 5为止,好像对它支持的不是很好,因为显示起来不太方便(至少没有在OS X上方便)。

首先导入CoreText.framework,并在需要使用的文件中导入:

#import<CoreText/CoreText.h>

创建一个NSMutableAttributedString:

NSMutableAttributedString *attriString = [[[NSMutableAttributedString alloc] initWithString:@"this is test!"]
                                              autorelease];

非常常规的创建方式,接下来我们给它配置属性:

//把this的字体颜色变为红色
[attriString addAttribute:(NSString *)kCTForegroundColorAttributeName
                    value:(id)[UIColor redColor].CGColor
                    range:NSMakeRange(0, 4)];
//把is变为黄色
[attriString addAttribute:(NSString *)kCTForegroundColorAttributeName
                    value:(id)[UIColor yellowColor].CGColor
                    range:NSMakeRange(5, 2)];
//改变this的字体,value必须是一个CTFontRef
[attriString addAttribute:(NSString *)kCTFontAttributeName
                    value:(id)CTFontCreateWithName((CFStringRef)[UIFont boldSystemFontOfSize:14].fontName,
                                                   14,
                                                   NULL)
                    range:NSMakeRange(0, 4)];
//给this加上下划线,value可以在指定的枚举中选择
[attriString addAttribute:(NSString *)kCTUnderlineStyleAttributeName
                    value:(id)[NSNumber numberWithInt:kCTUnderlineStyleDouble]
                    range:NSMakeRange(0, 4)];
return attriString;

这样就算是配置好了,但是我们可以发现NSAttributedString继承于NSObject,并且不支持任何draw的方法,那我们就只能自己draw了。写一个UIView的子类(假设命名为TView),在initWithFrame中把背景色设为透明(self.backgroundColor = [UIColor clearColor]),然后在重写drawRect方法:

-(void)drawRect:(CGRect)rect{
    [super drawRect:rect];

    NSAttributedString *attriString = getAttributedString();

    CGContextRef ctx = UIGraphicsGetCurrentContext();
    CGContextConcatCTM(ctx, CGAffineTransformScale(CGAffineTransformMakeTranslation(0, rect.size.height), 1.f, -1.f));

    CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)attriString);
    CGMutablePathRef path = CGPathCreateMutable();
    CGPathAddRect(path, NULL, rect);

    CTFrameRef frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, 0), path, NULL);
    CFRelease(path);
    CFRelease(framesetter);

    CTFrameDraw(frame, ctx);
    CFRelease(frame);
}

在代码中我们调整了CTM(current transformation matrix),这是因为Quartz 2D的坐标系统不同,比如(10, 10)到(20, 20)的直线坐标:

 

坐标类似于数学中的坐标,可以先不调整CTM,看它是什么样子的,下面两种调整方法是完全一样的:

CGContextConcatCTM(ctx, CGAffineTransformScale(CGAffineTransformMakeTranslation(0, rect.size.height), 1.f, -1.f));

==

CGContextTranslateCTM(ctx, 0, rect.size.height);
CGContextScaleCTM(ctx, 1, -1);

CTFramesetter是CTFrame的创建工厂,NSAttributedString需要通过CTFrame绘制到界面上,得到CTFramesetter后,创建path(绘制路径),然后得到CTFrame,最后通过CTFrameDraw方法绘制到界面上。

如果想要计算NSAttributedString所要的size,就需要用到这个API:

CTFramesetterSuggestFrameSizeWithConstraints,用NSString的sizeWithFont算多行时会算不准的,因为在CoreText里,行间距也是你来控制的。

设置行间距和换行模式都是设置一个属性:kCTParagraphStyleAttributeName,这个属性里面又分为很多子

属性,其中就包括

  • kCTLineBreakByCharWrapping
  • kCTParagraphStyleSpecifierLineSpacingAdjustment

设置如下:

//段落
    //line break
CTParagraphStyleSetting lineBreakMode;
CTLineBreakMode lineBreak = kCTLineBreakByCharWrapping; //换行模式
lineBreakMode.spec = kCTParagraphStyleSpecifierLineBreakMode;
lineBreakMode.value = &lineBreak;
lineBreakMode.valueSize = sizeof(CTLineBreakMode);
    //行间距
CTParagraphStyleSetting LineSpacing;
CGFloat spacing = 4.0;  //指定间距
LineSpacing.spec = kCTParagraphStyleSpecifierLineSpacingAdjustment;
LineSpacing.value = &spacing;
LineSpacing.valueSize = sizeof(CGFloat);

CTParagraphStyleSetting settings[] = {lineBreakMode,LineSpacing};
CTParagraphStyleRef paragraphStyle = CTParagraphStyleCreate(settings, 2);   //第二个参数为settings的长度
[attributedString addAttribute:(NSString *)kCTParagraphStyleAttributeName
                         value:(id)paragraphStyle
                         range:NSMakeRange(0, attributedString.length)];

-----------------------------------------猥琐的分界线-----------------------------------------

这并不是唯一的方法,还有另一种替代方案:

CATextLayer *textLayer = [CATextLayer layer];
textLayer.string = getAttributedString();
textLayer.frame = CGRectMake(0, CGRectGetMaxY(view.frame), 200, 200);
[self.view.layer addSublayer:textLayer];

CATextLayer可以直接支持NSAttributedString!

-----------------------------------------猥琐的分界线-----------------------------------------

效果图:

源码地址

-----------------------------------------猥琐的分界线-----------------------------------------

目前发现有一个问题,好像中文全都会被加粗,设置不加粗的字体也没用,应该是CoreText的bug,已经上报给了apple了。

-----------------------------------------对于cythb兄提出的问题-----------------------------------------

首先表示感谢关注

原文中确有描述不适当的地方,比如:The upper-left corner of the context is (0, 0) 。实际上Quartz2D的坐标系统确实在左下角,只是有一些技术在设置它们的graphics context时使用了不同于Quartz的默认坐标系统。相对于Quartz来说,这些坐标系统是修改的坐标系统(modified coordinate system)。

UPDATED

在iOS6之后,创建一个AttributedString变成了一件轻松的事情,<CoreText/CoreText.h>已经不需要导入了。如果我要设置字体的颜色,可以直接这样:

[textAttr addAttribute:NSForegroundColorAttributeName

                 value:[UIColor redColor]

                 range:NSMakeRange(0, text.length)];

如果要计算一个NSAttributedString的size,使用NSAttributedString的这个API:

- (CGRect)boundingRectWithSize:(CGSize)size options:(NSStringDrawingOptions)options context:(NSStringDrawingContext *)context NS_AVAILABLE_IOS(6_0);

但是需要注意一点,如果调用这个API的NSAttributedString不包含字体、行高等有利于计算的数据,那最终计算出来的size可能和实际有所出入。

时间: 2024-11-08 18:59:58

NSAttributedString 详解的相关文章

POPSpring动画参数详解

POPSpring动画参数详解   效果   源码 https://github.com/YouXianMing/Animations // // POPSpringParameterController.m // Animations // // Created by YouXianMing on 15/11/29. // Copyright 2015年 YouXianMing. All rights reserved. // #import "POPSpringParameterContro

详解Python中的type()方法的使用

  这篇文章主要介绍了详解Python中的type()方法的使用,是Python入门中的基础知识,需要的朋友可以参考下 type()方法返回传递变量的类型.如果传递变量是字典那么它将返回一个字典类型. 语法 以下是type()方法的语法: ? 1 type(dict) 参数 dict -- 这是字典 返回值 此方法返回传递变量的类型. 例子 下面的例子显示type()方法的使用 ? 1 2 3 4 #!/usr/bin/python   dict = {'Name': 'Zara', 'Age'

【转】java枚举使用详解

在实际编程中,往往存在着这样的"数据集",它们的数值在程序中是稳定的,而且"数据集"中的元素是有限的. 例如星期一到星期日七个数据元素组成了一周的"数据集",春夏秋冬四个数据元素组成了四季的"数据集". 在Java中如何更好的使用这些"数据集"呢?因此枚举便派上了用场,以下代码详细介绍了枚举的用法. Java代码   package com.ljq.test;      /**   * 枚举用法详解   *

状态模式(state pattern) 详解

状态模式(state pattern): 允许对象在内部状态改变时改变它的行为, 对象看起来好像修改了它的类. 建立Context类, 包含多个具体状态(concrete state)类的组合, 根据状态的不同调用具体的方法, state.handle(), 包含set\get方法改变状态. 状态接口(state interface), 包含抽象方法handle(), 具体状态类(concrete state)继承(implement)状态类(state), 实现handle()方法; 具体方法

kafka详解一、Kafka简介

背景:      当今社会各种应用系统诸如商业.社交.搜索.浏览等像信息工厂一样不断的生产出各种信息,在大数据时代,我们面临如下几个挑战: 如何收集这些巨大的信息 如何分析它        如何及时做到如上两点      以上几个挑战形成了一个业务需求模型,即生产者生产(produce)各种信息,消费者消费(consume)(处理分析)这些信息,而在生产者与消费者之间,需要一个沟通两者的桥梁-消息系统.      从一个微观层面来说,这种需求也可理解为不同的系统之间如何传递消息. Kafka诞生

Cocos2d-x win7 + vs2010 配置图文详解

Cocos2d-x win7 + vs2010 配置图文详解(亲测)   下载最新版的cocos2d-x.打开浏览器,输入cocos2d-x.org,然后选择Download,本教程写作时最新版本为cocos2d-1.01-x-0.9.1,具体下载位置如下图: 下载完之后,解压到当前文件夹.我把下载的程序放在F盘根目录,解压完毕之后,双击打开文件夹,看看里面有什么东西,红圈部分就是我们要安装使用的文件: 接下来,我们正式安装cocos2d-x到win7中去. 首先,双击上图中的cocos2d-w

ip-《TCP/IP 详解卷一》中90页中讲到,“由于子网号不相同,代理ARP不能使用”,这怎么理解?

问题描述 <TCP/IP 详解卷一>中90页中讲到,"由于子网号不相同,代理ARP不能使用",这怎么理解? <TCP/IP 详解卷一>中90页中讲到,"由于子网号不相同,代理ARP不能使用",这怎么理解? 解决方案 ARP主要用在一个子网中,用MAC地址来通信.数据链路层 不同子网,需要通过三层路由 解决方案二: 比如 N1 <-> GW <-> N2,N1和N2是同一个子网,GW上开启arp代理的效果是,N1和N2上

JS表格组件神器bootstrap table详解(基础版)_javascript技巧

一.Bootstrap Table的引入 关于Bootstrap Table的引入,一般来说还是两种方法: 1.直接下载源码,添加到项目里面来. 由于Bootstrap Table是Bootstrap的一个组件,所以它是依赖Bootstrap的,我们首先需要添加Bootstrap的引用. 2.使用我们神奇的Nuget 打开Nuget,搜索这两个包 Bootstrap已经是最新的3.3.5了,我们直接安装即可. 而Bootstrap Table的版本竟然是0.4,这也太坑爹了.所以博主建议Boot

网页文字该多大?文字字号详解

网页|详解 文字在网页上的应用: (只看小标题您就能知道这一部分要说什么了,为了避免罗嗦,在此文发布前决定把此处300余字删除,只保留了下面这一句.) 对于一个设计的好的网站来说,用户浏览时间最长的是文章正文. 中文网页文字的现状: 中文网页普遍使用12px和14px的宋体(simsun),这两种文字都可以加粗(font-weight: bold).这个规范似乎也成了网页设计师的基本 常识. 中国的网页设计师有这4种可供选择的文字表现方式,仅仅是4种,只有这4种,4种很小的文字.其中12px的宋