IOS CoreText --- 支持图片及链接的点击

上一节中,我详细的讲解了在Core Text进行图文混排。但实际应用中,也许还要支持超链接效果及图片与链接的点击事件。先看看最终效果图:

IOS CoreText --- 支持图片及链接的点击_第1张图片

1. 为了支持链接效果,与上节同样的道理,先修改数据结构,增加type为“link”的类别。

IOS CoreText --- 支持图片及链接的点击_第2张图片

2. 添加文字的链接效果,为了支持展示图片显示,上一节中定义了CoreTextImageData类,同样的道理,我们这里定义一个CoreTextLinkData类。

@interface CoreTextLinkData : NSObject
/* 文字标题 */
@property (nonatomic,copy) NSString *title;
/* 链接url */
@property (nonatomic,copy) NSString *url;
/* 文字所在的区域 */
@property (nonatomic,assign) NSRange range;
@end


3. 改造CoreTextData.h 文件,添加对CoreTextLinkData数组的强引用。

@interface CoreTextData : NSObject

@property (nonatomic,assign) CTFrameRef ctFrame;
@property (nonatomic,assign) CGFloat height;
@property (nonatomic,strong) NSArray *imageArray;
@property (nonatomic,strong) NSArray *linkArray;
@end

4. 在CTFrameParser类中修改loadTemplateFile方法,将CoreTextLinkData数据加入到数组中。

+ (NSAttributedString *)loadTemplateFile:(NSString *)path config:(CTFrameParserConfig *)config  imageArray:(NSMutableArray *)imageArray linkArray:(NSMutableArray *)linkArray{
    NSMutableAttributedString *result = [[NSMutableAttributedString alloc] init];
    // JSON方式获取数据
    //        NSArray *array = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:nil];
    NSArray *array = [NSArray arrayWithContentsOfFile:path];
    if (array) {
        if ([array isKindOfClass:[NSArray class]]) {
            for (NSDictionary *dict in array) {
                NSString *type = dict[@"type"];
                if ([type isEqualToString:@"txt"]) {
                    NSAttributedString *as = [self parseAttributedContentFromNSDictionary:dict config:config];
                    [result appendAttributedString:as];
                } else if ([type isEqualToString:@"img"]) {
                    CoreTextImageData *imageData = [[CoreTextImageData alloc] init];
                    imageData.name = dict[@"name"];
                    // 占位字符所在的位置
                    imageData.position = [result length];
                    [imageArray addObject:imageData];
                    // 创建空白占位符,并且设置它的CTRunDelegate信息
                    NSAttributedString *as = [self parseImageDataFromNSDictionary:dict config:config];
                    [result appendAttributedString:as];
                } else if ([type isEqualToString:@"link"]) {
                    NSUInteger startPos = result.length;
                    NSAttributedString *as = [self parseAttributedContentFromNSDictionary:dict config:config];
                    [result appendAttributedString:as];
                    // 创建CoreTextLinkData
                    NSUInteger length = result.length - startPos;
                    NSRange linkRange = NSMakeRange(startPos, length);
                    
                    CoreTextLinkData *linkData = [[CoreTextLinkData alloc] init];
                    linkData.title = dict[@"content"];
                    linkData.url = dict[@"url"];
                    linkData.range = linkRange;
                    [linkArray addObject:linkData];
                }
            }
        }
    }
    return result;
}

5. 文字下划线的效果,在上述的parseAttributedContentFromNSDictionary方法中体现。

+ (NSAttributedString *)parseAttributedContentFromNSDictionary:(NSDictionary *)dict config:(CTFrameParserConfig *)config {
    // 加载默认的文本属性
    NSMutableDictionary *attributes = (NSMutableDictionary *)[self attributesWithConfig:config];
    
    UIColor *color = [self colorFromTemplate:dict[@"color"]];
    if (color) {
        attributes[(id)kCTForegroundColorAttributeName] = (id)color.CGColor;
    }
    
    if ([dict[@"type"] isEqualToString:@"link"]) {
        // 设置文字下滑线及颜色
        attributes[(id)kCTUnderlineStyleAttributeName] = (id)[NSNumber numberWithInt:kCTUnderlineStyleSingle];
        UIColor *color = [self colorFromTemplate:dict[@"color"]];
        attributes[(id)kCTUnderlineColorAttributeName] = (id)color.CGColor;
    }
    
    CGFloat fontSize = [dict[@"size"] floatValue];
    if (fontSize > 0) {
        CTFontRef fontRef = CTFontCreateWithName((CFStringRef)@"ArialMT", fontSize, NULL);
        attributes[(id)kCTFontAttributeName] = (__bridge id)(fontRef);
        CFRelease(fontRef);
    }
    
    NSString *content = dict[@"content"];
    return [[NSAttributedString alloc] initWithString:content attributes:attributes];
}

完成上面的代码之后,就可以显示出我所列出的最终效果图了。下面的工作,就是对图片及链接添加点击效果。在书写代码之前,我们先进行整体的分析工作。

1)Core Text中显示的图片及链接最终是以一个整体的形式渲染到一个View上面的,所以根本“拿不到”所谓的图片对象或者链接对象。

2)根据第一点的结论,我们完全可以考虑使用手势技术(tap guesture),捕捉到图片或者链接所在的位置,然后执行手势方法来完成所谓的“点击事件”。

3)执行完手势操作之后,要将最终的结果回调到主控制器,考虑到主控制器中得层级结构也许很复杂,我们可以使用通知来完成传值工作。


6. 分析完以上3点之后,我们现在绘制的CTDisplayView中添加手势事件。

1)图片点击部分: 上一节中,我们已经获取到了图片的展位信息imageArray,所以,我们只要遍历imageArray,然后判断手指所点击的位置是否在展位字符的范围内,即可判断,时候点击到了图片。主要代码就是CGRectContainsPoint(rect,point)。

2)链接点击部分:主要思路应该是含有链接的这一串字符串所在的位置是否包含手指点击的点,如代码中的CoreTextUtils类所做得工作。

extern NSString *const CTDisplayViewImagePressedNotification;
extern NSString *const CTDisplayViewLinkPressedNotification;

@interface CTDisplayView : UIView
@property (nonatomic,strong) CoreTextData *data;
@end


NSString *const CTDisplayViewImagePressedNotification = @"CTDisplayViewImagePressedNotification";
NSString *const CTDisplayViewLinkPressedNotification = @"CTDisplayViewLinkPressedNotification";

@interface CTDisplayView() <UIGestureRecognizerDelegate>

@end

@implementation CTDisplayView

- (id)initWithCoder:(NSCoder *)aDecoder {
    if (self = [super initWithCoder:aDecoder]) {
        [self setupEvents];
    }
    return self;
}

- (instancetype)initWithFrame:(CGRect)frame {
    if (self = [super initWithFrame:frame]) {
        [self setupEvents];
    }
    return self;
}

- (void)setupEvents {
    UIGestureRecognizer *tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(userTapGestureDetected:)];
    tapRecognizer.delegate = self;
    [self addGestureRecognizer:tapRecognizer];
    self.userInteractionEnabled = YES;
}

- (void)userTapGestureDetected:(UIGestureRecognizer *)recognizer {
    CGPoint point = [recognizer locationInView:self];
    for (CoreTextImageData *imageData in self.data.imageArray) {
        // 翻转坐标系,因为imageData中得坐标是CoreText的坐标系
        CGRect imageRect = imageData.imagePosition;
        CGPoint imagePosition = imageRect.origin;
        imagePosition.y = self.bounds.size.height - imageRect.origin.y - imageRect.size.height;
        CGRect rect = CGRectMake(imagePosition.x, imagePosition.y, imageRect.size.width, imageRect.size.height);
        // 检测点击位置
        if (CGRectContainsPoint(rect, point)) {
            // 图片被点击了
            NSDictionary *userInfo = @{@"imageData" : imageData};
            [[NSNotificationCenter defaultCenter] postNotificationName:CTDisplayViewImagePressedNotification
                                                  object:self
                                                  userInfo:userInfo];
            
            return;
        }
    }
    
    CoreTextLinkData *linkData = [CoreTextUtils touchLinkInView:self atPoint:point data:self.data];
    if (linkData) {
        NSDictionary *userInfo = @{ @"linkData": linkData };
        [[NSNotificationCenter defaultCenter] postNotificationName:CTDisplayViewLinkPressedNotification
                                              object:self
                                              userInfo:userInfo];
        return;
    }
}

7. CoreTextUtils类,用来判断是否点击选中文字链接。

@interface CoreTextUtils : NSObject
+ (CoreTextLinkData *)touchLinkInView:(UIView *)view atPoint:(CGPoint)point data:(CoreTextData *)data;
@end

@implementation CoreTextUtils
+ (CoreTextLinkData *)touchLinkInView:(UIView *)view atPoint:(CGPoint)point data:(CoreTextData *)data {
    CTFrameRef textFrame = data.ctFrame;
    CFArrayRef lines = CTFrameGetLines(textFrame);
    if (!lines) return nil;
    
    CFIndex count = CFArrayGetCount(lines);
    CoreTextLinkData *foundLink = nil;
    
    // 获得每一行的origin坐标
    CGPoint origins[count];
    CTFrameGetLineOrigins(textFrame, CFRangeMake(0, 0), origins);
    
    // 翻转坐标系
    CGAffineTransform transform = CGAffineTransformMakeTranslation(0, view.bounds.size.height);
    transform = CGAffineTransformScale(transform, 1.0f, -1.0f);
    
    for (int i = 0; i < count; i++) {
        CGPoint linePoint = origins[i];
        CTLineRef line = CFArrayGetValueAtIndex(lines, i);
        // 获得每一行的rect信息
        CGRect flippedRect = [self getLineBounds:line point:linePoint];
        
        CGRect rect = CGRectApplyAffineTransform(flippedRect, transform);
        if (CGRectContainsPoint(rect, point)) {
            // 将点击的坐标转化为相对于当前行的坐标
            CGPoint relativePoint = CGPointMake(point.x - CGRectGetMinX(rect), CGRectGetMinY(rect));
            // 获取当前点击坐标对应的字符串偏移量
            CFIndex idx = CTLineGetStringIndexForPosition(line, relativePoint);
            // 判断偏移量是否在链接列表中
            foundLink = [self linkAtIndex:idx linkArray:data.linkArray];
            return foundLink;
        }
    }
    return nil;
}

+ (CGRect)getLineBounds:(CTLineRef)line point:(CGPoint)point {
    CGFloat ascent = 0.0f, descent = 0.0f, leading = 0.0f;
    CGFloat width = CTLineGetTypographicBounds(line, &ascent, &descent, &leading);
    CGFloat height = ascent + descent;
    return CGRectMake(point.x, point.y, width, height);
}

+ (CoreTextLinkData *)linkAtIndex:(CFIndex)index linkArray:(NSArray *)linkArray {
    CoreTextLinkData *link = nil;
    for (CoreTextLinkData *data in linkArray) {
        if (NSLocationInRange(index, data.range)) {
            link = data;
            break;
        }
    }
    return link;
}

@end

8.  在主控制器ViewController完成通知监听工作,实时完成监听事件的触发。

- (void)setupNotifications {
    [[NSNotificationCenter defaultCenter] addObserver:self
                                          selector:@selector(imagePressed:)
                                          name:CTDisplayViewImagePressedNotification
                                          object:nil];
    
    [[NSNotificationCenter defaultCenter] addObserver:self
                                          selector:@selector(linkPressed:)
                                          name:CTDisplayViewLinkPressedNotification
                                          object:nil];
    
}

- (void)imagePressed:(NSNotification*)notification {
    NSDictionary *userInfo = [notification userInfo];
    CoreTextImageData *imageData = userInfo[@"imageData"];
    
    ImageVC *vc = [[ImageVC alloc] init];
    vc.image = [UIImage imageNamed:imageData.name];
    [self presentViewController:vc animated:YES completion:nil];
}

- (void)linkPressed:(NSNotification*)notification {
    NSDictionary *userInfo = [notification userInfo];
    CoreTextLinkData *linkData = userInfo[@"linkData"];
    
    WebVC *vc = [[WebVC alloc] init];
    vc.url = linkData.url;
    [self presentViewController:vc animated:YES completion:nil];
}

至此,一个支持图文混排及图片、链接点击的Core Text的完整Demo总算完成了。

你可能感兴趣的:(UI,quartz,text,封装,graphics,core,core)