Skip to content

Instantly share code, notes, and snippets.

@yoshimin
Created August 10, 2014 16:09
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save yoshimin/81f7cbc22050b7e83e29 to your computer and use it in GitHub Desktop.
Save yoshimin/81f7cbc22050b7e83e29 to your computer and use it in GitHub Desktop.
CoreTextで描画した文字をリンカブルにする(タップされたら文字をハイライトさせる)
#import "YMNCoreTextView.h"
#import <CoreText/CoreText.h>
#import <QuartzCore/QuartzCore.h>
@interface YMNCoreTextView()
@property (nonatomic, strong) NSMutableAttributedString *attributedString;
@property (nonatomic, assign) CTFrameRef drawingFrame;
@property (nonatomic, assign) NSRange linkableWordRange;
@end
@implementation YMNCoreTextView
- (id)initWithText:(NSString*)text item:(NSArray*)items {
self = [super init];
if (self) {
[self setText];
}
return self;
}
- (void)setText {
NSString *text = @"Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.";
CTFontRef fontRef = CTFontCreateWithName((CFStringRef)@"HiraKakuProN-W3", 12.0f, nil);
NSDictionary *attrDictionary = @{(NSString *)kCTFontAttributeName:(__bridge id)fontRef};
CFRelease(fontRef);
self.attributedString = [[NSMutableAttributedString alloc] initWithString:text attributes:attrDictionary];
// リンカブルっぽく青文字で下線付きの属性を作る
CTFontRef linkableFontRef = CTFontCreateWithName((CFStringRef)@"HiraKakuProN-W6", 12.0f, nil);
NSDictionary *linkableAttrDictionary = @{(NSString *)kCTFontAttributeName:(__bridge id)linkableFontRef,
(NSString *)kCTForegroundColorAttributeName:(id)[UIColor blueColor].CGColor,
(NSString *)kCTUnderlineStyleAttributeName:@(kCTUnderlineStyleSingle)};
// リンカブルにしたい文字のrangeと属性を指定して attributedString に追加
self.linkableWordRange = [text rangeOfString:@"consectetur"];
[self.attributedString addAttributes:linkableAttrDictionary range:self.linkableWordRange];
CFRelease(linkableFontRef);
}
- (void)drawRect:(CGRect)rect {
[super drawRect:rect];
CGContextRef context = UIGraphicsGetCurrentContext();
// iPhone の座標系と Core Graphics の座標系は、左下が原点なためそのまま描画をすると反転してしまう
// CGContextSetTextMatrix で反転させ CGContextTranslateCTM で並行移動
CGContextSetTextMatrix(context, CGAffineTransformIdentity);
CGContextTranslateCTM(context, 0.f, rect.size.height);
CGContextScaleCTM(context, 1.f, -1.f);
// 描画範囲を設定して描画
CGMutablePathRef path = CGPathCreateMutable();
CGPathAddRect(path, NULL, CGRectMake(0.f, 0.f, rect.size.width, rect.size.height));
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)self.attributedString);
self.drawingFrame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0.f, self.attributedString.length), path, nil);
CTFrameDraw(self.drawingFrame, context);
CFRelease(framesetter);
CFRelease(path);
}
- (NSRange)rangeOfPoint:(CGPoint)point {
// CTFrameからCTLineの配列を取得
CFArrayRef lines = CTFrameGetLines(self.drawingFrame);
// CTFrameからCTLineの原点座標を取得
CGPoint *origins = malloc(sizeof(CGPoint) * CFArrayGetCount(lines));
CTFrameGetLineOrigins(self.drawingFrame, CFRangeMake(0, 0), origins);
NSRange range;
for (int i = 0; i < CFArrayGetCount(lines); i++) {
// i番目のCTLineを取得
CTLineRef line = CFArrayGetValueAtIndex(lines, i);
// i番目のCTLineの原点座標
CGPoint origin = *(origins + i);
// 行のframeを計算
CGFloat ascent = 0;
CGFloat descent = 0;
CGFloat lineWidth = CTLineGetTypographicBounds(line, &ascent, &descent, nil);
CGRect lineFrame = CGRectMake(origin.x, origin.y - descent, lineWidth, ascent + descent);
// タッチされた座標がCTLineのframe内にあるかどうか判定
if (CGRectContainsPoint(lineFrame, point)) {
// タッチされた文字列のインデックスを取得
CFIndex index = CTLineGetStringIndexForPosition(line, point);
if (index == kCFNotFound) {
continue;
}
// タッチされた文字列の属性を取得
[self.attributedString attributesAtIndex:index effectiveRange:&range];
}
}
if (origins) {
free(origins), origins = nil;
}
return range;
}
- (void)highlightLinkableWordWithRange:(NSRange)range highlight:(BOOL)highlight {
UIColor *textColor = [UIColor blueColor];
if (highlight) {
textColor = [UIColor lightGrayColor];
}
CTFontRef linkableFontRef = CTFontCreateWithName((CFStringRef)@"HiraKakuProN-W6", 12.0f, nil);
NSDictionary *linkableAttrDictionary = @{(NSString *)kCTFontAttributeName:(__bridge id)linkableFontRef,
(NSString *)kCTForegroundColorAttributeName:(id)textColor.CGColor,
(NSString *)kCTUnderlineStyleAttributeName:@(kCTUnderlineStyleSingle)};
[self.attributedString addAttributes:linkableAttrDictionary range:range];
CFRelease(linkableFontRef);
[self setNeedsDisplay];
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
CGPoint point = [(UITouch*)[touches anyObject] locationInView:self];
point.y = CGRectGetHeight(self.bounds) - point.y;
// タッチされた文字列のrangeを取得
NSRange touchedRange = [self rangeOfPoint:point];
// タッチされた文字列のrangeがリンク文字かどうか判定
if (NSIntersectionRange(touchedRange, self.linkableWordRange).length > 0) {
// タッチされた文字列をハイライトさせる
[self highlightLinkableWordWithRange:self.linkableWordRange highlight:YES];
}
[super touchesBegan:touches withEvent:event];
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
CGPoint point = [(UITouch*)[touches anyObject] locationInView:self];
point.y = CGRectGetHeight(self.bounds) - point.y;
// タッチされた文字列のrangeを取得
NSRange touchedRange = [self rangeOfPoint:point];
// タッチされた文字列のrangeがリンク文字かどうか判定
if (NSIntersectionRange(touchedRange, self.linkableWordRange).length > 0) {
// タッチされた文字列をハイライトさせる
[self highlightLinkableWordWithRange:self.linkableWordRange highlight:YES];
} else {
// タッチが文字から外れたら元の色に戻す
[self highlightLinkableWordWithRange:self.linkableWordRange highlight:NO];
}
[super touchesMoved:touches withEvent:event];
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
CGPoint point = [(UITouch*)[touches anyObject] locationInView:self];
point.y = CGRectGetHeight(self.bounds) - point.y;
// タッチされた文字列のrangeを取得
NSRange touchedRange = [self rangeOfPoint:point];
// タッチされた文字列のrangeがリンク文字のrangeに含まれているかどうか判定
if (NSIntersectionRange(touchedRange, self.linkableWordRange).length > 0) {
// 元の色に戻す
[self highlightLinkableWordWithRange:self.linkableWordRange highlight:NO];
NSLog(@"タップされたよ location:%lu length:%lu",self.linkableWordRange.location, self.linkableWordRange.length);
}
[super touchesEnded:touches withEvent:event];
}
@end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment