diff --git a/AttributedLabel Example/Classes/BasicDemo/BasicDemoViewController.xib b/AttributedLabel Example/Classes/BasicDemo/BasicDemoViewController.xib index 8d99974..ae4bdad 100644 --- a/AttributedLabel Example/Classes/BasicDemo/BasicDemoViewController.xib +++ b/AttributedLabel Example/Classes/BasicDemo/BasicDemoViewController.xib @@ -2,13 +2,13 @@ 1536 - 12C60 - 2843 + 12C3006 + 2844 1187.34 625.00 com.apple.InterfaceBuilder.IBCocoaTouchPlugin - 1929 + 1930 IBProxyObject @@ -261,7 +261,7 @@ cm5pYSAoKzEgNDA4LTk5Ni0xMDEwKSBuZXh0IHN1bmRheS4 7 NO IBCocoaTouchFramework - Some <b>basic</b> <font name="Courier" size="14">HTML</font> support is provided for <u>convenience</u> too. <font color="red">You can add your own parsers easily</font> by subclassing <font name="Courier" size="14">OHASMarkupParserBase</font> if needed. + Some <b>basic</b> <font name="Courier" size="14">HTML</font> support is provided for <u>convenience</u> too. <font color="red">You can add your own <a href="http://en.wikipedia.org/wiki/Parser">parsers</a> easily</font> by subclassing <font name="Courier" size="14">OHASMarkupParserBase</font> if needed. 1 MCAwIDEAA @@ -291,7 +291,7 @@ cm5pYSAoKzEgNDA4LTk5Ni0xMDEwKSBuZXh0IHN1bmRheS4 7 NO IBCocoaTouchFramework - Some *basic* `Markup` is _also_ provided, so that's even _*easier*_ to build `NSAttributedStrings`. {red|*cool*, right?} I hope you {#80c|enjoy} it! + Some *basic* `Markup` is _also_ provided, so that's even _*easier*_ to build `NSAttributedStrings` with [custom links](http://www.foodreporter.net) and all that. {red|*cool*, right?} I hope you {#80c|enjoy} it! 0 @@ -602,6 +602,6 @@ cm5pYSAoKzEgNDA4LTk5Ni0xMDEwKSBuZXh0IHN1bmRheS4 YES 3 - 1929 + 1930 diff --git a/AttributedLabel Example/Classes/CustomLinksDemo/CustomLinksViewController.m b/AttributedLabel Example/Classes/CustomLinksDemo/CustomLinksViewController.m index 9ee0fb8..cb1f01f 100644 --- a/AttributedLabel Example/Classes/CustomLinksDemo/CustomLinksViewController.m +++ b/AttributedLabel Example/Classes/CustomLinksDemo/CustomLinksViewController.m @@ -68,13 +68,12 @@ -(void)fillDemoLabel // now we only change the color of "FoodReporter" [attrStr setTextColor:[UIColor colorWithRed:0.f green:0.f blue:0.5 alpha:1.f] range:[txt rangeOfString:@TXT_BOLD]]; [attrStr setTextBold:YES range:[txt rangeOfString:@TXT_BOLD]]; - - /**(2)** Affect the NSAttributedString to the OHAttributedLabel *******/ - self.customLinkDemoLabel.attributedText = attrStr; + // and add a link to the "share your food!" text - [self.customLinkDemoLabel addCustomLink:[NSURL URLWithString:@"http://www.foodreporter.net"] inRange:[txt rangeOfString:@TXT_LINK]]; + [attrStr setLink:[NSURL URLWithString:@"http://www.foodreporter.net"] range:[txt rangeOfString:@TXT_LINK]]; - // "Hello World!" will be displayed in the label, justified, "Hello" in red and " World!" in gray. + /**(2)** Affect the NSAttributedString to the OHAttributedLabel *******/ + self.customLinkDemoLabel.attributedText = attrStr; } -(IBAction)toggleBold:(UISwitch*)boldSwitch @@ -88,9 +87,6 @@ -(IBAction)toggleBold:(UISwitch*)boldSwitch [mas setTextBold:boldSwitch.on range:[plainText rangeOfString:@TXT_BOLD]]; // Affect back the attributed string to the label self.customLinkDemoLabel.attributedText = mas; - - // Restore the link (as each time we change the attributedText we remove custom links to avoid inconsistencies - [self.customLinkDemoLabel addCustomLink:[NSURL URLWithString:@"http://www.foodreporter.net"] inRange:[plainText rangeOfString:@TXT_LINK]]; #if ! __has_feature(objc_arc) // Cleaning: balance the "mutableCopy" call with a "release" @@ -111,16 +107,17 @@ -(void)configureMentionLabel { // Detect all "@xxx" mention-like strings using the "@\w+" regular expression NSRegularExpression* userRegex = [NSRegularExpression regularExpressionWithPattern:@"@\\w+" options:0 error:nil]; + NSMutableAttributedString* mas = [self.mentionDemoLabel.attributedText mutableCopy]; [userRegex enumerateMatchesInString:self.mentionDemoLabel.text options:0 range:NSMakeRange(0,self.mentionDemoLabel.text.length) usingBlock:^(NSTextCheckingResult *match, NSMatchingFlags flags, BOOL *stop) { // For each "@xxx" user mention found, add a custom link: NSString* user = [[self.mentionDemoLabel.text substringWithRange:match.range] substringFromIndex:1]; // get the matched user name, removing the "@" NSString* linkURLString = [NSString stringWithFormat:@"user:%@", user]; // build the "user:" link - [self.mentionDemoLabel addCustomLink:[NSURL URLWithString:linkURLString] inRange:match.range]; // add it + [mas setLink:[NSURL URLWithString:linkURLString] range:match.range]; // add it }]; - - self.mentionDemoLabel.centerVertically = YES; + self.mentionDemoLabel.attributedText = mas; + self.mentionDemoLabel.centerVertically = YES; } diff --git a/AttributedLabel Example/Classes/UIAppearanceDemo/UIAppearanceDemoViewController.m b/AttributedLabel Example/Classes/UIAppearanceDemo/UIAppearanceDemoViewController.m index c5f9f82..e4bd55d 100644 --- a/AttributedLabel Example/Classes/UIAppearanceDemo/UIAppearanceDemoViewController.m +++ b/AttributedLabel Example/Classes/UIAppearanceDemo/UIAppearanceDemoViewController.m @@ -21,7 +21,7 @@ - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view from its nib. - [self.sampleLabel addCustomLink:nil inRange:NSMakeRange(8,11)]; + [self.sampleLabel addCustomLink:[NSURL URLWithString:@"#"] inRange:NSMakeRange(8,11)]; // Add fake link so we can see the UIAppearance effect self.sampleLabel.centerVertically = YES; } diff --git a/OHAttributedLabel/Source/NSAttributedString+Attributes.h b/OHAttributedLabel/Source/NSAttributedString+Attributes.h index 91c308c..a5e4425 100644 --- a/OHAttributedLabel/Source/NSAttributedString+Attributes.h +++ b/OHAttributedLabel/Source/NSAttributedString+Attributes.h @@ -29,6 +29,7 @@ #import #import +extern NSString* kOHLinkAttributeName; ///////////////////////////////////////////////////////////////////////////////////// #pragma mark - NSAttributedString Additions @@ -51,6 +52,8 @@ -(BOOL)textIsBoldAtIndex:(NSUInteger)index effectiveRange:(NSRangePointer)aRange; -(CTTextAlignment)textAlignmentAtIndex:(NSUInteger)index effectiveRange:(NSRangePointer)aRange; -(CTLineBreakMode)lineBreakModeAtIndex:(NSUInteger)index effectiveRange:(NSRangePointer)aRange; + +-(NSURL*)linkAtIndex:(NSUInteger)index effectiveRange:(NSRangePointer)aRange; @end @@ -74,6 +77,8 @@ -(void)setTextAlignment:(CTTextAlignment)alignment lineBreakMode:(CTLineBreakMode)lineBreakMode; -(void)setTextAlignment:(CTTextAlignment)alignment lineBreakMode:(CTLineBreakMode)lineBreakMode range:(NSRange)range; + +-(void)setLink:(NSURL*)link range:(NSRange)range; @end diff --git a/OHAttributedLabel/Source/NSAttributedString+Attributes.m b/OHAttributedLabel/Source/NSAttributedString+Attributes.m index af3bb86..7643030 100644 --- a/OHAttributedLabel/Source/NSAttributedString+Attributes.m +++ b/OHAttributedLabel/Source/NSAttributedString+Attributes.m @@ -35,6 +35,8 @@ #define MRC_AUTORELEASE(x) [(x) autorelease] #endif +NSString* kOHLinkAttributeName = @"NSLinkAttributeName"; // Use the same value as OSX, to be compatible in case Apple port this to iOS one day too + ///////////////////////////////////////////////////////////////////////////////////// #pragma mark - NSAttributedString Additions @@ -130,6 +132,12 @@ -(CTLineBreakMode)lineBreakModeAtIndex:(NSUInteger)index effectiveRange:(NSRange CTParagraphStyleGetValueForSpecifier(style, kCTParagraphStyleSpecifierLineBreakMode, sizeof(CTLineBreakMode), &lineBreakMode); return lineBreakMode; } + +-(NSURL*)linkAtIndex:(NSUInteger)index effectiveRange:(NSRangePointer)aRange +{ + return [self attribute:kOHLinkAttributeName atIndex:index effectiveRange:aRange]; +} + @end @@ -265,6 +273,15 @@ -(void)setTextAlignment:(CTTextAlignment)alignment lineBreakMode:(CTLineBreakMod CFRelease(aStyle); } +-(void)setLink:(NSURL*)link range:(NSRange)range +{ + [self removeAttribute:kOHLinkAttributeName range:range]; // Work around for Apple leak + if (link) + { + [self addAttribute:kOHLinkAttributeName value:(BRIDGE_CAST id)link range:range]; + } +} + @end diff --git a/OHAttributedLabel/Source/OHAttributedLabel.m b/OHAttributedLabel/Source/OHAttributedLabel.m index 9bd89db..92c158c 100755 --- a/OHAttributedLabel/Source/OHAttributedLabel.m +++ b/OHAttributedLabel/Source/OHAttributedLabel.m @@ -269,6 +269,18 @@ -(void)recomputeLinksInTextIfNeeded } }; + // Links set by text attribute + [_attributedText enumerateAttribute:kOHLinkAttributeName inRange:NSMakeRange(0, [_attributedText length]) + options:0 usingBlock:^(id value, NSRange range, BOOL *stop) + { + if (value) + { + NSTextCheckingResult* result = [NSTextCheckingResult linkCheckingResultWithRange:range URL:(BRIDGE_CAST NSURL*)value]; + applyLinkStyle(result); + } + }]; + + // Automatically Detected Links if (plainText && (self.automaticallyAddLinksForType > 0)) { [_linksDetector enumerateMatchesInString:plainText options:0 range:NSMakeRange(0,[plainText length]) @@ -277,6 +289,8 @@ -(void)recomputeLinksInTextIfNeeded applyLinkStyle(result); }]; } + + // Custom Links [_customLinks enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { applyLinkStyle((NSTextCheckingResult*)obj); @@ -298,8 +312,25 @@ -(NSTextCheckingResult*)linkAtCharacterIndex:(CFIndex)idx @autoreleasepool { NSString* plainText = [_attributedText string]; - if (plainText && (self.automaticallyAddLinksForType > 0)) + + // Links set by text attribute + if (_attributedText) + { + [_attributedText enumerateAttribute:kOHLinkAttributeName inRange:NSMakeRange(0, [_attributedText length]) + options:0 usingBlock:^(id value, NSRange range, BOOL *stop) + { + if (value && NSLocationInRange(idx, range)) + { + NSTextCheckingResult* result = [NSTextCheckingResult linkCheckingResultWithRange:range URL:(BRIDGE_CAST NSURL*)value]; + foundResult = MRC_RETAIN(result); + *stop = YES; + } + }]; + } + + if (!foundResult && plainText && (self.automaticallyAddLinksForType > 0)) { + // Automatically Detected Links [_linksDetector enumerateMatchesInString:plainText options:0 range:NSMakeRange(0,[plainText length]) usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) { @@ -314,6 +345,7 @@ -(NSTextCheckingResult*)linkAtCharacterIndex:(CFIndex)idx if (!foundResult) { + // Custom Links [_customLinks enumerateObjectsUsingBlock:^(id obj, NSUInteger aidx, BOOL *stop) { NSRange r = [(NSTextCheckingResult*)obj range]; diff --git a/OHAttributedLabel/TagParsers/OHASBasicHTMLParser.m b/OHAttributedLabel/TagParsers/OHASBasicHTMLParser.m index c058ae3..11cc936 100644 --- a/OHAttributedLabel/TagParsers/OHASBasicHTMLParser.m +++ b/OHAttributedLabel/TagParsers/OHASBasicHTMLParser.m @@ -21,49 +21,79 @@ +(NSDictionary*)tagMappings { return [NSDictionary dictionaryWithObjectsAndKeys: - ^NSAttributedString*(NSAttributedString* str, NSTextCheckingResult* match) { + ^NSAttributedString*(NSAttributedString* str, NSTextCheckingResult* match) + { NSRange textRange = [match rangeAtIndex:1]; - NSMutableAttributedString* foundString = [[str attributedSubstringFromRange:textRange] mutableCopy]; - if (textRange.length>0) [foundString setTextBold:YES range:NSMakeRange(0,textRange.length)]; - return MRC_AUTORELEASE(foundString); - }, @"(.*?)", + if (textRange.length>0) + { + NSMutableAttributedString* foundString = [[str attributedSubstringFromRange:textRange] mutableCopy]; + [foundString setTextBold:YES range:NSMakeRange(0,textRange.length)]; + return MRC_AUTORELEASE(foundString); + } else { + return nil; + } + }, @"(.+?)", - ^NSAttributedString*(NSAttributedString* str, NSTextCheckingResult* match) { + ^NSAttributedString*(NSAttributedString* str, NSTextCheckingResult* match) + { NSRange textRange = [match rangeAtIndex:1]; - NSMutableAttributedString* foundString = [[str attributedSubstringFromRange:textRange] mutableCopy]; - if (textRange.length>0) [foundString setTextIsUnderlined:YES]; - return MRC_AUTORELEASE(foundString); - }, @"(.*?)", + if (textRange.length>0) + { + NSMutableAttributedString* foundString = [[str attributedSubstringFromRange:textRange] mutableCopy]; + [foundString setTextIsUnderlined:YES]; + return MRC_AUTORELEASE(foundString); + } else { + return nil; + } + }, @"(.+?)", - ^NSAttributedString*(NSAttributedString* str, NSTextCheckingResult* match) { - NSString* fontName = [str attributedSubstringFromRange:[match rangeAtIndex:2]].string; - CGFloat fontSize = [str attributedSubstringFromRange:[match rangeAtIndex:4]].string.floatValue; + ^NSAttributedString*(NSAttributedString* str, NSTextCheckingResult* match) + { + NSRange fontNameRange = [match rangeAtIndex:2]; + NSRange fontSizeRange = [match rangeAtIndex:4]; NSRange textRange = [match rangeAtIndex:5]; - NSMutableAttributedString* foundString = [[str attributedSubstringFromRange:textRange] mutableCopy]; - if (textRange.length>0) [foundString setFontName:fontName size:fontSize]; - return MRC_AUTORELEASE(foundString); - }, @"(.*?)", + if ((fontNameRange.length>0) && (fontSizeRange.length>0) && (textRange.length>0)) + { + NSString* fontName = [str attributedSubstringFromRange:fontNameRange].string; + CGFloat fontSize = [str attributedSubstringFromRange:fontSizeRange].string.floatValue; + NSMutableAttributedString* foundString = [[str attributedSubstringFromRange:textRange] mutableCopy]; + [foundString setFontName:fontName size:fontSize]; + return MRC_AUTORELEASE(foundString); + } else { + return nil; + } + }, @"(.+?)", - ^NSAttributedString*(NSAttributedString* str, NSTextCheckingResult* match) { - NSString* colorName = [str attributedSubstringFromRange:[match rangeAtIndex:2]].string; - UIColor* color = UIColorFromString(colorName); + ^NSAttributedString*(NSAttributedString* str, NSTextCheckingResult* match) + { + NSRange colorRange = [match rangeAtIndex:2]; NSRange textRange = [match rangeAtIndex:3]; - NSMutableAttributedString* foundString = [[str attributedSubstringFromRange:textRange] mutableCopy]; - if (textRange.length>0) [foundString setTextColor:color]; - return MRC_AUTORELEASE(foundString); - }, @"(.*?)", + if ((colorRange.length>0) && (textRange.length>0)) + { + NSString* colorName = [str attributedSubstringFromRange:colorRange].string; + UIColor* color = UIColorFromString(colorName); + NSMutableAttributedString* foundString = [[str attributedSubstringFromRange:textRange] mutableCopy]; + [foundString setTextColor:color]; + return MRC_AUTORELEASE(foundString); + } else { + return nil; + } + }, @"(.+?)", - /* - // Disabled for now as there is no official CoreText attribute name to define links. - // To be able to do this, we have implement a custom attribute ourselves and add support for it in OHAttributedLabel - ^NSAttributedString*(NSAttributedString* str, NSTextCheckingResult* match) { - NSString* link = [str attributedSubstringFromRange:[match rangeAtIndex:1]].string; - NSRange textRange = [match rangeAtIndex:2]; - NSMutableAttributedString* foundString = [[str attributedSubstringFromRange:textRange] mutableCopy]; - [foundString addAttribute:@"NSLinkAttributeName" value:link range:NSMakeRange(0,textRange.length)]; - return [foundString autorelease]; - }, @"(.*?)", - */ + ^NSAttributedString*(NSAttributedString* str, NSTextCheckingResult* match) + { + NSRange linkRange = [match rangeAtIndex:2]; + NSRange textRange = [match rangeAtIndex:3]; + if ((linkRange.length>0) && (textRange.length>0)) + { + NSString* link = [str attributedSubstringFromRange:linkRange].string; + NSMutableAttributedString* foundString = [[str attributedSubstringFromRange:textRange] mutableCopy]; + [foundString setLink:[NSURL URLWithString:link] range:NSMakeRange(0,textRange.length)]; + return [foundString autorelease]; + } else { + return nil; + } + }, @"(.+?)", nil]; } diff --git a/OHAttributedLabel/TagParsers/OHASBasicMarkupParser.m b/OHAttributedLabel/TagParsers/OHASBasicMarkupParser.m index ab90bb0..d53cff6 100644 --- a/OHAttributedLabel/TagParsers/OHASBasicMarkupParser.m +++ b/OHAttributedLabel/TagParsers/OHASBasicMarkupParser.m @@ -21,36 +21,76 @@ +(NSDictionary*)tagMappings { return [NSDictionary dictionaryWithObjectsAndKeys: - ^NSAttributedString*(NSAttributedString* str, NSTextCheckingResult* match) { + ^NSAttributedString*(NSAttributedString* str, NSTextCheckingResult* match) + { NSRange textRange = [match rangeAtIndex:1]; - NSMutableAttributedString* foundString = [[str attributedSubstringFromRange:textRange] mutableCopy]; - if (textRange.length>0) [foundString setTextBold:YES range:NSMakeRange(0,textRange.length)]; - return MRC_AUTORELEASE(foundString); - }, @"\\*(.*?)\\*", + if (textRange.length>0) + { + NSMutableAttributedString* foundString = [[str attributedSubstringFromRange:textRange] mutableCopy]; + [foundString setTextBold:YES range:NSMakeRange(0,textRange.length)]; + return MRC_AUTORELEASE(foundString); + } else { + return nil; + } + }, @"\\*(.+?)\\*", /* "*xxx*" = xxx in bold */ - ^NSAttributedString*(NSAttributedString* str, NSTextCheckingResult* match) { + ^NSAttributedString*(NSAttributedString* str, NSTextCheckingResult* match) + { NSRange textRange = [match rangeAtIndex:1]; - NSMutableAttributedString* foundString = [[str attributedSubstringFromRange:textRange] mutableCopy]; - if (textRange.length>0) [foundString setTextIsUnderlined:YES]; - return MRC_AUTORELEASE(foundString); - }, @"_(.*?)_", + if (textRange.length>0) + { + NSMutableAttributedString* foundString = [[str attributedSubstringFromRange:textRange] mutableCopy]; + [foundString setTextIsUnderlined:YES]; + return MRC_AUTORELEASE(foundString); + } else { + return nil; + } + }, @"_(.+?)_", /* "_xxx_" = xxx in italics */ - ^NSAttributedString*(NSAttributedString* str, NSTextCheckingResult* match) { + ^NSAttributedString*(NSAttributedString* str, NSTextCheckingResult* match) + { NSRange textRange = [match rangeAtIndex:1]; - NSMutableAttributedString* foundString = [[str attributedSubstringFromRange:textRange] mutableCopy]; - CTFontRef font = [str fontAtIndex:textRange.location effectiveRange:NULL]; - if (textRange.length>0) [foundString setFontName:@"Courier" size:CTFontGetSize(font)]; - return MRC_AUTORELEASE(foundString); - }, @"`(.*?)`", + if (textRange.length>0) + { + NSMutableAttributedString* foundString = [[str attributedSubstringFromRange:textRange] mutableCopy]; + CTFontRef font = [str fontAtIndex:textRange.location effectiveRange:NULL]; + [foundString setFontName:@"Courier" size:CTFontGetSize(font)]; + return MRC_AUTORELEASE(foundString); + } else { + return nil; + } + }, @"`(.+?)`", /* "`xxx`" = xxx in Courier font */ - ^NSAttributedString*(NSAttributedString* str, NSTextCheckingResult* match) { - NSString* colorName = [str attributedSubstringFromRange:[match rangeAtIndex:1]].string; - UIColor* color = UIColorFromString(colorName); + ^NSAttributedString*(NSAttributedString* str, NSTextCheckingResult* match) + { + NSRange colorRange = [match rangeAtIndex:1]; NSRange textRange = [match rangeAtIndex:2]; - NSMutableAttributedString* foundString = [[str attributedSubstringFromRange:textRange] mutableCopy]; - if (textRange.length>0) [foundString setTextColor:color]; - return MRC_AUTORELEASE(foundString); - }, @"\\{(.*?)\\|(.*?)\\}", + if ((colorRange.length>0) && (textRange.length>0)) + { + NSString* colorName = [str attributedSubstringFromRange:colorRange].string; + UIColor* color = UIColorFromString(colorName); + NSMutableAttributedString* foundString = [[str attributedSubstringFromRange:textRange] mutableCopy]; + [foundString setTextColor:color]; + return MRC_AUTORELEASE(foundString); + } else { + return nil; + } + }, @"\\{(.+?)\\|(.+?)\\}", /* "{color|text}" = text in specified color */ + + ^NSAttributedString*(NSAttributedString* str, NSTextCheckingResult* match) + { + NSRange textRange = [match rangeAtIndex:1]; + NSRange linkRange = [match rangeAtIndex:2]; + if ((linkRange.length>0) && (textRange.length>0)) + { + NSString* linkString = [str attributedSubstringFromRange:linkRange].string; + NSMutableAttributedString* foundString = [[str attributedSubstringFromRange:textRange] mutableCopy]; + [foundString setLink:[NSURL URLWithString:linkString] range:NSMakeRange(0, foundString.length)]; + return MRC_AUTORELEASE(foundString); + } else { + return nil; + } + }, @"\\[(.+?)\\]\\((.+?)\\)", /* "[text](link)" = add link to text */ nil]; } diff --git a/OHAttributedLabel/TagParsers/OHASMarkupParserBase.m b/OHAttributedLabel/TagParsers/OHASMarkupParserBase.m index 8613453..9a2e270 100644 --- a/OHAttributedLabel/TagParsers/OHASMarkupParserBase.m +++ b/OHAttributedLabel/TagParsers/OHASMarkupParserBase.m @@ -38,9 +38,12 @@ +(void)processMarkupInAttributedString:(NSMutableAttributedString*)mutAttrString usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop2) { NSAttributedString* repl = block(processedString, result); - NSRange offsetRange = NSMakeRange(result.range.location - offset, result.range.length); - [mutAttrString replaceCharactersInRange:offsetRange withAttributedString:repl]; - offset += result.range.length - repl.length; + if (repl) + { + NSRange offsetRange = NSMakeRange(result.range.location - offset, result.range.length); + [mutAttrString replaceCharactersInRange:offsetRange withAttributedString:repl]; + offset += result.range.length - repl.length; + } }]; #if ! __has_feature(objc_arc) [processedString release];