Project import
diff --git a/TTTAttributedLabel.h b/TTTAttributedLabel.h
new file mode 100755
index 0000000..1b2f7e7
--- /dev/null
+++ b/TTTAttributedLabel.h
@@ -0,0 +1,283 @@
+// TTTAttributedLabel.h
+//
+// Copyright (c) 2011 Mattt Thompson (http://mattt.me)
+// 
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+// 
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+// 
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#import <UIKit/UIKit.h>
+#import <CoreText/CoreText.h>
+
+/**
+ Vertical alignment for text in a label whose bounds are larger than its text bounds
+ */
+typedef enum {
+    TTTAttributedLabelVerticalAlignmentCenter   = 0,
+    TTTAttributedLabelVerticalAlignmentTop      = 1,
+    TTTAttributedLabelVerticalAlignmentBottom   = 2,
+} TTTAttributedLabelVerticalAlignment;
+
+@protocol TTTAttributedLabelDelegate;
+
+// Override UILabel @property to accept both NSString and NSAttributedString
+@protocol TTTAttributedLabel <NSObject>
+@property (nonatomic, copy) id text;
+@end
+
+/**
+ `TTTAttributedLabel` is a drop-in replacement for `UILabel` that supports `NSAttributedString`, as well as automatically-detected and manually-added links to URLs, addresses, phone numbers, and dates.
+ 
+ # Differences Between `TTTAttributedLabel` and `UILabel`
+ 
+ For the most part, `TTTAttributedLabel` behaves just like `UILabel`. The following are notable exceptions, in which `TTTAttributedLabel` properties may act differently:
+ 
+ - `text` - This property now takes an `id` type argument, which can either be a kind of `NSString` or `NSAttributedString` (mutable or immutable in both cases)
+ - `lineBreakMode` - This property displays only the first line when the value is `UILineBreakModeHeadTruncation`, `UILineBreakModeTailTruncation`, or `UILineBreakModeMiddleTruncation`
+ - `adjustsFontsizeToFitWidth` - This property is effective for any value of `numberOfLines` greater than zero
+ */
+@interface TTTAttributedLabel : UILabel <TTTAttributedLabel> {
+@private
+    NSAttributedString *_attributedText;
+    CTFramesetterRef _framesetter;
+    BOOL _needsFramesetter;
+    
+    id _delegate;
+    UIDataDetectorTypes _dataDetectorTypes;
+    NSArray *_links;
+    NSDictionary *_linkAttributes;
+    
+    CGFloat _shadowRadius;
+    
+    CGFloat _leading;
+    CGFloat _lineHeightMultiple;
+    CGFloat _firstLineIndent;
+    UIEdgeInsets _textInsets;
+    TTTAttributedLabelVerticalAlignment _verticalAlignment;
+    
+    BOOL _userInteractionDisabled;
+}
+
+///-----------------------------
+/// @name Accessing the Delegate
+///-----------------------------
+
+/**
+ The receiver's delegate.
+ 
+ @discussion A `TTTAttributedLabel` delegate responds to messages sent by tapping on links in the label. You can use the delegate to respond to links referencing a URL, address, phone number, date, or date with a specified time zone and duration.
+ */
+@property (nonatomic, assign) id <TTTAttributedLabelDelegate> delegate;
+
+///--------------------------------------------
+/// @name Detecting, Accessing, & Styling Links
+///--------------------------------------------
+
+/**
+ A bitmask of `UIDataDetectorTypes` which are used to automatically detect links in the label text. This is `UIDataDetectorTypeNone` by default.
+ 
+ @warning You must specify `dataDetectorTypes` before setting the `text`, with either `setText:` or `setText:afterInheritingLabelAttributesAndConfiguringWithBlock:`.
+ */
+@property (nonatomic, assign) UIDataDetectorTypes dataDetectorTypes;
+
+/**
+ An array of `NSTextCheckingResult` objects for links detected or manually added to the label text.
+ */
+@property (readonly, nonatomic, retain) NSArray *links;
+
+/**
+ A dictionary containing the `NSAttributedString` attributes to be applied to links detected or manually added to the label text. The default link style is blue and underlined.
+ 
+ @warning You must specify `linkAttributes` before setting autodecting or manually-adding links for these attributes to be applied.
+ */
+@property (nonatomic, retain) NSDictionary *linkAttributes;
+
+///---------------------------------------
+/// @name Acccessing Text Style Attributes
+///---------------------------------------
+
+/**
+ The shadow blur radius for the label. A value of 0 indicates no blur, while larger values produce correspondingly larger blurring. This value must not be negative. The default value is 0. 
+ */
+@property (nonatomic, assign) CGFloat shadowRadius;
+
+///--------------------------------------------
+/// @name Acccessing Paragraph Style Attributes
+///--------------------------------------------
+
+/**
+ The distance, in points, from the leading margin of a frame to the beginning of the paragraph's first line. This value is always nonnegative, and is 0.0 by default. 
+ */
+@property (nonatomic, assign) CGFloat firstLineIndent;
+
+/**
+ The space in points added between lines within the paragraph. This value is always nonnegative and is 0.0 by default. 
+ */
+@property (nonatomic, assign) CGFloat leading;
+
+/**
+ The line height multiple. This value is 0.0 by default.
+ */
+@property (nonatomic, assign) CGFloat lineHeightMultiple;
+
+/**
+ The distance, in points, from the margin to the text container. This value is `UIEdgeInsetsZero` by default.
+ 
+ @discussion The `UIEdgeInset` members correspond to paragraph style properties rather than a particular geometry, and can change depending on the writing direction. 
+ 
+ ## `UIEdgeInset` Member Correspondence With `CTParagraphStyleSpecifier` Values:
+ 
+ - `top`: `kCTParagraphStyleSpecifierParagraphSpacingBefore`
+ - `left`: `kCTParagraphStyleSpecifierHeadIndent`
+ - `bottom`: `kCTParagraphStyleSpecifierParagraphSpacing`
+ - `right`: `kCTParagraphStyleSpecifierTailIndent`
+ 
+ */
+@property (nonatomic, assign) UIEdgeInsets textInsets;
+
+/**
+ The vertical text alignment for the label, for when the frame size is greater than the text rect size. The vertical alignment is `TTTAttributedLabelVerticalAlignmentCenter` by default.
+ */
+@property (nonatomic, assign) TTTAttributedLabelVerticalAlignment verticalAlignment;
+
+
+///----------------------------------
+/// @name Setting the Text Attributes
+///----------------------------------
+
+/**
+ Sets the text displayed by the label.
+ 
+ @param text An `NSString` or `NSAttributedString` object to be displayed by the label. If the specified text is an `NSString`, the label will display the text like a `UILabel`, inheriting the text styles of the label. If the specified text is an `NSAttributedString`, the label text styles will be overridden by the styles specified in the attributed string.
+  
+ @discussion This method overrides `UILabel -setText:` to accept both `NSString` and `NSAttributedString` objects. This string is `nil` by default.
+ */
+- (void)setText:(id)text;
+
+/**
+ Sets the text displayed by the label, after configuring an attributed string containing the text attributes inherited from the label in a block.
+ 
+ @param text An `NSString` or `NSAttributedString` object to be displayed by the label.
+ @param block A block object that returns an `NSMutableAttributedString` object and takes a single argument, which is an `NSMutableAttributedString` object with the text from the first parameter, and the text attributes inherited from the label text styles. For example, if you specified the `font` of the label to be `[UIFont boldSystemFontOfSize:14]` and `textColor` to be `[UIColor redColor]`, the `NSAttributedString` argument of the block would be contain the `NSAttributedString` attribute equivalents of those properties. In this block, you can set further attributes on particular ranges.
+ 
+ @discussion This string is `nil` by default.
+ */
+- (void)setText:(id)text afterInheritingLabelAttributesAndConfiguringWithBlock:(NSMutableAttributedString *(^)(NSMutableAttributedString *mutableAttributedString))block;
+
+///-------------------
+/// @name Adding Links
+///-------------------
+
+/**
+ Adds a link to a URL for a specified range in the label text.
+ 
+ @param url The url to be linked to
+ @param range The range in the label text of the link. The range must not exceed the bounds of the receiver.
+ */
+- (void)addLinkToURL:(NSURL *)url withRange:(NSRange)range;
+
+/**
+ Adds a link to an address for a specified range in the label text.
+ 
+ @param addressComponents A dictionary of address components for the address to be linked to
+ @param range The range in the label text of the link. The range must not exceed the bounds of the receiver.
+ 
+ @discussion The address component dictionary keys are described in `NSTextCheckingResult`'s "Keys for Address Components." 
+ */
+- (void)addLinkToAddress:(NSDictionary *)addressComponents withRange:(NSRange)range;
+
+/**
+ Adds a link to a phone number for a specified range in the label text.
+ 
+ @param phoneNumber The phone number to be linked to.
+ @param range The range in the label text of the link. The range must not exceed the bounds of the receiver.
+ */
+- (void)addLinkToPhoneNumber:(NSString *)phoneNumber withRange:(NSRange)range;
+
+/**
+ Adds a link to a date for a specified range in the label text.
+ 
+ @param date The date to be linked to.
+ @param range The range in the label text of the link. The range must not exceed the bounds of the receiver.
+ */
+- (void)addLinkToDate:(NSDate *)date withRange:(NSRange)range;
+
+/**
+ Adds a link to a date with a particular time zone and duration for a specified range in the label text.
+ 
+ @param date The date to be linked to.
+ @param timeZone The time zone of the specified date.
+ @param duration The duration, in seconds from the specified date.
+ @param range The range in the label text of the link. The range must not exceed the bounds of the receiver.
+ */
+- (void)addLinkToDate:(NSDate *)date timeZone:(NSTimeZone *)timeZone duration:(NSTimeInterval)duration withRange:(NSRange)range;
+
+@end
+
+/**
+ The `TTTAttributedLabelDelegate` protocol defines the messages sent to an attributed label delegate when links are tapped. All of the methods of this protocol are optional.
+ */
+@protocol TTTAttributedLabelDelegate <NSObject>
+
+///-----------------------------------
+/// @name Responding to Link Selection
+///-----------------------------------
+@optional
+
+/**
+ Tells the delegate that the user did select a link to a URL.
+ 
+ @param label The label whose link was selected.
+ @param url The URL for the selected link.
+ */
+- (void)attributedLabel:(TTTAttributedLabel *)label didSelectLinkWithURL:(NSURL *)url;
+
+/**
+ Tells the delegate that the user did select a link to an address.
+ 
+ @param label The label whose link was selected.
+ @param addressComponents The components of the address for the selected link.
+ */
+- (void)attributedLabel:(TTTAttributedLabel *)label didSelectLinkWithAddress:(NSDictionary *)addressComponents;
+
+/**
+ Tells the delegate that the user did select a link to a phone number.
+ 
+ @param label The label whose link was selected.
+ @param phoneNumber The phone number for the selected link.
+ */
+- (void)attributedLabel:(TTTAttributedLabel *)label didSelectLinkWithPhoneNumber:(NSString *)phoneNumber;
+
+/**
+ Tells the delegate that the user did select a link to a date.
+ 
+ @param label The label whose link was selected.
+ @param date The datefor the selected link.
+ */
+- (void)attributedLabel:(TTTAttributedLabel *)label didSelectLinkWithDate:(NSDate *)date;
+
+/**
+ Tells the delegate that the user did select a link to a date with a time zone and duration.
+ 
+ @param label The label whose link was selected.
+ @param date The date for the selected link.
+ @param timeZone The time zone of the date for the selected link.
+ @param duration The duration, in seconds from the date for the selected link.
+ */
+- (void)attributedLabel:(TTTAttributedLabel *)label didSelectLinkWithDate:(NSDate *)date timeZone:(NSTimeZone *)timeZone duration:(NSTimeInterval)duration;
+@end
+
diff --git a/TTTAttributedLabel.m b/TTTAttributedLabel.m
new file mode 100755
index 0000000..bc80d39
--- /dev/null
+++ b/TTTAttributedLabel.m
@@ -0,0 +1,535 @@
+// TTTAttributedLabel.m
+//
+// Copyright (c) 2011 Mattt Thompson (http://mattt.me)
+// 
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+// 
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+// 
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#import "TTTAttributedLabel.h"
+
+#define kTTTLineBreakWordWrapTextWidthScalingFactor (M_PI / M_E)
+
+static inline CTTextAlignment CTTextAlignmentFromUITextAlignment(UITextAlignment alignment) {
+	switch (alignment) {
+		case UITextAlignmentLeft: return kCTLeftTextAlignment;
+		case UITextAlignmentCenter: return kCTCenterTextAlignment;
+		case UITextAlignmentRight: return kCTRightTextAlignment;
+		default: return kCTNaturalTextAlignment;
+	}
+}
+
+static inline CTLineBreakMode CTLineBreakModeFromUILineBreakMode(UILineBreakMode lineBreakMode) {
+	switch (lineBreakMode) {
+		case UILineBreakModeWordWrap: return kCTLineBreakByWordWrapping;
+		case UILineBreakModeCharacterWrap: return kCTLineBreakByCharWrapping;
+		case UILineBreakModeClip: return kCTLineBreakByClipping;
+		case UILineBreakModeHeadTruncation: return kCTLineBreakByTruncatingHead;
+		case UILineBreakModeTailTruncation: return kCTLineBreakByTruncatingTail;
+		case UILineBreakModeMiddleTruncation: return kCTLineBreakByTruncatingMiddle;
+		default: return 0;
+	}
+}
+
+static inline NSTextCheckingType NSTextCheckingTypeFromUIDataDetectorType(UIDataDetectorTypes dataDetectorType) {
+    NSTextCheckingType textCheckingType = 0;
+    if (dataDetectorType & UIDataDetectorTypeAddress) {
+        textCheckingType |= NSTextCheckingTypeAddress;
+    }
+    
+    if (dataDetectorType & UIDataDetectorTypeCalendarEvent) {
+        textCheckingType |= NSTextCheckingTypeDate;
+    }
+    
+    if (dataDetectorType & UIDataDetectorTypeLink) {
+        textCheckingType |= NSTextCheckingTypeLink;
+    }
+    
+    if (dataDetectorType & UIDataDetectorTypePhoneNumber) {
+        textCheckingType |= NSTextCheckingTypePhoneNumber;
+    }
+    
+    return textCheckingType;
+}
+
+static inline NSDictionary * NSAttributedStringAttributesFromLabel(TTTAttributedLabel *label) {
+    NSMutableDictionary *mutableAttributes = [NSMutableDictionary dictionary]; 
+    
+    CTFontRef font = CTFontCreateWithName((CFStringRef)label.font.fontName, label.font.pointSize, NULL);
+    [mutableAttributes setObject:(id)font forKey:(NSString *)kCTFontAttributeName];
+    CFRelease(font);
+    
+    [mutableAttributes setObject:(id)[label.textColor CGColor] forKey:(NSString *)kCTForegroundColorAttributeName];
+    
+    CTTextAlignment alignment = CTTextAlignmentFromUITextAlignment(label.textAlignment);
+    CTLineBreakMode lineBreakMode = CTLineBreakModeFromUILineBreakMode(label.lineBreakMode);
+    CGFloat lineSpacing = label.leading;
+    CGFloat lineHeightMultiple = label.lineHeightMultiple;
+    CGFloat topMargin = label.textInsets.top;
+    CGFloat bottomMargin = label.textInsets.bottom;
+    CGFloat leftMargin = label.textInsets.left;
+    CGFloat rightMargin = label.textInsets.right;
+    CGFloat firstLineIndent = label.firstLineIndent + leftMargin;
+    CTParagraphStyleSetting paragraphStyles[9] = {
+		{.spec = kCTParagraphStyleSpecifierAlignment, .valueSize = sizeof(CTTextAlignment), .value = (const void *)&alignment},
+		{.spec = kCTParagraphStyleSpecifierLineBreakMode, .valueSize = sizeof(CTLineBreakMode), .value = (const void *)&lineBreakMode},
+        {.spec = kCTParagraphStyleSpecifierLineSpacing, .valueSize = sizeof(CGFloat), .value = (const void *)&lineSpacing},
+        {.spec = kCTParagraphStyleSpecifierLineHeightMultiple, .valueSize = sizeof(CGFloat), .value = (const void *)&lineHeightMultiple},
+        {.spec = kCTParagraphStyleSpecifierFirstLineHeadIndent, .valueSize = sizeof(CGFloat), .value = (const void *)&firstLineIndent},
+        {.spec = kCTParagraphStyleSpecifierParagraphSpacingBefore, .valueSize = sizeof(CGFloat), .value = (const void *)&topMargin},
+        {.spec = kCTParagraphStyleSpecifierParagraphSpacing, .valueSize = sizeof(CGFloat), .value = (const void *)&bottomMargin},
+        {.spec = kCTParagraphStyleSpecifierHeadIndent, .valueSize = sizeof(CGFloat), .value = (const void *)&leftMargin},
+        {.spec = kCTParagraphStyleSpecifierTailIndent, .valueSize = sizeof(CGFloat), .value = (const void *)&rightMargin},
+	};
+	CTParagraphStyleRef paragraphStyle = CTParagraphStyleCreate(paragraphStyles, 9);
+	[mutableAttributes setObject:(id)paragraphStyle forKey:(NSString *)kCTParagraphStyleAttributeName];
+	CFRelease(paragraphStyle);
+    
+    return [NSDictionary dictionaryWithDictionary:mutableAttributes];
+}
+
+static inline NSAttributedString * NSAttributedStringByScalingFontSize(NSAttributedString *attributedString, CGFloat scale, CGFloat minimumFontSize) {    
+    NSMutableAttributedString *mutableAttributedString = [[attributedString mutableCopy] autorelease];
+    [mutableAttributedString enumerateAttribute:(NSString *)kCTFontAttributeName inRange:NSMakeRange(0, [mutableAttributedString length]) options:0 usingBlock:^(id value, NSRange range, BOOL *stop) {
+        CTFontRef font = (CTFontRef)value;
+        if (font) {
+            CGFloat scaledFontSize = floorf(CTFontGetSize(font) * scale);
+            CTFontRef scaledFont = CTFontCreateCopyWithAttributes(font, fmaxf(scaledFontSize, minimumFontSize), NULL, NULL);
+            CFAttributedStringSetAttribute((CFMutableAttributedStringRef)mutableAttributedString, CFRangeMake(range.location, range.length), kCTFontAttributeName, scaledFont);
+        }
+    }];
+    
+    return mutableAttributedString;
+}
+
+@interface TTTAttributedLabel ()
+@property (readwrite, nonatomic, copy) NSAttributedString *attributedText;
+@property (readwrite, nonatomic, assign) CTFramesetterRef framesetter;
+@property (readwrite, nonatomic, assign) CTFramesetterRef highlightFramesetter;
+@property (readwrite, nonatomic, retain) NSArray *links;
+
+- (id)initCommon;
+- (void)setNeedsFramesetter;
+- (NSArray *)detectedLinksInString:(NSString *)string range:(NSRange)range error:(NSError **)error;
+- (NSTextCheckingResult *)linkAtCharacterIndex:(CFIndex)idx;
+- (NSTextCheckingResult *)linkAtPoint:(CGPoint)p;
+- (NSUInteger)characterIndexAtPoint:(CGPoint)p;
+- (void)drawFramesetter:(CTFramesetterRef)framesetter textRange:(CFRange)textRange inRect:(CGRect)rect context:(CGContextRef)c;
+@end
+
+@implementation TTTAttributedLabel
+@dynamic text;
+@synthesize attributedText = _attributedText;
+@synthesize framesetter = _framesetter;
+@synthesize highlightFramesetter = _highlightFramesetter;
+@synthesize delegate = _delegate;
+@synthesize dataDetectorTypes = _dataDetectorTypes;
+@synthesize links = _links;
+@synthesize linkAttributes = _linkAttributes;
+@synthesize shadowRadius = _shadowRadius;
+@synthesize leading = _leading;
+@synthesize lineHeightMultiple = _lineHeightMultiple;
+@synthesize firstLineIndent = _firstLineIndent;
+@synthesize textInsets = _textInsets;
+@synthesize verticalAlignment = _verticalAlignment;
+
+- (id)initWithFrame:(CGRect)frame {
+    self = [super initWithFrame:frame];
+    if (!self) {
+        return nil;
+    }
+    
+    return [self initCommon];
+}
+
+- (id)initWithCoder:(NSCoder *)coder {
+    self = [super initWithCoder:coder];
+    if (!self) {
+        return nil;
+    }
+    
+    return [self initCommon];
+}
+
+- (id)initCommon {
+    self.dataDetectorTypes = UIDataDetectorTypeNone;
+    self.links = [NSArray array];
+    
+    NSMutableDictionary *mutableLinkAttributes = [NSMutableDictionary dictionary];
+    [mutableLinkAttributes setValue:(id)[[UIColor blueColor] CGColor] forKey:(NSString*)kCTForegroundColorAttributeName];
+    [mutableLinkAttributes setValue:[NSNumber numberWithBool:YES] forKey:(NSString *)kCTUnderlineStyleAttributeName];
+    self.linkAttributes = [NSDictionary dictionaryWithDictionary:mutableLinkAttributes];
+    
+    self.textInsets = UIEdgeInsetsZero;
+    
+    return self;
+}
+
+- (void)dealloc {
+    if (_framesetter) CFRelease(_framesetter);
+    if (_highlightFramesetter) CFRelease(_highlightFramesetter);
+    
+    [_attributedText release];
+    [_links release];
+    [_linkAttributes release];
+    [super dealloc];
+}
+
+#pragma mark -
+
+- (void)setAttributedText:(NSAttributedString *)text {
+    if ([text isEqualToAttributedString:self.attributedText]) {
+        return;
+    }
+    
+    [self willChangeValueForKey:@"attributedText"];
+    [_attributedText release];
+    _attributedText = [text copy];
+    [self didChangeValueForKey:@"attributedText"];
+    
+    [self setNeedsFramesetter];
+}
+
+- (void)setNeedsFramesetter {
+    _needsFramesetter = YES;
+}
+
+- (CTFramesetterRef)framesetter {
+    if (_needsFramesetter) {
+        @synchronized(self) {
+            if (_framesetter) CFRelease(_framesetter);
+            if (_highlightFramesetter) CFRelease(_highlightFramesetter);
+            
+            self.framesetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)self.attributedText);
+            self.highlightFramesetter = nil;
+            _needsFramesetter = NO;
+        }
+    }
+    
+    return _framesetter;
+}
+
+- (BOOL)isUserInteractionEnabled {
+    return !_userInteractionDisabled && [self.links count] > 0;
+}
+
+- (void)setUserInteractionEnabled:(BOOL)userInteractionEnabled {
+    _userInteractionDisabled = !userInteractionEnabled;
+}
+
+- (BOOL)isExclusiveTouch {
+    return [self.links count] > 0;
+}
+
+#pragma mark -
+
+- (NSArray *)detectedLinksInString:(NSString *)string range:(NSRange)range error:(NSError **)error {
+    if (!string) {
+        return [NSArray array];
+    }
+    NSMutableArray *mutableLinks = [NSMutableArray array];
+    NSDataDetector *dataDetector = [NSDataDetector dataDetectorWithTypes:NSTextCheckingTypeFromUIDataDetectorType(self.dataDetectorTypes) error:error];
+    [dataDetector enumerateMatchesInString:string options:0 range:range usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) {
+        [mutableLinks addObject:result];
+    }];
+    
+    return [NSArray arrayWithArray:mutableLinks];
+}
+
+- (void)addLinkWithTextCheckingResult:(NSTextCheckingResult *)result {
+    self.links = [self.links arrayByAddingObject:result];
+    
+    if (self.linkAttributes) {
+        NSMutableAttributedString *mutableAttributedString = [[[NSMutableAttributedString alloc] initWithAttributedString:self.attributedText] autorelease];
+        [mutableAttributedString addAttributes:self.linkAttributes range:result.range];
+        self.attributedText = mutableAttributedString;        
+    }
+}
+
+- (void)addLinkToURL:(NSURL *)url withRange:(NSRange)range {
+    [self addLinkWithTextCheckingResult:[NSTextCheckingResult linkCheckingResultWithRange:range URL:url]];
+}
+
+- (void)addLinkToAddress:(NSDictionary *)addressComponents withRange:(NSRange)range {
+    [self addLinkWithTextCheckingResult:[NSTextCheckingResult addressCheckingResultWithRange:range components:addressComponents]];
+}
+
+- (void)addLinkToPhoneNumber:(NSString *)phoneNumber withRange:(NSRange)range {
+    [self addLinkWithTextCheckingResult:[NSTextCheckingResult phoneNumberCheckingResultWithRange:range phoneNumber:phoneNumber]];
+}
+
+- (void)addLinkToDate:(NSDate *)date withRange:(NSRange)range {
+    [self addLinkWithTextCheckingResult:[NSTextCheckingResult dateCheckingResultWithRange:range date:date]];
+}
+
+- (void)addLinkToDate:(NSDate *)date timeZone:(NSTimeZone *)timeZone duration:(NSTimeInterval)duration withRange:(NSRange)range {
+    [self addLinkWithTextCheckingResult:[NSTextCheckingResult dateCheckingResultWithRange:range date:date timeZone:timeZone duration:duration]];
+}
+
+#pragma mark -
+
+- (NSTextCheckingResult *)linkAtCharacterIndex:(CFIndex)idx {
+    for (NSTextCheckingResult *result in self.links) {
+        NSRange range = result.range;
+        if (range.location <= idx && idx <= range.location + range.length) {
+            return result;
+        }
+    }
+    
+    return nil;
+}
+
+- (NSTextCheckingResult *)linkAtPoint:(CGPoint)p {
+    CFIndex idx = [self characterIndexAtPoint:p];
+    return [self linkAtCharacterIndex:idx];
+}
+
+- (NSUInteger)characterIndexAtPoint:(CGPoint)p {
+    if (!CGRectContainsPoint(self.bounds, p)) {
+        return NSNotFound;
+    }
+    
+    CGRect textRect = [self textRectForBounds:self.bounds limitedToNumberOfLines:self.numberOfLines];
+    if (!CGRectContainsPoint(textRect, p)) {
+        return NSNotFound;
+    }
+    
+    // Convert tap coordinates (start at top left) to CT coordinates (start at bottom left)
+    p = CGPointMake(p.x, textRect.size.height - p.y);
+
+    CFIndex idx = NSNotFound;
+    CGMutablePathRef path = CGPathCreateMutable();
+    CGPathAddRect(path, NULL, textRect);
+    CTFrameRef frame = CTFramesetterCreateFrame(self.framesetter, CFRangeMake(0, [self.attributedText length]), path, NULL);
+    CFArrayRef lines = CTFrameGetLines(frame);
+    NSUInteger numberOfLines = CFArrayGetCount(lines);
+    CGPoint lineOrigins[numberOfLines];
+    CTFrameGetLineOrigins(frame, CFRangeMake(0, numberOfLines), lineOrigins);
+    NSUInteger lineIndex;
+
+    for (lineIndex = 0; lineIndex < (numberOfLines - 1); lineIndex++) {
+        CGPoint lineOrigin = lineOrigins[lineIndex];
+        if (lineOrigin.y < p.y) {
+            break;
+        }
+    }
+    
+    CGPoint lineOrigin = lineOrigins[lineIndex];
+    CTLineRef line = CFArrayGetValueAtIndex(lines, lineIndex);
+    // Convert CT coordinates to line-relative coordinates
+    CGPoint relativePoint = CGPointMake(p.x - lineOrigin.x, p.y - lineOrigin.y);
+    idx = CTLineGetStringIndexForPosition(line, relativePoint);
+    
+    CFRelease(frame);
+    CFRelease(path);
+        
+    return idx;
+}
+
+- (void)drawFramesetter:(CTFramesetterRef)framesetter textRange:(CFRange)textRange inRect:(CGRect)rect context:(CGContextRef)c {
+    CGMutablePathRef path = CGPathCreateMutable();
+    
+    CGPathAddRect(path, NULL, rect);
+    CTFrameRef frame = CTFramesetterCreateFrame(framesetter, textRange, path, NULL);    
+    
+    if (self.numberOfLines == 0) {
+        CTFrameDraw(frame, c);
+    } else {
+        CFArrayRef lines = CTFrameGetLines(frame);
+        NSUInteger numberOfLines = MIN(self.numberOfLines, CFArrayGetCount(lines));
+
+        CGPoint lineOrigins[numberOfLines];
+        CTFrameGetLineOrigins(frame, CFRangeMake(0, numberOfLines), lineOrigins);
+        
+        for (NSUInteger lineIndex = 0; lineIndex < numberOfLines; lineIndex++) {
+            CGPoint lineOrigin = lineOrigins[lineIndex];
+            CGContextSetTextPosition(c, lineOrigin.x, lineOrigin.y);
+            CTLineRef line = CFArrayGetValueAtIndex(lines, lineIndex);
+            CTLineDraw(line, c);
+        }
+    }
+    
+    CFRelease(frame);
+    CFRelease(path);
+}
+
+#pragma mark - TTTAttributedLabel
+
+- (void)setText:(id)text {
+    if ([text isKindOfClass:[NSString class]]) {
+        [self setText:text afterInheritingLabelAttributesAndConfiguringWithBlock:nil];
+    } else {
+        self.attributedText = text;
+    }
+
+    self.links = [NSArray array];
+    if (self.dataDetectorTypes != UIDataDetectorTypeNone) {
+        for (NSTextCheckingResult *result in [self detectedLinksInString:[self.attributedText string] range:NSMakeRange(0, [text length]) error:nil]) {
+            [self addLinkWithTextCheckingResult:result];
+        }
+    }
+        
+    [super setText:[self.attributedText string]];
+}
+
+- (void)setText:(id)text afterInheritingLabelAttributesAndConfiguringWithBlock:(NSMutableAttributedString *(^)(NSMutableAttributedString *mutableAttributedString))block {
+    if ([text isKindOfClass:[NSString class]]) {
+        self.attributedText = [[[NSAttributedString alloc] initWithString:text] autorelease];
+    }
+    
+    NSMutableAttributedString *mutableAttributedString = [[[NSMutableAttributedString alloc] initWithAttributedString:self.attributedText] autorelease];
+    [mutableAttributedString addAttributes:NSAttributedStringAttributesFromLabel(self) range:NSMakeRange(0, [mutableAttributedString length])];
+    
+    if (block) {
+        [self setText:block(mutableAttributedString)];
+    } else {
+        [self setText:mutableAttributedString];
+    }
+}
+
+#pragma mark - UILabel
+
+- (void)setHighlighted:(BOOL)highlighted {
+    [super setHighlighted:highlighted];
+    [self setNeedsDisplay];
+}
+
+- (void)drawTextInRect:(CGRect)rect {
+    if (!self.attributedText) {
+        [super drawTextInRect:rect];
+        return;
+    }
+    
+    NSAttributedString *originalAttributedText = nil;
+    // Adjust the font size to fit width, if necessarry 
+    if (self.adjustsFontSizeToFitWidth && self.numberOfLines > 0) {
+        CGFloat textWidth = [self sizeThatFits:CGSizeZero].width;
+        CGFloat availableWidth = self.frame.size.width * self.numberOfLines;
+        if (self.numberOfLines > 1 && self.lineBreakMode == UILineBreakModeWordWrap) {
+            textWidth *= kTTTLineBreakWordWrapTextWidthScalingFactor;
+        }
+        
+        if (textWidth > availableWidth && textWidth > 0.0f) {
+            originalAttributedText = [[self.attributedText copy] autorelease];
+            self.text = NSAttributedStringByScalingFontSize(self.attributedText, availableWidth / textWidth, self.minimumFontSize);
+        }
+    }
+    
+    CGContextRef c = UIGraphicsGetCurrentContext();
+    CGContextSetTextMatrix(c, CGAffineTransformIdentity);
+
+    // Inverts the CTM to match iOS coordinates (otherwise text draws upside-down; Mac OS's system is different)
+    CGContextTranslateCTM(c, 0.0f, self.bounds.size.height);
+    CGContextScaleCTM(c, 1.0f, -1.0f);
+    
+    CGRect textRect = rect;
+    CFRange textRange = CFRangeMake(0, [self.attributedText length]);
+    CFRange fitRange;
+
+    // First, adjust the text to be in the center vertically, if the text size is smaller than the drawing rect
+    CGSize textSize = CTFramesetterSuggestFrameSizeWithConstraints(self.framesetter, textRange, NULL, textRect.size, &fitRange);
+    
+    if (textSize.height < textRect.size.height) {
+        CGFloat yOffset = 0.0f;
+        switch (self.verticalAlignment) {
+            case TTTAttributedLabelVerticalAlignmentTop:
+                break;
+            case TTTAttributedLabelVerticalAlignmentCenter:
+                yOffset = floorf((textRect.size.height - textSize.height) / 2.0f);
+                break;
+            case TTTAttributedLabelVerticalAlignmentBottom:
+                yOffset = (textRect.size.height - textSize.height);
+                break;
+        }
+        
+        textRect.origin = CGPointMake(textRect.origin.x, textRect.origin.y + yOffset);
+        textRect.size = CGSizeMake(textRect.size.width, textRect.size.height - yOffset);
+    }
+
+    // Second, trace the shadow before the actual text, if we have one
+    if (self.shadowColor && !self.highlighted) {
+        CGContextSetShadowWithColor(c, self.shadowOffset, self.shadowRadius, [self.shadowColor CGColor]);
+    }
+    
+    // Finally, draw the text or highlighted text itself (on top of the shadow, if there is one)
+    if (self.highlightedTextColor && self.highlighted) {
+        if (!self.highlightFramesetter) {
+            NSMutableAttributedString *mutableAttributedString = [[self.attributedText mutableCopy] autorelease];
+            [mutableAttributedString addAttribute:(NSString *)kCTForegroundColorAttributeName value:(id)[self.highlightedTextColor CGColor] range:NSMakeRange(0, mutableAttributedString.length)];
+            self.highlightFramesetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)mutableAttributedString);
+        }
+        
+        [self drawFramesetter:self.highlightFramesetter textRange:textRange inRect:textRect context:c];
+    } else {
+        [self drawFramesetter:self.framesetter textRange:textRange inRect:textRect context:c];
+    }  
+    
+    // If we adjusted the font size, set it back to its original size
+    if (originalAttributedText) {
+        self.text = originalAttributedText;
+    }
+}
+
+#pragma mark - UIControl
+
+- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
+	UITouch *touch = [touches anyObject];	
+	NSTextCheckingResult *result = [self linkAtPoint:[touch locationInView:self]];
+    
+    if (result && self.delegate) {
+        switch (result.resultType) {
+            case NSTextCheckingTypeLink:
+                if ([self.delegate respondsToSelector:@selector(attributedLabel:didSelectLinkWithURL:)]) {
+                    [self.delegate attributedLabel:self didSelectLinkWithURL:result.URL];
+                }
+                break;
+            case NSTextCheckingTypeAddress:
+                if ([self.delegate respondsToSelector:@selector(attributedLabel:didSelectLinkWithAddress:)]) {
+                    [self.delegate attributedLabel:self didSelectLinkWithAddress:result.addressComponents];
+                }
+                break;
+            case NSTextCheckingTypePhoneNumber:
+                if ([self.delegate respondsToSelector:@selector(attributedLabel:didSelectLinkWithPhoneNumber:)]) {
+                    [self.delegate attributedLabel:self didSelectLinkWithPhoneNumber:result.phoneNumber];
+                }
+                break;
+            case NSTextCheckingTypeDate:
+                if (result.timeZone && [self.delegate respondsToSelector:@selector(attributedLabel:didSelectLinkWithDate:timeZone:duration:)]) {
+                    [self.delegate attributedLabel:self didSelectLinkWithDate:result.date timeZone:result.timeZone duration:result.duration];
+                } else if ([self.delegate respondsToSelector:@selector(attributedLabel:didSelectLinkWithDate:)]) {
+                    [self.delegate attributedLabel:self didSelectLinkWithDate:result.date];
+                }
+                break;
+        }
+    } else {
+        [self.nextResponder touchesBegan:touches withEvent:event];
+    }
+}
+
+#pragma mark - UIView
+
+- (CGSize)sizeThatFits:(CGSize)size {
+    if (!self.attributedText) {
+        return [super sizeThatFits:size];
+    }
+    
+    CGSize suggestedSize = CTFramesetterSuggestFrameSizeWithConstraints(self.framesetter, CFRangeMake(0, [self.attributedText length]), NULL, CGSizeMake(size.width, CGFLOAT_MAX), NULL);
+        
+    return CGSizeMake(ceilf(suggestedSize.width), ceilf(suggestedSize.height));
+}
+
+@end