| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773 |
- /***********************************************************************************
- *
- * The MIT License (MIT)
- *
- * Copyright (c) 2013 Matthew Styles
- *
- * 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 "KILabel.h"
- NSString * const KILabelLinkTypeKey = @"linkType";
- NSString * const KILabelRangeKey = @"range";
- NSString * const KILabelLinkKey = @"link";
- #pragma mark - Private Interface
- @interface KILabel()
- // Used to control layout of glyphs and rendering
- @property (nonatomic, retain) NSLayoutManager *layoutManager;
- // Specifies the space in which to render text
- @property (nonatomic, retain) NSTextContainer *textContainer;
- // Backing storage for text that is rendered by the layout manager
- @property (nonatomic, retain) NSTextStorage *textStorage;
- // Dictionary of detected links and their ranges in the text
- @property (nonatomic, copy) NSArray *linkRanges;
- // State used to trag if the user has dragged during a touch
- @property (nonatomic, assign) BOOL isTouchMoved;
- // During a touch, range of text that is displayed as selected
- @property (nonatomic, assign) NSRange selectedRange;
- @property (nonatomic, strong) NSRegularExpression *urlRegex;
- @end
- #pragma mark - Implementation
- @implementation KILabel
- {
- NSMutableDictionary *_linkTypeAttributes;
- }
- #pragma mark - Construction
- - (id)initWithFrame:(CGRect)frame
- {
- self = [super initWithFrame:frame];
- if (self)
- {
- [self setupTextSystem];
- }
-
- return self;
- }
- - (id)initWithCoder:(NSCoder *)aDecoder
- {
- self = [super initWithCoder:aDecoder];
- if (self)
- {
- [self setupTextSystem];
- }
-
- return self;
- }
- // Common initialisation. Must be done once during construction.
- - (void)setupTextSystem
- {
- // Create a text container and set it up to match our label properties
- _textContainer = [[NSTextContainer alloc] init];
- _textContainer.lineFragmentPadding = 0;
- _textContainer.maximumNumberOfLines = self.numberOfLines;
- _textContainer.lineBreakMode = self.lineBreakMode;
- _textContainer.size = self.frame.size;
-
- // Create a layout manager for rendering
- _layoutManager = [[NSLayoutManager alloc] init];
- _layoutManager.delegate = self;
- [_layoutManager addTextContainer:_textContainer];
-
- // Attach the layou manager to the container and storage
- [_textContainer setLayoutManager:_layoutManager];
-
- // Make sure user interaction is enabled so we can accept touches
- self.userInteractionEnabled = YES;
-
- // Don't go via public setter as this will have undesired side effect
- _automaticLinkDetectionEnabled = YES;
-
- // All links are detectable by default
- _linkDetectionTypes = KILinkTypeOptionAll;
-
- // Link Type Attributes. Default is empty (no attributes).
- _linkTypeAttributes = [NSMutableDictionary dictionary];
-
- // Don't underline URL links by default.
- _systemURLStyle = NO;
-
- // By default we hilight the selected link during a touch to give feedback that we are
- // responding to touch.
- _selectedLinkBackgroundColor = [UIColor colorWithWhite:0.95 alpha:1.0];
-
- // Establish the text store with our current text
- [self updateTextStoreWithText];
- }
- #pragma mark - Text and Style management
- - (void)setAutomaticLinkDetectionEnabled:(BOOL)decorating
- {
- _automaticLinkDetectionEnabled = decorating;
-
- // Make sure the text is updated properly
- [self updateTextStoreWithText];
- }
- - (void)setLinkDetectionTypes:(KILinkTypeOption)linkDetectionTypes
- {
- _linkDetectionTypes = linkDetectionTypes;
-
- // Make sure the text is updated properly
- [self updateTextStoreWithText];
- }
- - (NSDictionary *)linkAtPoint:(CGPoint)location
- {
- // Do nothing if we have no text
- if (_textStorage.string.length == 0)
- {
- return nil;
- }
-
- // Work out the offset of the text in the view
- CGPoint textOffset = [self calcGlyphsPositionInView];
-
- // Get the touch location and use text offset to convert to text cotainer coords
- location.x -= textOffset.x;
- location.y -= textOffset.y;
-
- NSUInteger touchedChar = [_layoutManager glyphIndexForPoint:location inTextContainer:_textContainer];
-
- // If the touch is in white space after the last glyph on the line we don't
- // count it as a hit on the text
- NSRange lineRange;
- CGRect lineRect = [_layoutManager lineFragmentUsedRectForGlyphAtIndex:touchedChar effectiveRange:&lineRange];
- if (CGRectContainsPoint(lineRect, location) == NO)
- return nil;
-
- // Find the word that was touched and call the detection block
- for (NSDictionary *dictionary in self.linkRanges)
- {
- NSRange range = [[dictionary objectForKey:KILabelRangeKey] rangeValue];
-
- if ((touchedChar >= range.location) && touchedChar < (range.location + range.length))
- {
- return dictionary;
- }
- }
-
- return nil;
- }
- // Applies background color to selected range. Used to hilight touched links
- - (void)setSelectedRange:(NSRange)range
- {
- // Remove the current selection if the selection is changing
- if (self.selectedRange.length && !NSEqualRanges(self.selectedRange, range))
- {
- [_textStorage removeAttribute:NSBackgroundColorAttributeName range:self.selectedRange];
- }
-
- // Apply the new selection to the text
- if (range.length && _selectedLinkBackgroundColor != nil)
- {
- [_textStorage addAttribute:NSBackgroundColorAttributeName value:_selectedLinkBackgroundColor range:range];
- }
-
- // Save the new range
- _selectedRange = range;
-
- [self setNeedsDisplay];
- }
- - (void)setNumberOfLines:(NSInteger)numberOfLines
- {
- [super setNumberOfLines:numberOfLines];
-
- _textContainer.maximumNumberOfLines = numberOfLines;
- }
- - (void)setText:(NSString *)text
- {
- // Pass the text to the super class first
- [super setText:text];
-
- // Update our text store with an attributed string based on the original
- // label text properties.
- if (!text)
- {
- text = @"";
- }
-
- NSAttributedString *attributedText = [[NSAttributedString alloc] initWithString:text attributes:[self attributesFromProperties]];
- [self updateTextStoreWithAttributedString:attributedText];
- }
- - (void)setAttributedText:(NSAttributedString *)attributedText
- {
- // Pass the text to the super class first
- [super setAttributedText:attributedText];
-
- [self updateTextStoreWithAttributedString:attributedText];
- }
- - (void)setSystemURLStyle:(BOOL)systemURLStyle
- {
- _systemURLStyle = systemURLStyle;
-
- // Force refresh
- self.text = self.text;
- }
- - (NSDictionary*)attributesForLinkType:(KILinkType)linkType
- {
- NSDictionary *attributes = _linkTypeAttributes[@(linkType)];
-
- if (!attributes)
- {
- attributes = @{NSForegroundColorAttributeName : self.tintColor};
- }
-
- return attributes;
- }
- - (void)setAttributes:(NSDictionary*)attributes forLinkType:(KILinkType)linkType
- {
- if (attributes)
- {
- _linkTypeAttributes[@(linkType)] = attributes;
- }
- else
- {
- [_linkTypeAttributes removeObjectForKey:@(linkType)];
- }
-
- // Force refresh text
- self.text = self.text;
- }
- #pragma mark - Text Storage Management
- - (void)updateTextStoreWithText
- {
- // Now update our storage from either the attributedString or the plain text
- if (self.attributedText)
- {
- [self updateTextStoreWithAttributedString:self.attributedText];
- }
- else if (self.text)
- {
- [self updateTextStoreWithAttributedString:[[NSAttributedString alloc] initWithString:self.text attributes:[self attributesFromProperties]]];
- }
- else
- {
- [self updateTextStoreWithAttributedString:[[NSAttributedString alloc] initWithString:@"" attributes:[self attributesFromProperties]]];
- }
-
- [self setNeedsDisplay];
- }
- - (void)updateTextStoreWithAttributedString:(NSAttributedString *)attributedString
- {
- if (attributedString.length != 0)
- {
- attributedString = [KILabel sanitizeAttributedString:attributedString];
- }
-
- if (self.isAutomaticLinkDetectionEnabled && (attributedString.length != 0))
- {
- self.linkRanges = [self getRangesForLinks:attributedString];
- attributedString = [self addLinkAttributesToAttributedString:attributedString linkRanges:self.linkRanges];
- }
- else
- {
- self.linkRanges = nil;
- }
-
- if (_textStorage)
- {
- // Set the string on the storage
- [_textStorage setAttributedString:attributedString];
- }
- else
- {
- // Create a new text storage and attach it correctly to the layout manager
- _textStorage = [[NSTextStorage alloc] initWithAttributedString:attributedString];
- [_textStorage addLayoutManager:_layoutManager];
- [_layoutManager setTextStorage:_textStorage];
- }
- }
- // Returns attributed string attributes based on the text properties set on the label.
- // These are styles that are only applied when NOT using the attributedText directly.
- - (NSDictionary *)attributesFromProperties
- {
- // Setup shadow attributes
- NSShadow *shadow = shadow = [[NSShadow alloc] init];
- if (self.shadowColor)
- {
- shadow.shadowColor = self.shadowColor;
- shadow.shadowOffset = self.shadowOffset;
- }
- else
- {
- shadow.shadowOffset = CGSizeMake(0, -1);
- shadow.shadowColor = nil;
- }
-
- // Setup color attributes
- UIColor *color = self.textColor;
- if (!self.isEnabled)
- {
- color = [UIColor lightGrayColor];
- }
- else if (self.isHighlighted)
- {
- color = self.highlightedTextColor;
- }
-
- // Setup paragraph attributes
- NSMutableParagraphStyle *paragraph = [[NSMutableParagraphStyle alloc] init];
- paragraph.alignment = self.textAlignment;
-
- // Create the dictionary
- NSDictionary *attributes = @{NSFontAttributeName : self.font,
- NSForegroundColorAttributeName : color,
- NSShadowAttributeName : shadow,
- NSParagraphStyleAttributeName : paragraph,
- };
- return attributes;
- }
- /**
- * Returns array of ranges for all special words, user handles, hashtags and urls in the specfied
- * text.
- *
- * @param text Text to parse for links
- *
- * @return Array of dictionaries describing the links.
- */
- - (NSArray *)getRangesForLinks:(NSAttributedString *)text
- {
- NSMutableArray *rangesForLinks = [[NSMutableArray alloc] init];
-
- if (self.linkDetectionTypes & KILinkTypeOptionUserHandle)
- {
- [rangesForLinks addObjectsFromArray:[self getRangesForUserHandles:text.string]];
- }
-
- if (self.linkDetectionTypes & KILinkTypeOptionHashtag)
- {
- [rangesForLinks addObjectsFromArray:[self getRangesForHashtags:text.string]];
- }
-
- if (self.linkDetectionTypes & KILinkTypeOptionURL)
- {
- [rangesForLinks addObjectsFromArray:[self getRangesForURLs:self.attributedText]];
- }
-
- return rangesForLinks;
- }
- - (NSArray *)getRangesForUserHandles:(NSString *)text
- {
- NSMutableArray *rangesForUserHandles = [[NSMutableArray alloc] init];
-
- // Setup a regular expression for user handles and hashtags
- static NSRegularExpression *regex = nil;
- static dispatch_once_t onceToken;
- dispatch_once(&onceToken, ^{
- NSError *error = nil;
- regex = [[NSRegularExpression alloc] initWithPattern:@"(?<!\\w)@([\\w\\_]+)?" options:0 error:&error];
- });
-
- // Run the expression and get matches
- NSArray *matches = [regex matchesInString:text options:0 range:NSMakeRange(0, text.length)];
-
- // Add all our ranges to the result
- for (NSTextCheckingResult *match in matches)
- {
- NSRange matchRange = [match range];
- NSString *matchString = [text substringWithRange:matchRange];
-
- if (![self ignoreMatch:matchString])
- {
- [rangesForUserHandles addObject:@{KILabelLinkTypeKey : @(KILinkTypeUserHandle),
- KILabelRangeKey : [NSValue valueWithRange:matchRange],
- KILabelLinkKey : matchString
- }];
- }
- }
-
- return rangesForUserHandles;
- }
- - (NSArray *)getRangesForHashtags:(NSString *)text
- {
- NSMutableArray *rangesForHashtags = [[NSMutableArray alloc] init];
-
- // Setup a regular expression for user handles and hashtags
- static NSRegularExpression *regex = nil;
- static dispatch_once_t onceToken;
- dispatch_once(&onceToken, ^{
- NSError *error = nil;
- regex = [[NSRegularExpression alloc] initWithPattern:@"(?<!\\w)#([\\w\\_]+)?" options:0 error:&error];
- });
-
- // Run the expression and get matches
- NSArray *matches = [regex matchesInString:text options:0 range:NSMakeRange(0, text.length)];
-
- // Add all our ranges to the result
- for (NSTextCheckingResult *match in matches)
- {
- NSRange matchRange = [match range];
- NSString *matchString = [text substringWithRange:matchRange];
-
- if (![self ignoreMatch:matchString])
- {
- [rangesForHashtags addObject:@{KILabelLinkTypeKey : @(KILinkTypeHashtag),
- KILabelRangeKey : [NSValue valueWithRange:matchRange],
- KILabelLinkKey : matchString,
- }];
- }
- }
-
- return rangesForHashtags;
- }
- - (NSArray *)getRangesForURLs:(NSAttributedString *)text
- {
-
- NSArray *matches = [self.urlRegex matchesInString:text.string options:0 range:NSMakeRange(0, [text.string length])];
-
-
- NSMutableArray *rangesForURLs = [[NSMutableArray alloc] init];;
-
- // Use a data detector to find urls in the text
-
- NSString *plainText = text.string;
-
- // Add a range entry for every url we found
- for (NSTextCheckingResult *match in matches)
- {
- NSRange matchRange = [match range];
-
- // If there's a link embedded in the attributes, use that instead of the raw text
- NSString *realURL = [plainText substringWithRange:matchRange];
-
- if (![self ignoreMatch:realURL])
- {
-
- [rangesForURLs addObject:@{KILabelLinkTypeKey : @(KILinkTypeURL),
- KILabelRangeKey : [NSValue valueWithRange:matchRange],
- KILabelLinkKey : realURL,
- }];
- }
- }
-
- return rangesForURLs;
- }
- - (BOOL)ignoreMatch:(NSString*)string
- {
- return [_ignoredKeywords containsObject:[string lowercaseString]];
- }
- - (NSAttributedString *)addLinkAttributesToAttributedString:(NSAttributedString *)string linkRanges:(NSArray *)linkRanges
- {
- NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithAttributedString:string];
- for (NSDictionary *dictionary in linkRanges)
- {
- NSRange range = [[dictionary objectForKey:KILabelRangeKey] rangeValue];
- KILinkType linkType = [dictionary[KILabelLinkTypeKey] unsignedIntegerValue];
-
- NSDictionary *attributes = [self attributesForLinkType:linkType];
-
- // 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 a link attribute using the stored link
- [attributedString addAttribute:NSLinkAttributeName value:dictionary[KILabelLinkKey] range:range];
- }
- }
-
- return attributedString;
- }
- #pragma mark - Layout and Rendering
- - (CGRect)textRectForBounds:(CGRect)bounds limitedToNumberOfLines:(NSInteger)numberOfLines
- {
- // Use our text container to calculate the bounds required. First save our
- // current text container setup
- CGSize savedTextContainerSize = _textContainer.size;
- NSInteger savedTextContainerNumberOfLines = _textContainer.maximumNumberOfLines;
-
- // Apply the new potential bounds and number of lines
- _textContainer.size = bounds.size;
- _textContainer.maximumNumberOfLines = numberOfLines;
-
- // Measure the text with the new state
- CGRect textBounds = [_layoutManager usedRectForTextContainer:_textContainer];
-
- // Position the bounds and round up the size for good measure
- textBounds.origin = bounds.origin;
- textBounds.size.width = ceil(textBounds.size.width);
- textBounds.size.height = ceil(textBounds.size.height);
- if (textBounds.size.height < bounds.size.height)
- {
- // Take verical alignment into account
- CGFloat offsetY = (bounds.size.height - textBounds.size.height) / 2.0;
- textBounds.origin.y += offsetY;
- }
- // Restore the old container state before we exit under any circumstances
- _textContainer.size = savedTextContainerSize;
- _textContainer.maximumNumberOfLines = savedTextContainerNumberOfLines;
-
- return textBounds;
- }
- - (void)drawTextInRect:(CGRect)rect
- {
- // Don't call super implementation. Might want to uncomment this out when
- // debugging layout and rendering problems.
- // [super drawTextInRect:rect];
-
- // Calculate the offset of the text in the view
- NSRange glyphRange = [_layoutManager glyphRangeForTextContainer:_textContainer];
- CGPoint glyphsPosition = [self calcGlyphsPositionInView];
-
- // Drawing code
- [_layoutManager drawBackgroundForGlyphRange:glyphRange atPoint:glyphsPosition];
- [_layoutManager drawGlyphsForGlyphRange:glyphRange atPoint:glyphsPosition];
- }
- // Returns the XY offset of the range of glyphs from the view's origin
- - (CGPoint)calcGlyphsPositionInView
- {
- CGPoint textOffset = CGPointZero;
-
- CGRect textBounds = [_layoutManager usedRectForTextContainer:_textContainer];
- textBounds.size.width = ceil(textBounds.size.width);
- textBounds.size.height = ceil(textBounds.size.height);
-
- if (textBounds.size.height < self.bounds.size.height)
- {
- CGFloat paddingHeight = (self.bounds.size.height - textBounds.size.height) / 2.0;
- textOffset.y = paddingHeight;
- }
-
- return textOffset;
- }
- - (void)setFrame:(CGRect)frame
- {
- [super setFrame:frame];
-
- _textContainer.size = self.bounds.size;
- }
- - (void)setBounds:(CGRect)bounds
- {
- [super setBounds:bounds];
-
- _textContainer.size = self.bounds.size;
- }
- - (void)layoutSubviews
- {
- [super layoutSubviews];
-
- // Update our container size when the view frame changes
- _textContainer.size = self.bounds.size;
- }
- - (void)setIgnoredKeywords:(NSSet *)ignoredKeywords
- {
- NSMutableSet *set = [NSMutableSet setWithCapacity:ignoredKeywords.count];
-
- [ignoredKeywords enumerateObjectsUsingBlock:^(id obj, BOOL *stop) {
- [set addObject:[obj lowercaseString]];
- }];
-
- _ignoredKeywords = [set copy];
- }
- #pragma mark - Interactions
- - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
- {
- _isTouchMoved = NO;
-
- // Get the info for the touched link if there is one
- NSDictionary *touchedLink;
- CGPoint touchLocation = [[touches anyObject] locationInView:self];
- touchedLink = [self linkAtPoint:touchLocation];
-
- if (touchedLink)
- {
- self.selectedRange = [[touchedLink objectForKey:KILabelRangeKey] rangeValue];
- }
- else
- {
- [super touchesBegan:touches withEvent:event];
- }
- }
- - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
- {
- [super touchesMoved:touches withEvent:event];
-
- _isTouchMoved = YES;
- }
- - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
- {
- [super touchesEnded:touches withEvent:event];
-
- // If the user dragged their finger we ignore the touch
- if (_isTouchMoved)
- {
- self.selectedRange = NSMakeRange(0, 0);
-
- return;
- }
-
- // Get the info for the touched link if there is one
- NSDictionary *touchedLink;
- CGPoint touchLocation = [[touches anyObject] locationInView:self];
- touchedLink = [self linkAtPoint:touchLocation];
-
- if (touchedLink)
- {
- NSRange range = [[touchedLink objectForKey:KILabelRangeKey] rangeValue];
- NSString *touchedSubstring = [touchedLink objectForKey:KILabelLinkKey];
- KILinkType linkType = (KILinkType)[[touchedLink objectForKey:KILabelLinkTypeKey] intValue];
-
- [self receivedActionForLinkType:linkType string:touchedSubstring range:range];
- }
- else
- {
- [super touchesBegan:touches withEvent:event];
- }
-
- self.selectedRange = NSMakeRange(0, 0);
- }
- - (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
- {
- [super touchesCancelled:touches withEvent:event];
-
- // Make sure we don't leave a selection when the touch is cancelled
- self.selectedRange = NSMakeRange(0, 0);
- }
- - (void)receivedActionForLinkType:(KILinkType)linkType string:(NSString*)string range:(NSRange)range
- {
- switch (linkType)
- {
- case KILinkTypeUserHandle:
- if (_userHandleLinkTapHandler)
- {
- _userHandleLinkTapHandler(self, string, range);
- }
- break;
-
- case KILinkTypeHashtag:
- if (_hashtagLinkTapHandler)
- {
- _hashtagLinkTapHandler(self, string, range);
- }
- break;
-
- case KILinkTypeURL:
- if (_urlLinkTapHandler)
- {
- _urlLinkTapHandler(self, string, range);
- }
- break;
- }
- }
- #pragma mark - setter & getter
- - (NSRegularExpression *)urlRegex {
- if (_urlRegex == nil) {
- NSString *regulaStr =@"((http[s]{0,1}|ftp)://[a-zA-Z0-9\\.\\-]+\\.([a-zA-Z]{2,4})(:\\d+)?(/[a-zA-Z0-9\\.\\-~!@#$%^&*+?:_/=<>]*)?)|(www.[a-zA-Z0-9\\.\\-]+\\.([a-zA-Z]{2,4})(:\\d+)?(/[a-zA-Z0-9\\.\\-~!@#$%^&*+?:_/=<>]*)?)";
- _urlRegex = [NSRegularExpression regularExpressionWithPattern:regulaStr options:NSRegularExpressionCaseInsensitive error:nil];
- }
- return _urlRegex;
- }
- #pragma mark - Layout manager delegate
- -(BOOL)layoutManager:(NSLayoutManager *)layoutManager shouldBreakLineByWordBeforeCharacterAtIndex:(NSUInteger)charIndex
- {
- // Don't allow line breaks inside URLs
- NSRange range;
- NSURL *linkURL = [layoutManager.textStorage attribute:NSLinkAttributeName atIndex:charIndex effectiveRange:&range];
-
- return !(linkURL && (charIndex > range.location) && (charIndex <= NSMaxRange(range)));
- }
- + (NSAttributedString *)sanitizeAttributedString:(NSAttributedString *)attributedString
- {
- // Setup paragraph alignement properly. IB applies the line break style
- // to the attributed string. The problem is that the text container then
- // breaks at the first line of text. If we set the line break to wrapping
- // then the text container defines the break mode and it works.
- // NOTE: This is either an Apple bug or something I've misunderstood.
-
- // Get the current paragraph style. IB only allows a single paragraph so
- // getting the style of the first char is fine.
- NSRange range;
- NSParagraphStyle *paragraphStyle = [attributedString attribute:NSParagraphStyleAttributeName atIndex:0 effectiveRange:&range];
-
- if (paragraphStyle == nil)
- {
- return attributedString;
- }
-
- // Remove the line breaks
- NSMutableParagraphStyle *mutableParagraphStyle = [paragraphStyle mutableCopy];
- mutableParagraphStyle.lineBreakMode = NSLineBreakByWordWrapping;
-
- // Apply new style
- NSMutableAttributedString *restyled = [[NSMutableAttributedString alloc] initWithAttributedString:attributedString];
- [restyled addAttribute:NSParagraphStyleAttributeName value:mutableParagraphStyle range:NSMakeRange(0, restyled.length)];
-
- return restyled;
- }
- @end
|