iOS 融云消息自定義[a](b)變成a高亮 類似于MD語法

公司需求要把后臺設(shè)置的【內(nèi)容】(具體鏈接)樣式變成 內(nèi)容 高亮顯示缝裁,最終效果如下:

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

后臺給的數(shù)據(jù)結(jié)構(gòu)

默認(rèn)支持以下3種協(xié)議
http http://qq.com 
email email@qq.com
phone 18870912166

v723開始支持
1.yikao協(xié)議
yikao://member/?id=28686130
2.md語法格式超鏈解析
[鏈接](以上4種協(xié)議)
[http](http://qq.com) 

[email](email@qq.com)

[phone](18870912166)

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

需要實現(xiàn)的效果

默認(rèn)支持以下3種協(xié)議
http http://qq.com 
email email@qq.com
phone 18870912166

v723開始支持
1.yikao協(xié)議
yikao://member/?id=28686130
2.md語法格式超鏈解析
[鏈接](以上4種協(xié)議)
http

email

phone

yikao

實現(xiàn)代碼

-(void)creatUI
{
   [self.view addSubview:self.textLabel];
    self.textLabel.text = @"默認(rèn)支持以下3種協(xié)議\nhttp http://qq.com \nemail email@qq.com\nphone 18870912166\n\nv723開始支持\n1.yikao協(xié)議\nyikao://member/?id=28686130\n2.md語法格式超鏈解析\n[鏈接](以上4種協(xié)議)\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>
/**
 * 可以通過設(shè)置attributeDataSource或者attributeDictionary、highlightedAttributeDictionary來自定義不同文本的字體顏色
 */
@property (nonatomic, strong) id<OSAttributedDataSource> attributeDataSource;
/**
 * 可以通過設(shè)置attributedStrings可以給一些字符添加點擊事件等,例如在實現(xiàn)的會話列表里修改文本消息內(nèi)容
 *  -(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;
/*!
 點擊回調(diào)
 */
@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點擊回調(diào)
 */
@protocol OSAttributedLabelDelegate <NSObject>
@optional

/*!
 點擊URL的回調(diào)

 @param label 當(dāng)前Label
 @param url   點擊的URL
 */
- (void)attributedLabel:(OSAttributedLabel *)label didSelectLinkWithURL:(NSURL *)url;

/*!
 點擊電話號碼的回調(diào)

 @param label       當(dāng)前Label
 @param phoneNumber 點擊的URL
 */
- (void)attributedLabel:(OSAttributedLabel *)label didSelectLinkWithPhoneNumber:(NSString *)phoneNumber;

/*!
 點擊Label的回調(diào)

 @param label   當(dāng)前Label
 @param content 點擊的內(nèi)容
 */
- (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" //判斷是不是手機(jī)號的 報錯的話替換一下自己用的工具類
#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 同步計算高亮結(jié)果,大于 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:處理篩選掉不需要和下面重復(fù)高亮的鏈接 否則點擊響應(yīng)事件會錯亂
            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:處理篩選掉不需要和下面重復(fù)高亮的鏈接 否則點擊響應(yīng)事件會錯亂
                    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];
    /**
        這里在結(jié)尾為 "\n" 的字符串后加 "\n" 仁堪,是因為 CTFramesetterCreateWithAttributedString 計算出的字符串在
       CTFramesetterSuggestFrameSizeWithConstraints 中計算行高會少算一行,CTFramesetterCreateFrame 這個函數(shù)的結(jié)果 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(@"當(dāng)前手勢點擊的坐標(biāo)-------%@",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;
    //設(shè)置內(nèi)容的時候弦聂,先做一次解析,保證準(zhǔn)確性
    [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) {
        //處理文本內(nèi)容
        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) {
        //處理文本內(nèi)容
        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: 所有匹配到的字符,根據(jù)() 包含級

    NSMutableArray *array = [NSMutableArray array];

    for (NSTextCheckingResult *match in matches) {

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

         [array addObject:component];
    }

    return array;
}
@end

  • 總結(jié):具體實現(xiàn)上面全有贸铜,水平有限堡纬,請指教聂受。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市隐轩,隨后出現(xiàn)的幾起案子饺饭,更是在濱河造成了極大的恐慌渤早,老刑警劉巖职车,帶你破解...
    沈念sama閱讀 211,348評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異鹊杖,居然都是意外死亡悴灵,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,122評論 2 385
  • 文/潘曉璐 我一進(jìn)店門骂蓖,熙熙樓的掌柜王于貴愁眉苦臉地迎上來积瞒,“玉大人,你說我怎么就攤上這事登下∶?祝” “怎么了?”我有些...
    開封第一講書人閱讀 156,936評論 0 347
  • 文/不壞的土叔 我叫張陵被芳,是天一觀的道長缰贝。 經(jīng)常有香客問我,道長畔濒,這世上最難降的妖魔是什么剩晴? 我笑而不...
    開封第一講書人閱讀 56,427評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮侵状,結(jié)果婚禮上赞弥,老公的妹妹穿的比我還像新娘。我一直安慰自己趣兄,他們只是感情好绽左,可當(dāng)我...
    茶點故事閱讀 65,467評論 6 385
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著艇潭,像睡著了一般拼窥。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上暴区,一...
    開封第一講書人閱讀 49,785評論 1 290
  • 那天闯团,我揣著相機(jī)與錄音,去河邊找鬼仙粱。 笑死房交,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的伐割。 我是一名探鬼主播候味,決...
    沈念sama閱讀 38,931評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼刃唤,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了白群?” 一聲冷哼從身側(cè)響起尚胞,我...
    開封第一講書人閱讀 37,696評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎帜慢,沒想到半個月后笼裳,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,141評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡粱玲,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,483評論 2 327
  • 正文 我和宋清朗相戀三年躬柬,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片抽减。...
    茶點故事閱讀 38,625評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡允青,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出卵沉,到底是詐尸還是另有隱情颠锉,我是刑警寧澤,帶...
    沈念sama閱讀 34,291評論 4 329
  • 正文 年R本政府宣布史汗,位于F島的核電站琼掠,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏淹办。R本人自食惡果不足惜眉枕,卻給世界環(huán)境...
    茶點故事閱讀 39,892評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望怜森。 院中可真熱鬧速挑,春花似錦、人聲如沸副硅。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽恐疲。三九已至腊满,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間培己,已是汗流浹背碳蛋。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評論 1 265
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留省咨,地道東北人肃弟。 一個月前我還...
    沈念sama閱讀 46,324評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親笤受。 傳聞我的和親對象是個殘疾皇子穷缤,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,492評論 2 348

推薦閱讀更多精彩內(nèi)容