Last active
December 19, 2015 03:29
-
-
Save d-ronnqvist/5890791 to your computer and use it in GitHub Desktop.
The complete implementation of my answer to a StackOverflow question about how to hit test the actual text of a UILabel.
http://stackoverflow.com/a/17379627/608157
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// TextHitTestingLabel.m | |
// rotate | |
// | |
// Created by David Rönnqvist on 2013-06-29. | |
// | |
#import "TextHitTestingLabel.h" | |
#import <CoreText/CoreText.h> | |
@interface TextHitTestingLabel (/*Private stuff*/) | |
@property (assign) CGPathRef textPath; | |
@end | |
@implementation TextHitTestingLabel | |
// Override -pointInside:withEvent to determine that ourselves. | |
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event { | |
// Check if the points is inside the text path. | |
return CGPathContainsPoint(self.textPath, NULL, point, NO); | |
} | |
- (void)setText:(NSString *)text { | |
[super setText:text]; | |
[self textChanged]; | |
} | |
- (void)setAttributedText:(NSAttributedString *)attributedText { | |
[super setAttributedText:attributedText]; | |
[self textChanged]; | |
} | |
- (void)awakeFromNib { | |
[self textChanged]; | |
} | |
- (void)textChanged | |
{ | |
// Get the text | |
NSAttributedString *attributedString = nil; | |
if ([self respondsToSelector:@selector(attributedText)]) { // Available in iOS 6 | |
attributedString = self.attributedText; | |
} | |
if (!attributedString) { // Either earlier than iOS6 or the `text` property was set instead of `attributedText` | |
attributedString = [[NSAttributedString alloc] initWithString:self.text | |
attributes:@{NSFontAttributeName: self.font}]; | |
} | |
// Create a mutable path for the paths of all the letters. | |
CGMutablePathRef letters = CGPathCreateMutable(); | |
// Some CoreText "magic" | |
// | |
// Core Text works with lines of text. Glyphs are representing letters on screen. | |
// Consecutive letters with the same font and style are grouped in a glyph run. | |
// (see moe comments below) | |
// Create a line from the attributed string and get glyph runs from that line | |
CTLineRef line = CTLineCreateWithAttributedString((CFAttributedStringRef)attributedString); | |
CFArrayRef runArray = CTLineGetGlyphRuns(line); | |
// A line with more then one font, style, size etc will have multiple fonts. | |
// "Hello" formatted as " *Hel* lo " (spaces added for clarity) is two glyph | |
// runs: one italics and one regular. The first run contains 3 glyphs and the | |
// second run contains 2 glyphs. | |
// Note that " He *ll* o " is 3 runs even though "He" and "o" have the same font. | |
for (CFIndex runIndex = 0; runIndex < CFArrayGetCount(runArray); runIndex++) | |
{ | |
// Get the font for this glyph run. | |
CTRunRef run = (CTRunRef)CFArrayGetValueAtIndex(runArray, runIndex); | |
CTFontRef runFont = CFDictionaryGetValue(CTRunGetAttributes(run), kCTFontAttributeName); | |
// This glyph run contains one or more glyphs (letters etc.) | |
for (CFIndex runGlyphIndex = 0; runGlyphIndex < CTRunGetGlyphCount(run); runGlyphIndex++) | |
{ | |
// Read the glyph itself and it position from the glyph run. | |
CFRange glyphRange = CFRangeMake(runGlyphIndex, 1); | |
CGGlyph glyph; | |
CGPoint position; | |
CTRunGetGlyphs(run, glyphRange, &glyph); | |
CTRunGetPositions(run, glyphRange, &position); | |
// Create a CGPath for the outline of the glyph | |
CGPathRef letter = CTFontCreatePathForGlyph(runFont, glyph, NULL); | |
// Translate it to its position. | |
CGAffineTransform t = CGAffineTransformMakeTranslation(position.x, position.y); | |
// Add the glyph to the | |
CGPathAddPath(letters, &t, letter); | |
CGPathRelease(letter); | |
} | |
} | |
CFRelease(line); | |
// Transform the path to not be upside down | |
CGAffineTransform t = CGAffineTransformMakeScale(1, -1); // flip 1 | |
CGSize pathSize = CGPathGetBoundingBox(letters).size; | |
t = CGAffineTransformTranslate(t, 0, -pathSize.height); // move down | |
// Create the final path by applying the transform | |
// CGPathRef finalPath = CGPathCreateMutableCopyByTransformingPath(letters, &t); | |
CGPathRef endPath = CGPathCreateMutableCopyByTransformingPath(letters, &t); | |
CGMutablePathRef finalPath = CGPathCreateMutableCopy(endPath); | |
CGPathRef strokedPath = CGPathCreateCopyByStrokingPath(endPath, NULL, 7, kCGLineCapRound, kCGLineJoinRound, 0); | |
CGPathAddPath(finalPath, NULL, strokedPath); | |
// Clean up all the unused paths | |
CGPathRelease(strokedPath); | |
CGPathRelease(letters); | |
CGPathRelease(endPath); | |
self.textPath = finalPath; | |
} | |
@end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment