diff --git a/KILabel.podspec b/KILabel.podspec index 6d99b6a..2260a7d 100644 --- a/KILabel.podspec +++ b/KILabel.podspec @@ -9,7 +9,7 @@ Pod::Spec.new do |s| s.name = "KILabel" - s.version = "1.0.1" + s.version = "1.0.2" s.summary = "Replacement for UILabel for iOS 7 and 8 that provides automatic detection of links such as URLs, twitter style usernames and hashtags." s.description = <<-DESC @@ -22,7 +22,7 @@ Pod::Spec.new do |s| s.screenshots = "https://raw.githubusercontent.com/Krelborn/KILabel/master/IKLabelDemoScreenshot.png" s.license = { :type => "MIT", :file => "LICENSE" } - s.author = { "Matt Styles" => "matt@compiledcreations.com" } + s.author = { "Matt Styles" => "matt@compiledcreations.com", "Mark H. Granoff" => "mark@granoff.net" } s.social_media_url = "http://twitter.com/Krelborn" s.platform = :ios, "7.0" diff --git a/KILabel/Source/KILabel.h b/KILabel/Source/KILabel.h index 74c6cc6..ab77541 100644 --- a/KILabel/Source/KILabel.h +++ b/KILabel/Source/KILabel.h @@ -35,7 +35,7 @@ typedef NS_ENUM(NSUInteger, KILinkType) /** * Usernames starting with "@" token */ - KILinkTypeUserHandle, + KILinkTypeUserHandle = 0, /** * Hashtags starting with "#" token @@ -46,6 +46,11 @@ typedef NS_ENUM(NSUInteger, KILinkType) * URLs, http etc */ KILinkTypeURL, + + /** + * Phone numbers + */ + KILinkTypePhoneNumber }; /** @@ -73,6 +78,11 @@ typedef NS_OPTIONS(NSUInteger, KILinkTypeOption) */ KILinkTypeOptionURL = 1 << KILinkTypeURL, + /** + * Specified to include KILinkTypePhoneNumber links + */ + KILinkTypeOptionPhoneNumber = 1 << KILinkTypePhoneNumber, + /** * Convenience contstant to include all link types */ @@ -107,7 +117,7 @@ IB_DESIGNABLE ** ****************************************************************************************** **/ /** - * Enable/disable automatic detection of links, hashtags and usernames. + * Enable/disable automatic detection of links, hashtags, usernames, and phone numbers. */ @property (nonatomic, assign, getter = isAutomaticLinkDetectionEnabled) IBInspectable BOOL automaticLinkDetectionEnabled; @@ -123,6 +133,17 @@ IB_DESIGNABLE */ @property (nullable, nonatomic, strong) NSSet *ignoredKeywords; +/** + * Ignore matches that do not already contain NSLinkAttributeName attributes. + * + * @discussion Affects detection using attributed strings only. Defaults to NO. + * + * When detecting links, do not select a match for highlighting unless it already contains an NSLinkAttributeName attribute. + * This is handy if you've pre-decorated an attributed string containing phone numbers, and you do NOT want the built-in + * phone number detector to include words like "Toll Free" or "Phone Number" or numbers you won't want called (like FAX numbers). + */ +@property (nonatomic, assign) BOOL ignoreMatchesWithoutLinkAttribute; + /** ****************************************************************************************** ** * @name Format & Appearance ** ****************************************************************************************** **/ @@ -176,6 +197,11 @@ IB_DESIGNABLE */ @property (nullable, nonatomic, copy) KILinkTapHandler urlLinkTapHandler; +/** + * Callback block for KILinkTypePhoneNumber link tap. + */ +@property (nullable, nonatomic, copy) KILinkTapHandler phoneNumberLinkTapHandler; + /** ****************************************************************************************** ** * @name Geometry ** ****************************************************************************************** **/ diff --git a/KILabel/Source/KILabel.m b/KILabel/Source/KILabel.m index 37dbbff..c508c23 100644 --- a/KILabel/Source/KILabel.m +++ b/KILabel/Source/KILabel.m @@ -114,6 +114,9 @@ - (void)setupTextSystem // Link Type Attributes. Default is empty (no attributes). _linkTypeAttributes = [NSMutableDictionary dictionary]; + // Highlight all matches for enabled link types + _ignoreMatchesWithoutLinkAttribute = NO; + // Don't underline URL links by default. _systemURLStyle = NO; @@ -388,6 +391,11 @@ - (NSArray *)getRangesForLinks:(NSAttributedString *)text [rangesForLinks addObjectsFromArray:[self getRangesForURLs:self.attributedText]]; } + if (self.linkDetectionTypes & KILinkTypeOptionPhoneNumber) + { + [rangesForLinks addObjectsFromArray:[self getRangesForPhoneNumbers:self.attributedText]]; + } + return rangesForLinks; } @@ -457,14 +465,13 @@ - (NSArray *)getRangesForHashtags:(NSString *)text return rangesForHashtags; } - -- (NSArray *)getRangesForURLs:(NSAttributedString *)text +- (NSArray *)_getRangesForDetectorType:(NSTextCheckingTypes)detectorType usingLinkType:(KILinkType)linkType forText:(NSAttributedString *)text { - NSMutableArray *rangesForURLs = [[NSMutableArray alloc] init];; + NSMutableArray *ranges = [[NSMutableArray alloc] init]; - // Use a data detector to find urls in the text + // Use a data detector to mathing text NSError *error = nil; - NSDataDetector *detector = [[NSDataDetector alloc] initWithTypes:NSTextCheckingTypeLink error:&error]; + NSDataDetector *detector = [[NSDataDetector alloc] initWithTypes:detectorType error:&error]; NSString *plainText = text.string; @@ -478,23 +485,49 @@ - (NSArray *)getRangesForURLs:(NSAttributedString *)text NSRange matchRange = [match range]; // If there's a link embedded in the attributes, use that instead of the raw text - NSString *realURL = [text attribute:NSLinkAttributeName atIndex:matchRange.location effectiveRange:nil]; - if (realURL == nil) - realURL = [plainText substringWithRange:matchRange]; + __block NSString *realText = nil; + __block NSRange realRange = matchRange; + + if (_ignoreMatchesWithoutLinkAttribute) { + [text enumerateAttributesInRange:matchRange + options:0 + usingBlock:^(NSDictionary *attrs, NSRange range, BOOL *stop) { + if (attrs[NSLinkAttributeName]) { + // this is the text to highlight + realText = attrs[NSLinkAttributeName]; + realRange = range; + *stop = YES; + } + }]; + } + + if (realText == nil && !_ignoreMatchesWithoutLinkAttribute) + realText = [plainText substringWithRange:realRange]; - if (![self ignoreMatch:realURL]) + if (realText && ![self ignoreMatch:realText]) { - if ([match resultType] == NSTextCheckingTypeLink) + if ([match resultType] == detectorType) { - [rangesForURLs addObject:@{KILabelLinkTypeKey : @(KILinkTypeURL), - KILabelRangeKey : [NSValue valueWithRange:matchRange], - KILabelLinkKey : realURL, - }]; + [ranges addObject:@{KILabelLinkTypeKey : @(linkType), + KILabelRangeKey : [NSValue valueWithRange:realRange], + KILabelLinkKey : realText, + }]; } } } - return rangesForURLs; + return ranges; + +} + +- (NSArray *)getRangesForURLs:(NSAttributedString *)text +{ + return [self _getRangesForDetectorType:NSTextCheckingTypeLink usingLinkType:KILinkTypeURL forText:text]; +} + +- (NSArray *)getRangesForPhoneNumbers:(NSAttributedString *)text +{ + return [self _getRangesForDetectorType:NSTextCheckingTypePhoneNumber usingLinkType:KILinkTypePhoneNumber forText:text]; } - (BOOL)ignoreMatch:(NSString*)string @@ -512,12 +545,15 @@ - (NSAttributedString *)addLinkAttributesToAttributedString:(NSAttributedString KILinkType linkType = [dictionary[KILabelLinkTypeKey] unsignedIntegerValue]; NSDictionary *attributes = [self attributesForLinkType:linkType]; - + + // Remove any previously set NSLinkAttributeName so our attributes take hold + [attributedString removeAttribute:NSLinkAttributeName range:range]; + // Use our tint color to hilight the link [attributedString addAttributes:attributes range:range]; - // Add an URL attribute if this is a URL - if (_systemURLStyle && ((KILinkType)[dictionary[KILabelLinkTypeKey] unsignedIntegerValue] == KILinkTypeURL)) + // Add an URL attribute if this is a URL or phone number + if (_systemURLStyle && (linkType == KILinkTypeURL || linkType == KILinkTypePhoneNumber)) { // Add a link attribute using the stored link [attributedString addAttribute:NSLinkAttributeName value:dictionary[KILabelLinkKey] range:range]; @@ -721,6 +757,13 @@ - (void)receivedActionForLinkType:(KILinkType)linkType string:(NSString*)string _urlLinkTapHandler(self, string, range); } break; + + case KILinkTypePhoneNumber: + if (_phoneNumberLinkTapHandler) + { + _phoneNumberLinkTapHandler(self, string, range); + } + break; } } diff --git a/KILabelDemo/KILabelDemo.xcodeproj/project.pbxproj b/KILabelDemo/KILabelDemo.xcodeproj/project.pbxproj index adb8754..dfe3777 100644 --- a/KILabelDemo/KILabelDemo.xcodeproj/project.pbxproj +++ b/KILabelDemo/KILabelDemo.xcodeproj/project.pbxproj @@ -224,7 +224,7 @@ isa = PBXProject; attributes = { CLASSPREFIX = KI; - LastUpgradeCheck = 0510; + LastUpgradeCheck = 1010; ORGANIZATIONNAME = "Matthew Styles"; TargetAttributes = { C97DB30A183A56F00028EA5C = { @@ -340,18 +340,32 @@ CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", @@ -364,7 +378,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.3; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; }; @@ -378,25 +392,38 @@ CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = YES; ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.3; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; SDKROOT = iphoneos; VALIDATE_PRODUCT = YES; }; @@ -410,6 +437,7 @@ GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "KILabelDemo/KILabelDemo-Prefix.pch"; INFOPLIST_FILE = "KILabelDemo/KILabelDemo-Info.plist"; + PRODUCT_BUNDLE_IDENTIFIER = "com.compiledcreations.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; WRAPPER_EXTENSION = app; }; @@ -423,6 +451,7 @@ GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "KILabelDemo/KILabelDemo-Prefix.pch"; INFOPLIST_FILE = "KILabelDemo/KILabelDemo-Info.plist"; + PRODUCT_BUNDLE_IDENTIFIER = "com.compiledcreations.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; WRAPPER_EXTENSION = app; }; @@ -444,6 +473,7 @@ "$(inherited)", ); INFOPLIST_FILE = "KILabelDemoTests/KILabelDemoTests-Info.plist"; + PRODUCT_BUNDLE_IDENTIFIER = "MatthewStyles.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = "$(TARGET_NAME)"; TEST_HOST = "$(BUNDLE_LOADER)"; WRAPPER_EXTENSION = xctest; @@ -462,6 +492,7 @@ GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "KILabelDemo/KILabelDemo-Prefix.pch"; INFOPLIST_FILE = "KILabelDemoTests/KILabelDemoTests-Info.plist"; + PRODUCT_BUNDLE_IDENTIFIER = "MatthewStyles.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = "$(TARGET_NAME)"; TEST_HOST = "$(BUNDLE_LOADER)"; WRAPPER_EXTENSION = xctest; diff --git a/KILabelDemo/KILabelDemo/Base.lproj/Main.storyboard b/KILabelDemo/KILabelDemo/Base.lproj/Main.storyboard index 1279791..1e6dea9 100644 --- a/KILabelDemo/KILabelDemo/Base.lproj/Main.storyboard +++ b/KILabelDemo/KILabelDemo/Base.lproj/Main.storyboard @@ -1,7 +1,7 @@ - + - + @@ -16,8 +16,8 @@ -