CoreText(六):用户点击

1、添加手势

- (void)configSettings{
    //添加手势
    UILongPressGestureRecognizer *longPressGesture = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(longPress:)];
    longPressGesture.minimumPressDuration = 0.01;
    longPressGesture.delegate = self;
    [self addGestureRecognizer:longPressGesture];
}

2、绘制

- (void)drawRect:(CGRect)rect{
    [super drawRect:rect];
    [self drawRectWithCheckClick];
}
#pragma mark - 绘制
- (void)drawRectWithCheckClick{

    // 1.创建需要绘制的文字
    NSMutableAttributedString *attributed = [[NSMutableAttributedString alloc] initWithString:self.text];

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

    // 2.2识别特定字符串并改变其颜色
    [self recognizeSpecialStringWithAttributed:attributed];

    //2.3加一个点击改变字符串颜色的效果
    if (self.pressRange.location != 0 && self.pressRange.length != 0){
        [attributed addAttribute:NSForegroundColorAttributeName value:[UIColor yellowColor] range:self.pressRange];
    }

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

    // 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);
    self.ctFrame = CFRetain(ctFrame);

    // 获取上下文
    CGContextRef contextRef = UIGraphicsGetCurrentContext();

    // 转换坐标系
    CGContextSetTextMatrix(contextRef, CGAffineTransformIdentity);
    CGContextTranslateCTM(contextRef, 0, self.textHeight); // 此处用计算出来的高度
    CGContextScaleCTM(contextRef, 1.0, -1.0);

    // 一行一行绘制
    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);

        CGPoint lineOrigin = lineOrigins[i];

        // 微调Y值,需要注意的是CoreText的Y值是在baseLine处,而不是下方的descent。
        CGFloat lineHeight = self.font.pointSize * kPerLineRatio;
        frameY = self.textHeight - (i + 1)*lineHeight - self.font.descender;
        lineOrigin.y = frameY;
        // 调整坐标
        CGContextSetTextPosition(contextRef, lineOrigin.x, lineOrigin.y);
        CTLineDraw(line, contextRef);
    }
    CFRelease(path);
    CFRelease(framesetter);
    CFRelease(ctFrame);
}

3、给字符串添加属性

#pragma mark - 工具方法
#pragma mark 给字符串添加全局属性,比如行距,字体大小,默认颜色
+ (void)addGlobalAttributeWithContent:(NSMutableAttributedString *)attributeString font:(UIFont *)aFont{
    CGFloat lineLeading = kGlobalLineLeading; // 行间距
    //设置部分颜色
    [attributeString addAttribute:(NSString *)kCTForegroundColorAttributeName value:(id)[UIColor greenColor].CGColor range:NSMakeRange(10, 10)];
    //设置文字
    CTFontRef fontRef = CTFontCreateWithName((CFStringRef)@"ArialMT", aFont.pointSize, NULL);
    [attributeString addAttribute:(NSString *)kCTFontAttributeName value:(__bridge id)fontRef range:NSMakeRange(0, attributeString.length)];
    CFRelease(fontRef);
    // 设置行距等样式
    CGFloat lineSpace = lineLeading; // 行距一般取决于这个值
    CGFloat lineSpaceMax = 20;
    CGFloat lineSpaceMin = 2;
    const CFIndex kNumberOfSettings = 3;
    // 结构体数组
    CTParagraphStyleSetting theSettings[kNumberOfSettings] = {
        {kCTParagraphStyleSpecifierLineSpacingAdjustment,sizeof(CGFloat),&lineSpace},
        {kCTParagraphStyleSpecifierMaximumLineSpacing,sizeof(CGFloat),&lineSpaceMax},
        {kCTParagraphStyleSpecifierMinimumLineSpacing,sizeof(CGFloat),&lineSpaceMin}
    };
    CTParagraphStyleRef theParagraphRef = CTParagraphStyleCreate(theSettings, kNumberOfSettings);

    [attributeString addAttribute:(id)kCTParagraphStyleAttributeName value:(__bridge  id)theParagraphRef range:NSMakeRange(0, attributeString.length)];

    // 内存管理
    CFRelease(theParagraphRef);
}

4、识别相关字符串

#pragma mark - 识别特定字符串并改其颜色,返回识别到的字符串所在的range
- (NSMutableArray *)recognizeSpecialStringWithAttributed:(NSMutableAttributedString *)attributed{
    NSMutableArray *rangeArray = [NSMutableArray array];

    // 识别@人名
    NSRegularExpression *atRegular = [NSRegularExpression regularExpressionWithPattern:kAtRegularExpression options:NSRegularExpressionCaseInsensitive error:nil];
    NSArray *atResults = [atRegular matchesInString:self.text options:NSMatchingWithTransparentBounds range:NSMakeRange(0, self.text.length)];
    for (NSTextCheckingResult *checkResult in atResults){
        if (attributed){
            [attributed addAttribute:NSForegroundColorAttributeName value:[UIColor redColor] range:NSMakeRange(checkResult.range.location, checkResult.range.length -1)];
        }
        [rangeArray addObject:[NSValue valueWithRange:checkResult.range]];
    }
    // 识别连续的数字
    NSRegularExpression *numberRegular = [NSRegularExpression regularExpressionWithPattern:kNumberRegularExpression options:NSRegularExpressionCaseInsensitive|NSRegularExpressionUseUnixLineSeparators error:nil];
    NSArray *numberResults = [numberRegular matchesInString:self.text options:NSMatchingWithTransparentBounds range:NSMakeRange(0, self.text.length)];
    for (NSTextCheckingResult *checkResult in numberResults){
        if (attributed){
            [attributed addAttribute:NSForegroundColorAttributeName value:[UIColor blueColor] range:NSMakeRange(checkResult.range.location, checkResult.range.length-1)];
        }
        [rangeArray addObject:[NSValue valueWithRange:NSMakeRange(checkResult.range.location, checkResult.range.length -1)]];
    }
    return rangeArray;
}

5、固定行高

/**
 *  固定行高
 *  高度 = 每行的固定高度 * 行数
 */
+ (CGFloat)textHeightWithText3:(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);

    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);

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

    CFRelease(pathRef);
    CFRelease(frameRef);

    return height;
}

6、手势相关

#pragma mark - 手势识别相关
- (void)longPress:(UIGestureRecognizer *)gesture{
    // 改变字符串的颜色并进行重绘
    if (gesture.state == UIGestureRecognizerStateBegan){
        if (self.pressRange.length != 0||self.pressRange.location != 0) {
            [self setNeedsDisplay];
        }
    }else if(gesture.state == UIGestureRecognizerStateEnded){
        if (self.pressRange.location != 0 && self.pressRange.length != 0){
            NSString *clickStr = [self.text substringWithRange:self.pressRange];
            NSLog(@"点击了 %@",clickStr);
            self.pressRange = NSMakeRange(0, 0);
            [self setNeedsDisplay];
        }
    }
}
#pragma mark - UIGestureRecognizerDelegate
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer{
    // 点击处在特定字符串内才进行识别
    BOOL gestureShouldBegin = NO;
    CGPoint location = [gestureRecognizer locationInView:self];
    //单行高度
    CGFloat lineHeight = self.font.pointSize * kPerLineRatio;
    //点击行数
    int lineIndex = location.y/lineHeight;
    // 把点击的坐标转换为CoreText坐标系下
    CGPoint clickPoint = CGPointMake(location.x, self.height - location.y);

    CFArrayRef lines = CTFrameGetLines(self.ctFrame);
    if (lineIndex < CFArrayGetCount(lines)){
        CTLineRef clickLine = CFArrayGetValueAtIndex(lines, lineIndex);
        // 点击处的字符位于总字符串的index
        CFIndex strIndex = CTLineGetStringIndexForPosition(clickLine, clickPoint);
        NSMutableAttributedString *mutableAttributed = [[NSMutableAttributedString alloc] initWithString:self.text];
        NSArray *checkResults = [self recognizeSpecialStringWithAttributed:mutableAttributed];
        for (NSValue *value in checkResults){
            NSRange range = [value rangeValue];
            if (strIndex >= range.location && strIndex <= range.location + range.length){
                self.pressRange = range;
                gestureShouldBegin = YES;
            }
        }
    }
    return gestureShouldBegin;
}
// 该方法可实现也可不实现,取决于应用场景
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRequireFailureOfGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
    if ([otherGestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]])
    {
        return YES; // 避免应用在UITableViewCell上时,挡住拖动tableView的手势
    }

    return NO;
}

这里写图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

xiaoxiaobukuang

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

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

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

打赏作者

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

抵扣说明:

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

余额充值