How to draw vertical text in UILabel - iphone

I'm currently working on drawing vertical Chinese text in a label. Here's what I am trying to achieve, albeit with Chinese Characters:
I've been planning to draw each character, rotate each character 90 degrees to the left, then rotating the entire label via affine transformations to get the final result. However, it feels awfully complicated. Is there an easier way to draw the text without complicated CoreGraphics magic that I'm missing?

Well, You can do like below:
labelObject.numberOfLines = 0;
labelObject.lineBreakMode = NSLineBreakByCharWrapping;
and setFrame with -- height:100, width:20 It will work fine..

It works
UILabel *lbl = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 30, 100)];
lbl.transform = CGAffineTransformMakeRotation((M_PI)/2);

Tried the method offered by Simha.IC but it didn't work well for me. Some characters are thinner than others and get placed two on a line. E.g.
W
ai
ti
n
g
The solution for me was to create a method that transforms the string itself into a multiline text by adding \n after each character. Here's the method:
- (NSString *)transformStringToVertical:(NSString *)originalString
{
NSMutableString *mutableString = [NSMutableString stringWithString:originalString];
NSRange stringRange = [mutableString rangeOfString:mutableString];
for (int i = 1; i < stringRange.length*2 - 2; i+=2)
{
[mutableString insertString:#"\n" atIndex:i];
}
return mutableString;
}
Then you just setup the label like this:
label.text = [self transformStringToVertical:myString];
CGRect labelFrame = label.frame;
labelFrame.size.width = label.font.pointSize;
labelFrame.size.height = label.font.lineHeight * myString.length;
label.frame = labelFrame;
Enjoy!

If you would like to rotate the whole label (including characters), you can do so as follows:
First add the QuartzCore library to your project.
Create a label:
UILabel* label = [[UILabel alloc] initWithFrame:CGRectMake(0.0, 0.0, 300.0, 30.0)];
[label setText:#"Label Text"];
Rotate the label:
[label setTransform:CGAffineTransformMakeRotation(-M_PI / 2)];
Depending on how you'd like to position the label you may need to set the anchor point. This sets the point around which a rotation occurs. Eg:
[label.layer setAnchorPoint:CGPointMake(0.0, 1.0)];

This is another way to draw vertical text, by subclassing UILabel. But it is some kind different of what the question want.
Objective-C
#implementation MyVerticalLabel
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect {
// Drawing code
CGContextRef context = UIGraphicsGetCurrentContext();
CGAffineTransform transform = CGAffineTransformMakeRotation(-M_PI_2);
CGContextConcatCTM(context, transform);
CGContextTranslateCTM(context, -rect.size.height, 0);
CGRect newRect = CGRectApplyAffineTransform(rect, transform);
newRect.origin = CGPointZero;
NSMutableParagraphStyle *textStyle = [[NSMutableParagraphStyle defaultParagraphStyle] mutableCopy];
textStyle.lineBreakMode = self.lineBreakMode;
textStyle.alignment = self.textAlignment;
NSDictionary *attributeDict =
#{
NSFontAttributeName : self.font,
NSForegroundColorAttributeName : self.textColor,
NSParagraphStyleAttributeName : textStyle,
};
[self.text drawInRect:newRect withAttributes:attributeDict];
}
#end
A sample image is following:
Swift
It can put on the storyboard, and watch the result directly. Like the image, it's frame will contain the vertical text. And text attributes, like textAlignment, font, work well too.
#IBDesignable
class MyVerticalLabel: UILabel {
override func drawRect(rect: CGRect) {
guard let text = self.text else {
return
}
// Drawing code
let context = UIGraphicsGetCurrentContext()
let transform = CGAffineTransformMakeRotation( CGFloat(-M_PI_2))
CGContextConcatCTM(context, transform)
CGContextTranslateCTM(context, -rect.size.height, 0)
var newRect = CGRectApplyAffineTransform(rect, transform)
newRect.origin = CGPointZero
let textStyle = NSMutableParagraphStyle.defaultParagraphStyle().mutableCopy() as! NSMutableParagraphStyle
textStyle.lineBreakMode = self.lineBreakMode
textStyle.alignment = self.textAlignment
let attributeDict: [String:AnyObject] = [
NSFontAttributeName: self.font,
NSForegroundColorAttributeName: self.textColor,
NSParagraphStyleAttributeName: textStyle,
]
let nsStr = text as NSString
nsStr.drawInRect(newRect, withAttributes: attributeDict)
}
}
Swift 4
override func draw(_ rect: CGRect) {
guard let text = self.text else {
return
}
// Drawing code
if let context = UIGraphicsGetCurrentContext() {
let transform = CGAffineTransform( rotationAngle: CGFloat(-Double.pi/2))
context.concatenate(transform)
context.translateBy(x: -rect.size.height, y: 0)
var newRect = rect.applying(transform)
newRect.origin = CGPoint.zero
let textStyle = NSMutableParagraphStyle.default.mutableCopy() as! NSMutableParagraphStyle
textStyle.lineBreakMode = self.lineBreakMode
textStyle.alignment = self.textAlignment
let attributeDict: [NSAttributedStringKey: AnyObject] = [NSAttributedStringKey.font: self.font, NSAttributedStringKey.foregroundColor: self.textColor, NSAttributedStringKey.paragraphStyle: textStyle]
let nsStr = text as NSString
nsStr.draw(in: newRect, withAttributes: attributeDict)
}
}

Swift 5
More easy way with CGAffineTransform
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var verticalText: UILabel
override func viewDidLoad() {
verticalText.transform = CGAffineTransform(rotationAngle:CGFloat.pi/2)
}
}

import UIKit
class VerticalLabel : UILabel {
private var _text : String? = nil
override var text : String? {
get {
return _text
}
set {
self.numberOfLines = 0
self.textAlignment = .center
self.lineBreakMode = .byWordWrapping
_text = newValue
if let t = _text {
var s = ""
for c in t {
s += "\(c)\n"
}
super.text = s
}
}
}
}

Related

UITextField - Rounded corner issue

I bring the issue forward which I face. I am creating a UITextField programmatically as below.
UItextField *mobileNumberField = [[UITextField alloc] initWithFrame:CGRectMake(10, 195, 300, 41)];
mobileNumberField.delegate = self;
mobileNumberField.borderStyle = UITextBorderStyleRoundedRect;
[mobileNumberField.layer setCornerRadius:14.0f];
mobileNumberField.placeholder = #"Mobile Number";
[self.paymentsHomeView addSubview:mobileNumberField];
The output is the attached image.
I dont know why is it breaking at the corners. Help me to fix my text field like the image attached below.
Just remove this line...
mobileNumberField.borderStyle = UITextBorderStyleRoundedRect;
and add this code also..
[mobileNumberField setBackgroundColor:[UIColor whiteColor]];
[mobileNumberField.layer setBorderColor:[UIColor grayColor].CGColor];
[mobileNumberField.layer setBorderWidth:1.0];
Update your like below.
UITextField *mobileNumberField = [[UITextField alloc] initWithFrame:CGRectMake(10, 195, 300, 41)];
mobileNumberField.delegate = self;
mobileNumberField.layer.borderWidth = 1.0f;
mobileNumberField.layer.borderColor = [UIColor lightGrayColor].CGColor;
mobileNumberField.
// mobileNumberField.borderStyle = UITextBorderStyleRoundedRect;
[mobileNumberField.layer setCornerRadius:14.0f];
mobileNumberField.placeholder = #"Mobile Number";
[self.paymentsHomeView addSubview:mobileNumberField];
textField.layer.cornerRadius=textfield.frame.size.height/2;
textField.clipsToBounds=YES;
The reason the corners are cut is because there is an enclosing view to the text field. When you set the corner radius, applies to THAT view and thus the corners of the inside text field seem to be cut - in reality they have not even changed.
The solution is to put the UITextField inside UIView, set textfield borderstyle to none. Then apply the border and corner radius specification to the uiview. Note the borderColor, which is very close, if not the same, to the UITextField borderColor.
As of writing, tested and works in Xcode 7.3.1, Swift 2.2, iOS 8 and 9.
Swift:
textField.borderStyle = UITextBorderStyle.None
textBorderView.layer.cornerRadius = 5
textBorderView.layer.borderWidth = 1
textBorderView.layer.borderColor = UIColor.lightGrayColor().colorWithAlphaComponent(0.2).CGColor
The following code has given me the following result in Swift 5, XCode 11.4.
Definitely more bells and whistles can be added. I think this good MVP
naviTextField = UITextField.init(frame: CGRect(x: 0, y: 0, width: (self.navigationController?.navigationBar.frame.size.width)!, height: 21))
self.navigationItem.titleView = naviTextField
naviTextField.becomeFirstResponder()
naviTextField.placeholder = "Type target name here"
naviTextField.borderStyle = .roundedRect
naviTextField.layer.cornerRadius = 5.0
naviTextField.textAlignment = .center
Here is the solution of your problem
UITextField * txtField = [[UITextField alloc]initWithFrame:CGRectMake(0, 0, 200, 50)];
[txtField setBorderStyle:UITextBorderStyleNone];
[txtField.layer setMasksToBounds:YES];
[txtField.layer setCornerRadius:10.0f];
[txtField.layer setBorderColor:[[UIColor lightGrayColor]CGColor]];
[txtField.layer setBorderWidth:1];
[txtField setTextAlignment:UITextAlignmentCenter];
[txtField setContentVerticalAlignment:UIControlContentVerticalAlignmentCenter];
[self.view addSubview:txtField];
Swift 3 solution:
I have written separate function to set border and corner radius to any layer in swift, you have to just pass the layer of any view, border width, corner radius and border color to the following function
` func setBorderAndCornerRadius(layer: CALayer, width: CGFloat, radius: CGFloat,color : UIColor ) {
layer.borderColor = color.cgColor
layer.borderWidth = width
layer.cornerRadius = radius
layer.masksToBounds = true
}
`
swift solution:
textField.layer.borderWidth = anyWidth
textField.layer.borderColor = anyColor
textField.layer.cornerRadius = textField.frame.size.height/2
textField.clipsToBounds = true
All above answer is good , but here I am adding the code through #IBDesignable.
#IBDesignable class DesignableUITextField: UITextField {
// Provides left padding for images
override func leftViewRect(forBounds bounds: CGRect) -> CGRect {
var textRect = super.leftViewRect(forBounds: bounds)
textRect.origin.x += leftPadding
return textRect
}
// Provides right padding for images
override func rightViewRect(forBounds bounds: CGRect) -> CGRect {
var textRect = super.rightViewRect(forBounds: bounds)
textRect.origin.x -= rightPadding
return textRect
}
#IBInspectable var leftImage: UIImage? {
didSet {
updateView()
}
}
#IBInspectable var rightImage: UIImage? {
didSet {
updateRightView()
}
}
#IBInspectable var leftPadding: CGFloat = 0
#IBInspectable var rightPadding: CGFloat = 0
#IBInspectable var gapPadding: CGFloat = 0
#IBInspectable var color: UIColor = UIColor.lightGray {
didSet {
updateView()
}
}
#IBInspectable var cornerRadius: CGFloat = 0
#IBInspectable var borderColor: UIColor? = .lightGray
override func draw(_ rect: CGRect) {
layer.cornerRadius = cornerRadius
layer.masksToBounds = true
layer.borderWidth = 1
layer.borderColor = borderColor?.cgColor
}
//#IBInspectable var roundCornersRadius: CGFloat = 0 {
// didSet{
// roundCornersRadiusTextField(radius: roundCornersRadius)
// }
// }
func roundCornersRadiusTextField(radius:CGFloat) {
roundCorners(corners: [UIRectCorner.topLeft, UIRectCorner.topRight, UIRectCorner.bottomLeft, UIRectCorner.bottomRight], radius:radius)
}
func roundBottomCornersRadius(radius:CGFloat) {
roundCorners(corners: [UIRectCorner.topLeft, UIRectCorner.topRight], radius:radius)
}
func updateView() {
if let image = leftImage {
leftViewMode = UITextField.ViewMode.always
let imageView = UIImageView(frame: CGRect(x: 0, y: 0, width: 20, height: 20))
imageView.contentMode = .scaleAspectFit
imageView.image = image
// Note: In order for your image to use the tint color, you have to select the image in the Assets.xcassets and change the "Render As" property to "Template Image".
imageView.tintColor = color
leftView = imageView
} else {
leftViewMode = UITextField.ViewMode.never
leftView = nil
}
// Placeholder text color
attributedPlaceholder = NSAttributedString(string: placeholder != nil ? placeholder! : "", attributes:[NSAttributedString.Key.foregroundColor: color])
}
func updateRightView() {
if let image = rightImage {
rightViewMode = UITextField.ViewMode.always
let imageView = UIImageView(frame: CGRect(x: 0, y: 0, width: 20, height: 20))
imageView.contentMode = .scaleAspectFit
imageView.image = image
// Note: In order for your image to use the tint color, you have to select the image in the Assets.xcassets and change the "Render As" property to "Template Image".
imageView.tintColor = color
rightView = imageView
} else {
rightViewMode = UITextField.ViewMode.never
rightView = nil
}
// Placeholder text color
attributedPlaceholder = NSAttributedString(string: placeholder != nil ? placeholder! : "", attributes:[NSAttributedString.Key.foregroundColor: color])
}
func roundCorners(corners:UIRectCorner, radius:CGFloat) {
let bounds = self.bounds
let maskPath = UIBezierPath(roundedRect: bounds, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius))
let maskLayer = CAShapeLayer()
maskLayer.frame = bounds
maskLayer.path = maskPath.cgPath
self.layer.mask = maskLayer
let frameLayer = CAShapeLayer()
frameLayer.frame = bounds
frameLayer.path = maskPath.cgPath
frameLayer.strokeColor = UIColor.darkGray.cgColor
frameLayer.fillColor = UIColor.init(red: 247, green: 247, blue: 247, alpha: 0).cgColor
self.layer.addSublayer(frameLayer)
}
private var textPadding: UIEdgeInsets {
let p: CGFloat = leftPadding + gapPadding + (leftView?.frame.width ?? 0)
return UIEdgeInsets(top: 0, left: p, bottom: 0, right: 5)
}
override open func textRect(forBounds bounds: CGRect) -> CGRect {
return bounds.inset(by: textPadding)
}
override open func placeholderRect(forBounds bounds: CGRect) -> CGRect {
return bounds.inset(by: textPadding)
}
override open func editingRect(forBounds bounds: CGRect) -> CGRect {
return bounds.inset(by: textPadding)
}}

How to add underline below text of UIButton in iPhone? [duplicate]

Can anyone suggest how to underline the title of a UIButton ? I have a UIButton of Custom type, and I want the Title to be underlined, but the Interface Builder does not provide any option to do so.
In Interface Builder when you select the Font Option for a Button, it provides option to select None, Single, Double, Color but none of these provide any changes to the Title on the Button.
Any help appreciated.
To use interface builder to underline, one has to:
Change it to attributed
Highlight the text in the Attributes inspector
Right click, choose Font and then Underline
Video someone else made
https://www.youtube.com/watch?v=5-ZnV3jQd9I
From iOS6 it is now possible to use an NSAttributedString to perform underlining (and anything else attributed strings support) in a much more flexible way:
NSMutableAttributedString *commentString = [[NSMutableAttributedString alloc] initWithString:#"The Quick Brown Fox"];
[commentString addAttribute:NSUnderlineStyleAttributeName value:[NSNumber numberWithInteger:NSUnderlineStyleSingle] range:NSMakeRange(0, [commentString length])];
[button setAttributedTitle:commentString forState:UIControlStateNormal];
Note: added this as another answer - as its a totally different solution to my previous one.
Edit:
oddly (in iOS8 at least) you have to underline the first character otherwise it doesn't work!
so as a workaround, set the first char underlined with clear colour!
// underline Terms and condidtions
NSMutableAttributedString* tncString = [[NSMutableAttributedString alloc] initWithString:#"View Terms and Conditions"];
// workaround for bug in UIButton - first char needs to be underlined for some reason!
[tncString addAttribute:NSUnderlineStyleAttributeName
value:#(NSUnderlineStyleSingle)
range:(NSRange){0,1}];
[tncString addAttribute:NSUnderlineColorAttributeName value:[UIColor clearColor] range:NSMakeRange(0, 1)];
[tncString addAttribute:NSUnderlineStyleAttributeName
value:#(NSUnderlineStyleSingle)
range:(NSRange){5,[tncString length] - 5}];
[tncBtn setAttributedTitle:tncString forState:UIControlStateNormal];
UIUnderlinedButton.h
#interface UIUnderlinedButton : UIButton {
}
+ (UIUnderlinedButton*) underlinedButton;
#end
UIUnderlinedButton.m
#implementation UIUnderlinedButton
+ (UIUnderlinedButton*) underlinedButton {
UIUnderlinedButton* button = [[UIUnderlinedButton alloc] init];
return [button autorelease];
}
- (void) drawRect:(CGRect)rect {
CGRect textRect = self.titleLabel.frame;
// need to put the line at top of descenders (negative value)
CGFloat descender = self.titleLabel.font.descender;
CGContextRef contextRef = UIGraphicsGetCurrentContext();
// set to same colour as text
CGContextSetStrokeColorWithColor(contextRef, self.titleLabel.textColor.CGColor);
CGContextMoveToPoint(contextRef, textRect.origin.x, textRect.origin.y + textRect.size.height + descender);
CGContextAddLineToPoint(contextRef, textRect.origin.x + textRect.size.width, textRect.origin.y + textRect.size.height + descender);
CGContextClosePath(contextRef);
CGContextDrawPath(contextRef, kCGPathStroke);
}
#end
You can do it in the interface builder itself.
Select the attribute inspector
Change the title type from plain to attributed
Set appropriate font size and text alignment
Then select the title text and set the font as underlined
It is very simple with attributed string
Creates a dictionary with set attributes and apply to the attributed string. Then you can set the attributed string as attibutedtitle in uibutton or attributedtext in uilabel.
NSDictionary *attrDict = #{NSFontAttributeName : [UIFont
systemFontOfSize:14.0],NSForegroundColorAttributeName : [UIColor
whiteColor]};
NSMutableAttributedString *title =[[NSMutableAttributedString alloc] initWithString:#"mybutton" attributes: attrDict];
[title addAttribute:NSUnderlineStyleAttributeName value:[NSNumber numberWithInteger:NSUnderlineStyleSingle] range:NSMakeRange(0,[commentString length])]; [btnRegLater setAttributedTitle:title forState:UIControlStateNormal];
Here is my function, works in Swift 1.2.
func underlineButton(button : UIButton, text: String) {
var titleString : NSMutableAttributedString = NSMutableAttributedString(string: text)
titleString.addAttribute(NSUnderlineStyleAttributeName, value: NSUnderlineStyle.StyleSingle.rawValue, range: NSMakeRange(0, count(text.utf8)))
button.setAttributedTitle(titleString, forState: .Normal)
}
UPDATE Swift 3.0 extension:
extension UIButton {
func underlineButton(text: String) {
let titleString = NSMutableAttributedString(string: text)
titleString.addAttribute(NSUnderlineStyleAttributeName, value: NSUnderlineStyle.styleSingle.rawValue, range: NSMakeRange(0, text.characters.count))
self.setAttributedTitle(titleString, for: .normal)
}
}
The Swift 5.0 version that works as of September 2019 in Xcode 10.3:
extension UIButton {
func underlineText() {
guard let title = title(for: .normal) else { return }
let titleString = NSMutableAttributedString(string: title)
titleString.addAttribute(
.underlineStyle,
value: NSUnderlineStyle.single.rawValue,
range: NSRange(location: 0, length: title.count)
)
setAttributedTitle(titleString, for: .normal)
}
}
To use it, set your button title first with button.setTitle("Button Title", for: .normal) and then call button.underlineText() to make that title underlined.
Nick's answer is a great, quick way to do this.
I added support in drawRect for shadows.
Nick's answer doesn't take into account if your button title has a shadow below the text:
But you can move the underline down by the height of the shadow like so:
CGFloat descender = self.titleLabel.font.descender;
CGContextRef contextRef = UIGraphicsGetCurrentContext();
CGFloat shadowHeight = self.titleLabel.shadowOffset.height;
descender += shadowHeight;
Then you'll get something like this:
For Swift 3 the following extension can be used:
extension UIButton {
func underlineButton(text: String) {
let titleString = NSMutableAttributedString(string: text)
titleString.addAttribute(NSUnderlineStyleAttributeName, value: NSUnderlineStyle.styleSingle.rawValue, range: NSMakeRange(0, text.characters.count))
self.setAttributedTitle(titleString, for: .normal)
}
}
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect {
CGRect textRect = self.titleLabel.frame;
// need to put the line at top of descenders (negative value)
CGFloat descender = self.titleLabel.font.descender;
CGContextRef contextRef = UIGraphicsGetCurrentContext();
UIColor *colr;
// set to same colour as text
if (self.isHighlighted || self.isSelected) {
colr=self.titleLabel.highlightedTextColor;
}
else{
colr= self.titleLabel.textColor;
}
CGContextSetStrokeColorWithColor(contextRef, colr.CGColor);
CGContextMoveToPoint(contextRef, textRect.origin.x, textRect.origin.y + textRect.size.height + descender);
CGContextAddLineToPoint(contextRef, textRect.origin.x + textRect.size.width, textRect.origin.y + textRect.size.height + descender);
CGContextClosePath(contextRef);
CGContextDrawPath(contextRef, kCGPathStroke);
}
//Override this to change the underline color to highlighted color
-(void)setHighlighted:(BOOL)highlighted
{
[super setHighlighted:highlighted];
// [self setNeedsDisplay];
}
Expanding on the answer by #Nick H247, I experienced an issue where firstly the underline was not redrawing when the button resized on rotation; this can be solved by setting your button to redraw like so:
myButton.contentMode = UIViewContentModeRedraw;
This forces the button to redraw when the bounds change.
Secondly, the original code assumed you only had 1 line of text in the button (my button wraps to 2 lines on rotation) and the underline only appears on the last line of text. The drawRect code can be modified to first calculate the number of lines in the button, then put an underline on every line rather than just the bottom, like so:
- (void) drawRect:(CGRect)rect {
CGRect textRect = self.titleLabel.frame;
// need to put the line at top of descenders (negative value)
CGFloat descender = self.titleLabel.font.descender;
CGContextRef contextRef = UIGraphicsGetCurrentContext();
// set to same colour as text
CGContextSetStrokeColorWithColor(contextRef, self.titleLabel.textColor.CGColor);
CGSize labelSize = [self.titleLabel.text sizeWithFont:self.titleLabel.font
constrainedToSize:self.titleLabel.frame.size
lineBreakMode:UILineBreakModeWordWrap];
CGSize labelSizeNoWrap = [self.titleLabel.text sizeWithFont:self.titleLabel.font forWidth:self.titleLabel.frame.size.width lineBreakMode:UILineBreakModeMiddleTruncation ];
int numberOfLines = abs(labelSize.height/labelSizeNoWrap.height);
for(int i = 1; i<=numberOfLines;i++) {
// Original code
// CGContextMoveToPoint(contextRef, textRect.origin.x, textRect.origin.y + textRect.size.height + descender + PADDING);
//
// CGContextAddLineToPoint(contextRef, textRect.origin.x + textRect.size.width, textRect.origin.y + textRect.size.height + descender);
CGContextMoveToPoint(contextRef, textRect.origin.x, textRect.origin.y + (labelSizeNoWrap.height*i) + descender + PADDING);
CGContextAddLineToPoint(contextRef, textRect.origin.x + textRect.size.width, textRect.origin.y + (labelSizeNoWrap.height*i) + descender);
CGContextClosePath(contextRef);
CGContextDrawPath(contextRef, kCGPathStroke);
}
}
Hope this code helps someone else!
In swift
func underlineButton(button : UIButton) {
var titleString : NSMutableAttributedString = NSMutableAttributedString(string: button.titleLabel!.text!)
titleString.addAttribute(NSUnderlineStyleAttributeName, value: NSUnderlineStyle.StyleSingle.rawValue, range: NSMakeRange(0, button.titleLabel!.text!.utf16Count))
button.setAttributedTitle(titleString, forState: .Normal)}
You can use this code to add underline with spacing in button.
When I tried to draw an underline from interface builder. It look like below image.
1 - Interface builder reference
And after using below code I achieved the result as I wanted.
2 - using described code
public func setTextUnderline()
{
let dummyButton: UIButton = UIButton.init()
dummyButton.setTitle(self.titleLabel?.text, for: .normal)
dummyButton.titleLabel?.font = self.titleLabel?.font
dummyButton.sizeToFit()
let dummyHeight = dummyButton.frame.size.height + 3
let bottomLine = CALayer()
bottomLine.frame = CGRect.init(x: (self.frame.size.width - dummyButton.frame.size.width)/2, y: -(self.frame.size.height - dummyHeight), width: dummyButton.frame.size.width, height: 1.0)
bottomLine.backgroundColor = self.titleLabel?.textColor.cgColor
self.layer.addSublayer(bottomLine)
}
How will one handle the case when we keep a button underlined pressed? In that case the button's textcolor changes according to highlighted color but line remains of original color. Let say if button text color in normal state is black then its underline will also have black color. The button's highlighted color is white. Keeping button pressed changes button text color from black to white but underline color remains black.
I believe it's some bug in font editor in XCode. If you using interface builder you have to change title from Plain to Attributed, open TextEdit create underlined text and copy-paste to textbox in XCode
Nick H247's answer but Swift approach:
import UIKit
class UnderlineUIButton: UIButton {
override func drawRect(rect: CGRect) {
super.drawRect(rect)
let textRect = self.titleLabel!.frame
var descender = self.titleLabel?.font.descender
var contextRef: CGContextRef = UIGraphicsGetCurrentContext();
CGContextSetStrokeColorWithColor(contextRef, self.titleLabel?.textColor.CGColor);
CGContextMoveToPoint(contextRef, textRect.origin.x, textRect.origin.y + textRect.size.height + descender!);
CGContextAddLineToPoint(contextRef, textRect.origin.x + textRect.size.width, textRect.origin.y + textRect.size.height + descender!);
CGContextClosePath(contextRef);
CGContextDrawPath(contextRef, kCGPathStroke);
}
}
func underline(text: String, state: UIControlState = .normal, color:UIColor? = nil) {
var titleString = NSMutableAttributedString(string: text)
if let color = color {
titleString = NSMutableAttributedString(string: text,
attributes: [NSForegroundColorAttributeName: color])
}
let stringRange = NSMakeRange(0, text.characters.count)
titleString.addAttribute(NSUnderlineStyleAttributeName,
value: NSUnderlineStyle.styleSingle.rawValue,
range: stringRange)
self.setAttributedTitle(titleString, for: state)
}
Swift 3 version for #NickH247's answer with custom underline color, linewidth and gap:
import Foundation
class UnderlinedButton: UIButton {
private let underlineColor: UIColor
private let thickness: CGFloat
private let gap: CGFloat
init(underlineColor: UIColor, thickness: CGFloat, gap: CGFloat, frame: CGRect? = nil) {
self.underlineColor = underlineColor
self.thickness = thickness
self.gap = gap
super.init(frame: frame ?? .zero)
}
override func draw(_ rect: CGRect) {
super.draw(rect)
guard let textRect = titleLabel?.frame,
let decender = titleLabel?.font.descender,
let context = UIGraphicsGetCurrentContext() else { return }
context.setStrokeColor(underlineColor.cgColor)
context.move(to: CGPoint(x: textRect.origin.x, y: textRect.origin.y + textRect.height + decender + gap))
context.setLineWidth(thickness)
context.addLine(to: CGPoint(x: textRect.origin.x + textRect.width, y: textRect.origin.y + textRect.height + decender + gap))
context.closePath()
context.drawPath(using: .stroke)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}

How to calculate UILabel height dynamically?

I want to calculate number of lines and height of UILabel dynamically from given text for same.
Try this
// UILabel *myLabel;
CGSize labelSize = [myLabel.text sizeWithFont:myLabel.font
constrainedToSize:myLabel.frame.size
lineBreakMode:NSLineBreakByWordWrapping];
CGFloat labelHeight = labelSize.height;
int lines = [myLabel.text sizeWithFont:myLabel.font
constrainedToSize:myLabel.frame.size
lineBreakMode:NSLineBreakByWordWrapping].height/16;
// '16' is font size
or
int lines = labelHeight/16;
NSLog(#"lines count : %i \n\n",lines);
or
int lines = [myLabel.text sizeWithFont:myLabel.font
constrainedToSize:myLabel.frame.size
lineBreakMode:UILineBreakModeWordWrap].height /myLabel.font.pointSize; //fetching font size from font
By Using Categories, Just Create the category class named as
UILabel+UILabelDynamicHeight.h
UILabel+UILabelDynamicHeight.m
No more tension about the height calculation. Please review the below implementation.
Updates for iOS7 & Above,iOS 7 below : Dynamically calculate the UILabel height
#define SYSTEM_VERSION_EQUAL_TO(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedSame)
#define SYSTEM_VERSION_GREATER_THAN(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedDescending)
#define SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] != NSOrderedAscending)
#define SYSTEM_VERSION_LESS_THAN(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedAscending)
#define SYSTEM_VERSION_LESS_THAN_OR_EQUAL_TO(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] != NSOrderedDescending)
#define iOS7_0 #"7.0"
UILabel+UILabelDynamicHeight.h
#import <UIKit/UIKit.h>
#interface UILabel (UILabelDynamicHeight)
#pragma mark - Calculate the size the Multi line Label
/*====================================================================*/
/* Calculate the size of the Multi line Label */
/*====================================================================*/
/**
* Returns the size of the Label
*
* #param aLabel To be used to calculte the height
*
* #return size of the Label
*/
-(CGSize)sizeOfMultiLineLabel;
#end
UILabel+UILabelDynamicHeight.m
#import "UILabel+UILabelDynamicHeight.h"
#implementation UILabel (UILabelDynamicHeight)
#pragma mark - Calculate the size,bounds,frame of the Multi line Label
/*====================================================================*/
/* Calculate the size,bounds,frame of the Multi line Label */
/*====================================================================*/
/**
* Returns the size of the Label
*
* #param aLabel To be used to calculte the height
*
* #return size of the Label
*/
-(CGSize)sizeOfMultiLineLabel{
//Label text
NSString *aLabelTextString = [self text];
//Label font
UIFont *aLabelFont = [self font];
//Width of the Label
CGFloat aLabelSizeWidth = self.frame.size.width;
if (SYSTEM_VERSION_LESS_THAN(iOS7_0)) {
//version < 7.0
return [aLabelTextString sizeWithFont:aLabelFont
constrainedToSize:CGSizeMake(aLabelSizeWidth, MAXFLOAT)
lineBreakMode:NSLineBreakByWordWrapping];
}
else if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(iOS7_0)) {
//version >= 7.0
//Return the calculated size of the Label
return [aLabelTextString boundingRectWithSize:CGSizeMake(aLabelSizeWidth, MAXFLOAT)
options:NSStringDrawingUsesLineFragmentOrigin
attributes:#{
NSFontAttributeName : aLabelFont
}
context:nil].size;
}
return [self bounds].size;
}
#end
Calling -sizeToFit on UILabel instance will automatically resize it to fit text it displays, no calculating required. If you need the size, you can get it from label's frame property after that.
label.numberOfLines = 0; // allows label to have as many lines as needed
label.text = #"some long text";
[label sizeToFit];
NSLog(#"Label's frame is: %#", NSStringFromCGRect(label.frame));
To summarize, you can calculate the height of a label by using its string and calling boundingRectWithSize. You must provide the font as an attribute, and include .usesLineFragmentOrigin for multi-line labels.
let labelWidth = label.frame.width
let maxLabelSize = CGSize(width: labelWidth, height: CGFloat.greatestFiniteMagnitude)
let actualLabelSize = label.text!.boundingRect(with: maxLabelSize, options: [.usesLineFragmentOrigin], attributes: [.font: label.font], context: nil)
let labelHeight = actualLabelSize.height(withWidth:labelWidth)
Some extensions to do just that:
Swift Version:
extension UILabel {
func textHeight(withWidth width: CGFloat) -> CGFloat {
guard let text = text else {
return 0
}
return text.height(withWidth: width, font: font)
}
func attributedTextHeight(withWidth width: CGFloat) -> CGFloat {
guard let attributedText = attributedText else {
return 0
}
return attributedText.height(withWidth: width)
}
}
extension String {
func height(withWidth width: CGFloat, font: UIFont) -> CGFloat {
let maxSize = CGSize(width: width, height: CGFloat.greatestFiniteMagnitude)
let actualSize = self.boundingRect(with: maxSize, options: [.usesLineFragmentOrigin], attributes: [.font : font], context: nil)
return actualSize.height
}
}
extension NSAttributedString {
func height(withWidth width: CGFloat) -> CGFloat {
let maxSize = CGSize(width: width, height: CGFloat.greatestFiniteMagnitude)
let actualSize = boundingRect(with: maxSize, options: [.usesLineFragmentOrigin], context: nil)
return actualSize.height
}
}
Objective-C Version:
UILabel+Utility.h
#import <UIKit/UIKit.h>
#interface UILabel (Utility)
- (CGFloat)textHeightForWidth:(CGFloat)width;
- (CGFloat)attributedTextHeightForWidth:(CGFloat)width;
#end
UILabel+Utility.m
#implementation NSString (Utility)
- (CGFloat)heightForWidth:(CGFloat)width font:(UIFont *)font {
CGSize maxSize = CGSizeMake(width, CGFLOAT_MAX);
CGSize actualSize = [self boundingRectWithSize:maxSize options:NSStringDrawingUsesLineFragmentOrigin attributes:#{NSFontAttributeName : font} context:nil].size;
return actualSize.height;
}
#end
#implementation NSAttributedString (Utility)
- (CGFloat)heightForWidth:(CGFloat)width {
CGSize maxSize = CGSizeMake(width, CGFLOAT_MAX);
CGSize actualSize = [self boundingRectWithSize:maxSize options:NSStringDrawingUsesLineFragmentOrigin context:nil].size;
return actualSize.height;
}
#end
#implementation UILabel (Utility)
- (CGFloat)textHeightForWidth:(CGFloat)width {
return [self.text heightForWidth:width font:self.font];
}
- (CGFloat)attributedTextHeightForWidth:(CGFloat)width {
return [self.attributedText heightForWidth:width];
}
#end
The current solution has been deprecated as of iOS 7.
Here is an updated solution:
+ (CGFloat)heightOfCellWithIngredientLine:(NSString *)ingredientLine
withSuperviewWidth:(CGFloat)superviewWidth
{
CGFloat labelWidth = superviewWidth - 30.0f;
// use the known label width with a maximum height of 100 points
CGSize labelContraints = CGSizeMake(labelWidth, 100.0f);
NSStringDrawingContext *context = [[NSStringDrawingContext alloc] init];
CGRect labelRect = [ingredientLine boundingRectWithSize:labelContraints
options:NSStringDrawingUsesLineFragmentOrigin
attributes:nil
context:context];
// return the calculated required height of the cell considering the label
return labelRect.size.height;
}
The reason that my solution is set up like this is because I am using a UITableViewCell and resizing the cell dynamically relative to how much room the label will take up.
Without calling sizeToFit, you can do this all numerically with a very plug and play solution:
+ (CGFloat)heightForText:(NSString*)text font:(UIFont*)font withinWidth:(CGFloat)width {
CGSize size = [text sizeWithAttributes:#{NSFontAttributeName:font}];
CGFloat area = size.height * size.width;
CGFloat height = roundf(area / width);
return ceilf(height / font.lineHeight) * font.lineHeight;
}
I use it a lot for UITableViewCells that have dynamically allocated heights.
Solves the attributes problem as well #Salman Zaidi.
Copy & paste this method & used It like:
[lblText setFrame:CGRectMake(lblText.frame.origin.x, lblText.frame.origin.y, width, [self getLabelHeight:lblText])];
- (CGFloat)getLabelHeight:(UILabel*)label
{
CGSize constraint = CGSizeMake(label.frame.size.width, CGFLOAT_MAX);
CGSize size;
NSStringDrawingContext *context = [[NSStringDrawingContext alloc] init];
CGSize boundingBox = [label.text boundingRectWithSize:constraint
options:NSStringDrawingUsesLineFragmentOrigin
attributes:#{NSFontAttributeName:label.font}
context:context].size;
size = CGSizeMake(ceil(boundingBox.width), ceil(boundingBox.height));
return size.height;
}
CGSize maxSize = CGSizeMake(lbl.frame.size.width, CGFLOAT_MAX);
CGSize requiredSize = [lbl sizeThatFits:maxSize];
CGFloat height=requiredSize.height
If you are using a UILabel with attributes, you can try the method textRect(forBounds:limitedToNumberOfLines).
This is my example:
let label = UILabel(frame: CGRect(x: 0, y: 0, width: 100, height: 30))
label.numberOfLines = 0
label.text = "Learn how to use RxSwift and RxCocoa to write applications that can react to changes in your underlying data without you telling it to do so."
let rectOfLabel = label.textRect(forBounds: CGRect(x: 0, y: 0, width: 100, height: CGFloat.greatestFiniteMagnitude), limitedToNumberOfLines: 0)
let rectOfLabelOneLine = label.textRect(forBounds: CGRect(x: 0, y: 0, width: 100, height: CGFloat.greatestFiniteMagnitude), limitedToNumberOfLines: 1)
let heightOfLabel = rectOfLabel.height
let heightOfLine = rectOfLabelOneLine.height
let numberOfLines = Int(heightOfLabel / heightOfLine)
And my results on the Playground:
To get height for the NSAttributedString use this function below. Where width - the width of your UILabel or UITextView
func getHeight(for attributedString: NSAttributedString, font: UIFont, width: CGFloat) -> CGFloat {
let textStorage = NSTextStorage(attributedString: attributedString)
let textContainter = NSTextContainer(size: CGSize(width: width, height: CGFloat.greatestFiniteMagnitude))
let layoutManager = NSLayoutManager()
layoutManager.addTextContainer(textContainter)
textStorage.addLayoutManager(layoutManager)
textStorage.addAttribute(NSAttributedString.Key.font, value: font, range: NSMakeRange(0, textStorage.length))
textContainter.lineFragmentPadding = 0.0
layoutManager.glyphRange(for: textContainter)
return layoutManager.usedRect(for: textContainter).size.height
}
To get height for String use this function, It is almost identical like the previous method:
func getHeight(for string: String, font: UIFont, width: CGFloat) -> CGFloat {
let textStorage = NSTextStorage(string: string)
let textContainter = NSTextContainer(size: CGSize(width: width, height: CGFloat.greatestFiniteMagnitude))
let layoutManager = NSLayoutManager()
layoutManager.addTextContainer(textContainter)
textStorage.addLayoutManager(layoutManager)
textStorage.addAttribute(NSAttributedString.Key.font, value: font, range: NSMakeRange(0, textStorage.length))
textContainter.lineFragmentPadding = 0.0
layoutManager.glyphRange(for: textContainter)
return layoutManager.usedRect(for: textContainter).size.height
}
You need to create an extension of String and call this method
func height(withConstrainedWidth width: CGFloat, font: UIFont) -> CGFloat {
let constraintRect = CGSize(width: width, height: .greatestFiniteMagnitude)
let boundingBox = self.boundingRect(with: constraintRect, options: .usesLineFragmentOrigin, attributes: [NSFontAttributeName: font], context: nil)
return ceil(boundingBox.height)
}
You must send the width of your label
In my case, I was using a fixed size header for each section but with a dynamically cell size in each header.
The cell's height, depends on the label's height.
Working with:
tableView.estimatedRowHeight = SomeNumber
tableView.rowHeight = UITableViewAutomaticDimension
Works but when using:
tableView.reloadSections(IndexSet(integer: sender.tag) , with: .automatic)
when a lot of headers are not collapsed, creates a lot of bugs such as header duplication (header type x below the same type) and weird animations when the framework reloads with animation, even when using with type .none (FYI, a fixed header height and cell height works).
The solution is making the use of heightForRowAt callback and calculate the height of the label by your self (plus the animation looks a lot better). Remember that the height is being called first.
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat
{
let object = dataDetailsController.getRowObject(forIndexPath: indexPath)
let label = UILabel(frame: tableView.frame)
let font = UIFont(name: "HelveticaNeue-Bold", size: 25)
label.text = object?.name
label.font = font
label.numberOfLines = 0
label.textAlignment = .center
label.sizeToFit()
let size = label.frame.height
return Float(size) == 0 ? 34 : size
}
This is the extension I use for calculating multiline UILabel heights, it's an adjusted snippet from a previous stack overflow post:
extension UILabel {
func estimatedHeight(forWidth: CGFloat, text: String, ofSize: CGFloat) -> CGFloat {
let size = CGSize(width: forWidth, height: CGFloat(MAXFLOAT))
let options = NSStringDrawingOptions.usesFontLeading.union(.usesLineFragmentOrigin)
let attributes = [NSFontAttributeName: UIFont.systemFont(ofSize: ofSize)]
let rectangleHeight = String(text).boundingRect(with: size, options: options, attributes: attributes, context: nil).height
return ceil(rectangleHeight)
}
}
To make UILabel fit the dynamic content you can use lines property in property inspector and set that as 0.
And you don't require to do any coding for this.
For more details you can check below demonstration video
https://www.youtube.com/watch?v=rVeDS_OGslU
if you want the label to take dynamic lines you may use this
label.numberOfLines = 0; // allows label to have as many lines as needed
label.text = #"some long text ";
[label sizeToFit];
NSLog(#"Label's frame is: %#", NSStringFromCGRect(label.frame));

How would I outline a UILabel in black? [duplicate]

All I want is a one pixel black border around my white UILabel text.
I got as far as subclassing UILabel with the code below, which I clumsily cobbled together from a few tangentially related online examples. And it works but it's very, very slow (except on the simulator) and I couldn't get it to center the text vertically either (so I hard-coded the y value on the last line temporarily). Ahhhh!
void ShowStringCentered(CGContextRef gc, float x, float y, const char *str) {
CGContextSetTextDrawingMode(gc, kCGTextInvisible);
CGContextShowTextAtPoint(gc, 0, 0, str, strlen(str));
CGPoint pt = CGContextGetTextPosition(gc);
CGContextSetTextDrawingMode(gc, kCGTextFillStroke);
CGContextShowTextAtPoint(gc, x - pt.x / 2, y, str, strlen(str));
}
- (void)drawRect:(CGRect)rect{
CGContextRef theContext = UIGraphicsGetCurrentContext();
CGRect viewBounds = self.bounds;
CGContextTranslateCTM(theContext, 0, viewBounds.size.height);
CGContextScaleCTM(theContext, 1, -1);
CGContextSelectFont (theContext, "Helvetica", viewBounds.size.height, kCGEncodingMacRoman);
CGContextSetRGBFillColor (theContext, 1, 1, 1, 1);
CGContextSetRGBStrokeColor (theContext, 0, 0, 0, 1);
CGContextSetLineWidth(theContext, 1.0);
ShowStringCentered(theContext, rect.size.width / 2.0, 12, [[self text] cStringUsingEncoding:NSASCIIStringEncoding]);
}
I just have a nagging feeling that I'm overlooking a simpler way to do this. Perhaps by overriding "drawTextInRect", but I can't seem to get drawTextInRect to bend to my will at all despite staring at it intently and frowning really really hard.
I was able to do it by overriding drawTextInRect:
- (void)drawTextInRect:(CGRect)rect {
CGSize shadowOffset = self.shadowOffset;
UIColor *textColor = self.textColor;
CGContextRef c = UIGraphicsGetCurrentContext();
CGContextSetLineWidth(c, 1);
CGContextSetLineJoin(c, kCGLineJoinRound);
CGContextSetTextDrawingMode(c, kCGTextStroke);
self.textColor = [UIColor whiteColor];
[super drawTextInRect:rect];
CGContextSetTextDrawingMode(c, kCGTextFill);
self.textColor = textColor;
self.shadowOffset = CGSizeMake(0, 0);
[super drawTextInRect:rect];
self.shadowOffset = shadowOffset;
}
A simpler solution is to use an Attributed String like so:
Swift 4:
let strokeTextAttributes: [NSAttributedStringKey : Any] = [
NSAttributedStringKey.strokeColor : UIColor.black,
NSAttributedStringKey.foregroundColor : UIColor.white,
NSAttributedStringKey.strokeWidth : -2.0,
]
myLabel.attributedText = NSAttributedString(string: "Foo", attributes: strokeTextAttributes)
Swift 4.2:
let strokeTextAttributes: [NSAttributedString.Key : Any] = [
.strokeColor : UIColor.black,
.foregroundColor : UIColor.white,
.strokeWidth : -2.0,
]
myLabel.attributedText = NSAttributedString(string: "Foo", attributes: strokeTextAttributes)
On a UITextField you can set the defaultTextAttributes and the attributedPlaceholder as well.
Note that the NSStrokeWidthAttributeName has to be negative in this case, i.e. only the inner outlines work.
After reading the accepted answer and the two corrections to it and the answer from Axel Guilmin, I decided to compile an overall solution in Swift, that suits me:
import UIKit
class UIOutlinedLabel: UILabel {
var outlineWidth: CGFloat = 1
var outlineColor: UIColor = UIColor.whiteColor()
override func drawTextInRect(rect: CGRect) {
let strokeTextAttributes = [
NSStrokeColorAttributeName : outlineColor,
NSStrokeWidthAttributeName : -1 * outlineWidth,
]
self.attributedText = NSAttributedString(string: self.text ?? "", attributes: strokeTextAttributes)
super.drawTextInRect(rect)
}
}
You can add this custom UILabel class to an existing label in the Interface Builder and change the thickness of the border and its color by adding User Defined Runtime Attributes like this:
Result:
There is one issue with the answer's implementation. Drawing a text with stroke has a slightly different character glyph width than drawing a text without stroke, which can produce "uncentered" results. It can be fixed by adding an invisible stroke around the fill text.
Replace:
CGContextSetTextDrawingMode(c, kCGTextFill);
self.textColor = textColor;
self.shadowOffset = CGSizeMake(0, 0);
[super drawTextInRect:rect];
with:
CGContextSetTextDrawingMode(context, kCGTextFillStroke);
self.textColor = textColor;
[[UIColor clearColor] setStroke]; // invisible stroke
self.shadowOffset = CGSizeMake(0, 0);
[super drawTextInRect:rect];
I'm not 100% sure, if that's the real deal, because I don't know if self.textColor = textColor; has the same effect as [textColor setFill], but it should work.
Disclosure: I'm the developer of THLabel.
I've released a UILabel subclass a while ago, which allows an outline in text and other effects. You can find it here: https://github.com/tobihagemann/THLabel
A Swift 4 class version based off the answer by kprevas
import Foundation
import UIKit
public class OutlinedText: UILabel{
internal var mOutlineColor:UIColor?
internal var mOutlineWidth:CGFloat?
#IBInspectable var outlineColor: UIColor{
get { return mOutlineColor ?? UIColor.clear }
set { mOutlineColor = newValue }
}
#IBInspectable var outlineWidth: CGFloat{
get { return mOutlineWidth ?? 0 }
set { mOutlineWidth = newValue }
}
override public func drawText(in rect: CGRect) {
let shadowOffset = self.shadowOffset
let textColor = self.textColor
let c = UIGraphicsGetCurrentContext()
c?.setLineWidth(outlineWidth)
c?.setLineJoin(.round)
c?.setTextDrawingMode(.stroke)
self.textColor = mOutlineColor;
super.drawText(in:rect)
c?.setTextDrawingMode(.fill)
self.textColor = textColor
self.shadowOffset = CGSize(width: 0, height: 0)
super.drawText(in:rect)
self.shadowOffset = shadowOffset
}
}
It can be implemented entirely in the Interface Builder by setting the UILabel's custom class to OutlinedText. You will then have the ability to set the outline's width and color from the Properties pane.
If your goal is something like this:
Here is how I achieved it: I added a new label of a custom class as a Subview to my current UILabel (inspired by this answer).
Just copy & paste it into your project and you are good to go:
extension UILabel {
func addTextOutline(usingColor outlineColor: UIColor, outlineWidth: CGFloat) {
class OutlinedText: UILabel{
var outlineWidth: CGFloat = 0
var outlineColor: UIColor = .clear
override public func drawText(in rect: CGRect) {
let shadowOffset = self.shadowOffset
let textColor = self.textColor
let c = UIGraphicsGetCurrentContext()
c?.setLineWidth(outlineWidth)
c?.setLineJoin(.round)
c?.setTextDrawingMode(.stroke)
self.textAlignment = .center
self.textColor = outlineColor
super.drawText(in:rect)
c?.setTextDrawingMode(.fill)
self.textColor = textColor
self.shadowOffset = CGSize(width: 0, height: 0)
super.drawText(in:rect)
self.shadowOffset = shadowOffset
}
}
let textOutline = OutlinedText()
let outlineTag = 9999
if let prevTextOutline = viewWithTag(outlineTag) {
prevTextOutline.removeFromSuperview()
}
textOutline.outlineColor = outlineColor
textOutline.outlineWidth = outlineWidth
textOutline.textColor = textColor
textOutline.font = font
textOutline.text = text
textOutline.tag = outlineTag
sizeToFit()
addSubview(textOutline)
textOutline.frame = CGRect(x: -(outlineWidth / 2), y: -(outlineWidth / 2),
width: bounds.width + outlineWidth,
height: bounds.height + outlineWidth)
}
}
USAGE:
yourLabel.addTextOutline(usingColor: .red, outlineWidth: 6)
it also works for a UIButton with all its animations:
yourButton.titleLabel?.addTextOutline(usingColor: .red, outlineWidth: 6)
If you want to animate something complicated, the best way is to programmaticly take a screenshot of it an animate that instead!
To take a screenshot of a view, you'll need code a little like this:
UIGraphicsBeginImageContext(mainContentView.bounds.size);
[mainContentView.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *viewImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
Where mainContentView is the view you want to take a screenshot of. Add viewImage to a UIImageView and animate that.
Hope that speeds up your animation!!
N
As MuscleRumble mentioned, the accepted answer's border is a bit off center. I was able to correct this by setting the stroke width to zero instead of changing the color to clear.
i.e. replacing:
CGContextSetTextDrawingMode(c, kCGTextFill);
self.textColor = textColor;
self.shadowOffset = CGSizeMake(0, 0);
[super drawTextInRect:rect];
with:
CGContextSetTextDrawingMode(c, kCGTextFillStroke);
self.textColor = textColor;
CGContextSetLineWidth(c, 0); // set stroke width to zero
self.shadowOffset = CGSizeMake(0, 0);
[super drawTextInRect:rect];
I would've just commented on his answer but apparently I'm not "reputable" enough.
This won't create an outline per-se, but it will put a shadow around the text, and if you make the shadow radius small enough it could resemble an outline.
label.layer.shadowColor = [[UIColor blackColor] CGColor];
label.layer.shadowOffset = CGSizeMake(0.0f, 0.0f);
label.layer.shadowOpacity = 1.0f;
label.layer.shadowRadius = 1.0f;
I don't know whether it is compatible with older versions of iOS..
Anyway, I hope it helps...
if ALL you want is a one pixel black border around my white UILabel text,
then
i do think you're making the problem harder than it is...
I don't know by memory which 'draw rect / frameRect' function you should use, but it will be easy for you to find. this method just demonstrates the strategy (let the superclass do the work!):
- (void)drawRect:(CGRect)rect
{
[super drawRect:rect];
[context frameRect:rect]; // research which rect drawing function to use...
}
I found an issue with the main answer. The text position is not necessarily centered correctly to sub-pixel location, so that the outline can be mismatched around the text. I fixed it using the following code, which uses CGContextSetShouldSubpixelQuantizeFonts(ctx, false):
- (void)drawTextInRect:(CGRect)rect
{
CGContextRef ctx = UIGraphicsGetCurrentContext();
[self.textOutlineColor setStroke];
[self.textColor setFill];
CGContextSetShouldSubpixelQuantizeFonts(ctx, false);
CGContextSetLineWidth(ctx, self.textOutlineWidth);
CGContextSetLineJoin(ctx, kCGLineJoinRound);
CGContextSetTextDrawingMode(ctx, kCGTextStroke);
[self.text drawInRect:rect withFont:self.font lineBreakMode:NSLineBreakByWordWrapping alignment:self.textAlignment];
CGContextSetTextDrawingMode(ctx, kCGTextFill);
[self.text drawInRect:rect withFont:self.font lineBreakMode:NSLineBreakByWordWrapping alignment:self.textAlignment];
}
This assumes that you defined textOutlineColor and textOutlineWidth as properties.
Here is the another answer to set outlined text on label.
extension UILabel {
func setOutLinedText(_ text: String) {
let attribute : [NSAttributedString.Key : Any] = [
NSAttributedString.Key.strokeColor : UIColor.black,
NSAttributedString.Key.foregroundColor : UIColor.white,
NSAttributedString.Key.strokeWidth : -2.0,
NSAttributedString.Key.font : UIFont.boldSystemFont(ofSize: 12)
] as [NSAttributedString.Key : Any]
let customizedText = NSMutableAttributedString(string: text,
attributes: attribute)
attributedText = customizedText
}
}
set outlined text simply using the extension method.
lblTitle.setOutLinedText("Enter your email address or username")
it is also possible to subclass UILabel with the following logic:
- (void)setText:(NSString *)text {
[self addOutlineForAttributedText:[[NSAttributedString alloc] initWithString:text]];
}
- (void)setAttributedText:(NSAttributedString *)attributedText {
[self addOutlineForAttributedText:attributedText];
}
- (void)addOutlineForAttributedText:(NSAttributedString *)attributedText {
NSDictionary *strokeTextAttributes = #{
NSStrokeColorAttributeName: [UIColor blackColor],
NSStrokeWidthAttributeName : #(-2)
};
NSMutableAttributedString *attrStr = [[NSMutableAttributedString alloc] initWithAttributedString:attributedText];
[attrStr addAttributes:strokeTextAttributes range:NSMakeRange(0, attrStr.length)];
super.attributedText = attrStr;
}
and if you set text in Storyboard then:
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
self = [super initWithCoder:aDecoder];
if (self) {
// to apply border for text from storyboard
[self addOutlineForAttributedText:[[NSAttributedString alloc] initWithString:self.text]];
}
return self;
}
Why don't you create a 1px border UIView in Photoshop, then set a UIView with the image, and position it behind your UILabel?
Code:
UIView *myView;
UIImage *imageName = [UIImage imageNamed:#"1pxBorderImage.png"];
UIColor *tempColour = [[UIColor alloc] initWithPatternImage:imageName];
myView.backgroundColor = tempColour;
[tempColour release];
It's going to save you subclassing an object and it's fairly simple to do.
Not to mention if you want to do animation, it's built into the UIView class.
To put a border with rounded edges around a UILabel I do the following:
labelName.layer.borderWidth = 1;
labelName.layer.borderColor = [[UIColor grayColor] CGColor];
labelName.layer.cornerRadius = 10;
(don't forget to include QuartzCore/QuartzCore.h)

Add text to CALayer

Is it possible to add a UILabel to a CALayer without subclassing and drawing it in drawInContext:?
Thanks!
CATextLayer *label = [[CATextLayer alloc] init];
[label setFont:#"Helvetica-Bold"];
[label setFontSize:20];
[label setFrame:validFrame];
[label setString:#"Hello"];
[label setAlignmentMode:kCAAlignmentCenter];
[label setForegroundColor:[[UIColor whiteColor] CGColor]];
[layer addSublayer:label];
[label release];
I don't think you can add a UIView subclass to a CALayer object. However if you want to draw text on a CALayer object, it can be done using the drawing functions provided in NSString UIKit additions as shown below. While my code is done in the delegate's drawLayer:inContext method, the same can be used in subclass' drawInContext: method. Is there any specific UILabel functionality that you want to leverage?
- (void) drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx {
CGContextSetFillColorWithColor(ctx, [[UIColor darkTextColor] CGColor]);
UIGraphicsPushContext(ctx);
/*[word drawInRect:layer.bounds
withFont:[UIFont systemFontOfSize:32]
lineBreakMode:UILineBreakModeWordWrap
alignment:UITextAlignmentCenter];*/
[word drawAtPoint:CGPointMake(30.0f, 30.0f)
forWidth:200.0f
withFont:[UIFont boldSystemFontOfSize:32]
lineBreakMode:UILineBreakModeClip];
UIGraphicsPopContext();
}
Just to document my approach, I did it like this in Swift 4+ :
let textlayer = CATextLayer()
textlayer.frame = CGRect(x: 20, y: 20, width: 200, height: 18)
textlayer.fontSize = 12
textlayer.alignmentMode = .center
textlayer.string = stringValue
textlayer.isWrapped = true
textlayer.truncationMode = .end
textlayer.backgroundColor = UIColor.white.cgColor
textlayer.foregroundColor = UIColor.black.cgColor
caLayer.addSublayer(textlayer) // caLayer is and instance of parent CALayer
Your UILabel already has a CALayer behind it. If you are putting together several CALayers, you can just add the UILabel's layer as a sublayer of one of those (by using its layer property).
If it's direct text drawing in a layer that you want, the UIKit NSString additions that Deepak points to are the way to go. For an example of this in action, the Core Plot framework has a Mac / iPhone platform-independent CALayer subclass which does text rendering, CPTextLayer.
Add a CATextLayer as a sublayer and set the string property. That would be easiest and you can easily use a layout manager to make it very generic.
The answers below are fine, just make sure you add otherwise you text will be blurry:
textLayer.contentsScale = UIScreen.main.scale
Final code for Swift:
let textLayer = CATextLayer()
textLayer.frame = CGRect(x: 0, y: 0, width: 60, height: 15)
textLayer.fontSize = 12
textLayer.string = "my text"
textLayer.foregroundColor = UIColor.red.cgColor
textLayer.contentsScale = UIScreen.main.scale
class MyCALayer: CALayer {
......
override func draw(in ctx: CGContext) {
UIGraphicsPushContext(ctx)
let text = "這是一段普通的文字"
let textAttrs: [NSAttributedString.Key: Any] = [.font: UIFont.systemFont(ofSize: 20), .foregroundColor: UIColor.blue]
var drawPoint = CGPoint(x: 0, y: 0)
for char in text {
let word = NSAttributedString(string: String(char),
attributes: textAttrs)
let wordBounds = word.boundingRect(with: CGSize(width: .max, height: .max), context: nil)
word.draw(at: drawPoint)
drawPoint = CGPoint(x: drawPoint.x + wordBounds.width, y: drawPoint.y)
let whitespace = NSAttributedString(string: " ", attributes: textAttrs)
let whitespaceBounds = whitespace.boundingRect(with: CGSize(width: .max, height: .max), context: nil)
whitespace.draw(at: drawPoint)
drawPoint = CGPoint(x: drawPoint.x + whitespaceBounds.width, y: drawPoint.y)
}
UIGraphicsPopContext()
}
......
}
The result
Always remember to remove previous sublayers, if you gonna add another one, to prevent duplicating views:
if let sublayers = layer.sublayers {
for sublayer in sublayers {
sublayer.removeFromSuperlayer()
}
}