CGRect for selected UITextRange adjustment for multiline text? - ios

I've used this answer in order to create a CGRect for a certain range of text.
In this UITextView I've set it's attributedText (so I've got a bunch of styled text with varying glyph sizes).
This works great for the first line of text that's left aligned, but it has some really strange results when working with NSTextAlignmentJustified or NSTextAlignmentCenter.
It also doesn't calculate properly when the lines wrap around or (sometimes) if there are \n line breaks.
I get stuff like this (this is center aligned):
When instead I expect this:
This one has a \n line break - the first two code bits were highlighted successfully, but the last one more code for you to see was not because the text wrapping isn't factored into the x,y calculations.
Here's my implementation:
- (void)formatMarkdownCodeBlockWithAttributes:(NSDictionary *)attributesDict
withHighlightProperties:(NSDictionary *)highlightProperties
forFontSize:(CGFloat)pointSize
{
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:#"`.+?`" options:NO error:nil];
NSArray *matchesArray = [regex matchesInString:[self.attributedString string] options:NO range:NSMakeRange(0, self.attributedString.length)];
for (NSTextCheckingResult *match in matchesArray)
{
NSRange range = [match range];
if (range.location != NSNotFound) {
self.textView.attributedText = self.attributedString;
CGRect codeRect = [self frameOfTextRange:range forString:[[self.attributedString string] substringWithRange:range] forFontSize:pointSize];
UIView *highlightView = [[UIView alloc] initWithFrame:codeRect];
highlightView.layer.cornerRadius = 4;
highlightView.layer.borderWidth = 1;
highlightView.backgroundColor = [highlightProperties valueForKey:#"backgroundColor"];
highlightView.layer.borderColor = [[highlightProperties valueForKey:#"borderColor"] CGColor];
[self.contentView insertSubview:highlightView atIndex:0];
[self.attributedString addAttributes:attributesDict range:range];
//strip first and last `
[[self.attributedString mutableString] replaceOccurrencesOfString:#"(^`|`$)" withString:#" " options:NSRegularExpressionSearch range:range];
}
}
}
- (CGRect)frameOfTextRange:(NSRange)range forString:(NSString *)string forFontSize:(CGFloat)pointSize
{
self.textView.selectedRange = range;
UITextRange *textRange = [self.textView selectedTextRange];
CGRect rect = [self.textView firstRectForRange:textRange];
//These three lines are a workaround for getting the correct width of the string since I'm always using the monospaced Menlo font.
rect.size.width = ((pointSize / 1.65) * string.length) - 4;
rect.origin.x+=2;
rect.origin.y+=2;
return rect;
}
Oh, and in case you want it, here's the string I'm playing with:
*This* is **awesome** #mention `code` more \n `code and code` #hashtag [markdown](http://google.com) __and__ #mention2 {#FFFFFF|colored text} This**will also** work but ** will not ** **work** Also, some `more code for you to see`
Note: Please don't suggest I use TTTAttributedLabel or OHAttributedLabel.

I think all your problems are because of incorrect order of instructions.
You have to
Set text aligment
Find required substrings and add specific attributes to them
And only then highlight strings with subviews.
Also you will not need to use "a workaround for getting the correct width of the string since I'm always using the monospaced Menlo font" in such a case.
I have simplified your code a little to make it more understandable.
Result:
- (void)viewDidLoad
{
[super viewDidLoad];
NSDictionary *basicAttributes = #{ NSFontAttributeName : [UIFont boldSystemFontOfSize:18],
NSForegroundColorAttributeName : [UIColor blackColor] };
NSDictionary *attributes = #{ NSFontAttributeName : [UIFont systemFontOfSize:15],
NSForegroundColorAttributeName : [UIColor darkGrayColor]};
_textView.attributedText = [[NSAttributedString alloc] initWithString:
#"*This* is **awesome** #mention `code` more \n `code and code` #hashtag [markdown](http://google.com) __and__ #mention2 {#FFFFFF|colored text} This**will also** work but ** will not ** **work** Also, some `more code for you to see`" attributes:attributes];
_textView.textAlignment = NSTextAlignmentCenter;
[self formatMarkdownCodeBlockWithAttributes:basicAttributes];
}
- (void)formatMarkdownCodeBlockWithAttributes:(NSDictionary *)attributesDict
{
NSMutableString *theString = [_textView.attributedText.string mutableCopy];
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:#"`.+?`" options:NO error:nil];
NSArray *matchesArray = [regex matchesInString:theString options:NO range:NSMakeRange(0, theString.length)];
NSMutableAttributedString *theAttributedString = [_textView.attributedText mutableCopy];
for (NSTextCheckingResult *match in matchesArray)
{
NSRange range = [match range];
if (range.location != NSNotFound) {
[theAttributedString addAttributes:attributesDict range:range];
}
}
_textView.attributedText = theAttributedString;
for (NSTextCheckingResult *match in matchesArray)
{
NSRange range = [match range];
if (range.location != NSNotFound) {
CGRect codeRect = [self frameOfTextRange:range];
UIView *highlightView = [[UIView alloc] initWithFrame:codeRect];
highlightView.layer.cornerRadius = 4;
highlightView.layer.borderWidth = 1;
highlightView.backgroundColor = [UIColor yellowColor];
highlightView.layer.borderColor = [[UIColor redColor] CGColor];
[_textView insertSubview:highlightView atIndex:0];
}
}
}
- (CGRect)frameOfTextRange:(NSRange)range
{
self.textView.selectedRange = range;
UITextRange *textRange = [self.textView selectedTextRange];
CGRect rect = [self.textView firstRectForRange:textRange];
return rect;
}

I just had to do something similar to this. Assuming you are using iOS 7:
// Build the range that you want for your text
NSRange range = NSMakeRange(location, length);
// Get the substring of the attributed text at that range
NSAttributedString *substring = [textView.attributedText attributedSubstringFromRange:range];
// Find the frame that would enclose the substring of text.
CGRect frame = [substring boundingRectWithSize:maxSize
options:(NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading)
context:nil];
This should use the NSTextAlignment assigned to the attributed string.

As #Avt answered https://stackoverflow.com/a/22572201/3549781 this question. I'm just answering for the newline problem. This newline problem occurs on iOS 7+ even if you use
[self.textView selectedTextRange] or [self.textView positionFromPosition: offset:]
We just have to ensure the layout of the textView before calling firstRectForRange by
[self.textView.layoutManager ensureLayoutForTextContainer:self.textView.textContainer];
Courtesy : https://stackoverflow.com/a/25983067/3549781
P.S : At first I added this as a comment to the question. As most people don't read comments I added this as an answer.

Related

Get string of first line in a UILabel

I have some text in a UILabel. I want to get the text of the first label in a string variable.
For example:
label.text = #"You make an object by creating an instance of a particular class. You do this by allocating the object and initializing it with acceptable default values. When you allocate an object, you set aside enough memory for the object and set all instance variables to zero. Initialization sets an object’s initial state—that is, its instance variables and properties—to reasonable values and then returns the object. The purpose of initialization is to return a usable object. You need to both allocate and initialize an object to be able to use it.";
Now I want to get the text of the first line in the UILabel.
1.Add CoreText.framework. 2. Import #import CoreText/CoreText.h>.
Then use below method -
-(NSArray *)getLinesArrayOfStringInLabel:(UILabel *)label
{
NSString *text = [label text];
UIFont *font = [label font];
CGRect rect = [label frame];
CTFontRef myFont = CTFontCreateWithName(( CFStringRef)([font fontName]), [font pointSize], NULL);
NSMutableAttributedString *attStr = [[NSMutableAttributedString alloc] initWithString:text];
[attStr addAttribute:(NSString *)kCTFontAttributeName value:( id)myFont range:NSMakeRange(0, attStr.length)];
CFRelease(myFont);
CTFramesetterRef frameSetter = CTFramesetterCreateWithAttributedString(( CFAttributedStringRef)attStr);
CGMutablePathRef path = CGPathCreateMutable();
CGPathAddRect(path, NULL, CGRectMake(0,0,rect.size.width,100000));
CTFrameRef frame = CTFramesetterCreateFrame(frameSetter, CFRangeMake(0, 0), path, NULL);
NSArray *lines = ( NSArray *)CTFrameGetLines(frame);
NSMutableArray *linesArray = [[NSMutableArray alloc]init];
for (id line in lines)
{
CTLineRef lineRef = ( CTLineRef )line;
CFRange lineRange = CTLineGetStringRange(lineRef);
NSRange range = NSMakeRange(lineRange.location, lineRange.length);
NSString *lineString = [text substringWithRange:range];
CFAttributedStringSetAttribute((CFMutableAttributedStringRef)attStr, lineRange, kCTKernAttributeName, (CFTypeRef)([NSNumber numberWithFloat:0.0]));
CFAttributedStringSetAttribute((CFMutableAttributedStringRef)attStr, lineRange, kCTKernAttributeName, (CFTypeRef)([NSNumber numberWithInt:0.0]));
//NSLog(#"''''''''''''''''''%#",lineString);
[linesArray addObject:lineString];
}
[attStr release];
CGPathRelease(path);
CFRelease( frame );
CFRelease(frameSetter);
return (NSArray *)linesArray;
}
I found this answer from below url - How to get text from nth line of UILabel?
NSString *firstLineString = [[self getLinesArrayOfStringInLabel:yourLabel] objectAtIndex:0];
NSString *text = label.text;
//remove any leading or trailing whitespace or line breaks
text = [text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
//find the the range of the first occuring line break, if any.
NSRange range = [text rangeOfString:#"\n"];
//if there is a line break, get a substring up to that line break
if(range.location != NSNotFound)
text = [text substringToIndex:range.location];
This may help you
var array = string.componentsSeparatedByString("\r")
This will separate the string with new line, and you can get the line wise text in array.

UILabel Bold / Highlight All occurrences SubString

I have multiple UILabels within a Custom Table cell. These labels contain varied text or varied length.
As it stands i have UILabel Subclassed allowing me to implement these methods
- (void)boldRange:(NSRange)range {
if (![self respondsToSelector:#selector(setAttributedText:)]) {
return;
}
NSMutableAttributedString *attributedText;
if (!self.attributedText) {
attributedText = [[NSMutableAttributedString alloc] initWithString:self.text];
} else {
attributedText = [[NSMutableAttributedString alloc]initWithAttributedString:self.attributedText];
}
[attributedText setAttributes:#{NSFontAttributeName:[UIFont boldSystemFontOfSize:self.font.pointSize]} range:range];
self.attributedText = attributedText;
NSLog(#"%#", NSStringFromRange(range));
}
- (void)boldSubstring:(NSString*)substring {
NSRange range = [self.text rangeOfString:substring];
[self boldRange:range];
}
This allows me to call [cell.StoryLabel boldSubstring:#"test"]; which will BOLD the first occurrence of the word 'test'.
What i am after is the ability to either create new subclass methods or extend the ones i already have, to allow me to replace ALL occurrences of a specified word within the label.
I have looked into a number of methods including 3rd party frameworks. The trouble i have is this is a learning process for me. I would be far more beneficial for me to try and complete this myself.
Thanks in advance!
rangeOfString returns the first occurrence, that's normal behavior.
From the Doc:
Finds and returns the range of the first occurrence of a given string
within the receiver.
You could use a NSRegularExpression, and use matchesInString:options:range to get a NSArray of NSTextCheckingResult (that have a NSRange property), an use a for loop to bold it.
This should do the trick:
- (void)boldSubstring:(NSString*)substring
{
if (![self respondsToSelector:#selector(setAttributedText:)])
{
return;
}
NSError *error;
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern: substring options:NSRegularExpressionCaseInsensitive error:&error];
if (!error)
{
NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:[self text]];
NSArray *allMatches = [regex matchesInString:[self text] options:0 range:NSMakeRange(0, [[self text] length])];
for (NSTextCheckingResult *aMatch in allMatches)
{
NSRange matchRange = [aMatch range];
[attributedString setAttributes:#{NSFontAttributeName:[UIFont boldSystemFontOfSize:self.font.pointSize]} range: matchRange];
}
[self setAttributedText:attributedString];
}
}

Replace the truncation ellipsis of UILabel in iOS 7

How can I replace the truncation ellipsis ("…") of a UILabel in iOS 7 with another attributed character? For example, with a colored ">".
I was hoping Text Kit's NSLayoutManager would make this possible, but it appears UILabel doesn't make it public if it uses it.
Also, can I safely assume that an ellipsis is used as the truncation character in every localisation? Maybe different languages have different truncation characters.
I recommend you use TTTAttributedLabel, just set property "attributedTruncationToken" to your custom string.
I don't think it gives you access to this. I think you would have do handle it manually. For example, use TextKit to determine the size of your string, if it doesn't fit in the available area, truncate it yourself and append a ">" and then put your new string in the label.
NSAttributedString has methods for getting the size of the string.
Let me know if you need any more detail on this..?
I think you can do some customization in -replaceElipsesForLabel method provided by Fonix to get your desired result.
I have written a method to do it, and works in iOS7
-(void)setCustomEllipsis:(NSString*)customEllipsis inLabel:(UILabel*)label with:(NSString*)string{
//Replace the ellipsis
NSMutableString* result = [[NSMutableString alloc] initWithString:#""];
NSArray* strings = [string componentsSeparatedByString:#" "];
for (NSString* s in strings) {
CGRect newSize = [[NSString stringWithFormat:#"%#%#%#",result,s,customEllipsis] boundingRectWithSize:CGSizeMake(label.frame.size.width,0) options:NSStringDrawingUsesLineFragmentOrigin attributes:#{NSFontAttributeName:label.font} context:nil];
if (newSize.size.height < label.frame.size.height) {
[result appendString:s];
[result appendString:#" "];
}else{
[result appendString:customEllipsis];
break;
}
}
[label setText:result];
//Set different font to the ellipsis
const CGFloat fontSize = 13;
UIFont *boldFont = [UIFont boldSystemFontOfSize:fontSize];
UIFont *regularFont = [UIFont systemFontOfSize:fontSize];
UIColor *foregroundColor = [UIColor lightGrayColor];
NSDictionary *attrs = [NSDictionary dictionaryWithObjectsAndKeys:regularFont, NSFontAttributeName,foregroundColor, NSForegroundColorAttributeName, nil];
NSDictionary *subAttrs = [NSDictionary dictionaryWithObjectsAndKeys:boldFont, NSFontAttributeName, nil];
const NSRange range = [label.text rangeOfString:customEllipsis];
NSMutableAttributedString *attributedText = [[NSMutableAttributedString alloc] initWithString:result
attributes:attrs];
[attributedText setAttributes:subAttrs range:range];
[label setAttributedText:attributedText];
}

UITextView: assigning attributedText on iOS 6 leads to unexpected result

I'm trying to implement keywords highlighting using UITextView control.
Here is what's performing in UITextView delegate method:
- (void)textViewDidChange:(UITextView *)textView {
NSAttributedString *attrStr = textView.attributedText;
NSString * string = [attrStr string];
NSRegularExpression* regex = NameRegularExpression();
NSArray * matches = [regex matchesInString:string options:0 range:NSMakeRange(0, [string length])];
NSMutableAttributedString *attrMutableStr = [[NSMutableAttributedString alloc] initWithString:string];
for (NSTextCheckingResult* match in matches ) {
[attrMutableStr addAttribute:NSForegroundColorAttributeName value:[UIColor blueColor] range:match.range];
}
textView.attributedText = attrMutableStr;
textView.contentSize = CGSizeMake(textView.frame.size.width, textView.contentSize.height);
}
User input parses with regexp and all extracted elements are highlighted. It works perfect on iOS 7 but absolutely crazy on iOS 6:
Any suggestions of what am I doing wrong?
By the way, it happens even if just assign exactly the same attributedString back:
- (void)textViewDidChange:(UITextView *)textView {
NSAttributedString *attrStr = textView.attributedText;
textView.attributedText = attrStr;
}

How to make TTTAttributedLabel align center programatically in IOS

I am developing an app where I have a string that consists of tags with prefix #.
I am using TTTAttribute Label to add links to words which are having prefix # in a given string.
When I added links to TTTAttribute label. It successfully added and when clicking on that I am able to get that selected word having prefix # in that string..
But I was not able to align center the TTTAttribute label based on string length..
The default property
attributedLabel.verticalAlignment=TTTAttributedLabelVerticalAlignmentCenter;
is not working while applying links..I want the label align center based on its length as shown below..
If it is normal TTTAttribute label without applying links then default align property is applying correctly..
Here is the code I used for adding links..
- (void)viewDidLoad
{
[super viewDidLoad];
NSRange matchRange;
NSString *tweet = #"#MYTWEET ,#tweet, #fashion #Share";
NSScanner *scanner = [NSScanner scannerWithString:tweet];
if (![scanner scanUpToString:#"#" intoString:nil]) {
// there is no opening tag
}
NSString *result = nil;
if (![scanner scanUpToString:#" " intoString:&result]) {
// there is no closing tag
}
//#"theString is:%#",result);
NSArray *words = [tweet componentsSeparatedByString:#" "];
TTTAttributedLabel *attributedLabel=[[TTTAttributedLabel alloc]initWithFrame:CGRectMake(5, 200, 320, 40)];
attributedLabel.textAlignment=NSTextAlignmentCenter;
attributedLabel.text=tweet;
words = [tweet componentsSeparatedByCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:#",0123456789`~!#$%^&*()_-+=.></?;:'* "]];
for (NSString *word in words)
{
if ([word hasPrefix:#"#"])
{
//#"word %#",word);
// Colour your 'word' here
matchRange=[tweet rangeOfString:word];
[attributedLabel addLinkToURL:[NSURL URLWithString:word] withRange:matchRange];
[tagsarray addObject:word];
}
}
attributedLabel.delegate=self;
}
- (void)attributedLabel:(TTTAttributedLabel *)label didSelectLinkWithURL:(NSURL *)url
{
//#"result ==== %#",url);
NSString *webString=[NSString stringWithFormat:#"%#",url];
NSString *tagstring = [webString stringByReplacingOccurrencesOfString:#"#" withString:#""];
NSLog(#"Tag String is:%#",tagstring);
}
I don't want to resize the frames of TTTAttribute label..
Any suggestions or help will be appreciated..
Thanks in Advance..
You need to set the link attributes with the paragraph style set:
NSMutableParagraphStyle* attributeStyle = [[NSMutableParagraphStyle alloc] init];
attributeStyle.alignment = NSTextAlignmentCenter;
NSDictionary *attributes = #{NSFontAttributeName:[UIFont fontWithName:#"Helvetica Neue" size:11], NSForegroundColorAttributeName:[UIColor colorWithRed:0.324 green:0.0 blue:0.580 alpha:1.0], NSParagraphStyleAttributeName:attributeStyle};
[self.atributedLabel setLinkAttributes:attributes];
Its very simple, use the following code:
attributedLabel.textAlignment = NSTextAlignmentCenter;

Resources