UITextView, NSAttributedString and custom attributes - ios

I have searched a lot on Stack Overflow but I couldn't find a solution. Perhaps I just misinterpreted some answers.
I have created a UITextView and I am using NSAttributedStrings to work with the UITextView which is just fine.
Now, after adding a custom attribute, I am stuck.
Where can I hook in to render my custom attribute within the UITextView? Is there a delegate method, or will I have to create my own UITextView and overwrite a method?

You can custom NSLayoutManager, and implement it's -drawGlyphsForGlyphRange:atPoint: method.
For example, you want a custom background with a corner radius
textView init:
NSTextStorage *textStorage = [NSTextStorage new];
CustomLayoutManager *layoutManager = [[CustomLayoutManager alloc] init];
CGSize containerSize = CGSizeMake(self.view.bounds.size.width, CGFLOAT_MAX);
NSTextContainer *textContainer = [[NSTextContainer alloc] initWithSize:containerSize];
textContainer.widthTracksTextView = YES;
[layoutManager addTextContainer:textContainer];
[textStorage addLayoutManager:layoutManager];
self.textView = [[UITextView alloc] initWithFrame:yourFrame textContainer:textContainer];
And apply your custom attribute:
NSMutableAttributedString *mAttrStr = [[NSMutableAttributedString alloc] initWithString:#"SampleText"];
[mAttrStr addAttribute:YourCustomAttributeName value:[UIColor redColor] range:NSMakeRange(0, mAttrStr.length)]; //for example, you want a custom background with a corner radius
[self.textView.textStorage appendAttributedString:mAttrStr];
In CustomLayoutManager.m
-(void)drawGlyphsForGlyphRange:(NSRange)glyphsToShow atPoint:(CGPoint)origin {
NSRange range = [self characterRangeForGlyphRange:glyphsToShow
actualGlyphRange:NULL];
//enumerate custom attribute in the range
[self.textStorage enumerateAttribute:YourCustomAttributeName inRange:range options:NSAttributedStringEnumerationLongestEffectiveRangeNotRequired usingBlock:^(id _Nullable value, NSRange range, BOOL * _Nonnull stop) {
if (value) {
UIColor *color = value; //the color set above
NSRange glyphRange = [self glyphRangeForCharacterRange:range
actualCharacterRange:NULL];
NSTextContainer *container = [self textContainerForGlyphAtIndex:glyphRange.location
effectiveRange:NULL];
//draw background
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSaveGState(context);
CGContextTranslateCTM(context, origin.x, origin.y);
[color setFill];
CGRect rect = [self boundingRectForGlyphRange:glyphRange inTextContainer:container];
//UIBezierPath with rounded
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:rect cornerRadius:100];
[path fill];
CGContextRestoreGState(context);
//end draw
[super drawGlyphsForGlyphRange:range atPoint:origin];
}
else {
[super drawGlyphsForGlyphRange:range atPoint:origin];
}
}];
}
Now the 'SampleText' has a red rounded background.

Please refer this simple code snippet to set attributed string to a textview
let attributedString = NSMutableAttributedString(string:"Test string to add attributes")
attributedString.addAttributes([NSForegroundColorAttributeName:UIColor.greenColor()], range: NSMakeRange(0, attributedString.string.characters.count))
textView.attributedText = attributedString
For Objective-C
NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc]initWithString:#"Test string to add attributes"];
[attributedString addAttributes:#{NSForegroundColorAttributeName:[UIColor greenColor]} range:NSMakeRange(0, attributedString.string.length)];
textView.attributedText = attributedString;
Hope this helps.

If you want apply particular attributes for particular textView rather then string then you should subclass UITextView and make custom initmethod or some method that return UITextView object with specified attribute!! You can pass custom attributes as parameter in method also if attributes are change i mean not fix. and if attribute will remain same implicitly then set attributes in that class by default.

Related

Wrong bounding rect for right aligned text in UILabel

- (CGRect)boundingRectForGlyphRange:(NSRange)glyphRange inTextContainer:(NSTextContainer *)container;
returns wrong rect for glyph range when attributed text is right aligned in UILabel. How to fix this please ?
Code to calculate the bounding rect of attributed text when tapped on UILabel (textAlignment set to right in UILabel)
- (void)tap:(UITapGestureRecognizer *)recognizer {
UILabel *label = (UILabel *)recognizer.view;
CGSize labelSize = recognizer.view.bounds.size;
NSTextContainer *container = [[NSTextContainer alloc] initWithSize:labelSize];
container.lineFragmentPadding = 0.0;
container.lineBreakMode = label.lineBreakMode;
container.maximumNumberOfLines = label.numberOfLines;
NSLayoutManager *manager = [NSLayoutManager new];
[manager addTextContainer:container];
NSTextStorage *storage = [[NSTextStorage alloc] initWithAttributedString:label.attributedText];
[storage addLayoutManager:manager];
CGPoint touchPoint = [recognizer locationInView:label];
NSInteger indexOfCharacter = [manager characterIndexForPoint:touchPoint
inTextContainer:container
fractionOfDistanceBetweenInsertionPoints:nil];
NSRange range = NSMakeRange(indexOfCharacter, 1);
NSRange glyphRange;
[manager characterRangeForGlyphRange:range actualGlyphRange:&glyphRange];
CGRect rect = [manager boundingRectForGlyphRange:glyphRange inTextContainer:container];
}
The problem is that you are setting the label's text and textAlignment, and then pulling out the label's attributedText and handing it to the text kit stack, in the belief that it magically translates your label configuration into an attributed string. It doesn't!
If you want right-aligned text that the text kit stack can see as right-aligned, set your label's attributedText only, endowing it with features such as a right-aligned paragraph style.
Solved this!!
The problem was the label was made to be right aligned using textAlignment property of UILabel. As mentioned in comments label.attributedText doesn't know anything about it.
Instead add text alignment using NSParagraphStyleAttributeName as :
NSMutableAttributedString *mutableString = [NSMutableAttributedString new];
//Add to your string
NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc] init];
[paragraphStyle setAlignment:NSTextAlignmentRight];
[mutableString addAttribute:NSParagraphStyleAttributeName value:paragraphStyle range:NSMakeRange(0, mutableString.length)];
label.attributedText = mutableString;

Underline UIlabel so the underlining always reaches the bounds

What I want to achieve is to have UILabel underlined but in a specific way.
I know how to make UILabel underlined, but since this is going to be a dynamic text, I don't know how long it will be.
Anytime the label enters a new line, I'd like to make the underlining align with the one above regardless of the text length.
I sketched it up to give you a better notion of what I actually try to achieve:
What is your opinion, how to approach such problem?
Should I add the white line as UIView anytime text skips to another line?
Or maybe add some whitespace in code when the text lengths is shorter than bounds of current line?
first you need to set text for you label then call this method :
- (void)underlineLabel:(UILabel*)lbl {
if (![lbl respondsToSelector:#selector(setAttributedText:)]) {
return;
}
NSMutableAttributedString *attributedText;
if (!lbl.attributedText) {
attributedText = [[NSMutableAttributedString alloc] initWithString:lbl.text];
} else {
attributedText = [[NSMutableAttributedString alloc] initWithAttributedString:lbl.attributedText];
}
long len = [lbl.text length];
[attributedText addAttribute:NSUnderlineColorAttributeName value:[UIColor grayColor] range:NSMakeRange(0,len)];
[attributedText addAttribute:NSUnderlineStyleAttributeName value:[NSNumber numberWithInteger:1] range:NSMakeRange(0, len)];//Underline color
lbl.attributedText = attributedText;
}
func underlineLabel(label: UILabel) {
if !lbl.respondsToSelector("setAttributedText:") {
return
}
var attributedText = NSMutableAttributedString()
if !(lbl.attributedText != nil) {
attributedText = NSMutableAttributedString(string:label.text!)
}
else {
attributedText = NSMutableAttributedString(attributedString: label.attributedText!)
}
let str = label.text;
let len = str?.characters.count;
attributedText.addAttribute(NSUnderlineColorAttributeName, value: UIColor.redColor(), range: NSMakeRange(0, len!))
attributedText.addAttribute(NSUnderlineStyleAttributeName , value:1, range: NSMakeRange(0, len!))
//Underline color
lbl.attributedText = attributedText
}
I have came up with solution with custom label class and override drawRect Method in that custom class of UIlabel.
CustomLabel.h
#import <UIKit/UIKit.h>
#interface CustomLabel : UILabel
#end
CustomLabel.m
#import "CustomLabel.h"
#implementation CustomLabel
- (void)drawRect:(CGRect)rect
{
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextSetLineWidth(ctx, 1.0f);
float Left = self.center.x - self.frame.size.width/2.0;
float Right = self.center.x + self.frame.size.width/2.0;
CGContextMoveToPoint(ctx, Left, self.bounds.size.height - 1);
CGContextAddLineToPoint(ctx, Right, self.bounds.size.height - 1);
CGContextStrokePath(ctx);
[super drawRect:rect];
}
#end
In Your Class Just import this custom Class.
#import "CustomLabel.h"
////// you can create labels now which are having underline to bounds.
-(void)CreateCustomLabel
{
CustomLabel *custom = [[CustomLabel alloc] initWithFrame:CGRectMake(0, 150, SCREEN_WIDTH-40, 50)];
custom.text = #"Your Text Here";
custom.backgroundColor = [UIColor redColor];
[self.view addSubview:custom];
}

Example of NSTextContainer with non regular shape?

Hi I'm working with the new TextKit API for iOS7 and I'm trying to produce a UITextView with an irregular shape. So far I have in a view controller:
-(void) loadView
{
self.view = [[UIView alloc] initWithFrame:CGRectMake(0,0,320,548)];
NSTextStorage *textStorage = [[NSTextStorage alloc] init];
NSLayoutManager *layoutManager = [[NSLayoutManager alloc] init];
[textStorage addLayoutManager: layoutManager];
BaseTextContainer *textContainer = [[BaseTextContainer alloc] initWithSize:CGSizeMake(100, 100)];
[layoutManager addTextContainer: textContainer];
BaseTextView *textView = [[BaseTextView alloc] initWithFrame:CGRectMake(110,124, 100, 100) textContainer:textContainer];
textView.backgroundColor = [UIColor blueColor];
textView.editable = YES;
[self.view addSubview:textView];
}
Then in my subclassed NSTextContainer, I want to have a mutablePath drawn as the shape of the text container, but not sure how to accomplish this. I have:
- (BOOL) isSimpleRectangularTextContainer
{
return NO;
}
- (void) drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx
{
NSLog(#"TEST");
CGContextRef context = ctx;
CGSize layerSize = layer.frame.size;
CGAffineTransform transform = CGAffineTransformMakeScale(layerSize.width / self.initialSize.width, layerSize.height / self.initialSize.height);
CGMutablePathRef newGraphicMutablePath = CGPathCreateMutableCopyByTransformingPath(self.mutablePath, &transform);
CGContextAddPath(context, newGraphicMutablePath);
CGPathRelease(newGraphicMutablePath);
CGContextSetFillColorWithColor(context, [UIColor redColor].CGColor);
CGContextDrawPath(context, kCGPathFill);
}
Just a bit confused about how to get this to work. I cannot find an example anywhere of an NSTextContainer with an irregular shape.
There is no need for all that code constructing the Text Kit stack, as you are not modifying the architecture of the stack. Just start with a normal UITextView - let's say it's self.textView - and then assign one or more UIBezierPath objects to its exclusion paths:
self.tv.textContainer.exclusionPaths = myArrayOfBezierPaths;
These paths are exclusion paths, so for an ellipse you will want to make four paths, each one describing a corner of the text container.
Alternatively, you can build the Text Kit stack yourself so as to insert your own text container subclass, and modify where the text is allowed to go by overriding lineFragmentForProposedRect:, perhaps similar to what I do here: https://github.com/mattneub/Programming-iOS-Book-Examples/blob/master/bk2ch10p537exclusionPath2/ch23p813textKitShapes/MyTextContainer.swift
Some experiments:

How do I trim the text and add Ellipsis (...) while using drawInRect: on iOS 7?

I've create a subview and implementing the drawRect: method for custom drawing. How do I achieve a behavior similar to that of a UILabel which automatically adds the Ellipsis (...) if the text is too long to fit in its frame.
Here is the code
- (void)drawRect:(CGRect)rect
{
NSDictionary *attributes = #{NSFontAttributeName : [UIFont systemFontOfSize:16.0f], NSForegroundColorAttributeName : [UIColor blackColor]};
[self.sampleText drawInRect:CGRectMake(10.0f, 10.0f, self.frame.size.width - 20.0f, self.frame.size.height - 20.0f) withAttributes:attributes];
}
If the sampleText is long then it just gets clipped to fit within the specified rect.
How do I add the '...' appropriately?
You need to use one of the methods like drawInRect:withAttributes: and use the attributed string attributes to set the line truncation style.
Try:
NSMutableParagraphStyle *ps = [[NSMutableParagraphStyle alloc] init];
[ps setLineBreakMode:NSLineBreakByTruncatingTail];
[attributes setObject:ps forKey:NSParagraphStyleAttributeName];

Underline text in UIlabel

How can I underline a text that could be multiple lines of string?
I find some people suggest UIWebView, but it is obviously too heavy a class for just text rendering.
My thoughts was to figure out the start point and length of each string in each line.
And draw a line under it accordingly.
I meet problems at how to figure out the length and start point for the string.
I tried to use -[UILabel textRectForBounds:limitedToNumberOfLines:], this should be the drawing bounding rect for the text right?
Then I have to work on the alignment?
How can I get the start point of each line when it is center-justified and right justified?
You may subclass from UILabel and override drawRect method:
- (void)drawRect:(CGRect)rect {
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextSetRGBStrokeColor(ctx, 207.0f/255.0f, 91.0f/255.0f, 44.0f/255.0f, 1.0f); // RGBA
CGContextSetLineWidth(ctx, 1.0f);
CGContextMoveToPoint(ctx, 0, self.bounds.size.height - 1);
CGContextAddLineToPoint(ctx, self.bounds.size.width, self.bounds.size.height - 1);
CGContextStrokePath(ctx);
[super drawRect:rect];
}
UPD:
As of iOS 6 Apple added NSAttributedString support for UILabel, so now it's much easier and works for multiple lines:
NSDictionary *underlineAttribute = #{NSUnderlineStyleAttributeName: #(NSUnderlineStyleSingle)};
myLabel.attributedText = [[NSAttributedString alloc] initWithString:#"Test string"
attributes:underlineAttribute];
If you still wish to support iOS 4 and iOS 5, I'd recommend to use TTTAttributedLabel rather than underline label manually. However if you need to underline one-line UILabel and don't want to use third-party components, code above would still do the trick.
In Swift:
let underlineAttriString = NSAttributedString(string: "attriString",
attributes: [NSAttributedString.Key.underlineStyle: NSUnderlineStyle.single.rawValue])
label.attributedText = underlineAttriString
This is what i did. It works like butter.
1) Add CoreText.framework to your Frameworks.
2) import <CoreText/CoreText.h> in the class where you need underlined label.
3) Write the following code.
NSMutableAttributedString *attString = [[NSMutableAttributedString alloc] initWithString:#"My Messages"];
[attString addAttribute:(NSString*)kCTUnderlineStyleAttributeName
value:[NSNumber numberWithInt:kCTUnderlineStyleSingle]
range:(NSRange){0,[attString length]}];
self.myMsgLBL.attributedText = attString;
self.myMsgLBL.textColor = [UIColor whiteColor];
Use an attribute string:
NSMutableAttributedString* attrString = [[NSMutableAttributedString alloc] initWithString:#"Your String"]
[attrString addAttribute:(NSString*)kCTUnderlineStyleAttributeName
value:[NSNumber numberWithInt:kCTUnderlineStyleSingle]
range:(NSRange){0,[attrString length]}];
And then override the label - (void)drawTextInRect:(CGRect)aRect and render the text in something like:
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextSaveGState(ctx);
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)attrString);
drawingRect = self.bounds;
CGMutablePathRef path = CGPathCreateMutable();
CGPathAddRect(path, NULL, drawingRect);
textFrame = CTFramesetterCreateFrame(framesetter,CFRangeMake(0,0), path, NULL);
CGPathRelease(path);
CFRelease(framesetter);
CTFrameDraw(textFrame, ctx);
CGContextRestoreGState(ctx);
Or better yet instead of overriding just use the OHAttributedLabel created by Olivier Halligon
I've combined some of provided answers, to create better (at least for my requirements) UILabel subclass, which supports:
multiline text with various label bounds (text can be in the middle of label frame, or accurate size)
underline
strikeout
underline/strikeout line offset
text alignment
different font sizes
https://github.com/GuntisTreulands/UnderLineLabel
People, who do not want to subclass the view (UILabel/UIButton) etc...
'forgetButton' can be replace by any lable too.
-(void) drawUnderlinedLabel {
NSString *string = [forgetButton titleForState:UIControlStateNormal];
CGSize stringSize = [string sizeWithFont:forgetButton.titleLabel.font];
CGRect buttonFrame = forgetButton.frame;
CGRect labelFrame = CGRectMake(buttonFrame.origin.x + buttonFrame.size.width - stringSize.width,
buttonFrame.origin.y + stringSize.height + 1 ,
stringSize.width, 2);
UILabel *lineLabel = [[UILabel alloc] initWithFrame:labelFrame];
lineLabel.backgroundColor = [UIColor blackColor];
//[forgetButton addSubview:lineLabel];
[self.view addSubview:lineLabel];
}
NSString *tem =self.detailCustomerCRMCaseLabel.text;
if (tem != nil && ![tem isEqualToString:#""]) {
NSMutableAttributedString *temString=[[NSMutableAttributedString alloc]initWithString:tem];
[temString addAttribute:NSUnderlineStyleAttributeName
value:[NSNumber numberWithInt:1]
range:(NSRange){0,[temString length]}];
self.detailCustomerCRMCaseLabel.attributedText = temString;
}
Another solution could be (since iOS 7) given a negative value to NSBaselineOffsetAttributeName, for example your NSAttributedString could be:
NSAttributedString *attributedText = [[NSAttributedString alloc] initWithString:#"my text goes here'
attributes:#{NSFontAttributeName: [UIFont fontWithName:#"Helvetica-Regular" size:12],
NSForegroundColorAttributeName: [UIColor blackColor],
NSUnderlineStyleAttributeName: #(NSUnderlineStyleSingle), NSBaselineOffsetAttributeName: #(-3)}];
Hope this will help ;-)
NSMutableAttributedString *text = [self.myUILabel.attributedText mutableCopy];
[text addAttribute:NSUnderlineStyleAttributeName value:#(NSUnderlineStyleSingle) range:NSMakeRange(0, text.length)];
self.myUILabel.attributedText = text;
You can create a custom label with name UnderlinedLabel and edit drawRect function.
#import "UnderlinedLabel.h"
#implementation UnderlinedLabel
- (void)drawRect:(CGRect)rect
{
NSString *normalTex = self.text;
NSDictionary *underlineAttribute = #{NSUnderlineStyleAttributeName: #(NSUnderlineStyleSingle)};
self.attributedText = [[NSAttributedString alloc] initWithString:normalTex
attributes:underlineAttribute];
[super drawRect:rect];
}
Here is the easiest solution which works for me without writing additional codes.
// To underline text in UILable
NSMutableAttributedString *text = [[NSMutableAttributedString alloc] initWithString:#"Type your text here"];
[text addAttribute:NSUnderlineStyleAttributeName value:#(NSUnderlineStyleSingle) range:NSMakeRange(0, text.length)];
lblText.attributedText = text;
Sometimes we developer stuck in small designing part of any UI screen. One of the most irritating requirement is under line text. Don’t worry here is the solution.
Underlining a text in a UILabel using Objective C
UILabel *label=[[UILabel alloc]initWithFrame:CGRectMake(0, 0, 320, 480)];
label.backgroundColor=[UIColor lightGrayColor];
NSMutableAttributedString *attributedString;
attributedString = [[NSMutableAttributedString alloc] initWithString:#"Apply Underlining"];
[attributedString addAttribute:NSUnderlineStyleAttributeName value:#1 range:NSMakeRange(0,
[attributedString length])];
[label setAttributedText:attributedString];
Underlining a text in UILabel using Swift
label.backgroundColor = .lightGray
let attributedString = NSMutableAttributedString.init(string: "Apply UnderLining")
attributedString.addAttribute(NSUnderlineStyleAttributeName, value: 1, range:
NSRange.init(location: 0, length: attributedString.length))
label.attributedText = attributedString
An enhanced version of the code of Kovpas (color and line size)
#implementation UILabelUnderlined
- (void)drawRect:(CGRect)rect {
CGContextRef ctx = UIGraphicsGetCurrentContext();
const CGFloat* colors = CGColorGetComponents(self.textColor.CGColor);
CGContextSetRGBStrokeColor(ctx, colors[0], colors[1], colors[2], 1.0); // RGBA
CGContextSetLineWidth(ctx, 1.0f);
CGSize tmpSize = [self.text sizeWithFont:self.font constrainedToSize:CGSizeMake(200, 9999)];
CGContextMoveToPoint(ctx, 0, self.bounds.size.height - 1);
CGContextAddLineToPoint(ctx, tmpSize.width, self.bounds.size.height - 1);
CGContextStrokePath(ctx);
[super drawRect:rect];
}
#end
I have Created for multiline uilabel with underline :
For Font size 8 to 13 set int lineHeight = self.font.pointSize+3;
For font size 14 to 20 set int lineHeight = self.font.pointSize+4;
- (void)drawRect:(CGRect)rect
{
CGContextRef ctx = UIGraphicsGetCurrentContext();
const CGFloat* colors = CGColorGetComponents(self.textColor.CGColor);
CGContextSetRGBStrokeColor(ctx, colors[0], colors[1], colors[2], 1.0); // RGBA
CGContextSetLineWidth(ctx, 1.0f);
CGSize tmpSize = [self.text sizeWithFont:self.font constrainedToSize:CGSizeMake(self.frame.size.width, 9999)];
int height = tmpSize.height;
int lineHeight = self.font.pointSize+4;
int maxCount = height/lineHeight;
float totalWidth = [self.text sizeWithFont:self.font constrainedToSize:CGSizeMake(1000, 9999)].width;
for(int i=1;i<=maxCount;i++)
{
float width=0.0;
if((i*self.frame.size.width-totalWidth)<=0)
width = self.frame.size.width;
else
width = self.frame.size.width - (i* self.frame.size.width - totalWidth);
CGContextMoveToPoint(ctx, 0, lineHeight*i-1);
CGContextAddLineToPoint(ctx, width, lineHeight*i-1);
}
CGContextStrokePath(ctx);
[super drawRect:rect];
}
Swift 4.1 ver:
let underlineAttriString = NSAttributedString(string:"attriString", attributes:
[NSAttributedStringKey.underlineStyle: NSUnderlineStyle.styleSingle.rawValue])
label.attributedText = underlineAttriString
As kovpas has shown you can use the bounding box in most cases, although it is not always guaranteed that the bounding box will fit neatly around the text. A box with a height of 50 and font size of 12 may not give the results you want depending on the UILabel configuration.
Query the UIString within the UILabel to determine its exact metrics and use these to better place your underline regardless of the enclosing bounding box or frame using the drawing code already provided by kovpas.
You should also look at UIFont's "leading" property that gives the distance between baselines based on a particular font. The baseline is where you would want your underline to be drawn.
Look up the UIKit additions to NSString:
(CGSize)sizeWithFont:(UIFont *)font
//Returns the size of the string if it were to be rendered with the specified font on a single line.
(CGSize)sizeWithFont:(UIFont *)font constrainedToSize:(CGSize)size
// Returns the size of the string if it were rendered and constrained to the specified size.
(CGSize)sizeWithFont:(UIFont *)font constrainedToSize:(CGSize)size lineBreakMode:(UILineBreakMode)lineBreakMode
//Returns the size of the string if it were rendered with the specified constraints.
I use an open source line view and just added it to the button subviews:
UILabel *label = termsButton.titleLabel;
CGRect frame = label.frame;
frame.origin.y += frame.size.height - 1;
frame.size.height = 1;
SSLineView *line = [[SSLineView alloc] initWithFrame:frame];
line.lineColor = [UIColor lightGrayColor];
[termsButton addSubview:line];
This was inspired by Karim above.
Based on Kovpas & Damien Praca's Answers, here is an implementation of UILabelUnderligned which also support textAlignemnt.
#import <UIKit/UIKit.h>
#interface UILabelUnderlined : UILabel
#end
and the implementation:
#import "UILabelUnderlined.h"
#implementation DKUILabel
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
}
return self;
}
- (void)drawRect:(CGRect)rect {
CGContextRef ctx = UIGraphicsGetCurrentContext();
const CGFloat* colors = CGColorGetComponents(self.textColor.CGColor);
CGContextSetRGBStrokeColor(ctx, colors[0], colors[1], colors[2], 1.0); // RGBA
CGContextSetLineWidth(ctx, 1.0f);
CGSize textSize = [self.text sizeWithFont:self.font constrainedToSize:CGSizeMake(200, 9999)];
// handle textAlignement
int alignementXOffset = 0;
switch (self.textAlignment) {
case UITextAlignmentLeft:
break;
case UITextAlignmentCenter:
alignementXOffset = (self.frame.size.width - textSize.width)/2;
break;
case UITextAlignmentRight:
alignementXOffset = self.frame.size.width - textSize.width;
break;
}
CGContextMoveToPoint(ctx, alignementXOffset, self.bounds.size.height - 1);
CGContextAddLineToPoint(ctx, alignementXOffset+textSize.width, self.bounds.size.height - 1);
CGContextStrokePath(ctx);
[super drawRect:rect];
}
#end
Here's another, simpler solution (underline's width is not most accurate but it was good enough for me)
I have a UIView (_view_underline) that has White background, height of 1 pixel and I update its width everytime I update the text
// It's a shame you have to do custom stuff to underline text
- (void) underline {
float width = [[_txt_title text] length] * 10.0f;
CGRect prev_frame = [_view_underline frame];
prev_frame.size.width = width;
[_view_underline setFrame:prev_frame];
}
NSUnderlineStyleAttributeName which takes an NSNumber (where 0 is no underline) can be added to an attribute dictionary.
I don't know if this is any easier. But, it was easier for my purposes.
NSDictionary *attributes;
attributes = #{NSFontAttributeName:font, NSParagraphStyleAttributeName: style, NSUnderlineStyleAttributeName:[NSNumber numberWithInteger:1]};
[text drawInRect:CGRectMake(self.contentRect.origin.x, currentY, maximumSize.width, textRect.size.height) withAttributes:attributes];
You can use this my custom label!
You can also use interface builder to set
import UIKit
class YHYAttributedLabel : UILabel{
#IBInspectable
var underlineText : String = ""{
didSet{
self.attributedText = NSAttributedString(string: underlineText,
attributes: [NSAttributedString.Key.underlineStyle: NSUnderlineStyle.single.rawValue])
}
}
}

Resources