自制土方法实现CoreText超文本点击

学习了一段时间CoreText 还是迷迷糊糊,下文是实现超链接自动识别,电话号码识别的一个土方法,大神直接飘过就好了。。。


#import <UIKit/UIKit.h>
#import <CoreText/CoreText.h>


typedef enum{
    CLICK_TYPE_NONE = 0,
    CLICK_TYPE_LINK ,
    CLICK_TYPE_PHONE,
}CLICK_TYPE;

@interface LinkView : UIView

{
    CTFrameRef _frame;
    NSInteger clickNum;
    BOOL isClick;
    CLICK_TYPE clickType;
}

@property (assign, nonatomic) CLICK_TYPE clickType;
@property (assign, nonatomic) BOOL isClick;
@property (assign, nonatomic) NSInteger clickNum;//点击的位置
@property (retain, nonatomic) NSString *text;
@property (retain, nonatomic) NSArray *linkArray;//存链接的Range数组
@property (retain, nonatomic) NSArray *phoneArray;//存电话号码的Range数组

@end



 
@synthesize text;
@synthesize linkArray;
@synthesize phoneArray;
@synthesize isClick;
@synthesize clickNum;
@synthesize clickType;

- (void)dealloc{
    [text release];
    [linkArray release];
    [phoneArray release];
    [super dealloc];
}

- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        // Initialization code
        self.backgroundColor = [UIColor clearColor];
    }
    return self;
}

- (void)longPress:(UILongPressGestureRecognizer *)longPress{
    if (longPress.state == UIGestureRecognizerStateBegan) {
        [self showMenu];
    }
}



//是否包含汉字
- (BOOL) containsChinese:(NSString *)str {
    for(int i = 0; i < [str length]; i++) {
        int a = [str characterAtIndex:i];
        if( a > 0x4e00 && a < 0x9fff)
            return TRUE;
    }
    return FALSE;
}
//是否包含特殊符号,可以在这里判断超链接 结尾
- (BOOL) containsSpecialSymbol:(NSString *)str{
    NSSet *symbols = [NSSet setWithObjects:@" ",@",", nil];
    for(int i = 0; i < [str length]; i++) {
        NSString *c = [str substringWithRange:NSMakeRange(i, 1)];
        if ([symbols containsObject:c]) {
            return YES;
        }
    }
    return NO;
}
//电话号码是否合法
- (BOOL)isValidatePhone:(NSString *)phone{
    
    return NO;//本版本不进行电话号码得判断
    
    NSString *phoneRegex = @"\\b(1)[358][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]\\b";
    NSPredicate *phoneTest = [NSPredicate predicateWithFormat:@"SELF MATCHES %@" , phoneRegex];
    BOOL ret = [phoneTest evaluateWithObject:phone];
    return ret;
}
- (BOOL)isMobileNumber:(NSString *)mobileNum
{
    /**
     * 手机号码
     * 移动:134[0-8],135,136,137,138,139,150,151,157,158,159,182,187,188
     * 联通:130,131,132,152,155,156,185,186
     * 电信:133,1349,153,180,189
     */
    NSString * MOBILE = @"^1(3[0-9]|5[0-35-9]|8[025-9])\\d{8}$";
    /**
     10         * 中国移动:China Mobile
     11         * 134[0-8],135,136,137,138,139,150,151,157,158,159,182,187,188
     12         */
    NSString * CM = @"^1(34[0-8]|(3[5-9]|5[017-9]|8[278])\\d)\\d{7}$";
    /**
     15         * 中国联通:China Unicom
     16         * 130,131,132,152,155,156,185,186
     17         */
    NSString * CU = @"^1(3[0-2]|5[256]|8[56])\\d{8}$";
    /**
     20         * 中国电信:China Telecom
     21         * 133,1349,153,180,189
     22         */
    NSString * CT = @"^1((33|53|8[09])[0-9]|349)\\d{7}$";
    /**
     25         * 大陆地区固话及小灵通
     26         * 区号:010,020,021,022,023,024,025,027,028,029
     27         * 号码:七位或八位
     28         */
    NSString * PHS = @"^0(10|2[0-5789]|\\d{3})\\d{7,8}$";
    
    NSPredicate *regextestmobile = [NSPredicate predicateWithFormat:@"SELF MATCHES %@", MOBILE];
    NSPredicate *regextestcm = [NSPredicate predicateWithFormat:@"SELF MATCHES %@", CM];
    NSPredicate *regextestcu = [NSPredicate predicateWithFormat:@"SELF MATCHES %@", CU];
    NSPredicate *regextestct = [NSPredicate predicateWithFormat:@"SELF MATCHES %@", CT];
    NSPredicate *regextestphx = [NSPredicate predicateWithFormat:@"SELF MATCHES %@", PHS];
    
    if (([regextestmobile evaluateWithObject:mobileNum] == YES)
        || ([regextestcm evaluateWithObject:mobileNum] == YES)
        || ([regextestct evaluateWithObject:mobileNum] == YES)
        || ([regextestcu evaluateWithObject:mobileNum] == YES)
        || ([regextestphx evaluateWithObject:mobileNum]))
    {
        return YES;
    }
    else
    {
        return NO;
    }
}

//是否是连接开头
- (BOOL)isValidateLink:(NSString *)Link{
    if ([Link hasPrefix:@"http://"] || [Link hasPrefix:@"www."]) {
        return YES;
    }
    return NO;
}

//获取链接的NSRangeArray  array 里面存的是链接的 NSRange
- (NSArray *)getLinkRangeWithString:(NSString *)string{
    NSMutableArray *rangeArray = [NSMutableArray arrayWithCapacity:0];
    for (int loc = 0; loc < [string length]; ) {
        for (int len = 0; len <= [string length] - loc; ) {
            NSString *lenStr = [string substringWithRange:NSMakeRange(loc, len)];
            if ([self isValidateLink:lenStr]) {
                if (![self containsSpecialSymbol:lenStr]) {
                    //不包含特殊字符 以特殊字符或汉字结尾,或者结尾为空
                    if (loc + len == [string length]) {
                        NSString *rangeStr = NSStringFromRange(NSMakeRange(loc, len));
                        [rangeArray addObject:rangeStr];
                        loc += len - 1;
                        break;
                    }else{
                        if ( [self containsSpecialSymbol:[string substringWithRange:NSMakeRange(loc + len, 1)]]) {
                            NSString *rangeStr = NSStringFromRange(NSMakeRange(loc, len));
                            [rangeArray addObject:rangeStr];
                            loc += len - 1;
                            break;
                        }
                    }
                }
            }
            len++;
        }
        loc++;
    }
    return rangeArray;
}
//获取电话的NSRange
- (NSArray *)getPhoneRangeWithString:(NSString *)string{
    
    NSMutableArray *rangeArray = [[NSMutableArray alloc]initWithCapacity:0];
    for (int loc = 0; loc < [string length]; ) {
        for (int len = 0; len <= [string length] - loc; ) {
            NSString *lenStr = [string substringWithRange:NSMakeRange(loc, len)];
//            NSLog(@"lenStr: %@",lenStr);
            if ([self isMobileNumber:lenStr]) {
                NSString *rangeStr = NSStringFromRange(NSMakeRange(loc, len));
                [rangeArray addObject:rangeStr];
                loc += len - 1;
                break;
            }
            len++;
        }
        loc++;
    }
    return rangeArray;
}


//设置换行模式
- (void)setAttString:(NSMutableAttributedString *)attString LineBreakMode:(CTLineBreakMode)mode{
    CTParagraphStyleSetting lineBreakMode;
    CTLineBreakMode lineBreak = mode;
    lineBreakMode.spec = kCTParagraphStyleSpecifierLineBreakMode;
    lineBreakMode.value = &lineBreak;
    lineBreakMode.valueSize = sizeof(CTLineBreakMode);
    
    CTParagraphStyleSetting settings[] = {
        lineBreakMode
    };
    
    CTParagraphStyleRef style = CTParagraphStyleCreate(settings, 1);
    
    NSMutableDictionary *attributes = [NSMutableDictionary dictionaryWithObject:(id)style forKey:(id)kCTParagraphStyleAttributeName];
    
    [attString addAttributes:attributes range:NSMakeRange(0, [attString length])];
}

- (void)drawRect:(CGRect)rect{
    
    CGContextRef context = UIGraphicsGetCurrentContext();
    
    CGContextSetTextMatrix(context, CGAffineTransformIdentity);
    
    CGContextSaveGState(context);
    
    //x,y轴方向移动
    CGContextTranslateCTM(context, 0, self.bounds.size.height);
    //缩放x,y轴方向缩放,-1.0为反向1.0倍,坐标系转换,沿x轴翻转180度
    CGContextScaleCTM(context, 1.0, -1.0);

    CGMutablePathRef path = CGPathCreateMutable();
    
    CGPathAddRect(path, NULL, self.bounds);
    
    NSMutableAttributedString *attString = [[NSMutableAttributedString alloc]initWithString:self.text];
    
    [attString beginEditing];
    
    //设置字体属性
    CTFontRef font = [self CTFontRefCTFontCreateFromUIFont:[UIFont systemFontOfSize:16.0f]];
    [attString addAttribute:(id)kCTFontAttributeName value:(id)font range:NSMakeRange(0, [attString length])];
    
    //让文字细一点////
    CGFloat widthValue = -1.0;
    
    CFNumberRef strokeWidth = CFNumberCreate(NULL,kCFNumberFloatType,&widthValue);
    
    [attString addAttribute:(NSString*)(kCTStrokeWidthAttributeName) value:(id)strokeWidth range:NSMakeRange(0,[text length])];
    
    [attString addAttribute:(NSString*)(kCTStrokeColorAttributeName) value:(id)[[UIColor whiteColor]CGColor] range:NSMakeRange(0,[text length])];
    /////细一点
    
    
    self.phoneArray = [self getPhoneRangeWithString:self.text];
    self.linkArray = [self getLinkRangeWithString:self.text];
    
    //设置字体颜色
    [attString addAttribute:(id)kCTForegroundColorAttributeName value:(id)[UIColor blackColor].CGColor range:NSMakeRange(0, [attString length])];
    for (int i = 0; i < [self.linkArray count]; i ++) {
        [attString addAttribute:(id)kCTForegroundColorAttributeName value:(id)[UIColor blueColor].CGColor range:NSRangeFromString([self.linkArray objectAtIndex:i])];
        [attString addAttribute:(id)kCTUnderlineStyleAttributeName value:(id)[NSNumber numberWithInt: kCTUnderlineStyleSingle] range:NSRangeFromString([self.linkArray objectAtIndex:i])];
        [attString addAttribute:(id)kCTUnderlineColorAttributeName value:(id)[UIColor greenColor] range:NSRangeFromString([self.linkArray objectAtIndex:i])];
    }
    for (int i = 0; i < [self.phoneArray count]; i ++) {
        [attString addAttribute:(id)kCTForegroundColorAttributeName value:(id)[UIColor redColor].CGColor range:NSRangeFromString([self.phoneArray objectAtIndex:i])];
    }
    if (self.isClick) {
        switch (self.clickType) {
            case CLICK_TYPE_LINK:
                [attString removeAttribute:(id)kCTForegroundColorAttributeName range:NSRangeFromString([self.linkArray objectAtIndex:self.clickNum])];
                [attString addAttribute:(id)kCTForegroundColorAttributeName value:(id)[UtilityHelper colorWithHexString:@"#002f6f"].CGColor range:NSRangeFromString([self.linkArray objectAtIndex:self.clickNum])];
                break;
            case CLICK_TYPE_PHONE:
                [attString removeAttribute:(id)kCTForegroundColorAttributeName range:NSRangeFromString([self.phoneArray objectAtIndex:self.clickNum])];
                [attString addAttribute:(id)kCTForegroundColorAttributeName value:(id)[UIColor redColor].CGColor range:NSRangeFromString([self.phoneArray objectAtIndex:self.clickNum])];
                break;
            default:
                break;
        }
        
    }
    //换行模式
//    [self setAttString:attString LineBreakMode:kCTLineBreakByWordWrapping];
    [self setText:attString Alignment:kCTTextAlignmentLeft lineBreakMode:kCTLineBreakByCharWrapping];
    
    [attString endEditing];
    
    CTFramesetterRef frameSetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)attString);
    
    CTFrameRef frame = CTFramesetterCreateFrame(frameSetter, CFRangeMake(0, [attString length]), path, NULL);
    _frame = frame;

    CTFrameDraw(frame, context);
    
    //    CFRelease(frame);
    
    CFRelease(frameSetter);
    
    CFRelease(path);
}

//设置换行和对其方式
-(void)setText:(NSMutableAttributedString *)attString Alignment:(CTTextAlignment)alignment lineBreakMode:(CTLineBreakMode)lineBreakMode {
	[self setText:attString Alignment:alignment lineBreakMode:lineBreakMode range:NSMakeRange(0,[attString length])];
}
-(void)setText:(NSMutableAttributedString *)attString Alignment:(CTTextAlignment)alignment lineBreakMode:(CTLineBreakMode)lineBreakMode range:(NSRange)range {
	// kCTParagraphStyleAttributeName > kCTParagraphStyleSpecifierAlignment
	CTParagraphStyleSetting paraStyles[2] = {
		{.spec = kCTParagraphStyleSpecifierAlignment, .valueSize = sizeof(CTTextAlignment), .value = (const void*)&alignment},
		{.spec = kCTParagraphStyleSpecifierLineBreakMode, .valueSize = sizeof(CTLineBreakMode), .value = (const void*)&lineBreakMode},
	};
	CTParagraphStyleRef aStyle = CTParagraphStyleCreate(paraStyles, 2);
	[attString removeAttribute:(NSString*)kCTParagraphStyleAttributeName range:range]; // Work around for Apple leak
	[attString addAttribute:(NSString*)kCTParagraphStyleAttributeName value:(id)aStyle range:range];
	CFRelease(aStyle);
}


//根据 点 判断 字符index
CFIndex CFIndexGet(CGPoint point,CTFrameRef frame){

    //获取每一行
    CFArrayRef lines = CTFrameGetLines(frame);
    CGPoint origins[CFArrayGetCount(lines)];
    //获取每行的原点坐标
    CTFrameGetLineOrigins(frame, CFRangeMake(0, 0), origins);
    CTLineRef line = NULL;
    CGPoint lineOrigin = CGPointZero;
    for (int i= 0; i < CFArrayGetCount(lines); i++)
    {
        CGPoint origin = origins[i];
        CGPathRef path = CTFrameGetPath(frame);
        //获取整个CTFrame的大小
        CGRect rect = CGPathGetBoundingBox(path);
        
        //坐标转换,把每行的原点坐标转换为uiview的坐标体系
        CGFloat y = rect.origin.y + rect.size.height - origin.y;
        //判断点击的位置处于那一行范围内
        if ((point.y <= y) && (point.x >= origin.x))
        {
            line = CFArrayGetValueAtIndex(lines, i);
            lineOrigin = origin;
            break;
        }
    }
    point.x -= lineOrigin.x;
    //获取点击位置所处的字符位置,就是相当于点击了第几个字符
    CFIndex index = CTLineGetStringIndexForPosition(line, point);
    
    return index;
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
    UITouch *touch = [touches anyObject];
    //获取触摸点击当前view的坐标位置
    CGPoint location = [touch locationInView:self];
    
    CFIndex index = CFIndexGet(location, _frame);
    
    for (int i = 0; i< [self.linkArray count]; i++) {
        NSString *rangeStr = [self.linkArray objectAtIndex:i];
        NSRange range = NSRangeFromString(rangeStr);
        if (index >= range.location && index <= range.location + range.length) {
            self.isClick = YES;
            self.clickNum = i;
            self.clickType = CLICK_TYPE_LINK;
            [self setNeedsDisplay];
        }
    }
    for (int i = 0; i< [self.phoneArray count]; i++) {
        NSString *rangeStr = [self.phoneArray objectAtIndex:i];
        NSRange range = NSRangeFromString(rangeStr);
        if (index >= range.location && index <= range.location + range.length) {
            self.isClick = YES;
            self.clickNum = i;
            self.clickType = CLICK_TYPE_PHONE;
            [self setNeedsDisplay];
        }
    }
    
}

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event{
    UITouch *touch = [touches anyObject];
    //获取触摸点击当前view的坐标位置
    CGPoint location = [touch locationInView:self];
    
    CFIndex index = CFIndexGet(location, _frame);
    
//    NSLog(@"indexEnd:%ld",index);
    for (int i = 0; i< [self.linkArray count]; i++) {
        NSString *rangeStr = [self.linkArray objectAtIndex:i];
        NSRange range = NSRangeFromString(rangeStr);
        if (index >= range.location && index <= range.location + range.length) {
            if (i == self.clickNum) {
                NSMutableString *linkText = [NSMutableString stringWithString:[self.text substringWithRange:range]];
                if ([linkText hasPrefix:@"www:"]) {
                    linkText = [NSMutableString stringWithFormat:@"http://%@",linkText];
                }
                [UtilityHelper gatrackEvent:@"客户聊天详情页" label:@"跳转超链接"];
                [[UIApplication sharedApplication] openURL:[NSURL URLWithString:linkText]];
            }
        }
    }
    for (int i = 0; i< [self.phoneArray count]; i++) {
        NSString *rangeStr = [self.phoneArray objectAtIndex:i];
        NSRange range = NSRangeFromString(rangeStr);
        if (index >= range.location && index <= range.location + range.length) {
            if (i == self.clickNum) {
                [[UIApplication sharedApplication] openURL:[NSURL URLWithString:[NSString stringWithFormat:@"tel://%@",[self.text substringWithRange:range]]]];
            }
        }
    }
    self.isClick = NO;
    self.clickNum = -1;
    self.clickType = CLICK_TYPE_NONE;
    [self setNeedsDisplay];
}

- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event{
    self.isClick = NO;
    self.clickNum = -1;
    self.clickType = CLICK_TYPE_NONE;
    [self setNeedsDisplay];
}



//根据UIFont 获取 CTFontRef
- (CTFontRef)CTFontRefCTFontCreateFromUIFont:(UIFont *)font
{
    CTFontRef ctFont =CTFontCreateWithName((CFStringRef)font.fontName,
                                           font.pointSize,
                                           NULL);
    return ctFont;
}
@end



你可能感兴趣的:(自制土方法实现CoreText超文本点击)