对应的位置,这说来就是另外一个难搞的问题了,接下来我们就来细说一下:
CoreText的组成其实可以理解为一个frame(注意这里不是UIView的frame,而是另一个概念,可以理解为帧),这个frame是怎么来的呢?其实就是根据你给定的一个attributedString和一个绘制区域,CoreText自动将能放下的文字放到这个区域,然后形成了一个帧,我们知道,如果区域大小不同,那么能放下的文字不同,比如,你给一张A4纸张,要写下字体15的字,可能最多只能写2000字,但是给一张更大的纸,可能就可以写下1万字,那多余的字哪去了?很抱歉,CoreText帮你忽略了,那我就这么个区域,能写下多少字呢?这个很幸运的是,CT有给你接口, CFRange frameRange=CTFrameGetVisibleStringRange(ctFrame); 这个返回值就是告诉你,我这里写了哪些范围的字了。那你可能就又问,那我想把所有字写出来,我应该准备多大的纸是吧,很好,这个CT也有接口给我们,一般准备多大张的纸,一定的文字量,如果给的纸张的宽度越大,那么需要的纸张的高度就肯定越小,而且一般我们会限定纸张的宽度(比如手机看的话,手机宽度一般固定,手机高度不够我们可以用滚动视图,来保证足够高)。
CGSize sz = CTFramesetterSuggestFrameSizeWithConstraints (framesetter, CFRangeMake ( 0 ,string. length ), NULL , _constrainSize ,&fitCFRange);
以上方法就是,在约定的不超出的范围内,需要用到得区域大小。
说到这里,大家应该就明白了,一个聊天气泡应该需要多大了。但是依然没有说到,聊天表情应该绘制在哪个位置。
上面我们说了frame,frame其实就是由N行字组成的,那每行字又是由文字片段组成,这里文字片段是啥概念呢?并不是一个文字一个片,可能是属性很相似的的几个字符组成的字符串,字数不定。(更具体可参考另一篇日志: http://user.qzone.qq.com/511107989/blog/1369214175 )
CT给我们提供了一个接口,告诉了我们每一行的起始坐标位置,然后有给了一个接口,告诉我们该行每个文字片段距离起始位置的偏移。而我们是可以通过这个文字片,获取到它所在的文字区域的设为A,这时候好了 ,我们本来就应该知道表情的区域的(设为B) ,因此,只要A和B相等,那么这个表情的具体坐标我们就可以知道了,而表情的大小,我们也是早就在回调里设置了得。到这里,我们就可以完整的知道表情应该绘制的位置和大小(frame)。我们就可以在绘制完了帧后,在对应的位置绘制image,就完成了图文混排的效果,同样,如果你要显示的事gif效果图,你可以在对应位置加上UIImageView即可。
上面说到的,我们本该知道的表情区域,是怎么知道的?其实用QQ聊天举例,当我们点击表情后,输入框就会追加一个\表情代号,比如“ 你太天真了\天真,努力学习吧\奋斗 ”,其实对这么一句话,我们可以用正则表达式,获取到每个表情的区域的。
NSArray* chunks = [regex matchesInString:text options:0
range:NSMakeRange(0, [text length])];
int lastP=0;
NSMutableArray *pieces=[NSMutableArray array];
for (NSTextCheckingResult* chunk in chunks)
{
NSRange range=chunk.range;
}
最后绘制:
CTFrameDraw((CTFrameRef)ctFrame, context);
NSPredicate *pre=[NSPredicate predicateWithFormat:@"SELF.type=%d",ChatPieceTypeImage];
NSArray *iamges=[_chatPieces filteredArrayUsingPredicate:pre];
for(ChatPiece *imagePice in iamges){
UIImage *img=[UIImage imageNamed:imagePice.text];
CGContextDrawImage(context, imagePice.frame, img.CGImage);
NSLog(@"drawing image at :%@",NSStringFromCGRect(imagePice.frame));
}
那上面的frame是怎么来的?上面提到了,就是通过字符串,生成来的。
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)[selfattributeString]);
CTFrameRef frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, 0), path, NULL);
上面参数里的path,也就是所谓的纸张区域了。
那上面提到的,图片用“ ”代替,然后设置回调,又是怎么实现的呢?
-(NSAttributedString *)string
{
NSString *st=_text;
NSDictionary *attrDictionaryDelegate;
if (_type==ChatPieceTypeImage) {
UIImage *image=[UIImage imageNamed:_text];
st=@" ";
NSNumber *width=[NSNumber numberWithFloat:image.size.width];
NSNumber *height=[NSNumber numberWithFloat:image.size.height];
NSDictionary* imgAttr = [NSDictionary dictionaryWithObjectsAndKeys: //2
width, @"width",
height, @"height",
nil];
CTRunDelegateCallbacks callbacks;
callbacks.version = kCTRunDelegateVersion1;
callbacks.getAscent = ascentCallback;
callbacks.getDescent = descentCallback;
callbacks.getWidth = widthCallback;
callbacks.dealloc = deallocCallback;
CTRunDelegateRef delegate = CTRunDelegateCreate(&callbacks, (__bridge void *)(imgAttr)); //3
attrDictionaryDelegate = [NSDictionary dictionaryWithObjectsAndKeys:
//set the delegate
(__bridge id)delegate, (NSString*)kCTRunDelegateAttributeName,
nil];
}
NSAttributedString *attString=[[NSAttributedString alloc] initWithString:st attributes:attrDictionaryDelegate];
return attString;
}
static void deallocCallback( void* ref ){
//[(__bridge id)ref release];
}
static CGFloat ascentCallback( void *ref ){
return [(NSString*)[(__bridge NSDictionary*)ref objectForKey:@"height"] floatValue]*3/4.;
}
static CGFloat descentCallback( void *ref ){
return [(NSString*)[(__bridge NSDictionary*)ref objectForKey:@"height"] floatValue]*1/4.;//[(NSString*)[(__bridge NSDictionary*)ref objectForKey:@"descent"] floatValue];
}
static CGFloat widthCallback( void* ref ){
float w=[(NSString*)[(__bridge NSDictionary*)ref objectForKey:@"width"] floatValue];
return w;
}
其实写到这里,我发现不知道为什么原因,如果表情是最后一个文字片,好像会出点小问题,我的做法是在后面追加了一个结束符。
-(void)setChatPieces:(NSArray *)chatPieces
{
ChatPiece *p=[chatPieces lastObject];
if (p.type==ChatPieceTypeImage) {
ChatPiece *piece=[[ChatPiece alloc] init];
piece.text=@"\0";
chatPieces= [chatPieces arrayByAddingObject:piece];
}
_chatPieces=chatPieces;
//这里还得设置ctframe;
}