Creating UIButton attributed title based on its original plain text state - ios

I'm trying to create a subclass of UIButton that enforces text kerning. As such, when I build a button in Interface Builder, set the text colour (using plain text) I'm expecting to be able to take the existing text, colour, paragraph style, and font from the plain titleLabel instance and translate it into an attributed label with the same properties.
I written two categories that I thought might help:
+ (NSMutableAttributedString*)attributedStringWithTitle:(NSString*)title fromExistingAttributedString:(NSAttributedString*)attributedString
{
NSDictionary *attributes = [attributedString attributesAtIndex:0 effectiveRange:NULL];
return [[NSMutableAttributedString alloc] initWithString:title attributes:attributes];
}
+ (NSMutableAttributedString*)attributedStringWithTitle:(NSString*)title fromPlainTextLabel:(UILabel*)label
{
NSMutableAttributedString* mutableTitle = [[NSMutableAttributedString alloc] initWithString:title];
[mutableTitle addAttribute:NSFontAttributeName value:label.font range:[mutableTitle fullRange]];
[mutableTitle addAttribute:NSForegroundColorAttributeName value:label.textColor range:[mutableTitle fullRange]];
NSMutableParagraphStyle *style = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
style.alignment = label.textAlignment;
[mutableTitle addAttribute:NSParagraphStyleAttributeName value:style range:[mutableTitle fullRange]];
return mutableTitle;
}
And then in my UIButton subclass, I'm overriding these like so:
- (void)setAttributedTitle:(NSAttributedString *)title forState:(UIControlState)state
{
NSMutableAttributedString* mutableTitle = [title mutableCopy];
[mutableTitle addAttributes:#{NSKernAttributeName: #(kDefaultKerning)} range:[mutableTitle fullRange]];
[super setAttributedTitle:mutableTitle forState:state];
[self setNeedsDisplay];
}
- (void)setTitle:(NSString *)title forState:(UIControlState)state
{
NSMutableAttributedString* mutableTitle;
if ([self attributedTitleForState:state]) {
mutableTitle = [NSMutableAttributedString attributedStringWithTitle:title fromExistingAttributedString:[self attributedTitleForState:state]];
} else {
mutableTitle = [NSMutableAttributedString attributedStringWithTitle:title fromPlainTextLabel:self.titleLabel];
}
[self setAttributedTitle:mutableTitle forState:state];
}
But something's not working - particularly of note, [self attributedTitleForState:state] and [self titleForState:state] always seem to be nil. Seems to me like there's some disconnect in the way the properties are set in Interface Builder, in that while the text comes through OK, all of the styling is missing from it.

Got this to work finally. Turns out that there are several properties of a plain text UIButton that are in several places. Some have helper methods to retrieve directly from the UIButton, some do not.
Font: self.titleLabel.font (no helper method)
Colour: [self titleColorForState:state]
Text: [self titleForState:state]
Attributed Text: [self attributedTitleForState]
And a plain text UIButton does not have any text alignment by default, since that's controlled by content horizontal alignment.
As such, I changed up my helper methods (the first is identical, but I had to change the second):
+ (NSMutableAttributedString*)attributedStringWithTitle:(NSString*)title fromExistingAttributedString:(NSAttributedString*)attributedString
{
NSDictionary *attributes = [attributedString attributesAtIndex:0 effectiveRange:NULL];
return [[NSMutableAttributedString alloc] initWithString:title attributes:attributes];
}
+ (NSMutableAttributedString*)attributedStringWithTitle:(NSString*)title font:(UIFont*)font color:(UIColor*)color
{
NSMutableAttributedString* mutableTitle = [[NSMutableAttributedString alloc] initWithString:title];
[mutableTitle addAttribute:NSFontAttributeName value:font range:[mutableTitle fullRange]];
[mutableTitle addAttribute:NSForegroundColorAttributeName value:color range:[mutableTitle fullRange]];
NSMutableParagraphStyle *style = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
[mutableTitle addAttribute:NSParagraphStyleAttributeName value:style range:[mutableTitle fullRange]];
return mutableTitle;
}
And I've implemented it like this:
- (void)setAttributedTitle:(NSAttributedString *)title forState:(UIControlState)state
{
NSMutableAttributedString* mutableTitle = [title mutableCopy];
[mutableTitle addAttributes:#{NSKernAttributeName: #(kDefaultKerning)} range:[mutableTitle fullRange]];
[super setAttributedTitle:mutableTitle forState:state];
[self setNeedsDisplay];
}
- (void)setTitle:(NSString *)title forState:(UIControlState)state
{
NSMutableAttributedString* mutableTitle;
if ([self attributedTitleForState:state]) {
mutableTitle = [NSMutableAttributedString attributedStringWithTitle:title fromExistingAttributedString:[self attributedTitleForState:state]];
} else {
mutableTitle = [NSMutableAttributedString attributedStringWithTitle:title font:self.titleLabel.font color:[self titleColorForState:state]];
}
[self setAttributedTitle:mutableTitle forState:state];
}

Related

more and less button with textview or label

hi i want a textview or label with see more and less functionality. i tried with label and successfully got result as:
but if text will increase it button more will look like :
and in some conditions it will look :
i am trying to set button will automatically set according to text.can anyone suggest me better way to got this.
If you are using UILabel than you can use TTTAttributedLabel in that you can set clickable text.
Append your string "see more" or "less functionality" with label text and make it clickable.
So there is my simple code that create label with TTTAttributedLabel to implement 'more...' and 'less...' functionality for label:
- (void) setupStoreTitleLabel {
self.titleLabel.delegate = self;
[self.titleLabel setText:self.card.name];
self.titleLabel.numberOfLines = 2;
NSAttributedString *showMore = [[NSAttributedString alloc] initWithString:#" more..." attributes:#{
NSForegroundColorAttributeName:[UIColor blueColor],
NSFontAttributeName : [UIFont boldSystemFontOfSize:12],
NSLinkAttributeName : [NSURL URLWithString:#"more..."]
}];
[self.titleLabel setAttributedTruncationToken:showMore];
}
#pragma mark - TTTAttributedLabelDelegate
- (void)attributedLabel:(__unused TTTAttributedLabel *)label didSelectLinkWithURL:(NSURL *)url
{
NSLog(#"%# pressed", url);
if ([[url absoluteString] isEqualToString:#"more..."]) {
self.titleLabel.numberOfLines = 99;
NSString *cardNameWithLess = [NSString stringWithFormat:#"%# %#",self.card.name, #" less..."];
[self.titleLabel setText:cardNameWithLess afterInheritingLabelAttributesAndConfiguringWithBlock:^NSMutableAttributedString *(NSMutableAttributedString *mutableAttributedString) {
//code
NSRange linkRange = [[mutableAttributedString string] rangeOfString:#" less..." options:NSCaseInsensitiveSearch];
[mutableAttributedString addAttribute:NSForegroundColorAttributeName value:[UIColor blueColor] range:linkRange];
[mutableAttributedString addAttribute:NSFontAttributeName value:[UIFont boldSystemFontOfSize:12] range:linkRange];
[mutableAttributedString addAttribute:NSLinkAttributeName value:[NSURL URLWithString:#"less..."] range:linkRange];
return mutableAttributedString;
}];
} else {
self.titleLabel.numberOfLines = 2;
NSAttributedString *showMore = [[NSAttributedString alloc] initWithString:#" more..." attributes:#{
NSForegroundColorAttributeName:[UIColor blueColor],
NSFontAttributeName : [UIFont boldSystemFontOfSize:12],
NSLinkAttributeName : [NSURL URLWithString:#"more..."]
}];
[self.titleLabel setAttributedTruncationToken:showMore];
[self.titleLabel setText:self.card.name];
}
}

Different styles for links in a UITextView

I want my UITextView includes plain text, links, phone numbers and other UIDataDetectorTypes. For instance, plain text - black color, links - blue color with underline, hashtags - green color. So, I parse text and add links to hashtags:
[attributedString addAttribute:NSLinkAttributeName value:[NSString stringWithFormat:#"hashtag://%#", hashtag] range:range];
Other links are detected by default, if dataDetectorTypes set to UIDataDetectorTypeAll. But how to change link style for hashtags?
You can use the official Twitter library for tracking hashtags called twitter text along with TTTAttributedLabel.
Hashtags have special conditions, these could be more than alphanumeric characters preceded by #.
The twitter-text library will automatically find the ranges of hashtags for you then you should be able to format it with TTTAttributedLabel.
Here is a sample implementation of both libraries together.
- (void)scanForHashtag:(NSArray *)hashtags fromLabel:(id)sender
{
TTTAttributedLabel *label = (TTTAttributedLabel *)sender;
for (TwitterTextEntity *entity in hashtags) {
UIFont *boldSystemFont = [UIFont fontWithName:#"Helvetica-Bold" size:12];
CTFontRef font = CTFontCreateWithName((__bridge CFStringRef)boldSystemFont.fontName, boldSystemFont.pointSize, NULL);
NSArray *keys = [[NSArray alloc] initWithObjects:(id)kCTForegroundColorAttributeName,(id)kCTUnderlineStyleAttributeName,(NSString *)kCTFontAttributeName, nil];
UIColor *hashtagColor;
hashtagColor = [UIColor greenColor];
NSArray *objects = [[NSArray alloc] initWithObjects:hashtagColor,[NSNumber numberWithInt:kCTUnderlineStyleNone],(__bridge id)font, nil];
NSDictionary *linkAttributes = [[NSDictionary alloc] initWithObjects:objects forKeys:keys];
label.linkAttributes = linkAttributes;
NSString *hashtag = [label.text substringWithRange:entity.range];
if (entity.type == 2) { //Hashtag
[label addLinkToPhoneNumber:hashtag withRange:entity.range];
} else if (entity.type == 0) { //URL
NSURL *url = [NSURL URLWithString:[label.text substringWithRange:entity.range]];
[label addLinkToURL:url withRange:entity.range];
}
}
}
- (void)attributedLabel:(TTTAttributedLabel *)label didSelectLinkWithPhoneNumber:(NSString *)phoneNumber
{
//Do whatever you need when the hashtag is tapped.
}
[self scanForHashtag:[TwitterText hashtagsInText:#"The text that contains the tweet with hashtags." checkingURLOverlap:NO] fromLabel:yourLabel]; //yourLabel is the label that contains the text so it would be formatted.
EDIT FOR UITextView
NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:str];
[attributedString addAttribute:NSBackgroundColorAttributeName
value:[UIColor yellowColor]
range:entity.range];
You need to find hashtags ranges and than add special attributes for them.
For example.
NSError *error = nil;
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:#"YOUR HASHTAG PATTERN" options:0 error:&error];
if (!error) {
[regex enumerateMatchesInString:messageText options:0 range:NSMakeRange(0, messageText.length) usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) {
[mutableAttributedString addAttributes:#{
NSFontAttributeName: [UIFont fontLinkMessageCell],
(__bridge NSString*) kCTForegroundColorAttributeName: [UIColor colorLinkOutCell],
NSUnderlineStyleAttributeName: #(NSUnderlineStyleNone),
} range:result.range];
}];
}
self.textView.linkTextAttributes = #{};
self.textView.textStorage.delegate = self;
- (void) textStorage: (NSTextStorage*) textStorage didProcessEditing: (NSTextStorageEditActions) editedMask range: (NSRange) editedRange changeInLength: (NSInteger) delta
{
[textStorage enumerateAttribute: NSLinkAttributeName
inRange: editedRange
options: 0
usingBlock: ^(id value, NSRange range, BOOL* stop)
{
if (value)
{
[textStorage addAttribute: NSForegroundColorAttributeName
value: [UIColor redColor]
range: range];
}
}];
}

How to bold some words in my UITextView using NSMutableAttributedString?

I have a UITextView and there are certain words I'm casting with NSString stringWithFormat that I'd like to be bolded.
I have looked around Stack Overflow and tried to follow the the postings but I guess I'm not understanding it.
Here's what I've been playing around with:
NSRange boldedRange = NSMakeRange(0, 4);
NSString *boldFontName = [[UIFont fontWithName:#"Helvetica-Bold" size:100]fontName];
NSMutableAttributedString *attrString = [[NSMutableAttributedString alloc] initWithString:self.name];
[attrString beginEditing];
[attrString addAttribute:NSFontAttributeName
value:boldFontName
range:boldedRange];
[attrString endEditing];
self.resultsTextView.attributedText = attrString;
self.resultsTextView.text = [NSString stringWithFormat:#"One day, %# was taking a walk and saw a %# boy. He was %# a %#.", attrString, self.adjective, self.adverb, self.noun];
You can also set it the following way if you want by setting a dictionary as a whole, as attribute
NSString *strTextView = #"This is some demo Text to set BOLD";
NSRange rangeBold = [strTextView rangeOfString:#"BOLD"];
UIFont *fontText = [UIFont boldSystemFontOfSize:10];
NSDictionary *dictBoldText = [NSDictionary dictionaryWithObjectsAndKeys:fontText, NSFontAttributeName, nil];
NSMutableAttributedString *mutAttrTextViewString = [[NSMutableAttributedString alloc] initWithString:strTextView];
[mutAttrTextViewString setAttributes:dictBoldText range:rangeBold];
[textViewTermsPolicy setAttributedText:mutAttrTextViewString];
Use the below code to set Attribute string in TextView.
NSString *infoString =#"I am Kirit Modi from Deesa.";
NSMutableAttributedString *attString=[[NSMutableAttributedString alloc] initWithString:infoString];
UIFont *font_regular=[UIFont fontWithName:#"Helvetica" size:20.0f];
UIFont *font_bold=[UIFont fontWithName:#"Helvetica-Bold" size:20.0f];
[attString addAttribute:NSFontAttributeName value:font_regular range:NSMakeRange(0, 4)];
[attString addAttribute:NSFontAttributeName value:font_bold range:NSMakeRange(5, 15)];
[attString addAttribute:NSFontAttributeName value:font_regular range:NSMakeRange(16, infoString.length - 15 - 1)];
[self.txtView setAttributedText:attString];
OutPut :
Check out #CrazyYoghurt improvement on #Bbrame and #BenoitJadinon on this previous SO question 3586871
I've been using it for over a year and it works great. One limitation: I don't think you can bold multiple times the same string if it appears more than once in your original string. But you can probably expend the code to make it do so if you wish.
If you also need to filter some word from the UITextView and make it underline/change color of that particular text only then you can use the below code.
Here, I'm getting all the doc text in the Content string and filter some particular text that is in Hebrew language.
NSMutableAttributedString *aStr = [[NSMutableAttributedString alloc]initWithString:content attributes:nil];
[aStr addAttribute:NSLinkAttributeName value:#"http://www.apple.com" range:[content rangeOfString:#"מדיניות פרטיות"]];
[aStr addAttribute:NSLinkAttributeName value:#"http://www.google.com" range:[content rangeOfString:#"לינק"]];
textview.linkTextAttributes = #{NSUnderlineStyleAttributeName : #(NSUnderlineStyleSingle)};
textview.delegate = (id)self;
//...You can as per your custom color
[aStr addAttribute:NSForegroundColorAttributeName value:[UIColor redColor] range:[content rangeOfString:#"מדיניות פרטיות"]];
[aStr addAttribute:NSForegroundColorAttributeName value:[UIColor redColor] range:[content rangeOfString:#"לינק"]];
//Here You can also add the tap gesture on that text.
//Tap gesture
UITapGestureRecognizer *tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(tappedTextView:)];
[textview addGestureRecognizer:tapRecognizer];
[textview setAttributedText:aStr];
textview.textAlignment=NSTextAlignmentRight;
//For getting the text location in the tap gesture
-(void)tappedTextView:(UITapGestureRecognizer *)tapGesture
{
UITextView *textView = (UITextView *)tapGesture.view;
CGPoint tapLocation = [tapGesture locationInView:textView];
UITextPosition *textPosition = [textView closestPositionToPoint:tapLocation];
NSDictionary *attributes = [textView textStylingAtPosition:textPosition inDirection:UITextStorageDirectionForward];
NSString *urlStr = attributes[NSLinkAttributeName];
if (urlStr)
{
//[[UIApplication sharedApplication] openURL:[NSURL URLWithString:[NSString stringWithFormat:#"%#",url]]];
PrivacyViewController *next = [PrivacyViewController new];
[self.navigationController pushViewController:next animated:YES];
}
}

UITextView text is aligned to the bottom

I am creating a UIScrollView and setting the attributedText. For some reason the text was not appearing...until I actually scrolled down. It seems the text is being aligned to the bottom of the UIScrollView, and I have no idea why?!
NSString *description = NSLocalizedString(#"forgot.description", nil);
NSMutableAttributedString *paragraph = [[NSMutableAttributedString alloc] initWithString:description attributes:#{}];
NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc] init];
[paragraphStyle setLineSpacing:4];
[paragraph addAttribute:NSParagraphStyleAttributeName value:paragraphStyle range:NSMakeRange(0, [paragraph length])];
[paragraph addAttribute:NSFontAttributeName value:[UIFont systemFontOfSize:12] range:NSMakeRange(0, [paragraph length])];
[paragraph addAttribute:NSForegroundColorAttributeName value:[UIColor colorWithRed:43/255.0f green:47/255.0f blue:61/255.0f alpha:1.0f] range:NSMakeRange(0, [paragraph length])];
_descriptionTextView = [[UITextView alloc] init];
_descriptionTextView.backgroundColor = [UIColor whiteColor];
_descriptionTextView.attributedText = paragraph;
_descriptionTextView.userInteractionEnabled = NO;
_descriptionTextView.scrollEnabled = NO;
_descriptionTextView.clipsToBounds = NO;
CGFloat const ComponentWidth = 280.0;
CGFloat const ComponentHeight = 40.0;
self.descriptionTextView.frame = CGRectIntegral(CGRectMake(x, y, ComponentWidth, ComponentHeight));
[self.view addSubview:self.descriptionTextView];
I think this has to do with the navigationBar when I set it to not hidden
- (void)viewWillAppear:(BOOL)animated
{
[self.navigationController setNavigationBarHidden:NO animated:NO];
[super viewWillAppear:animated];
}

Revert strikeThrough text to plain text after some operation is performed in TTTAttributedLabel

I am using this code to change color of label and set text as strike through:
sliderlabel = [[TTTAttributedLabel alloc] initWithFrame:CGRectMake(10, 260, 310, 30)];
sliderlabel.font = [UIFont fontWithName:#"Optima-Bold" size:14];
[sliderlabel setTag:112];
sliderlabel.lineBreakMode = UILineBreakModeWordWrap;
[sliderlabel setBackgroundColor:[UIColor clearColor]];
NSString *sliderlabeltext = [NSString stringWithFormat:#"Change To: In-Progress (%d %%)",(int)slider.value];
[sliderlabel setText:sliderlabeltext afterInheritingLabelAttributesAndConfiguringWithBlock:^ NSMutableAttributedString *(NSMutableAttributedString *mutableAttributedString) {
NSRange boldRange = [[mutableAttributedString string] rangeOfString:[NSString stringWithFormat:#"In-Progress (%d %%)",(int)slider.value] options:NSCaseInsensitiveSearch];
NSRange strikeRange = [[mutableAttributedString string] rangeOfString:sliderlabeltext options:NSCaseInsensitiveSearch];
UIFont *boldSystemFont = [UIFont fontWithName:#"Optima-Bold" size:14];
CTFontRef font = CTFontCreateWithName((CFStringRef)boldSystemFont.fontName, boldSystemFont.pointSize, NULL);
if (font) {
[mutableAttributedString addAttribute:(NSString *)kCTForegroundColorAttributeName value:(id)[UIColor colorWithRed:8/255.0 green:156/255.0 blue:94/255.0 alpha:1.0].CGColor range:boldRange];//34-139-34
[mutableAttributedString addAttribute:kTTTStrikeOutAttributeName value:[NSNumber numberWithBool:YES] range:strikeRange];
CFRelease(font);
}
return mutableAttributedString;
}];
[self.view addSubview:sliderlabel];
[sliderlabel release];
Now I want it to be without strike through when I perform some operation like click on a button, passing [NSNumber numberWithBool:NO] in addAttribute:value:range doesnt work. Any suggestions?
Try this additions.
#implementation TTTAttributedLabel (Additions)
- (void)setStrikeThroughOn:(BOOL)isStrikeThrough {
NSString* text = self.text;
[self setText:text afterInheritingLabelAttributesAndConfiguringWithBlock:^ NSMutableAttributedString *(NSMutableAttributedString *mutableAttributedString) {
NSRange strikeRange = [[mutableAttributedString string] rangeOfString:text options:NSCaseInsensitiveSearch];
[mutableAttributedString addAttribute:kTTTStrikeOutAttributeName value:[NSNumber numberWithBool:isStrikeThrough] range:strikeRange];
return mutableAttributedString;
}];
// must trigger redraw
[self setNeedsDisplay];
}
#end

Resources