Center align placeholder in textfield - ios

I know this question been asked many times but as drawinrect deprecated and I need this in ios 8.As I have a textfield and I need the placeholder in center align and rest of the test left align.Please help me out.

You can center the placeholder by using an attributedPlaceholder with a paragraph style whose alignment is set to .center:
let centeredParagraphStyle = NSMutableParagraphStyle()
centeredParagraphStyle.alignment = .center
textField.attributedPlaceholder = NSAttributedString(
string: "Placeholder",
attributes: [.paragraphStyle: centeredParagraphStyle]
)

Create and connect IBOutlet to your textField. In YourViewController.m
#interface YourViewController () <UITextFieldDelegate>
#property (weak, nonatomic) IBOutlet UITextField *txt;
In your viewDidLoad
self.txt.delegate=self;
self.txt.textAlignment=NSTextAlignmentCenter;
Write this delegate method..this method calls everytime when text in text field changes.
- (BOOL) textField: (UITextField *)theTextField shouldChangeCharactersInRange: (NSRange)range replacementString: (NSString *)string {
NSRange textFieldRange = NSMakeRange(0, [self.txt.text length]);
// Check If textField is empty. If empty align your text field to center, so that placeholder text will show center aligned
if (NSEqualRanges(range, textFieldRange) && [string length] == 0) {
self.txt.textAlignment=NSTextAlignmentCenter;
}
else //else align textfield to left.
{
self.txt.textAlignment=NSTextAlignmentLeft;
}
return YES;
}

The answer by #Clay Ellis is correct, here it is for Objective-C:
UITextField* field = [[UITextField alloc] initWithFrame: fieldRect];
NSTextAlignment alignment = NSTextAlignmentCenter;
NSMutableParagraphStyle* alignmentSetting = [[NSMutableParagraphStyle alloc] init];
alignmentSetting.alignment = alignment;
NSDictionary *attributes = #{NSParagraphStyleAttributeName : alignmentSetting};
NSAttributedString *str = [[NSAttributedString alloc] initWithString:placeholder attributes: attributes];
field.attributedPlaceholder = str;

The answer by #Clay Ellis in Swift 5
let centeredParagraphStyle = NSMutableParagraphStyle()
centeredParagraphStyle.alignment = .center
let attributedPlaceholder = NSAttributedString(string: "Placeholder", attributes: [NSAttributedString.Key.paragraphStyle: centeredParagraphStyle])
textField.attributedPlaceholder = attributedPlaceholder

based on Clay Ellis answer
Details
Xcode Version 10.2.1 (10E1001), Swift 5
Solution 1
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.alignment = .center
textField.attributedText = NSAttributedString(string: placeholder, attributes: [.paragraphStyle: paragraphStyle])
Solution 2
import Foundation
extension String {
func toAttributed(alignment: NSTextAlignment) -> NSAttributedString {
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.alignment = .center
return toAttributed(attributes: [.paragraphStyle: paragraphStyle])
}
func toAttributed(attributes: [NSAttributedString.Key : Any]? = nil) -> NSAttributedString {
return NSAttributedString(string: self, attributes: attributes)
}
}
Usage of the solution 2
// Way 1
textField.attributedPlaceholder = text.attributedString(alignment: .center)
// Way 2
textField.attributedPlaceholder = "title".attributedString(alignment: .center)
Full sample
Do not forget to add the solution code here
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let textField = UITextField()
textField.borderStyle = .roundedRect
view.addSubview(textField)
//textField.attributedPlaceholder = getAttributedString1()
textField.attributedPlaceholder = getAttributedString2()
textField.translatesAutoresizingMaskIntoConstraints = false
textField.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 24).isActive = true
textField.leftAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leftAnchor, constant: 16).isActive = true
view.safeAreaLayoutGuide.rightAnchor.constraint(equalTo: textField.rightAnchor, constant: 16).isActive = true
}
private func getAttributedString1() -> NSAttributedString {
return "placeholder".toAttributed(alignment: .center)
}
private func getAttributedString2() -> NSAttributedString {
var attributes = [NSAttributedString.Key: Any]()
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.alignment = .center
attributes[.paragraphStyle] = paragraphStyle
attributes[.font] = UIFont.systemFont(ofSize: 12, weight: .bold)
attributes[.foregroundColor] = UIColor.black.withAlphaComponent(0.5)
attributes[.underlineStyle] = NSUnderlineStyle.single.rawValue
attributes[.underlineColor] = UIColor.red
return "placeholder".toAttributed(attributes: attributes)
}
}
Results

Related

Swift - UITextView text is getting cut when applying custom font

I am using UITextView inside super view UIView to display text with many different types of fonts.
But UITextView text is getting cut when applying specific font Smoothie_Life_Swirls_2.0
I am creating textView programmatically as below,
textView = ResizableTextView(frame: CGRect(x: 0, y: 0, width: contentView.frame.size.width, height: contentView.frame.size.height))
textView.isScrollEnabled = false
textView.alignment = .center
textView.backgroundColor = UIColor.clear
textView.fontName = "Helveticas"
textView.fontSize = contentView.frame.size.height
textView.textColor = textColor
textView.autoresizingMask = [.flexibleTopMargin, .flexibleRightMargin, .flexibleBottomMargin, .flexibleLeftMargin, .flexibleWidth, .flexibleHeight]
textView.delegate = self
contentView.addSubview(textView)
textView.becomeFirstResponder()
Here ResizableTextView is my UITextView class, in which fontName and fontSize will be set
public class ResizableTextView: UITextView {
public private(set) var textAttributes: [String: AnyObject] = [:]
public var fontName: String = "Helvetica" {
didSet {
let font = UIFont(name: fontName, size: fontSize)
textAttributes[NSFontAttributeName] = font
self.attributedText = NSAttributedString(string: self.text, attributes: textAttributes)
self.font = font
}
}
public var fontSize: CGFloat = 20 {
didSet {
let font = UIFont(name: fontName, size: fontSize)
textAttributes[NSFontAttributeName] = font
self.attributedText = NSAttributedString(string: self.text, attributes: textAttributes)
self.font = font
}
}
}
When I set Smoothie_Life_Swirls_2.0 font to textView using ResizableTextView's fontName property, at that time text is getting cut.
Text1 - Getting Output
Text2 - Required Output
I want to fit the text in UITextView same as Text2, but I am getting the result as displayed in Text1.

Adjust font size of NSMutableAttributedString proportional to UILabel's frame height

In my project, I am using swift 3.0. Right now I am using following class (UILabel subclass) to adjust font size based on UILabel frame height. When UILabel frame change occurs, layoutSubviews recalculates proportional font size.
class Label: UILabel {
// FIXME: - properties
var fontSize: CGFloat = 0
var frameHeight: CGFloat = 0
// FIXME: - proportional font size adjustment
override func layoutSubviews() {
super.layoutSubviews()
font = font.withSize(frame.size.height * (fontSize / frameHeight))
}
}
HOW TO USE:
private let id: Label = {
let label = Label()
label.textAlignment = .left
label.numberOfLines = 1
label.font = UIFont.systemFont(ofSize: 17, weight: .semibold)
label.textColor = UIColor(hex: 0x212121, alpha: 1)
label.fontSize = 17
label.frameHeight = 20
label.clipsToBounds = true
return label
}()
Now I want to show some part of String in UILabel as BOLD TEXT and remaining in REGULAR TEXT. So I have found some help on this thread: Making text bold using attributed string in swift
I am using "Prajeet Shrestha's" extension for NSMutableAttributedString.
// "Prajeet Shrestha's" extension
extension NSMutableAttributedString {
func bold(_ text:String) -> NSMutableAttributedString {
let attrs:[String:AnyObject] = [NSFontAttributeName : UIFont(name: "AvenirNext-Medium", size: 12)!]
let boldString = NSMutableAttributedString(string:"\(text)", attributes:attrs)
self.append(boldString)
return self
}
func normal(_ text:String)->NSMutableAttributedString {
let normal = NSAttributedString(string: text)
self.append(normal)
return self
}
}
But I am not getting how I can change font size of this NSMutableAttributedString, when UILabel frame change occurs?
Any help appeciated.
Try this
Source Looping Through NSAttributedString Attributes to Increase Font SIze
mutableStringObj?.enumerateAttribute(NSFontAttributeName, in: NSRange(location: 0, length: mutableStringObj?.length), options: [], usingBlock: {(_ value: Any, _ range: NSRange, _ stop: Bool) -> Void in
if value {
var oldFont: UIFont? = (value as? UIFont)
var newFont: UIFont? = oldFont?.withSize(CGFloat(oldFont?.pointSize * 2))
res?.removeAttribute(NSFontAttributeName, range: range)
res?.addAttribute(NSFontAttributeName, value: newFont, range: range)
}
})
Finally come up with an answer.
I created seperate custom UILabel subclass as follows:
class AttrLabel: UILabel {
// FIXME: - properties
var fontSize: CGFloat = 0
var frameHeight: CGFloat = 0
// FIXME: - proportional font size adjustment
override func layoutSubviews() {
super.layoutSubviews()
guard let oldAttrText = attributedText else {
return
}
let mutableAttributedText = NSMutableAttributedString(attributedString: oldAttrText)
mutableAttributedText.beginEditing()
mutableAttributedText.enumerateAttribute(NSFontAttributeName, in: NSRange(location: 0, length: mutableAttributedText.length), options: []) { (_ value: Any?, _ range: NSRange, _ stop: UnsafeMutablePointer<ObjCBool>) in
if let attributeFont = value as? UIFont {
let newFont = attributeFont.withSize(self.frame.size.height * (self.fontSize / self.frameHeight))
mutableAttributedText.removeAttribute(NSFontAttributeName, range: range)
mutableAttributedText.addAttribute(NSFontAttributeName, value: newFont, range: range)
}
}
mutableAttributedText.endEditing()
attributedText = mutableAttributedText
}
}
HOW TO USE:
private let id: AttrLabel = {
let label = AttrLabel()
label.textAlignment = .left
label.numberOfLines = 1
label.fontSize = 17
label.frameHeight = 20
label.clipsToBounds = true
return label
}()
SETTING ATTRIBUTED TEXT
let idStr = NSMutableAttributedString()
id.attributedText = idStr.attrStr(text: "BOLD TEXT: ", font: UIFont.systemFont(ofSize: 17, weight: .semibold), textColor: UIColor(hex: 0x212121, alpha: 1)).attrStr(text: "REGULAR WEIGHT TEXT.", font: UIFont.systemFont(ofSize: 17, weight: .regular), textColor: UIColor(hex: 0x212121, alpha: 1))
"Prajeet Shrestha's" extension for NSMutableAttributedString modified by me
extension NSMutableAttributedString {
func attrStr(text: String, font: UIFont, textColor: UIColor) -> NSMutableAttributedString {
let attributes: [String: Any] = [
NSFontAttributeName: font,
NSForegroundColorAttributeName: textColor
]
let string = NSMutableAttributedString(string: text, attributes: attributes)
self.append(string)
return self
}
}
Try Using label property adjustsFontSizeToFitWidth AND minimumScaleFactor like this:
label.adjustsFontSizeToFitWidth = true
label.minimumScaleFactor = 0.2
then you also need to increase number of lines like this any number instead of 10
label.numberOfLines = 10

How to Increase Line spacing in UILabel in Swift

I have a label which has few lines of text and I want to increase the spacing between the lines. There are similar questions asked by others but the solutions don't solve my problems. Also my label may or may not contain paragraphs. I am new to Swift. Is there a solution using storyboard? Or only through NSAttributedString its possible?
Programatically add LineSpacing to your UILabel using following snippet.
Earlier Swift version
let attributedString = NSMutableAttributedString(string: "Your text")
// *** Create instance of `NSMutableParagraphStyle`
let paragraphStyle = NSMutableParagraphStyle()
// *** set LineSpacing property in points ***
paragraphStyle.lineSpacing = 2 // Whatever line spacing you want in points
// *** Apply attribute to string ***
attributedString.addAttribute(NSParagraphStyleAttributeName, value:paragraphStyle, range:NSMakeRange(0, attributedString.length))
// *** Set Attributed String to your label ***
label.attributedText = attributedString
Swift 4.0
let attributedString = NSMutableAttributedString(string: "Your text")
// *** Create instance of `NSMutableParagraphStyle`
let paragraphStyle = NSMutableParagraphStyle()
// *** set LineSpacing property in points ***
paragraphStyle.lineSpacing = 2 // Whatever line spacing you want in points
// *** Apply attribute to string ***
attributedString.addAttribute(NSAttributedStringKey.paragraphStyle, value:paragraphStyle, range:NSMakeRange(0, attributedString.length))
// *** Set Attributed String to your label ***
label.attributedText = attributedString
Swift 4.2
let attributedString = NSMutableAttributedString(string: "Your text")
// *** Create instance of `NSMutableParagraphStyle`
let paragraphStyle = NSMutableParagraphStyle()
// *** set LineSpacing property in points ***
paragraphStyle.lineSpacing = 2 // Whatever line spacing you want in points
// *** Apply attribute to string ***
attributedString.addAttribute(NSAttributedString.Key.paragraphStyle, value:paragraphStyle, range:NSMakeRange(0, attributedString.length))
// *** Set Attributed String to your label ***
label.attributedText = attributedString
From Interface Builder:
Programmatically:
SWift 4 & 4.2
Using label extension
extension UILabel {
func setLineSpacing(lineSpacing: CGFloat = 0.0, lineHeightMultiple: CGFloat = 0.0) {
guard let labelText = self.text else { return }
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.lineSpacing = lineSpacing
paragraphStyle.lineHeightMultiple = lineHeightMultiple
let attributedString:NSMutableAttributedString
if let labelattributedText = self.attributedText {
attributedString = NSMutableAttributedString(attributedString: labelattributedText)
} else {
attributedString = NSMutableAttributedString(string: labelText)
}
// (Swift 4.2 and above) Line spacing attribute
attributedString.addAttribute(NSAttributedString.Key.paragraphStyle, value:paragraphStyle, range:NSMakeRange(0, attributedString.length))
// (Swift 4.1 and 4.0) Line spacing attribute
attributedString.addAttribute(NSAttributedStringKey.paragraphStyle, value:paragraphStyle, range:NSMakeRange(0, attributedString.length))
self.attributedText = attributedString
}
}
Now call extension function
let label = UILabel()
let stringValue = "Set\nUILabel\nline\nspacing"
// Pass value for any one argument - lineSpacing or lineHeightMultiple
label.setLineSpacing(lineSpacing: 2.0) . // try values 1.0 to 5.0
// or try lineHeightMultiple
//label.setLineSpacing(lineHeightMultiple = 2.0) // try values 0.5 to 2.0
Or using label instance (Just copy & execute this code to see result)
let label = UILabel()
let stringValue = "Set\nUILabel\nline\nspacing"
let attrString = NSMutableAttributedString(string: stringValue)
var style = NSMutableParagraphStyle()
style.lineSpacing = 24 // change line spacing between paragraph like 36 or 48
style.minimumLineHeight = 20 // change line spacing between each line like 30 or 40
// Line spacing attribute
attrString.addAttribute(NSAttributedStringKey.paragraphStyle, value: style, range: NSRange(location: 0, length: stringValue.characters.count))
// Character spacing attribute
attrString.addAttribute(NSAttributedStringKey.kern, value: 2, range: NSMakeRange(0, attrString.length))
label.attributedText = attrString
Swift 3
let label = UILabel()
let stringValue = "Set\nUILabel\nline\nspacing"
let attrString = NSMutableAttributedString(string: stringValue)
var style = NSMutableParagraphStyle()
style.lineSpacing = 24 // change line spacing between paragraph like 36 or 48
style.minimumLineHeight = 20 // change line spacing between each line like 30 or 40
attrString.addAttribute(NSParagraphStyleAttributeName, value: style, range: NSRange(location: 0, length: stringValue.characters.count))
label.attributedText = attrString
You can control the line spacing in storyboard.
Same question.
Recent solution for Swift 5.0
private extension UILabel {
// MARK: - spacingValue is spacing that you need
func addInterlineSpacing(spacingValue: CGFloat = 2) {
// MARK: - Check if there's any text
guard let textString = text else { return }
// MARK: - Create "NSMutableAttributedString" with your text
let attributedString = NSMutableAttributedString(string: textString)
// MARK: - Create instance of "NSMutableParagraphStyle"
let paragraphStyle = NSMutableParagraphStyle()
// MARK: - Actually adding spacing we need to ParagraphStyle
paragraphStyle.lineSpacing = spacingValue
// MARK: - Adding ParagraphStyle to your attributed String
attributedString.addAttribute(
.paragraphStyle,
value: paragraphStyle,
range: NSRange(location: 0, length: attributedString.length
))
// MARK: - Assign string that you've modified to current attributed Text
attributedText = attributedString
}
}
And the usage:
let yourLabel = UILabel()
let yourText = "Hello \n world \n !"
yourLabel.text = yourText
yourLabel.addInterlineSpacing(spacingValue: 1.5)
Swift 4 and Swift 5
extension NSAttributedString {
func withLineSpacing(_ spacing: CGFloat) -> NSAttributedString {
let attributedString = NSMutableAttributedString(attributedString: self)
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.lineBreakMode = .byTruncatingTail
paragraphStyle.lineSpacing = spacing
attributedString.addAttribute(.paragraphStyle,
value: paragraphStyle,
range: NSRange(location: 0, length: string.count))
return NSAttributedString(attributedString: attributedString)
}
}
How to use
let example = NSAttributedString(string: "This is Line 1 \nLine 2 \nLine 3 ").withLineSpacing(15)
testLabel.attributedText = example
You can use this reusable extension:
extension String {
func lineSpaced(_ spacing: CGFloat) -> NSAttributedString {
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.lineSpacing = spacing
let attributedString = NSAttributedString(string: self, attributes: [NSAttributedString.Key.paragraphStyle: paragraphStyle])
return attributedString
}
}
Dipen's answer updated for Swift 4
let attr = NSMutableAttributedString(string: today)
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.lineSpacing = 2
attr.addAttribute(.paragraphStyle, value: paragraphStyle, range: NSMakeRange(0, attr.length))
label.attributedText = attr;
extension UILabel {
var spacing: CGFloat {
get {return 0}
set {
let textAlignment = self.textAlignment
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.lineSpacing = newValue
let attributedString = NSAttributedString(string: self.text ?? "", attributes: [NSAttributedString.Key.paragraphStyle: paragraphStyle])
self.attributedText = attributedString
self.textAlignment = textAlignment
}
}
}
let label = UILabel()
label.text = "test"
label.spacing = 10
//Swift 4:
func set(text:String,
inLabel:UILabel,
withLineSpacing:CGFloat,
alignment:NSTextAlignment){
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.lineSpacing = withLineSpacing
let attrString = NSMutableAttributedString(string: text)
attrString.addAttribute(NSAttributedStringKey.paragraphStyle,
value:paragraphStyle,
range:NSMakeRange(0, attrString.length))
inLabel.attributedText = attrString
inLabel.textAlignment = alignment
}
Create LabelStyle
struct LabelStyle {
let font: UIFont
let fontMetrics: UIFontMetrics?
let lineHeight: CGFloat?
let tracking: CGFloat
init(font: UIFont, fontMetrics: UIFontMetrics? = nil, lineHeight: CGFloat? = nil, tracking: CGFloat = 0) {
self.font = font
self.fontMetrics = fontMetrics
self.lineHeight = lineHeight
self.tracking = tracking
}
func attributes(for alignment: NSTextAlignment, lineBreakMode: NSLineBreakMode) -> [NSAttributedString.Key: Any] {
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.alignment = alignment
paragraphStyle.lineBreakMode = lineBreakMode
var baselineOffset: CGFloat = .zero
if let lineHeight = lineHeight {
let lineHeightMultiple = lineHeight / font.lineHeight
paragraphStyle.lineHeightMultiple = lineHeightMultiple
baselineOffset = 1 / lineHeightMultiple
let scaledLineHeight: CGFloat = fontMetrics?.scaledValue(for: lineHeight) ?? lineHeight
paragraphStyle.minimumLineHeight = scaledLineHeight
paragraphStyle.maximumLineHeight = scaledLineHeight
}
return [
NSAttributedString.Key.paragraphStyle: paragraphStyle,
NSAttributedString.Key.kern: tracking,
NSAttributedString.Key.baselineOffset: baselineOffset,
NSAttributedString.Key.font: font
]
}
}
Create custom Label class and use our style
public class Label: UILabel {
var style: LabelStyle? { nil }
public override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
super.traitCollectionDidChange(previousTraitCollection)
if previousTraitCollection?.preferredContentSizeCategory != traitCollection.preferredContentSizeCategory {
updateText()
}
}
convenience init(text: String?, textColor: UIColor) {
self.init()
self.text = text
self.textColor = textColor
}
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
updateText()
}
private func commonInit() {
font = style?.font
adjustsFontForContentSizeCategory = true
}
private func updateText() {
text = super.text
}
public override var text: String? {
get {
guard style?.attributes != nil else {
return super.text
}
return attributedText?.string
}
set {
guard let style = style else {
super.text = newValue
return
}
guard let newText = newValue else {
attributedText = nil
super.text = nil
return
}
let attributes = style.attributes(for: textAlignment, lineBreakMode: lineBreakMode)
attributedText = NSAttributedString(string: newText, attributes: attributes)
}
}
}
Create concrete Label
public final class TitleLabel {
override var style: LabelStyle? {
LabelStyle(
font: UIFont.Title(),
lineHeight: 26.21,
tracking: 0.14
)
}
}
and use it
#IBOutlet weak var titleLabel: TitleLabel!
In addition to using attributed strings & paragraph styles, for small adjustemnts, font descriptors can also come in handy.
For instance:
let font: UIFont = .init(
descriptor: UIFontDescriptor
.preferredFontDescriptor(withTextStyle: .body)
.withSymbolicTraits(.traitLooseLeading)!,
size: 0
)
This will create a font with a looser leading, resulting in a text with a slightly larger line height (it adds 2 points) than the default system font. traitTightLeading can also be used for the opposite effect (it reduces the leading of the font by 2 points).
I wrote a blog post comparing the approaches here: https://bootstragram.com/blog/line-height-with-uikit/.
This solution worked for swift 5
this is reference to answer of https://stackoverflow.com/a/62116213/13171606
I Made some changes for "NSMutableAttributedString" and included the full example, i think it will help u all
Note: Please Adjust Color and Font style if found any error.
Extension
extension NSAttributedString {
func withLineSpacing(_ spacing: CGFloat) -> NSMutableAttributedString {
let attributedString = NSMutableAttributedString(attributedString: self)
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.lineBreakMode = .byTruncatingTail
paragraphStyle.lineSpacing = spacing
attributedString.addAttribute(.paragraphStyle,
value: paragraphStyle,
range: NSRange(location: 0, length: string.count))
return NSMutableAttributedString(attributedString: attributedString)
}
}
Implementation Example
let myAttributedText = NSMutableAttributedString(string: "Please enter the required details to change your AAAAAAAAA AAAAA AAAAA. Maximum AAAAA can be AAA AA AAA AA.\n\nNote: If you do not have a AAAAA AAAA then please AAAAAAA us at 111-111-111 or send us an email AAAA AAAA AAA AAAAAAAAAA AAAAA address at xxxxxxxxxxxxxxxxxxxxxxxxxxxx.", attributes: [
.font: UIFont.systemFont(ofSize: 14),
.foregroundColor: UIColor.gray,
.kern: 0.0]).withLineSpacing(8)
myAttributedText.addAttributes([
.font: UIFont.systemFont(ofSize: 14),
.foregroundColor: UIColor.blue],
range: NSRange(location: 174, length: 11))
myAttributedText.addAttributes([
.font: UIFont.systemFont(ofSize: 14),
.foregroundColor: UIColor.blue],
range: NSRange(location: 248, length: 28))
UILable
let myLabel: UILabel = {
let label = UILabel()
label.textAlignment = .left
label.numberOfLines = 0
label.attributedText = myAttributedText //Here is your Attributed String
return label
}()

Swift - UIButton with two lines of text

I was wondering if it is possible to create a UIButton with two lines of text. I need each line to have a different font size. The first line will be 17 point and the second will be 11 point. I've tried messing with putting two labels inside of a UIButton, but I can't get them to stay inside the bounds of the button.
I'm attempting to do all of this in the ui builder, and not programmatically.
Thanks
There are two questions.
I was wondering if it is possible to create a UIButton with two lines
of text
This is possible through using the storyboard or programmatically.
Storyboard:
Change the 'Line Break Mode' to Character Wrap or Word Wrap and use Alt/Option + Enter key to enter a new line in the UIButton's Title field.
Programmatically:
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
btnTwoLine?.titleLabel?.lineBreakMode = NSLineBreakMode.ByWordWrapping;
}
I need each line to have a different font size
1
The worst case is, you can use a custom UIButton class and add two labels within it.
The better way is, make use of NSMutableAttributedString. Note that,this can be achieved through only programmatically.
Swift 5:
#IBOutlet weak var btnTwoLine: UIButton?
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
//applying the line break mode
textResponseButton?.titleLabel?.lineBreakMode = NSLineBreakMode.byWordWrapping;
let buttonText: NSString = "hello\nthere"
//getting the range to separate the button title strings
let newlineRange: NSRange = buttonText.range(of: "\n")
//getting both substrings
var substring1 = ""
var substring2 = ""
if(newlineRange.location != NSNotFound) {
substring1 = buttonText.substring(to: newlineRange.location)
substring2 = buttonText.substring(from: newlineRange.location)
}
//assigning diffrent fonts to both substrings
let font1: UIFont = UIFont(name: "Arial", size: 17.0)!
let attributes1 = [NSMutableAttributedString.Key.font: font1]
let attrString1 = NSMutableAttributedString(string: substring1, attributes: attributes1)
let font2: UIFont = UIFont(name: "Arial", size: 11.0)!
let attributes2 = [NSMutableAttributedString.Key.font: font2]
let attrString2 = NSMutableAttributedString(string: substring2, attributes: attributes2)
//appending both attributed strings
attrString1.append(attrString2)
//assigning the resultant attributed strings to the button
textResponseButton?.setAttributedTitle(attrString1, for: [])
}
Older Swift
#IBOutlet weak var btnTwoLine: UIButton?
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
//applying the line break mode
btnTwoLine?.titleLabel?.lineBreakMode = NSLineBreakMode.ByWordWrapping;
var buttonText: NSString = "hello\nthere"
//getting the range to separate the button title strings
var newlineRange: NSRange = buttonText.rangeOfString("\n")
//getting both substrings
var substring1: NSString = ""
var substring2: NSString = ""
if(newlineRange.location != NSNotFound) {
substring1 = buttonText.substringToIndex(newlineRange.location)
substring2 = buttonText.substringFromIndex(newlineRange.location)
}
//assigning diffrent fonts to both substrings
let font:UIFont? = UIFont(name: "Arial", size: 17.0)
let attrString = NSMutableAttributedString(
string: substring1 as String,
attributes: NSDictionary(
object: font!,
forKey: NSFontAttributeName) as [NSObject : AnyObject])
let font1:UIFont? = UIFont(name: "Arial", size: 11.0)
let attrString1 = NSMutableAttributedString(
string: substring2 as String,
attributes: NSDictionary(
object: font1!,
forKey: NSFontAttributeName) as [NSObject : AnyObject])
//appending both attributed strings
attrString.appendAttributedString(attrString1)
//assigning the resultant attributed strings to the button
btnTwoLine?.setAttributedTitle(attrString, forState: UIControlState.Normal)
}
Output
I was looking for nearly the same topic, except that I don't need two different font sizes. In case someone is looking for a simple solution:
let button = UIButton()
button.titleLabel?.numberOfLines = 0
button.titleLabel?.lineBreakMode = .byWordWrapping
button.setTitle("Foo\nBar", for: .normal)
button.titleLabel?.textAlignment = .center
button.sizeToFit()
button.addTarget(self, action: #selector(rightBarButtonTapped), for: .allEvents)
navigationItem.rightBarButtonItem = UIBarButtonItem(customView: button)
I have notice an issue in most of the solutions which is while making line break mode to "Character Wrap" the second line will be left aligned to the first line
To make all the lines centered.
just change the title From Plain to Attributed and then you can make each line centered
change line break to character wrap , select your button and in attribute inspector go to line break and change it to character wrap
SWIFT 3 Syntax
let str = NSMutableAttributedString(string: "First line\nSecond Line")
str.addAttribute(NSFontAttributeName, value: UIFont.systemFont(ofSize: 17), range: NSMakeRange(0, 10))
str.addAttribute(NSFontAttributeName, value: UIFont.systemFont(ofSize: 12), range: NSMakeRange(11, 11))
button.setAttributedTitle(str, for: .normal)
I have fixed this and my solution it was only in the Storyboard.
Changes:
It added in Identity Inspector -> User Defined Runtime Attributes (these KeyPaths):
numberOfLines = 2
titleLabel.textAlignment = 1
User Defined Runtime Attributes
I added this in attributes inspector:
line break = word wrap
Word wrap
You need to do some of this in code. you can't set 2 different fonts in IB. In addition to changing the line break mode to character wrap, you need something like this to set the title,
override func viewDidLoad() {
super.viewDidLoad()
var str = NSMutableAttributedString(string: "First line\nSecond Line")
str.addAttribute(NSFontAttributeName, value: UIFont.systemFontOfSize(17), range: NSMakeRange(0, 10))
str.addAttribute(NSFontAttributeName, value: UIFont.systemFontOfSize(12), range: NSMakeRange(11, 11))
button.setAttributedTitle(str, forState: .Normal)
}
New with Xcode 13 (iOS 15)
Starting with Xcode 13, the button's title and subtitle may have their attributes set separately.
Using Storyboard:
In the Attribute Inspector for the button, select "Attributed" by Title. Then change font size of the title and the subtitle.
Or Programmatically:
// Create Title
let titleSettings = AttributeContainer.font( UIFont(name: "HelveticaNeue-Italic", size: 17)! )
yourButton.configuration?.attributedTitle = AttributedString("Button's Title", attributes: titleSettings)
// Create Subtitle
let subtitleSettings = AttributeContainer.font( UIFont(name: "HelveticaNeue-Italic", size: 11)! )
yourButton.configuration?.attributedSubtitle = AttributedString("Button's Subtitle", attributes: subtitleSettings)
One way to do it is with labels, I guess. I did this, and it seems to work ok. I could create this as a UIButton and then expose the labels, I guess. I don't know if this makes any sense.
let firstLabel = UILabel()
firstLabel.backgroundColor = UIColor.lightGrayColor()
firstLabel.text = "Hi"
firstLabel.textColor = UIColor.blueColor()
firstLabel.textAlignment = NSTextAlignment.Center
firstLabel.frame = CGRectMake(0, testButton.frame.height * 0.25, testButton.frame.width, testButton.frame.height * 0.2)
testButton.addSubview(firstLabel)
let secondLabel = UILabel()
secondLabel.backgroundColor = UIColor.lightGrayColor()
secondLabel.textColor = UIColor.blueColor()
secondLabel.font = UIFont(name: "Arial", size: 12)
secondLabel.text = "There"
secondLabel.textAlignment = NSTextAlignment.Center
secondLabel.frame = CGRectMake(0, testButton.frame.height * 0.5, testButton.frame.width, testButton.frame.height * 0.2)
testButton.addSubview(secondLabel)
The suggested solutions unfortunately did not work out for me when I wanted to have a mutliline button inside a CollectionView. Then a colleague showed me a workaround which I wanted to share in case someone has the same problem - hope this helps! Create a class which inherits from UIControl and extend it with a label, which will then behave similar like a button.
class MultilineButton: UIControl {
let label: UILabel = {
$0.translatesAutoresizingMaskIntoConstraints = false
$0.numberOfLines = 0
$0.textAlignment = .center
return $0
}(UILabel())
override init(frame: CGRect) {
super.init(frame: frame)
addSubview(label)
NSLayoutConstraint.activate([
label.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor),
label.trailingAnchor.constraint(equalTo: layoutMarginsGuide.trailingAnchor),
label.topAnchor.constraint(equalTo: layoutMarginsGuide.topAnchor),
label.bottomAnchor.constraint(equalTo: layoutMarginsGuide.bottomAnchor)
])
}
override var isHighlighted: Bool {
didSet {
backgroundColor = backgroundColor?.withAlphaComponent(isHighlighted ? 0.7 : 1.0)
label.textColor = label.textColor.withAlphaComponent(isHighlighted ? 0.7 : 1.0)
}
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
my way:
func setButtonTitle(title: String, subtitle: String, button: UIButton){
//applying the line break mode
button.titleLabel?.lineBreakMode = NSLineBreakMode.byWordWrapping;
let title = NSMutableAttributedString(string: title, attributes: Attributes.biggestLabel)
let subtitle = NSMutableAttributedString(string: subtitle, attributes: Attributes.label)
let char = NSMutableAttributedString(string: "\n", attributes: Attributes.biggestLabel)
title.append(char)
title.append(subtitle)
button.setAttributedTitle(title, for: .normal)
}

iOS NSAttributedString on UIButton

I'm using iOS 6, so attributed strings should be easy to use, right? Well... not so much.
What I want to do:
Using a custom subclass of UIButton (it doesn't do anything custom to titleLabel), I'd like to have a multi-line, attributed title that is:
All caps (I realize that's not part of the attributes) on the first line
Bolded on the first line
Underlined on the first line
"Normal" weight on the second line
Non-underlined on the second line
Centered on both lines
I've been able to get #'s 1 through 5 so far (at least, I thought I did, but current testing is yielding errors with multi-line text), but when I tried to do something (anything!) to get the text to be centered, my app keeps crashing. When I try to get all 6 items working (through various methods), I get the following crash/error:
Terminating app due to uncaught exception
'NSInternalInconsistencyException', reason:
'NSAttributedString invalid for autoresizing,
it must have a single spanning paragraph style
(or none) with a non-wrapping lineBreakMode.'
Based on what I've tried, it appears that I can have one of the following options, but not both:
A multi-line, centered label
An attributed label
I can live with one or the other if I must, but I can't believe that I can't have what seems to be a fairly straightforward concept.
Can someone please tell me what I've got wrong?
Here's the last iteration of the code I'm trying:
NSMutableParagraphStyle *style = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
[style setAlignment:NSTextAlignmentCenter];
[style setLineBreakMode:NSLineBreakByWordWrapping];
UIFont *font1 = [UIFont fontWithName:#"HelveticaNeue-Medium" size:20.0f];
UIFont *font2 = [UIFont fontWithName:#"HelveticaNeue-Light" size:20.0f];
NSDictionary *dict1 = #{NSUnderlineStyleAttributeName:#(NSUnderlineStyleSingle),
NSFontAttributeName:font1};
NSDictionary *dict2 = #{NSUnderlineStyleAttributeName:#(NSUnderlineStyleNone),
NSFontAttributeName:font2};
NSMutableAttributedString *attString = [[NSMutableAttributedString alloc] init];
[attString appendAttributedString:[[NSAttributedString alloc] initWithString:#"LINE 1\n" attributes:dict1]];
[attString appendAttributedString:[[NSAttributedString alloc] initWithString:#"line 2" attributes:dict2]];
[[self buttonToStyle] setAttributedTitle:attString forState:UIControlStateNormal];
[[[self buttonToStyle] titleLabel] setNumberOfLines:0];
[[[self buttonToStyle] titleLabel] setLineBreakMode:NSLineBreakByWordWrapping];
It looks to me like you forgot in your code to use the "style" object that you set up.. you just instantiated it. You should modify your code to look like this:
NSMutableParagraphStyle *style = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
[style setAlignment:NSTextAlignmentCenter];
[style setLineBreakMode:NSLineBreakByWordWrapping];
UIFont *font1 = [UIFont fontWithName:#"HelveticaNeue-Medium" size:20.0f];
UIFont *font2 = [UIFont fontWithName:#"HelveticaNeue-Light" size:20.0f];
NSDictionary *dict1 = #{NSUnderlineStyleAttributeName:#(NSUnderlineStyleSingle),
NSFontAttributeName:font1,
NSParagraphStyleAttributeName:style}; // Added line
NSDictionary *dict2 = #{NSUnderlineStyleAttributeName:#(NSUnderlineStyleNone),
NSFontAttributeName:font2,
NSParagraphStyleAttributeName:style}; // Added line
NSMutableAttributedString *attString = [[NSMutableAttributedString alloc] init];
[attString appendAttributedString:[[NSAttributedString alloc] initWithString:#"LINE 1\n" attributes:dict1]];
[attString appendAttributedString:[[NSAttributedString alloc] initWithString:#"line 2" attributes:dict2]];
[self.resolveButton setAttributedTitle:attString forState:UIControlStateNormal];
[[self.resolveButton titleLabel] setNumberOfLines:0];
[[self.resolveButton titleLabel] setLineBreakMode:NSLineBreakByWordWrapping];
Note that I only added the lines that define the NSParagraphStyleAttributeName.. everything else is the same.. and this is what I get for the button:
And here it is in Swift 3.0
let style = NSMutableParagraphStyle()
style.alignment = .center
style.lineBreakMode = .byWordWrapping
guard
let font1 = UIFont(name: "HelveticaNeue-Medium", size: 20),
let font2 = UIFont(name: "HelveticaNeue-Light", size: 20) else { return }
let dict1:[String:Any] = [
NSUnderlineStyleAttributeName:NSUnderlineStyle.styleSingle.rawValue,
NSFontAttributeName:font1,
NSParagraphStyleAttributeName:style
]
let dict2:[String:Any] = [
NSUnderlineStyleAttributeName:NSUnderlineStyle.styleNone.rawValue,
NSFontAttributeName:font2,
NSParagraphStyleAttributeName:style
]
let attString = NSMutableAttributedString()
attString.append(NSAttributedString(string: "LINE 1", attributes: dict1))
attString.append(NSAttributedString(string: "line 2", attributes: dict2))
button.setAttributedTitle(attString, for: .normal)
button.titleLabel?.numberOfLines = 0
button.titleLabel?.lineBreakMode = .byWordWrapping
With Swift 5.1 and iOS 13.1, you can use the UIButton subclass implementation below in order to solve your problem:
import UIKit
class CustomButton: UIButton {
required init(title: String, subtitle: String) {
super.init(frame: CGRect.zero)
let style = NSMutableParagraphStyle()
style.alignment = NSTextAlignment.center
style.lineBreakMode = NSLineBreakMode.byWordWrapping
let titleAttributes: [NSAttributedString.Key : Any] = [
NSAttributedString.Key.foregroundColor: UIColor.label,
NSAttributedString.Key.underlineStyle : NSUnderlineStyle.single.rawValue,
NSAttributedString.Key.font : UIFont.preferredFont(forTextStyle: UIFont.TextStyle.largeTitle),
NSAttributedString.Key.paragraphStyle : style
]
let subtitleAttributes = [
NSAttributedString.Key.foregroundColor: UIColor.label,
NSAttributedString.Key.font : UIFont.preferredFont(forTextStyle: UIFont.TextStyle.body),
NSAttributedString.Key.paragraphStyle : style
]
let attributedString = NSMutableAttributedString(string: title, attributes: titleAttributes)
attributedString.append(NSAttributedString(string: "\n"))
attributedString.append(NSAttributedString(string: subtitle, attributes: subtitleAttributes))
setAttributedTitle(attributedString, for: UIControl.State.normal)
titleLabel?.numberOfLines = 0
titleLabel?.lineBreakMode = NSLineBreakMode.byWordWrapping
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Usage:
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let button = CustomButton(title: "Title", subtitle: "Subtitle")
button.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(button)
let horizontalConstraint = button.centerXAnchor.constraint(equalTo: view.centerXAnchor)
let verticalConstraint = button.centerYAnchor.constraint(equalTo: view.centerYAnchor)
NSLayoutConstraint.activate([horizontalConstraint, verticalConstraint])
}
}
As an alternative if you really need a button of type system, you may use the following code:
import UIKit
extension UIButton {
static func customSystemButton(title: String, subtitle: String) -> UIButton {
let style = NSMutableParagraphStyle()
style.alignment = NSTextAlignment.center
style.lineBreakMode = NSLineBreakMode.byWordWrapping
let titleAttributes: [NSAttributedString.Key : Any] = [
NSAttributedString.Key.underlineStyle : NSUnderlineStyle.single.rawValue,
NSAttributedString.Key.font : UIFont.preferredFont(forTextStyle: UIFont.TextStyle.largeTitle),
NSAttributedString.Key.paragraphStyle : style
]
let subtitleAttributes = [
NSAttributedString.Key.font : UIFont.preferredFont(forTextStyle: UIFont.TextStyle.body),
NSAttributedString.Key.paragraphStyle : style
]
let attributedString = NSMutableAttributedString(string: title, attributes: titleAttributes)
attributedString.append(NSAttributedString(string: "\n"))
attributedString.append(NSAttributedString(string: subtitle, attributes: subtitleAttributes))
let button = UIButton(type: UIButton.ButtonType.system)
button.setAttributedTitle(attributedString, for: UIControl.State.normal)
button.titleLabel?.numberOfLines = 0
button.titleLabel?.lineBreakMode = NSLineBreakMode.byWordWrapping
return button
}
}
Usage:
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let button = UIButton.customSystemButton(title: "Title", subtitle: "Subtitle")
button.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(button)
let horizontalConstraint = button.centerXAnchor.constraint(equalTo: view.centerXAnchor)
let verticalConstraint = button.centerYAnchor.constraint(equalTo: view.centerYAnchor)
NSLayoutConstraint.activate([horizontalConstraint, verticalConstraint])
}
}
The two screen shots below show the result display for the UIButton subclass (on the left) and for the system type button (on the right):
Two line UIButton with NSAttributedString title in Swift 5.1:
func customizeSubscribeButton() {
subscribeButton.titleLabel?.numberOfLines = 2
let title = NSMutableAttributedString()
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.alignment = .center
let part1 = NSAttributedString(string: "SUBSCRIBE FOR 12 MONTH\n",
attributes: [NSAttributedString.Key.foregroundColor: UIColor.white,
NSAttributedString.Key.font: UIFont.systemFont(ofSize: 24, weight: .semibold),
NSAttributedString.Key.paragraphStyle: paragraphStyle])
let part2 = NSAttributedString(string: "999.00 RUB ECONOMY 85%",
attributes: [NSAttributedString.Key.foregroundColor: UIColor.white,
NSAttributedString.Key.font: UIFont.systemFont(ofSize: 12, weight: .light),
NSAttributedString.Key.paragraphStyle: paragraphStyle])
title.append(part1)
title.append(part2)
subscribeButton.setAttributedTitle(title, for: .normal)
}

Resources