iOS 融云消息自定义[a](b)变成a高亮 类似于MD语法

公司需求要把后台设置的【内容】(具体链接)样式变成 内容 高亮显示,最终效果如下:

截屏2022-06-07 下午5.55.35.png

后台给的数据结构

默认支持以下3种协议
http http://qq.com 
email email@qq.com
phone 18870912166

v723开始支持
1.yikao协议
yikao://member/?id=28686130
2.md语法格式超链解析
[链接](以上4种协议)
[http](http://qq.com) 

[email](email@qq.com)

[phone](18870912166)

[yikao](yikao://member/?id=28686130)

需要实现的效果

默认支持以下3种协议
http http://qq.com 
email email@qq.com
phone 18870912166

v723开始支持
1.yikao协议
yikao://member/?id=28686130
2.md语法格式超链解析
[链接](以上4种协议)
http

email

phone

yikao

实现代码

-(void)creatUI
{
   [self.view addSubview:self.textLabel];
    self.textLabel.text = @"默认支持以下3种协议\nhttp http://qq.com \nemail email@qq.com\nphone 18870912166\n\nv723开始支持\n1.yikao协议\nyikao://member/?id=28686130\n2.md语法格式超链解析\n[链接](以上4种协议)\n[http](http://qq.com) \n\n[email](email@qq.com)\n\n[phone](18870912166)\n\n[yikao](yikao://member/?id=28686130)";
    self.textLabel.frame =  CGRectMake(10, 40,220, 300);
}

- (OSAttributedLabel *)textLabel{
    if (!_textLabel) {
        _textLabel = [[OSAttributedLabel alloc] initWithFrame:CGRectZero];
        _textLabel.font = [UIFont systemFontOfSize:14];
        _textLabel.numberOfLines = 0;
        [_textLabel setLineBreakMode:NSLineBreakByWordWrapping];
        [_textLabel setTextAlignment:NSTextAlignmentLeft];
        _textLabel.delegate = self;
        _textLabel.textCheckingTypes = NSTextCheckingTypePhoneNumber|NSTextCheckingTypeLink;
        _textLabel.backgroundColor = UIColor.systemPinkColor;
    }
    return _textLabel;
}

用到的两个类.h

//
//  OSAttributedLabel.h
//  YiKao
//
//  Created by John.lee on 2022/3/24.
//  Copyright © 2022 YiKao. All rights reserved.
//

#import <UIKit/UIKit.h>

NS_ASSUME_NONNULL_BEGIN
/**
 *  RCAttributedDataSource
 */
@protocol OSAttributedDataSource <NSObject>
/**
 *  attributeDictionaryForTextType
 *
 *  @param textType textType
 *
 *  @return return NSDictionary
 */
- (NSDictionary *)attributeDictionaryForTextType:(NSTextCheckingTypes)textType;
/**
 *  highlightedAttributeDictionaryForTextType
 *
 *  @param textType textType
 *
 *  @return NSDictionary
 */
- (NSDictionary *)highlightedAttributeDictionaryForTextType:(NSTextCheckingType)textType;

@end

@protocol OSAttributedLabelDelegate;

/**
 *  Override UILabel @property to accept both NSString and NSAttributedString
 */
@protocol OSAttributedLabel <NSObject>

/**
 *  text
 */
@property (nonatomic, copy) id text;

@end
@interface OSAttributedLabel : UILabel<OSAttributedDataSource, UIGestureRecognizerDelegate>
/**
 * 可以通过设置attributeDataSource或者attributeDictionary、highlightedAttributeDictionary来自定义不同文本的字体颜色
 */
@property (nonatomic, strong) id<OSAttributedDataSource> attributeDataSource;
/**
 * 可以通过设置attributedStrings可以给一些字符添加点击事件等,例如在实现的会话列表里修改文本消息内容
 *  -(void)willDisplayConversationTableCell:(RCMessageBaseCell *)cell atIndexPath:(NSIndexPath *)indexPath{
 *
 *   if ([cell isKindOfClass:[RCTextMessageCell class]]) {
 *      RCTextMessageCell *newCell = (RCTextMessageCell *)cell;
 *      if (newCell.textLabel.text.length>3) {
 *          NSTextCheckingResult *textCheckingResult = [NSTextCheckingResult linkCheckingResultWithRange:(NSMakeRange(0,
 *3)) URL:[NSURL URLWithString:@"http://www.baidu.com"]]; [newCell.textLabel.attributedStrings
 *addObject:textCheckingResult]; [newCell.textLabel setTextHighlighted:YES atPoint:CGPointMake(0, 3)];
 *       }
 *    }
 *}
 *
 */
@property (nonatomic, strong) NSMutableArray *attributedStrings;
/*!
 点击回调
 */
@property (nonatomic, weak) id<OSAttributedLabelDelegate> delegate;
/**
 *  attributeDictionary
 */
@property (nonatomic, strong) NSDictionary *attributeDictionary;
/**
 *  highlightedAttributeDictionary
 */
@property (nonatomic, strong) NSDictionary *highlightedAttributeDictionary;
/**
 *  NSTextCheckingTypes 格式类型
 */
@property (nonatomic, assign) NSTextCheckingTypes textCheckingTypes;
/**
 *  NSTextCheckingTypes current格式类型
 */
@property (nonatomic, readonly, assign) NSTextCheckingType currentTextCheckingType;
/**
 *  setTextdataDetectorEnabled
 *
 *  @param text                text
 *  @param dataDetectorEnabled dataDetectorEnabled
 */
- (void)setText:(NSString *)text dataDetectorEnabled:(BOOL)dataDetectorEnabled;
///**
// *  textInfoAtPoint
// *
// *  @param point point
// *
// *  @return RCAttributedLabelClickedTextInfo
// */
//- (RCAttributedLabelClickedTextInfo *)textInfoAtPoint:(CGPoint)point;
/**
 *  setTextHighlighted
 *
 *  @param highlighted highlighted
 *  @param point       point
 */
- (void)setTextHighlighted:(BOOL)highlighted atPoint:(CGPoint)point;


@end


/*!
 RCAttributedLabel点击回调
 */
@protocol OSAttributedLabelDelegate <NSObject>
@optional

/*!
 点击URL的回调

 @param label 当前Label
 @param url   点击的URL
 */
- (void)attributedLabel:(OSAttributedLabel *)label didSelectLinkWithURL:(NSURL *)url;

/*!
 点击电话号码的回调

 @param label       当前Label
 @param phoneNumber 点击的URL
 */
- (void)attributedLabel:(OSAttributedLabel *)label didSelectLinkWithPhoneNumber:(NSString *)phoneNumber;

/*!
 点击Label的回调

 @param label   当前Label
 @param content 点击的内容
 */
- (void)attributedLabel:(OSAttributedLabel *)label didTapLabel:(NSString *)content;

@end

NS_ASSUME_NONNULL_END

用到的两个类.m

//
//  OSAttributedLabel.m
//  YiKao
//
//  Created by John.lee on 2022/3/24.
//  Copyright © 2022 YiKao. All rights reserved.
//

#import "OSAttributedLabel.h"
#import <CoreText/CoreText.h>
#import "NSString+Pinyin.h" //判断是不是手机号的 报错的话替换一下自己用的工具类
#pragma mark - System Version
@interface OSAttributedLabel ()

@property (nonatomic, copy) NSString *originalString;
@property (nonatomic, assign) BOOL dataDetectorEnabled;
@property (nonatomic, assign) BOOL needGenerateAttributed;
@property (nonatomic, assign) NSRange rangeOfTextHighlighted;
@property (nonatomic, strong) UITapGestureRecognizer *tapGestureRecognizer;

@end

@implementation OSAttributedLabel

- (void)layoutSubviews {
    [self generateAttributed];
    [super layoutSubviews];
}

#pragma mark - Public Methods

- (void)setTextHighlighted:(BOOL)highlighted atPoint:(CGPoint)point {
    if (highlighted == NO) {
        self.rangeOfTextHighlighted = NSMakeRange(0, 0);
    } else {
        self.rangeOfTextHighlighted = [self textRangeAtPoint:point];
    }
    [self generateAttributedString];
}

#pragma mark - RCAttributedDataSource
- (NSDictionary *)attributeDictionaryForTextType:(NSTextCheckingTypes)textType {
    if (self.attributeDictionary) {
        NSNumber *textCheckingTypesNumber = [NSNumber numberWithUnsignedLongLong:textType];
        return [self.attributeDictionary objectForKey:textCheckingTypesNumber];
    }
    if (self.attributeDataSource) {
        return [self.attributeDataSource attributeDictionaryForTextType:textType];
    }
    switch (textType) {
    case NSTextCheckingTypePhoneNumber: {
        _currentTextCheckingType = NSTextCheckingTypePhoneNumber;
        return @{
            NSForegroundColorAttributeName :
                [UIColor blueColor],
            NSUnderlineStyleAttributeName : @(NSUnderlineStyleSingle),
            NSUnderlineColorAttributeName : [UIColor yellowColor]
        };
    }
        case NSTextCheckingTypeLink: {
        _currentTextCheckingType = NSTextCheckingTypeLink;
        return @{
            NSForegroundColorAttributeName :
                [UIColor blueColor],
            NSUnderlineStyleAttributeName : @(NSUnderlineStyleSingle),
            NSUnderlineColorAttributeName :
                [UIColor blueColor]
        };
    }
        case NSTextCheckingTypeRegularExpression: {
        _currentTextCheckingType = NSTextCheckingTypeRegularExpression;
        return @{
            NSForegroundColorAttributeName :
                [UIColor blueColor],
            NSUnderlineStyleAttributeName : @(NSUnderlineStyleSingle),
            NSUnderlineColorAttributeName :
                [UIColor blueColor]
        };
    }
    default:
        break;
    }
    return nil;
}

- (NSDictionary *)highlightedAttributeDictionaryForTextType:(NSTextCheckingType)textType {
    if (self.attributeDictionary) {
        NSNumber *textCheckingTypesNumber = [NSNumber numberWithUnsignedLongLong:textType];
        return [self.attributeDictionary objectForKey:textCheckingTypesNumber];
    }
    if (self.attributeDataSource) {
        return [self.attributeDataSource highlightedAttributeDictionaryForTextType:textType];
    }
    switch (textType) {
    case NSTextCheckingTypePhoneNumber: {
        _currentTextCheckingType = NSTextCheckingTypePhoneNumber;
          return @{
                NSForegroundColorAttributeName : [UIColor yellowColor],
                NSUnderlineStyleAttributeName : @(NSUnderlineStyleSingle),
                NSUnderlineColorAttributeName : [UIColor yellowColor]
            };
    }
    case NSTextCheckingTypeLink: {
        _currentTextCheckingType = NSTextCheckingTypeLink;
        
            return @{
                NSForegroundColorAttributeName : [UIColor greenColor],
                NSUnderlineStyleAttributeName : @(NSUnderlineStyleSingle),
                NSUnderlineColorAttributeName : [UIColor greenColor]
            };
    }
    case NSTextCheckingTypeRegularExpression: {
            _currentTextCheckingType = NSTextCheckingTypeRegularExpression;
                return @{
                    NSForegroundColorAttributeName : [UIColor greenColor],
                    NSUnderlineStyleAttributeName : @(NSUnderlineStyleSingle),
                    NSUnderlineColorAttributeName : [UIColor greenColor]
                };
        }
    default:
        break;
    }
    return nil;
}

#pragma mark - UIGestureRecognizer
- (void)handleTap:(UITapGestureRecognizer *)gestureRecognizer {
    if ([gestureRecognizer state] != UIGestureRecognizerStateEnded) {
        return;
    }
    NSTextCheckingResult *result = [self linkAtPoint:[gestureRecognizer locationInView:self]];
    if (!result) {
        if (self.delegate != nil) {
            if ([self.delegate respondsToSelector:@selector(attributedLabel:didTapLabel:)]) {
                [self.delegate attributedLabel:self didTapLabel:self.originalString];
            }
        }
        return;
    }

    switch (result.resultType) {
        case NSTextCheckingTypeLink: case NSTextCheckingTypeRegularExpression:            NSLog(@"result---------%@",result.URL);
        break;
    case NSTextCheckingTypePhoneNumber:
            NSLog(@"result---------%@",result.phoneNumber);
        break;
    default:
        break;
    }
}

#pragma mark - Private Methods
- (NSRange)textRangeAtPoint:(CGPoint)point {
    if (self.dataDetectorEnabled == NO) {
        return NSMakeRange(0, 0);
    }
    CFIndex charIndex = [self characterIndexAtPoint:point];
    for (NSTextCheckingResult *textCheckingResult in self.attributedStrings) {
        for (int i = 0; i < textCheckingResult.numberOfRanges; i++) {
            NSRange range = [textCheckingResult rangeAtIndex:i];
            if (NSLocationInRange(charIndex, range)) {
                return range;
            }
        }
    }
    return NSMakeRange(0, 0);
}

- (NSTextCheckingResult *)linkAtCharacterIndex:(CFIndex)idx {
//    NSLog(@"idx------------%ld",idx);
    NSMutableAttributedString *attributedString =
        [[NSMutableAttributedString alloc] initWithString:self.originalString];
    for (NSTextCheckingResult *result in self.attributedStrings) {
        NSRange range = result.range;
        NSString *str = [self.originalString substringWithRange:result.range];
//        NSLog(@"range-------%ld----%ld---%@",range.location,range.length,str);
        if ([str hasPrefix:@"["]) {
            NSString *b = [self matchString:str toRegexString:@"(\\[(.*?)\\])"].firstObject;
            b = [b stringByReplacingOccurrencesOfString:@"[" withString:@""];
            b = [b stringByReplacingOccurrencesOfString:@"]" withString:@""];
            b = [NSString  stringWithFormat:@"%@\n",b];

            NSAttributedString *endString = [[NSAttributedString alloc]initWithString:b attributes:@{}];
//           NSLog(@"endString------%@",endString);
            //这里rang 范围会改变 所以需要重新求一下范围
            NSRange oldRang = [attributedString.mutableString rangeOfString:str];
            [attributedString deleteCharactersInRange:oldRang];
            [attributedString insertAttributedString:endString atIndex:oldRang.location];
            NSRange newRang = NSMakeRange(oldRang.location, b.length);
            range = newRang;
            if ((CFIndex)range.location <= idx && idx <= (CFIndex)(range.location + range.length)) {
                return result;
            }
        }else{
            if ((CFIndex)range.location <= idx && idx <= (CFIndex)(range.location + range.length)) {
                return result;
            }
        }


    }

    return nil;
}
- (void)generateAttributed {
    if (self.dataDetectorEnabled && self.needGenerateAttributed) {
        self.needGenerateAttributed = NO;
        [self generateAttributedStrings];
        [self generateAttributedString];
    }
}

- (void)generateAttributedStrings {
    if (!self.originalString) {
        return;
    }
    NSError *error = nil;
      
    NSDataDetector *dataDetector = [[NSDataDetector alloc] initWithTypes:self.textCheckingTypes error:&error];
    if (error != nil) {
      
        [super setText:self.originalString];
        return;
    }
    self.attributedStrings = [NSMutableArray array];
    
//    NSString *regulaStr = @"((http[s]{0,1}|ftp)://[a-zA-Z0-9\\.\\-]+\\.([a-zA-Z]{2,4})(:\\d+)?(/[a-zA-Z0-9\\.\\-~!@#$%^&*+?:_/=<>]*)?)|(www.[a-zA-Z0-9\\.\\-]+\\.([a-zA-Z]{2,4})(:\\d+)?(/[a-zA-Z0-9\\.\\-~!@#$%^&*+?:_/=<>]*)?)";
//    NSRegularExpression *pattern = [NSRegularExpression regularExpressionWithPattern:regulaStr options:NSRegularExpressionAnchorsMatchLines error:nil];
    
    NSRegularExpression *regularExpression = [NSRegularExpression regularExpressionWithPattern:@"(\\[(.*?)\\])(\\(.*?\\))" options:0 error:nil];
    
    __weak typeof(self) weakSelf = self;
    //文本少于 500 同步计算高亮结果,大于 500 异步计算
    if(self.originalString.length < 500) {
        [dataDetector enumerateMatchesInString:self.originalString
           options:kNilOptions
             range:NSMakeRange(0, self.originalString.length)
        usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) {
            __strong typeof(weakSelf) strongSelf = weakSelf;
            //tip:处理筛选掉不需要和下面重复高亮的链接 否则点击响应事件会错乱
            NSString * str;
            if (strongSelf.originalString.length == result.range.length) {
                str = [strongSelf.originalString substringWithRange:result.range];
            }else{
                str = [strongSelf.originalString substringWithRange:NSMakeRange(result.range.location-1, result.range.length+1)];
            }
            if ([str hasPrefix:@"("]) {
            }else{
                strongSelf->_currentTextCheckingType = result.resultType;
                [strongSelf.attributedStrings addObject:result];
            }
        }];
//        [pattern enumerateMatchesInString:self.originalString options:kNilOptions range:NSMakeRange(0, self.originalString.length) usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) {
//            __strong typeof(weakSelf) strongSelf = weakSelf;
//            strongSelf->_currentTextCheckingType = result.resultType;
//            [strongSelf.attributedStrings addObject:result];
//        }];
        
        [regularExpression enumerateMatchesInString:self.originalString options:kNilOptions range:NSMakeRange(0, self.originalString.length) usingBlock:^(NSTextCheckingResult * _Nullable result, NSMatchingFlags flags, BOOL * _Nonnull stop) {
            __strong typeof(weakSelf) strongSelf = weakSelf;
            NSString * str = [strongSelf.originalString substringWithRange:result.range];
//            NSLog(@"str----------%@",str);
            //取出名称
            NSString *b = [strongSelf matchString:str toRegexString:@"(\\[(.*?)\\])"].firstObject;
             b = [b stringByReplacingOccurrencesOfString:@"[" withString:@""];
             b = [b stringByReplacingOccurrencesOfString:@"]" withString:@""];
//           b = [NSString  stringWithFormat:@"%@ \n",b];
            //取出链接
            NSString *c = [strongSelf matchString:str toRegexString:@"(\\((.*?)\\))"].firstObject;
            c = [c stringByReplacingOccurrencesOfString:@"(" withString:@""];
            c = [c stringByReplacingOccurrencesOfString:@")" withString:@""];

            //判断是否包含 邮箱、电话、yikao、http
            if ([NSString isValidEmail:c]||[c hasPrefix:@"yikao://"]||[NSString isValidPhone:c]||[c hasPrefix:@"http"]) {
                NSTextCheckingResult *textCheckingResult = [NSTextCheckingResult linkCheckingResultWithRange:(result.range) URL:[NSURL URLWithString:c]];
                strongSelf->_currentTextCheckingType = textCheckingResult.resultType;
                [strongSelf.attributedStrings addObject:textCheckingResult];
            }
        }];
        
        
        
    }else {
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            [dataDetector enumerateMatchesInString:self.originalString
               options:kNilOptions
                 range:NSMakeRange(0, self.originalString.length)
            usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) {
                dispatch_async(dispatch_get_main_queue(), ^{
                    __strong typeof(weakSelf) strongSelf = weakSelf;
                    //tip:处理筛选掉不需要和下面重复高亮的链接 否则点击响应事件会错乱
                    NSString * str = [self.originalString substringWithRange:result.range];
                    if ([str hasPrefix:@"("]) {
                    }else{
                        strongSelf->_currentTextCheckingType = result.resultType;
                        [strongSelf.attributedStrings addObject:result];
                    }
                    
                });
            }];
//            [pattern enumerateMatchesInString:self.originalString options:kNilOptions range:NSMakeRange(0, self.originalString.length) usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) {
//                __strong typeof(weakSelf) strongSelf = weakSelf;
//                strongSelf->_currentTextCheckingType = result.resultType;
//                [strongSelf.attributedStrings addObject:result];
//            }];
            
            [regularExpression enumerateMatchesInString:self.originalString options:kNilOptions range:NSMakeRange(0, self.originalString.length) usingBlock:^(NSTextCheckingResult * _Nullable result, NSMatchingFlags flags, BOOL * _Nonnull stop) {
                __strong typeof(weakSelf) strongSelf = weakSelf;
                NSString * str = [self.originalString substringWithRange:result.range];
//                NSLog(@"str----------%@",str);
                //取出名称
                NSString *b = [self matchString:str toRegexString:@"(\\[(.*?)\\])"].firstObject;
                 b = [b stringByReplacingOccurrencesOfString:@"[" withString:@""];
                 b = [b stringByReplacingOccurrencesOfString:@"]" withString:@""];
    //           b = [NSString  stringWithFormat:@"%@ \n",b];
                //取出链接
                NSString *c = [self matchString:str toRegexString:@"(\\((.*?)\\))"].firstObject;
                c = [c stringByReplacingOccurrencesOfString:@"(" withString:@""];
                c = [c stringByReplacingOccurrencesOfString:@")" withString:@""];

                //判断是否包含 邮箱、电话、yikao、http
                if ([NSString isValidEmail:c]||[c hasPrefix:@"yikao://"]||[NSString isValidPhone:c]||[c hasPrefix:@"http"]) {
                    NSTextCheckingResult *textCheckingResult = [NSTextCheckingResult linkCheckingResultWithRange:(result.range) URL:[NSURL URLWithString:c]];
                    strongSelf->_currentTextCheckingType = textCheckingResult.resultType;
                    [strongSelf.attributedStrings addObject:textCheckingResult];
                }
            }];
        });
    }
}

- (void)generateAttributedString {
    if (!self.originalString) {
        return;
    }
    NSMutableAttributedString *attributedString =
        [[NSMutableAttributedString alloc] initWithString:self.originalString];
    for (NSTextCheckingResult *textCheckingResult in self.attributedStrings) {
        for (int i = 0; i < textCheckingResult.numberOfRanges; i++) {
            NSRange range = [textCheckingResult rangeAtIndex:i];
            NSString *str = [self.originalString substringWithRange:textCheckingResult.range];
            NSLog(@"str2------------%@",str);
            NSDictionary *attributeDictionary;
            if ([str hasPrefix:@"["]) {
                NSRegularExpression *regularExpression = [NSRegularExpression regularExpressionWithPattern:@"(\\(.*?\\))" options:0 error:nil];
                NSArray *matches = [regularExpression matchesInString:str options:0 range:NSMakeRange(0, str.length)];
                NSString *newStr = [str substringWithRange:[matches.firstObject range] ];
                newStr = [str stringByReplacingOccurrencesOfString:@"(" withString:@""];
                newStr = [str stringByReplacingOccurrencesOfString:@")" withString:@""];
                if ([NSString isValidPhone:newStr]) {
                    attributeDictionary = [self attributeDictionaryForTextType:NSTextCheckingTypePhoneNumber];
                }else{
                    attributeDictionary = [self attributeDictionaryForTextType:NSTextCheckingTypeLink];
                }
            }else{
                attributeDictionary = [self attributeDictionaryForTextType:textCheckingResult.resultType];
            }
            if (NSEqualRanges(range, self.rangeOfTextHighlighted))
            attributeDictionary = [self highlightedAttributeDictionaryForTextType:textCheckingResult.resultType];
            if (attributeDictionary) {
                if (self.originalString.length >= (range.location + range.length)) {
                    //取出名称
                    if ([str hasPrefix:@"["]) {
                        NSString *b = [self matchString:str toRegexString:@"(\\[(.*?)\\])"].firstObject;
                        b = [b stringByReplacingOccurrencesOfString:@"[" withString:@""];
                        b = [b stringByReplacingOccurrencesOfString:@"]" withString:@""];
//                      b = [NSString  stringWithFormat:@"%@\n",b];

                       NSAttributedString *endString = [[NSAttributedString alloc]initWithString:b attributes:attributeDictionary];
                       NSLog(@"endString------%@",endString);
                        //这里rang 范围会改变 所以需要重新求一下范围
                        NSRange newRang = [attributedString.mutableString rangeOfString:str];
                        [attributedString deleteCharactersInRange:newRang];
                        [attributedString insertAttributedString:endString atIndex:newRang.location];
                        
                    }else{
                       
                       NSAttributedString *subString =
                           [[NSAttributedString alloc] initWithString:[self.originalString substringWithRange:range]
                                                           attributes:attributeDictionary];
                     
                       NSLog(@"subString------%@",subString);
                       
                       [attributedString replaceCharactersInRange:range withAttributedString:subString];
                    }
                     
                }
            }
        }
    }
    self.attributedText = attributedString;
}

- (NSUInteger)characterIndexAtPoint:(CGPoint)p {
    if (!CGRectContainsPoint(self.bounds, p)) {
        return NSNotFound;
    }
    //(11,129)
    p = CGPointMake(p.x - self.bounds.origin.x, self.bounds.size.height - p.y);

    NSMutableAttributedString *optimizedAttributedText = [self.attributedText mutableCopy];
    /**
        这里在结尾为 "\n" 的字符串后加 "\n" ,是因为 CTFramesetterCreateWithAttributedString 计算出的字符串在
       CTFramesetterSuggestFrameSizeWithConstraints 中计算行高会少算一行,CTFramesetterCreateFrame 这个函数的结果 frame
       中可以查看到计算出多少行。
     */
    if (optimizedAttributedText.string.length > 0 && [[optimizedAttributedText.string substringFromIndex:optimizedAttributedText.string.length - 1]
            isEqualToString:@"\n"]) {
        [optimizedAttributedText appendAttributedString:[[NSAttributedString alloc] initWithString:@"\n"]];
    }
    // use label's font and lineBreakMode properties in case the attributedText does not contain such attributes
    [optimizedAttributedText
        enumerateAttributesInRange:NSMakeRange(0, [optimizedAttributedText length])
                           options:0
                        usingBlock:^(NSDictionary *attrs, NSRange range, BOOL *stop) {
                            if (!attrs[(NSString *)kCTFontAttributeName]) {
                                [optimizedAttributedText addAttribute:(NSString *)kCTFontAttributeName
                                                                value:self.font
                                                                range:NSMakeRange(0, [optimizedAttributedText length])];
                            }

                            if (!attrs[(NSString *)kCTParagraphStyleAttributeName]) {
                                NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc] init];
                                [paragraphStyle setLineBreakMode:self.lineBreakMode];
                                [optimizedAttributedText addAttribute:(NSString *)kCTParagraphStyleAttributeName
                                                                value:paragraphStyle
                                                                range:range];
                            }
                        }];

    // modify kCTLineBreakByTruncatingTail lineBreakMode to NSLineBreakByWordWrapping
    [optimizedAttributedText
        enumerateAttribute:(NSString *)kCTParagraphStyleAttributeName
                   inRange:NSMakeRange(0, [optimizedAttributedText length])
                   options:0
                usingBlock:^(id value, NSRange range, BOOL *stop) {
                    NSMutableParagraphStyle *paragraphStyle = [value mutableCopy];
                    if (paragraphStyle.lineBreakMode == NSLineBreakByTruncatingTail) {
                        [paragraphStyle setLineBreakMode:NSLineBreakByWordWrapping];
                    }
                    [paragraphStyle setAlignment:self.textAlignment];
                    [optimizedAttributedText removeAttribute:(NSString *)kCTParagraphStyleAttributeName range:range];
                    [optimizedAttributedText addAttribute:(NSString *)kCTParagraphStyleAttributeName
                                                    value:paragraphStyle
                                                    range:range];
                }];

    CTFramesetterRef framesetter =
        CTFramesetterCreateWithAttributedString((CFAttributedStringRef)optimizedAttributedText);

    CGRect textRect = UIEdgeInsetsInsetRect(self.bounds, UIEdgeInsetsZero);
    CGSize textSize =
        CTFramesetterSuggestFrameSizeWithConstraints(framesetter, CFRangeMake(0, [optimizedAttributedText length]),
                                                     NULL, CGSizeMake(self.bounds.size.width, CGFLOAT_MAX), NULL);
    textSize = CGSizeMake(ceil(textSize.width), ceil(textSize.height));
    textRect.origin.y += floor((self.bounds.size.height - textSize.height) / 2.0f);
    textRect.size.height = textSize.height;
    CGMutablePathRef path = CGPathCreateMutable();
    CGPathAddRect(path, NULL, textRect);
    CTFrameRef frame =
        CTFramesetterCreateFrame(framesetter, CFRangeMake(0, [optimizedAttributedText length]), path, NULL);
    if (frame == NULL) {
        if (framesetter != NULL) {
            CFRelease(framesetter);
        }
        if (path != NULL) {
            CFRelease(path);
        }
        return NSNotFound;
    }

    CFArrayRef lines = CTFrameGetLines(frame);
    NSUInteger numberOfLines = CFArrayGetCount(lines);
    if (numberOfLines == 0) {
        if (framesetter != NULL) {
            CFRelease(framesetter);
        }
        if (frame != NULL) {
            CFRelease(frame);
        }
        if (path != NULL) {
            CFRelease(path);
        }
        return NSNotFound;
    }

    CGPoint lineOrigins[numberOfLines];
    CTFrameGetLineOrigins(frame, CFRangeMake(0, numberOfLines), lineOrigins);

    NSUInteger lineIndex;
    for (lineIndex = 0; lineIndex < numberOfLines; lineIndex++) {
        if (lineIndex == numberOfLines - 1) {
            break;
        } else {
            CGPoint lineOrigin = lineOrigins[lineIndex];
            if (lineOrigin.y <= p.y) {
                break;
            }
        }
    }

    if (lineIndex >= numberOfLines) {
        if (framesetter != NULL) {
            CFRelease(framesetter);
        }
        if (frame != NULL) {
            CFRelease(frame);
        }
        if (path != NULL) {
            CFRelease(path);
        }
        return NSNotFound;
    }

    CGPoint lineOrigin = lineOrigins[lineIndex];
    CTLineRef line = CFArrayGetValueAtIndex(lines, lineIndex);
    // Convert CT coordinates to line-relative coordinates
    CGPoint relativePoint = CGPointMake(p.x - lineOrigin.x, p.y - lineOrigin.y);
    CFIndex idx = CTLineGetStringIndexForPosition(line, relativePoint);

    if (framesetter != NULL) {
        CFRelease(framesetter);
    }
    if (frame != NULL) {
        CFRelease(frame);
    }
    if (path != NULL) {
        CFRelease(path);
    }
    return idx;
}

- (NSTextCheckingResult *)linkAtPoint:(CGPoint)p {
    NSLog(@"当前手势点击的坐标-------%@",NSStringFromCGPoint(p));
    CFIndex idx = [self characterIndexAtPoint:p];
    return [self linkAtCharacterIndex:idx];
}

#pragma mark - Getters and Setters
- (void)setText:(NSString *)text {
    [self setText:text dataDetectorEnabled:YES];
}

- (void)setText:(NSString *)text dataDetectorEnabled:(BOOL)dataDetectorEnabled {
    self.dataDetectorEnabled = dataDetectorEnabled;
    if (self.dataDetectorEnabled == NO) {
        [super setText:text];
        return;
    }
    
    self.originalString = text;
    //设置内容的时候,先做一次解析,保证准确性
    [super setText:text];
    self.needGenerateAttributed = YES;
    [self generateAttributed];

    self.tapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTap:)];
    [self.tapGestureRecognizer setDelegate:self];
    [self addGestureRecognizer:self.tapGestureRecognizer];
    self.userInteractionEnabled = YES;

}
- (void)setAttributeDictionary:(NSDictionary *)attributeDictionary {
    _attributeDictionary = attributeDictionary;
    self.needGenerateAttributed = YES;
}

- (void)setHighlightedAttributeDictionary:(NSDictionary *)highlightedAttributeDictionary {
    _highlightedAttributeDictionary = highlightedAttributeDictionary;
    self.needGenerateAttributed = YES;
}

- (void)setAttributeDataSource:(id<OSAttributedDataSource>)attributeDataSource {
    _attributeDataSource = attributeDataSource;
    self.needGenerateAttributed = YES;
}

- (NSString *)text {
    [self generateAttributed];
    return [super text];
}

- (NSAttributedString *)attributedText {
    [self generateAttributed];
    return [super attributedText];
}

- (NSTextCheckingTypes)textCheckingTypes {
    if (_textCheckingTypes) {
        return _textCheckingTypes;
    }
    return NSTextCheckingTypePhoneNumber;
}
-(NSString *)getKuohaoStr:(NSString *)content
{
    NSString *str = content;
    if (str) {
        //处理文本内容
        NSError *error;
        NSRegularExpression *regularExpression = [NSRegularExpression regularExpressionWithPattern:@"(\\[(.*?)\\])(\\(.*?\\))" options:0 error:&error];
        NSArray *matches = [regularExpression matchesInString:str options:0 range:NSMakeRange(0, str.length)];
        NSMutableArray *array = [NSMutableArray array];
        for (NSTextCheckingResult *match in matches) {
            NSRange matchRange = [match range];
            NSString *a = [str substringWithRange:matchRange];
            //取出名称
            NSString *b = [self matchString:a toRegexString:@"(\\[(.*?)\\])"].firstObject;
             b = [b stringByReplacingOccurrencesOfString:@"[" withString:@""];
             b = [b stringByReplacingOccurrencesOfString:@"]" withString:@""];
//             b = [NSString  stringWithFormat:@"%@ \n",b];
            //取出链接
            NSString *c = [self matchString:a toRegexString:@"(\\((.*?)\\))"].firstObject;
            c = [c stringByReplacingOccurrencesOfString:@"(" withString:@""];
            c = [c stringByReplacingOccurrencesOfString:@")" withString:@""];

            NSMutableDictionary *dict = [NSMutableDictionary new];
            dict[@"name"] = a;
            dict[@"value"] = b;
            dict[@"url"] = c;
            dict[@"local"] = [NSString stringWithFormat:@"%ld",matchRange.location];
            dict[@"length"] = [NSString stringWithFormat:@"%ld",matchRange.length];

            //判断是否包含 邮箱、电话、yikao、http
            if ([NSString isValidEmail:c]||[c hasPrefix:@"yikao://"]||[NSString isValidPhone:c]||[c hasPrefix:@"http"]) {
                [array addObject:dict];
            }
        }
        if (array.count > 0) {
            return [array.firstObject objectForKey:@"url"];
        }else{
            return  content;
        }
    }
    return content;
}

-(NSString *)handText:(NSString *)content
{
    NSString *str = content;
    if (str) {
        //处理文本内容
        NSError *error;
        NSRegularExpression *regularExpression = [NSRegularExpression regularExpressionWithPattern:@"(\\[(.*?)\\])(\\(.*?\\))" options:0 error:&error];
        NSArray *matches = [regularExpression matchesInString:str options:0 range:NSMakeRange(0, str.length)];

        NSMutableArray *array = [NSMutableArray array];
        for (NSTextCheckingResult *match in matches) {
            NSRange matchRange = [match range];
            
            NSString *a = [str substringWithRange:matchRange];
            //取出名称
            NSString *b = [self matchString:a toRegexString:@"(\\[(.*?)\\])"].firstObject;
             b = [b stringByReplacingOccurrencesOfString:@"[" withString:@""];
             b = [b stringByReplacingOccurrencesOfString:@"]" withString:@""];
//             b = [NSString  stringWithFormat:@"%@ \n",b];
            //取出链接
            NSString *c = [self matchString:a toRegexString:@"(\\((.*?)\\))"].firstObject;
            c = [c stringByReplacingOccurrencesOfString:@"(" withString:@""];
            c = [c stringByReplacingOccurrencesOfString:@")" withString:@""];

            NSMutableDictionary *dict = [NSMutableDictionary new];
            dict[@"name"] = a;
            dict[@"value"] = b;
            dict[@"url"] = c;
            dict[@"local"] = [NSString stringWithFormat:@"%ld",matchRange.location];
            dict[@"length"] = [NSString stringWithFormat:@"%ld",matchRange.length];

            //判断是否包含 邮箱、电话、yikao、http
            if ([NSString isValidEmail:c]||[c hasPrefix:@"yikao://"]||[NSString isValidPhone:c]||[c hasPrefix:@"http"]) {
                [array addObject:dict];
            }
        }

        for (NSDictionary *dict in array) {
            NSString *a = dict[@"name"];
            NSString *b = dict[@"value"];
            str = [str stringByReplacingOccurrencesOfString:a withString:b];
        }
    }
    return  str;
}

- (NSArray *)matchString:(NSString *)string toRegexString:(NSString *)regexStr
{

    NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:regexStr options:NSRegularExpressionCaseInsensitive error:nil];

    NSArray * matches = [regex matchesInString:string options:0 range:NSMakeRange(0, [string length])];

    //match: 所有匹配到的字符,根据() 包含级

    NSMutableArray *array = [NSMutableArray array];

    for (NSTextCheckingResult *match in matches) {

            //以正则中的(),划分成不同的匹配部分
         NSString *component = [string substringWithRange:[match rangeAtIndex:1]];

         [array addObject:component];
    }

    return array;
}
@end

  • 总结:具体实现上面全有,水平有限,请指教。
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,080评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,422评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,630评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,554评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,662评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,856评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,014评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,752评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,212评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,541评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,687评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,347评论 4 331
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,973评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,777评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,006评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,406评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,576评论 2 349

推荐阅读更多精彩内容