CoreText(五):省略号

直接附代码:

#import "MyView.h"
#import <CoreText/CoreText.h>

// 行距
const CGFloat kGlobalLineLeading = 5.0;

// 在15字体下,比值小于这个计算出来的高度会导致emoji显示不全
const CGFloat kPerLineRatio = 1.4;

@interface MyView()


@property (nonatomic ,assign) CGFloat textHeight;

@end


@implementation MyView
- (instancetype)initWithFrame:(CGRect)frame{
    self = [super initWithFrame:frame];
    if (self) {
        self.text = @"我自横刀向天笑,去留肝胆两昆仑。--谭嗣同同学你好啊。This is my first CoreText demo,how are you ?I love three things,the sun,the moon,and you.the sun for the day,the moon for the night,and you forever.��������������去年今日此门中,人面桃花相映红。人面不知何处去,桃花依旧笑春风。��������������少年不知愁滋味,爱上层楼,爱上层楼,为赋新词强说愁。56321363464.而今识尽愁滋味,欲说还休,欲说还休,却道天凉好个秋。123456,7890,56321267895434。缺月挂疏桐,漏断人初静。谁见幽人独往来,缥缈孤鸿影。惊起却回头,有恨无人省。捡尽寒枝不肯栖,寂寞沙洲冷。";
        self.font = [UIFont systemFontOfSize:15];
    }
    return self;
}
/**
 *  高度 = 每行的固定高度 * 行数
 */
+ (CGFloat)textHeightWithText:(NSString *)aText width:(CGFloat)aWidth font:(UIFont *)aFont{
    NSMutableAttributedString *content = [[NSMutableAttributedString alloc] initWithString:aText];
    // 给字符串设置字体行距等样式
    [self addGlobalAttributeWithContent:content font:aFont];
    CTFramesetterRef framesetterRef = CTFramesetterCreateWithAttributedString((__bridge CFAttributedStringRef)content);
    // 粗略的高度,该高度不准,仅供参考
    CGSize suggestSize = CTFramesetterSuggestFrameSizeWithConstraints(framesetterRef, CFRangeMake(0, content.length), NULL, CGSizeMake(aWidth, MAXFLOAT), NULL);
    NSLog(@"suggestHeight = %f",suggestSize.height);

    CGMutablePathRef pathRef = CGPathCreateMutable();
    CGPathAddRect(pathRef, NULL, CGRectMake(0, 0, aWidth, suggestSize.height));

    CTFrameRef frameRef = CTFramesetterCreateFrame(framesetterRef, CFRangeMake(0, content.length), pathRef, NULL);

    CFArrayRef lines = CTFrameGetLines(frameRef);
    CFIndex lineCount = CFArrayGetCount(lines);

    NSLog(@"行数 = %ld",lineCount);
    // 总高度 = 行数*每行的高度,其中每行的高度为指定的值,不同字体大小不一样
    CGFloat accurateHeight = lineCount * (aFont.pointSize * kPerLineRatio);

    CGFloat height = accurateHeight;

    CFRelease(pathRef);
    CFRelease(frameRef);

    return height;
}
#pragma mark - 工具方法
#pragma mark 给字符串添加全局属性,比如行距,字体大小,默认颜色
+ (void)addGlobalAttributeWithContent:(NSMutableAttributedString *)aContent font:(UIFont *)aFont
{
    CGFloat lineLeading = kGlobalLineLeading; // 行间距

    const CFIndex kNumberOfSettings = 2;
    //设置段落格式
    CTParagraphStyleSetting lineBreakStyle;
    CTLineBreakMode lineBreakMode = kCTLineBreakByWordWrapping;
    lineBreakStyle.spec = kCTParagraphStyleSpecifierLineBreakMode;
    lineBreakStyle.valueSize = sizeof(CTLineBreakMode);
    lineBreakStyle.value = &lineBreakMode;

    //设置行距
    CTParagraphStyleSetting lineSpaceStyle;
    CTParagraphStyleSpecifier spec;
    spec = kCTParagraphStyleSpecifierLineSpacingAdjustment;
    lineSpaceStyle.spec = spec;
    lineSpaceStyle.valueSize = sizeof(CGFloat);
    lineSpaceStyle.value = &lineLeading;

    // 结构体数组
    CTParagraphStyleSetting theSettings[kNumberOfSettings] = {
        lineBreakStyle,
        lineSpaceStyle,
    };
    CTParagraphStyleRef theParagraphRef = CTParagraphStyleCreate(theSettings, kNumberOfSettings);

    // 将设置的行距应用于整段文字
    [aContent addAttribute:NSParagraphStyleAttributeName value:(__bridge id)(theParagraphRef) range:NSMakeRange(0, aContent.length)];

    CFStringRef fontName = (__bridge CFStringRef)aFont.fontName;
    CTFontRef fontRef = CTFontCreateWithName(fontName, aFont.pointSize, NULL);
    // 将字体大小应用于整段文字
    [aContent addAttribute:NSFontAttributeName value:(__bridge id)fontRef range:NSMakeRange(0, aContent.length)];

    // 给整段文字添加默认颜色
    [aContent addAttribute:NSForegroundColorAttributeName value:[UIColor blackColor] range:NSMakeRange(0, aContent.length)];
    // 内存管理
    CFRelease(theParagraphRef);
    CFRelease(fontRef);
}
#pragma mark - 一行一行绘制,行高确定,行与行之间对齐
#pragma mark - 一行一行绘制,行高确定,高度不够时加上省略号
- (void)drawRectWithLineByLineAlignmentAndEllipses{
    // 1.创建需要绘制的文字
    NSMutableAttributedString *attributed = [[NSMutableAttributedString alloc] initWithString:self.text];

    // 2.设置行距等样式
    [[self class] addGlobalAttributeWithContent:attributed font:self.font];

    self.textHeight = [[self class] textHeightWithText:self.text width:CGRectGetWidth(self.bounds) font:self.font];

    // 3.创建绘制区域,path的高度对绘制有直接影响,如果高度不够,则计算出来的CTLine的数量会少一行或者少多行
    CGMutablePathRef path = CGPathCreateMutable();
    CGPathAddRect(path, NULL, CGRectMake(0, 0, CGRectGetWidth(self.bounds), self.textHeight*2));

    // 4.根据NSAttributedString生成CTFramesetterRef
    CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)attributed);
    CTFrameRef ctFrame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, attributed.length), path, NULL);

    // 重置高度
    CGFloat realHeight = self.textHeight;
    // 绘制全部文本需要的高度大于实际高度则调整,并加上省略号
    if (realHeight > CGRectGetHeight(self.frame)){
        realHeight = CGRectGetHeight(self.frame);
    }
    NSLog(@"realHeight = %f",realHeight);
    // 获取上下文
    CGContextRef contextRef = UIGraphicsGetCurrentContext();

    // 转换坐标系
    CGContextSetTextMatrix(contextRef, CGAffineTransformIdentity);
    CGContextTranslateCTM(contextRef, 0, realHeight); // 这里跟着调整
    CGContextScaleCTM(contextRef, 1.0, -1.0);

    // 这里可调整可不调整
    CGPathAddRect(path, NULL, CGRectMake(0, 0, CGRectGetWidth(self.bounds), realHeight));

    // 一行一行绘制
    CFArrayRef lines = CTFrameGetLines(ctFrame);
    CFIndex lineCount = CFArrayGetCount(lines);
    CGPoint lineOrigins[lineCount];
    // 把ctFrame里每一行的初始坐标写到数组里,注意CoreText的坐标是左下角为原点
    CTFrameGetLineOrigins(ctFrame, CFRangeMake(0, 0), lineOrigins);
    CGFloat frameY = 0;
    for (CFIndex i = 0; i < lineCount; i++){
        // 遍历每一行CTLine
        CTLineRef line = CFArrayGetValueAtIndex(lines, i);
        CGFloat lineAscent;
        CGFloat lineDescent;
        CGFloat lineLeading; // 行距
        // 该函数除了会设置好ascent,descent,leading之外,还会返回这行的宽度
        CTLineGetTypographicBounds(line, &lineAscent, &lineDescent, &lineLeading);
        //CoreText的origin的Y值是在baseLine处,而不是下方的descent。
        CGPoint lineOrigin = lineOrigins[i];
        //行高
        CGFloat lineHeight = self.font.pointSize * kPerLineRatio;
        //self.font.descender为负值
        frameY = realHeight - (i + 1)*lineHeight - self.font.descender;
        NSLog(@"frameY = %f",frameY);
        lineOrigin.y = frameY;
        //调整坐标
        CGContextSetTextPosition(contextRef, lineOrigin.x, lineOrigin.y);
        if (frameY + self.font.descender > lineHeight){
            CTLineDraw(line, contextRef);
        }else{
            NSLog(@"最后一行");
            // 最后一行,加上省略号
            static NSString* const kEllipsesCharacter = @"\u2026";
            CFRange lastLineRange = CTLineGetStringRange(line);
            // 一个emoji表情占用两个长度单位
            NSLog(@"range.location = %ld,range.length = %ld,总长度 = %ld",lastLineRange.location,lastLineRange.length,attributed.length);
            if (lastLineRange.location + lastLineRange.length < (CFIndex)attributed.length){
                // 这一行放不下所有的字符(下一行还有字符),则把此行后面的回车、空格符去掉后,再把最后一个字符替换成省略号
                CTLineTruncationType truncationType = kCTLineTruncationEnd;
                NSUInteger truncationAttributePosition = lastLineRange.location + lastLineRange.length - 1;

                // 拿到最后一个字符的属性字典
                NSDictionary *tokenAttributes = [attributed attributesAtIndex:truncationAttributePosition
                                                               effectiveRange:NULL];
                // 给省略号字符设置字体大小、颜色等属性
                NSAttributedString *tokenString = [[NSAttributedString alloc] initWithString:kEllipsesCharacter
                                                                                  attributes:tokenAttributes];

                // 用省略号单独创建一个CTLine,下面在截断重新生成CTLine的时候会用到
                CTLineRef truncationToken = CTLineCreateWithAttributedString((__bridge CFAttributedStringRef)tokenString);

                // 把这一行的属性字符串复制一份,如果要把省略号放到中间或其他位置,只需指定复制的长度即可
                NSUInteger copyLength = lastLineRange.length;

                NSMutableAttributedString *truncationString = [[attributed attributedSubstringFromRange:NSMakeRange(lastLineRange.location, copyLength)] mutableCopy];

                if (lastLineRange.length > 0)
                {
                    // Remove any whitespace at the end of the line.
                    unichar lastCharacter = [[truncationString string] characterAtIndex:copyLength - 1];

                    // 如果复制字符串的最后一个字符是换行、空格符,则删掉
                    if ([[NSCharacterSet whitespaceAndNewlineCharacterSet] characterIsMember:lastCharacter])
                    {
                        [truncationString deleteCharactersInRange:NSMakeRange(copyLength - 1, 1)];
                    }
                }

                // 拼接省略号到复制字符串的最后
                [truncationString appendAttributedString:tokenString];

                // 把新的字符串创建成CTLine
                CTLineRef truncationLine = CTLineCreateWithAttributedString((__bridge CFAttributedStringRef)truncationString);

                // 创建一个截断的CTLine,该方法不能少,具体作用还有待研究
                CTLineRef truncatedLine = CTLineCreateTruncatedLine(truncationLine, self.frame.size.width, truncationType, truncationToken);

                if (!truncatedLine)
                {
                    // If the line is not as wide as the truncationToken, truncatedLine is NULL
                    truncatedLine = CFRetain(truncationToken);
                }

                CFRelease(truncationLine);
                CFRelease(truncationToken);

                CTLineDraw(truncatedLine, contextRef);
                CFRelease(truncatedLine);
            } else{
                // 这一行刚好是最后一行,且最后一行的字符可以完全绘制出来
                CTLineDraw(line, contextRef);
            }
            // 跳出循环,避免绘制剩下的多余的CTLine
            break;
        }
    }
    CFRelease(path);
    CFRelease(framesetter);
    CFRelease(ctFrame);
}



- (void)drawRect:(CGRect)rect
{
    [self drawRectWithLineByLineAlignmentAndEllipses];
}
@end

调用:

- (void)viewDidLoad {
    [super viewDidLoad];
    self.view.backgroundColor = [UIColor whiteColor];
    // Do any additional setup after loading the view, typically from a nib.
    MyView *view = [[MyView alloc]initWithFrame:CGRectMake(0, 20, self.view.width, 100)];
    view.backgroundColor = [UIColor redColor];
    view.text = @"我自横刀向天笑,去留肝胆两昆仑。--谭嗣同同学你好啊。This is my first CoreText demo,how are you ?I love three things,the sun,the moon,and you.the sun for the day,the moon for the night,and you forever.��������������去年今日此门中,人面桃花相映红。人面不知何处去,桃花依旧笑春风。��������������少年不知愁滋味,爱上层楼,爱上层楼,为赋新词强说愁。56321363464.而今识尽愁滋味,欲说还休,欲说还休,却道天凉好个秋。123456,7890,56321267895434。缺月挂疏桐,漏断人初静。谁见幽人独往来,缥缈孤鸿影。惊起却回头,有恨无人省。捡尽寒枝不肯栖,寂寞沙洲冷。";
    view.font = [UIFont systemFontOfSize:15];
    [self.view addSubview:view];
}

结果:
这里写图片描述

参考:
CoreText使用教程(四)

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

xiaoxiaobukuang

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值