公司需求要把后台设置的【内容】(具体链接)样式变成 内容 高亮显示,最终效果如下:
截屏2022-06-07 下午5.55.35.png
后台给的数据结构
默认支持以下3种协议
http http://qq.com
email [email protected]
phone 18870912166
v723开始支持
1.yikao协议
yikao://member/?id=28686130
2.md语法格式超链解析
[链接](以上4种协议)
[http](http://qq.com)
[email]([email protected])
[phone](18870912166)
[yikao](yikao://member/?id=28686130)
需要实现的效果
默认支持以下3种协议
http http://qq.com
email [email protected]
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 protected]\nphone 18870912166\n\nv723开始支持\n1.yikao协议\nyikao://member/?id=28686130\n2.md语法格式超链解析\n[链接](以上4种协议)\n[http](http://qq.com) \n\n[email]([email protected])\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
NS_ASSUME_NONNULL_BEGIN
/**
* RCAttributedDataSource
*/
@protocol OSAttributedDataSource
/**
* 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
/**
* text
*/
@property (nonatomic, copy) id text;
@end
@interface OSAttributedLabel : UILabel
/**
* 可以通过设置attributeDataSource或者attributeDictionary、highlightedAttributeDictionary来自定义不同文本的字体颜色
*/
@property (nonatomic, strong) id 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 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
@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
#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)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
- 总结:具体实现上面全有,水平有限,请指教。