美文网首页iOS技术专题收藏iosiOS开发技术
iOS 计算富文本,检索网址,号码,表情,并且计算高度,设置最大

iOS 计算富文本,检索网址,号码,表情,并且计算高度,设置最大

作者: 雪_晟 | 来源:发表于2017-03-01 16:43 被阅读1014次
    fuwb.gif

    前言:项目中用到检索表情,网址与号码,但是看了TTTAttributeLabel,emojyLabel,奈何都不太满意,plist格式不太符合,而且这两个第三方用到检索都是系统自带的检索,检测网址方面不准确, 所以就需要自己使用正则进行检索。

    关于以上两个三方检索不准确的可以参考:检索网址

    接下来写一下实现的过程, 没有高度封装,仅供参考

    关于网址与号码的正则再说明下:
    网址:KURlREGULAR @"((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\\.\\-~!@#$%^&*+?:_/=<>]*)?)|(((http[s]{0,1}|ftp)://|)((?:(?:25[0-5]|2[0-4]\\d|((1\\d{2})|([1-9]?\\d)))\\.){3}(?:25[0-5]|2[0-4]\\d|((1\\d{2})|([1-9]?\\d))))(:\\d+)?(/[a-zA-Z0-9\\.\\-~!@#$%^&*+?:_/=<>]*)?)"
    号码: KPHONENUMBERREGLAR @"\\d{3}-\\d{8}|\\d{4}-\\d{7}|\\d{11}"
    比如我要转的字符串为
    @"简书:http://jianshu.com哈哈哈[调皮][流汗][偷笑][再见][可爱][色][害羞][委屈][委屈][抓狂][酷][酷][嘘][嘘][龇牙][大哭][大哭][大哭][龇牙][嘘][嘘][调皮][调皮]哈哈哈哈[嘘][调皮][调皮]18637963241他大舅他二舅都是舅,高桌子地板头都是木头"

    我需要做的是检索网址并且替换为和微博一样的链接,号码和链接有选中状态,因为UITextview有检测url 的方法可以添加点击事件,所以就采用UITextview进行封装。主要采取正则表达式与RegexKitLite配合做检索

    1 . 首先建立一个模型,把文字检索为富文本,检索出 表情,网址以及号码。中间需要使用一个模型存储检索出来的结果。对于特殊符号的表情建立一个模型。其实富文本的都是逐个检索,然后处理,最后拼接为一个NSMutableAttributedString。

    
    #import <Foundation/Foundation.h>
    #import <UIKit/UIKit.h>
    @interface ZLStatus : NSObject
    //源内容
    @property (nonatomic, copy) NSString *text;
    
    /** string  信息内容 -- 带有属性的(特殊文字会高亮显示\显示表情)*/
    @property (nonatomic, copy) NSAttributedString *attributedText;
    
    @end
    
    #import "ZLStatus.h"
    #import "ZLSpecial.h"
    #import "ZLTextPart.h"
    #import "RegexKitLite.h"
    @implementation ZLStatus
    - (void)setText:(NSString *)text
    {
        _text = [text copy];
        
        // 利用text生成attributedText
        self.attributedText = [self attributedTextWithText:text];
    }
    /**
     *  普通文字 --> 属性文字
     *
     *  @param text 普通文字
     *
     *  @return 属性文字
     */
    - (NSAttributedString *)attributedTextWithText:(NSString *)text
    {
        NSMutableAttributedString *attributedText = [[NSMutableAttributedString alloc] init];
        
        // 表情的规则
        NSString *emotionPattern = @"\\[[0-9a-zA-Z\\u4e00-\\u9fa5]+\\]";
        // @的规则
        NSString *atPattern = @"@[0-9a-zA-Z\\u4e00-\\u9fa5-_]+";
        // #话题#的规则
        NSString *topicPattern = @"#[0-9a-zA-Z\\u4e00-\\u9fa5]+#";
        // url链接的规则
        NSString *urlPattern = @"((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\\.\\-~!@#$%^&*+?:_/=<>]*)?)|(((http[s]{0,1}|ftp)://|)((?:(?:25[0-5]|2[0-4]\\d|((1\\d{2})|([1-9]?\\d)))\\.){3}(?:25[0-5]|2[0-4]\\d|((1\\d{2})|([1-9]?\\d))))(:\\d+)?(/[a-zA-Z0-9\\.\\-~!@#$%^&*+?:_/=<>]*)?)";
        NSString *phoneNumber =@"\\d{3}-\\d{8}|\\d{3}-\\d{7}|\\d{4}-\\d{8}|\\d{4}-\\d{7}|1+[3578]+\\d{9}|\\d{8}|\\d{7}"
        ;
        NSString *pattern = [NSString stringWithFormat:@"%@|%@|%@|%@|%@", emotionPattern, atPattern, topicPattern, urlPattern,phoneNumber];
        
        // 遍历所有的特殊字符串
        NSMutableArray *parts = [NSMutableArray array];
        [text enumerateStringsMatchedByRegex:pattern usingBlock:^(NSInteger captureCount, NSString *const __unsafe_unretained *capturedStrings, const NSRange *capturedRanges, volatile BOOL *const stop) {
            if ((*capturedRanges).length == 0) return;
            
            ZLTextPart *part = [[ZLTextPart alloc] init];
            part.special = YES;
            part.text = *capturedStrings;
            part.emotion = [part.text hasPrefix:@"["] && [part.text hasSuffix:@"]"];
            part.range = *capturedRanges;
            [parts addObject:part];
        }];
        // 遍历所有的非特殊字符
        [text enumerateStringsSeparatedByRegex:pattern usingBlock:^(NSInteger captureCount, NSString *const __unsafe_unretained *capturedStrings, const NSRange *capturedRanges, volatile BOOL *const stop) {
            if ((*capturedRanges).length == 0) return;
            
            ZLTextPart *part = [[ZLTextPart alloc] init];
            part.text = *capturedStrings;
            part.range = *capturedRanges;
            [parts addObject:part];
        }];
        
        // 排序
        // 系统是按照从小 -> 大的顺序排列对象
        [parts sortUsingComparator:^NSComparisonResult(ZLTextPart *part1, ZLTextPart *part2) {
            // NSOrderedAscending = -1L, NSOrderedSame, NSOrderedDescending
            // 返回NSOrderedSame:两个一样大
            // NSOrderedAscending(升序):part2>part1
            // NSOrderedDescending(降序):part1>part2
            if (part1.range.location > part2.range.location) {
                // part1>part2
                // part1放后面, part2放前面
                return NSOrderedDescending;
            }
            // part1<part2
            // part1放前面, part2放后面
            return NSOrderedAscending;
        }];
        
        UIFont *font = [UIFont systemFontOfSize:15];
        NSMutableArray *specials = [NSMutableArray array];
        NSString *filePath = [[NSBundle mainBundle] pathForResource:@"face.plist" ofType:nil];
        NSArray  *face = [NSArray arrayWithContentsOfFile:filePath];
        // 按顺序拼接每一段文字
        for (ZLTextPart *part in parts) {
            // 等会需要拼接的子串
            NSAttributedString *substr = nil;
            if (part.isEmotion) { // 表情  表情处理的时候,需要根据你自己的plist进行单独处理,像一些第三方里自定义的plist,格式要是也是很严格的
                NSString *str = [text substringWithRange:part.range];
                for (int i = 0; i < face.count; i ++) {
                    if ([face[i][@"face_name"] isEqualToString:str]) {
                        //face[i][@"png"]就是我们要加载的图片
                        //新建文字附件来存放我们的图片,iOS7才新加的对象
                        NSTextAttachment *textAttachment = [[NSTextAttachment alloc] init];
                        //给附件添加图片
                        textAttachment.image = [UIImage imageNamed:face[i][@"face_image_name"]];
                        //调整一下图片的位置,如果你的图片偏上或者偏下,调整一下bounds的y值即可
                        textAttachment.bounds = CGRectMake(0, -6, 25, 25);
                        //把附件转换成可变字符串,用于替换掉源字符串中的表情文字
                       substr = [NSAttributedString attributedStringWithAttachment:textAttachment];
    
                        break;
                    }
                }
    
            } else if (part.special) { // 非表情的特殊文字
                NSURL *url =[NSURL URLWithString:part.text];
                if (url.scheme) {
                    substr = [[NSAttributedString alloc] initWithString:part.text attributes:@{
                                                                                               NSForegroundColorAttributeName : [UIColor redColor]
                                                                                               }];
    
                   NSString *string =@"网页链接";
                                NSTextAttachment *textAttachment = [[NSTextAttachment alloc] init];
                                    //给附件添加图片
                                    textAttachment.image = [UIImage imageNamed:@"链接"];
                                    //调整一下图片的位置,如果你的图片偏上或者偏下,调整一下bounds的y值即可
                                    textAttachment.bounds = CGRectMake(0, -6, 25, 25);
                    NSMutableAttributedString *tempAttribute = [[NSMutableAttributedString alloc]initWithAttributedString:[NSAttributedString attributedStringWithAttachment:textAttachment]];
                     NSMutableAttributedString *tempAttribute2 = [[NSMutableAttributedString alloc]initWithAttributedString:[NSAttributedString attributedStringWithAttachment:textAttachment]];
                    NSAttributedString *text =[[NSAttributedString alloc]initWithString:string attributes:@{NSForegroundColorAttributeName:[UIColor blueColor]}];
                    [tempAttribute appendAttributedString:text];
                    substr = [[NSAttributedString alloc]initWithAttributedString:tempAttribute];
                    // 创建特殊对象
                    
                    ZLSpecial *s = [[ZLSpecial alloc] init];
                    s.text = string;
                  //需要添加附属图片的长度,负责点击范围会变化
                    NSUInteger loc = attributedText.length+tempAttribute2.length;
                    NSUInteger len = string.length;
                    s.range = NSMakeRange(loc, len);
                    s.urlString = part.text;
                    [specials addObject:s];
                }else{
                    
                    NSLog(@"%@",part.text);
                    substr = [[NSAttributedString alloc] initWithString:part.text attributes:@{
                                                                                               NSForegroundColorAttributeName : [UIColor redColor]
                                                                                               }];
                    // 创建特殊对象
                    ZLSpecial *s = [[ZLSpecial alloc] init];
                    s.text = part.text;
                    NSUInteger loc = attributedText.length;
                    NSUInteger len = part.text.length;
                    s.range = NSMakeRange(loc, len);
                    s.urlString = part.text;
                    [specials addObject:s];
                }
               
                
               
            } else { // 非特殊文字
                substr = [[NSAttributedString alloc] initWithString:part.text];
            }
            [attributedText appendAttributedString:substr];
        }
        
        // 一定要设置字体,保证计算出来的尺寸是正确的
        [attributedText addAttribute:NSFontAttributeName value:font range:NSMakeRange(0, attributedText.length)];
        [attributedText addAttribute:@"specials" value:specials range:NSMakeRange(0, 1)];
        
        return attributedText;
    }
    
    中间存储检索结果与特殊字符的model:
    
    #import <Foundation/Foundation.h>
    
    @interface ZLTextPart : NSObject
    /** 这段文字的内容 */
    @property (nonatomic, copy) NSString *text;
    /** 这段文字的范围 */
    @property (nonatomic, assign) NSRange range;
    /** 是否为特殊文字 */
    @property (nonatomic, assign, getter = isSpecical) BOOL special;
    /** 是否为表情 */
    @property (nonatomic, assign, getter = isEmotion) BOOL emotion;
    
    
    @end
    
    #import <Foundation/Foundation.h>
    
    @interface ZLSpecial : NSObject
    /** 这段特殊文字的内容 */
    @property (nonatomic, copy) NSString *text;
    /** 这段特殊文字的范围 */
    @property (nonatomic, assign) NSRange range;
    @property(nonatomic,copy)NSString *urlString;
    
    设置完内容后我们需要计算内容高度,然后复制给Textview,建立一个Frame模型。
    #import <Foundation/Foundation.h>
    #import "ZLStatus.h"
    @interface ZLFrame : NSObject
    
    //设置
    
    /** 限制最大行数  这一步必须在设置完contentLabelF之后设置*/
    @property(nonatomic,assign)int  maxNumLine;
    @property(nonatomic,strong)ZLStatus *status;
    @property(nonatomic,assign)CGFloat frameX;
    @property(nonatomic,assign)CGFloat frameY;
    @property(nonatomic,assign)CGFloat maxWidth;
    //取值
    /**   */
    /** 正文 */
    @property (nonatomic, assign) CGRect contentLabelF;
    @property(nonatomic,assign)CGRect   maxNumLabelF;
    
    @end
    
    
    #import "ZLFrame.h"
    @interface ZLFrame()
    //检测高度的label;
    @property(nonatomic,strong)UILabel *templateLabel;
    @end
    @implementation ZLFrame
    -(void)setStatus:(ZLStatus *)status{
        _status = status;
        CGSize contentSize = [status.attributedText boundingRectWithSize:CGSizeMake(self.maxWidth, MAXFLOAT) options:NSStringDrawingUsesLineFragmentOrigin context:nil].size;
        self.contentLabelF = (CGRect){{self.frameX , self.frameY}, contentSize};
        
    }
    -(void)setMaxNumLine:(int)maxNumLine{
        _maxNumLine = maxNumLine;
        
        self.templateLabel.frame =CGRectMake(self.frameX, self.frameY, self.maxWidth, 0);
        self.templateLabel.attributedText = self.status.attributedText;
        self.templateLabel.numberOfLines = maxNumLine;
        [self.templateLabel sizeToFit];
        self.maxNumLabelF = self.templateLabel.frame;
        
    }
    -(void)setFrameX:(CGFloat)frameX{
        _frameX = frameX;
    }
    -(void)setFrameY:(CGFloat)frameY{
        _frameY = frameY;
    }
    -(void)setMaxWidth:(CGFloat)maxWidth{
        _maxWidth = maxWidth;
    }
    
    -(UILabel *)templateLabel{
        if (!_templateLabel) {
            _templateLabel =[[UILabel alloc]init];
            
        }
        return _templateLabel;
    }
    @end
    
    

    最后自定义Textview,引入Frame模型

    #import <UIKit/UIKit.h>
    #import "ZLFrame.h"
    @interface ZLStatusTextView : UITextView
    /** 所有的特殊字符串(里面存放着HWSpecial) */
    @property (nonatomic, strong) NSArray *specials;
    @property(nonatomic,strong)ZLFrame *zlFrame;
    @property(nonatomic,assign)int maxLine;
    @property(nonatomic,assign)BOOL isShowAll;//是否全部显示
    @end
    
    #import "ZLStatusTextView.h"
    #import "ZLSpecial.h"
    #define ZLStatusTextViewCoverTag 999
    @implementation ZLStatusTextView
    - (id)initWithFrame:(CGRect)frame
    {
        self = [super initWithFrame:frame];
        if (self) {
            self.backgroundColor = [UIColor clearColor];
            self.editable = NO;
            self.textContainerInset = UIEdgeInsetsMake(0, -5, 0, -5);
            // 禁止滚动, 让文字完全显示出来
            self.scrollEnabled = NO;
        }
        return self;
    }
    - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
    {
        // 触摸对象
        UITouch *touch = [touches anyObject];
        
        // 触摸点
        CGPoint point = [touch locationInView:self];
        
        NSArray *specials = [self.attributedText attribute:@"specials" atIndex:0 effectiveRange:NULL];
        BOOL contains = NO;
        
        for (ZLSpecial *special in specials) {
            self.selectedRange = special.range;
            // self.selectedRange --影响--> self.selectedTextRange
            // 获得选中范围的矩形框
            NSArray *rects = [self selectionRectsForRange:self.selectedTextRange];
            // 清空选中范围
            self.selectedRange = NSMakeRange(0, 0);
            
            for (UITextSelectionRect *selectionRect in rects) {
                CGRect rect = selectionRect.rect;
                if (rect.size.width == 0 || rect.size.height == 0) continue;
                
                if (CGRectContainsPoint(rect, point)) { // 点中了某个特殊字符串
                    contains = YES;
                    break;
                }
            }
            
            if (contains) {
                for (UITextSelectionRect *selectionRect in rects) {
                    CGRect rect = selectionRect.rect;
                    if (rect.size.width == 0 || rect.size.height == 0) continue;
                    
                    UIView *cover = [[UIView alloc] init];
                    cover.backgroundColor = [UIColor greenColor];
                    cover.frame = rect;
                    cover.tag = ZLStatusTextViewCoverTag;
                    cover.layer.cornerRadius = 5;
                    [self insertSubview:cover atIndex:0];
                }
                
                break;
            }
        }
        
        // 在被触摸的特殊字符串后面显示一段高亮的背景
    }
    - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
    {
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.25 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            // 触摸对象
            UITouch *touch = [touches anyObject];
            
            // 触摸点
            CGPoint point = [touch locationInView:self];
            
            NSArray *specials = [self.attributedText attribute:@"specials" atIndex:0 effectiveRange:NULL];
            BOOL contains = NO;
            
            for (ZLSpecial *special in specials) {
                self.selectedRange = special.range;
                // self.selectedRange --影响--> self.selectedTextRange
                // 获得选中范围的矩形框
                NSArray *rects = [self selectionRectsForRange:self.selectedTextRange];
                // 清空选中范围
                self.selectedRange = NSMakeRange(0, 0);
                
                for (UITextSelectionRect *selectionRect in rects) {
                    CGRect rect = selectionRect.rect;
                    if (rect.size.width == 0 || rect.size.height == 0) continue;
                    
                    if (CGRectContainsPoint(rect, point)) { // 点中了某个特殊字符串
                        contains = YES;
                        break;
                    }
                }
                
                if (contains) {
                    for (UITextSelectionRect *selectionRect in rects) {
                        CGRect rect = selectionRect.rect;
                        if (rect.size.width == 0 || rect.size.height == 0) continue;
                        
                        if (special.urlString) {
                            NSString *urlStr = special.urlString;
                            NSURL *url =[NSURL URLWithString:urlStr];
                            if (url.scheme) {
                                [[UIApplication sharedApplication]openURL:url];
                            }else{
                                NSMutableString *str=[[NSMutableString alloc] initWithFormat:@"tel:%@",special.text];
                                UIWebView *callWebview = [[UIWebView alloc] init];
                                [callWebview loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:str]]];
                                [self addSubview:callWebview];
                        }
                        }
                    }
                    
                    break;
                }
            }
    
            [self touchesCancelled:touches withEvent:event];
        });
    }
    
    - (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
    {
        // 去掉特殊字符串后面的高亮背景
        for (UIView *child in self.subviews) {
            if (child.tag == ZLStatusTextViewCoverTag) [child removeFromSuperview];
        }
    }
    -(void)setZlFrame:(ZLFrame *)zlFrame{
        _zlFrame = zlFrame;
        self.attributedText = zlFrame.status.attributedText;
        self.frame = zlFrame.contentLabelF;
        
        
    }
    -(void)setMaxLine:(int)maxLine{
        _maxLine = maxLine;
        [self.zlFrame setMaxNumLine:maxLine];
        self.frame = self.zlFrame.maxNumLabelF;
    }
    -(void)setIsShowAll:(BOOL)isShowAll{
        _isShowAll = isShowAll;
        if (isShowAll) {
            self.frame = self.zlFrame.contentLabelF;
    
        }else{
            [self setMaxLine:3];
        }
    }
    @end
    
    

    最后在ViewController里引入即可:

    #import "ViewController.h"
    #import "ZLStatusTextView.h"
    #define kTempText  @"简书:http://jianshu.com哈哈哈[调皮][流汗][偷笑][再见][可爱][色][害羞][委屈][委屈][抓狂][酷][酷][嘘][嘘][龇牙][大哭][大哭][大哭][龇牙][嘘][嘘][调皮][调皮]哈哈哈哈[嘘][调皮][调皮]18637963241他大舅他二舅都是舅,高桌子地板头都是木头"
    #define  KURlREGULAR @"((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\\.\\-~!@#$%^&*+?:_/=<>]*)?)|(((http[s]{0,1}|ftp)://|)((?:(?:25[0-5]|2[0-4]\\d|((1\\d{2})|([1-9]?\\d)))\\.){3}(?:25[0-5]|2[0-4]\\d|((1\\d{2})|([1-9]?\\d))))(:\\d+)?(/[a-zA-Z0-9\\.\\-~!@#$%^&*+?:_/=<>]*)?)"
    #define KPHONENUMBERREGLAR @"\\d{3}-\\d{8}|\\d{3}-\\d{7}|\\d{4}-\\d{8}|\\d{4}-\\d{7}|1+[3578]+\\d{9}|\\d{8}|\\d{7}"
    #import "ZLStatus.h"
    #import "ZLFrame.h"
    #import "LxButton.h"
    @interface ViewController ()<UITextViewDelegate>
    @property(nonatomic,strong)ZLStatusTextView *textview;
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        // Do any additional setup after loading the view, typically from a nib.
        
        self.textview =[[ZLStatusTextView alloc]initWithFrame:CGRectMake(20, 100, 250, 200)];
        ZLStatus *status = [[ZLStatus alloc]init];
        status.text = kTempText;
       
       
        ZLFrame *zlFrame =[[ZLFrame alloc]init];
        zlFrame.frameX = self.textview.frame.origin.x;
        zlFrame.frameY = self.textview.frame.origin.y;
        zlFrame.maxWidth = self.textview.frame.size.width;
        zlFrame.status = status;
        
        self.textview.zlFrame = zlFrame;
        //设置最大行数用于展开
        self.textview.maxLine = 3;
        self.textview.isShowAll = YES;
        [self.view addSubview:self.textview];
        self.textview.backgroundColor =[UIColor lightGrayColor];
       
      
        LxButton *button =[LxButton LXButtonWithTitle:@"限制最大行数" titleFont:[UIFont systemFontOfSize:15] Image:nil backgroundImage:nil backgroundColor:[UIColor brownColor] titleColor:[UIColor blueColor] frame:CGRectMake(20, 40, 150, 40)];
        
        [self.view addSubview:button];
        __weak ViewController *weakSelf = self;
        [button addClickBlock:^(UIButton *button) {
           
            weakSelf.textview.isShowAll =!weakSelf.textview.isShowAll;
        }];
    
    }
    
    
    
    @end
    

    demo地址:富文本检索表情,网址,替换链接,限制最大输入行

    相关文章

      网友评论

        本文标题:iOS 计算富文本,检索网址,号码,表情,并且计算高度,设置最大

        本文链接:https://www.haomeiwen.com/subject/wzzqgttx.html