Make part of a UILabel bold in Swift
You will want to use attributedString
which allows you to style parts of a string etc. This can be done like this by having two styles, one normal, one bold, and then attaching them together:
let boldText = "Filter:"
let attrs = [NSAttributedString.Key.font : UIFont.boldSystemFont(ofSize: 15)]
let attributedString = NSMutableAttributedString(string:boldText, attributes:attrs)
let normalText = "Hi am normal"
let normalString = NSMutableAttributedString(string:normalText)
attributedString.append(normalString)
When you want to assign it to a label:
label.attributedText = attributedString
Swift 4 alternative:
let attrs = [NSAttributedStringKey.font : UIFont.boldSystemFont(ofSize: 14)]
let attributedString = NSMutableAttributedString(string: "BOLD TEXT", attributes:attrs)
let normalString = NSMutableAttributedString(string: "normal text")
attributedString.append(normalString)
myLabel.attributedText = attributedString
Result:
Swift 4.2 & 5.0:
First off we create a protocol that UILabel
, UITextField
and UITextView
can adopt.
public protocol ChangableFont: AnyObject {
var rangedAttributes: [RangedAttributes] { get }
func getText() -> String?
func set(text: String?)
func getAttributedText() -> NSAttributedString?
func set(attributedText: NSAttributedString?)
func getFont() -> UIFont?
func changeFont(ofText text: String, with font: UIFont)
func changeFont(inRange range: NSRange, with font: UIFont)
func changeTextColor(ofText text: String, with color: UIColor)
func changeTextColor(inRange range: NSRange, with color: UIColor)
func resetFontChanges()
}
We want to be able to add multiple changes to our text, therefore we create the rangedAttributes
property. It's a custom struct that holds attributes and the range in which they are applied.
public struct RangedAttributes {
public let attributes: [NSAttributedString.Key: Any]
public let range: NSRange
public init(_ attributes: [NSAttributedString.Key: Any], inRange range: NSRange) {
self.attributes = attributes
self.range = range
}
}
Another problem is that UILabel
its font
property is strong and UITextField
its font
property is weak/optional. To make them both work with our ChangableFont
protocol we include the getFont() -> UIFont?
method. This also counts for UITextView its text
and attributedText
properties. That's why we implement the getter and setter methods for them as well.
extension UILabel: ChangableFont {
public func getText() -> String? {
return text
}
public func set(text: String?) {
self.text = text
}
public func getAttributedText() -> NSAttributedString? {
return attributedText
}
public func set(attributedText: NSAttributedString?) {
self.attributedText = attributedText
}
public func getFont() -> UIFont? {
return font
}
}
extension UITextField: ChangableFont {
public func getText() -> String? {
return text
}
public func set(text: String?) {
self.text = text
}
public func getAttributedText() -> NSAttributedString? {
return attributedText
}
public func set(attributedText: NSAttributedString?) {
self.attributedText = attributedText
}
public func getFont() -> UIFont? {
return font
}
}
extension UITextView: ChangableFont {
public func getText() -> String? {
return text
}
public func set(text: String?) {
self.text = text
}
public func getAttributedText() -> NSAttributedString? {
return attributedText
}
public func set(attributedText: NSAttributedString?) {
self.attributedText = attributedText
}
public func getFont() -> UIFont? {
return font
}
}
Now we can go ahead and create the default implementation for UILabel
, UITextField
and UITextView
by extending our protocol.
public extension ChangableFont {
var rangedAttributes: [RangedAttributes] {
guard let attributedText = getAttributedText() else {
return []
}
var rangedAttributes: [RangedAttributes] = []
let fullRange = NSRange(
location: 0,
length: attributedText.string.count
)
attributedText.enumerateAttributes(
in: fullRange,
options: []
) { (attributes, range, stop) in
guard range != fullRange, !attributes.isEmpty else { return }
rangedAttributes.append(RangedAttributes(attributes, inRange: range))
}
return rangedAttributes
}
func changeFont(ofText text: String, with font: UIFont) {
guard let range = (self.getAttributedText()?.string ?? self.getText())?.range(ofText: text) else { return }
changeFont(inRange: range, with: font)
}
func changeFont(inRange range: NSRange, with font: UIFont) {
add(attributes: [.font: font], inRange: range)
}
func changeTextColor(ofText text: String, with color: UIColor) {
guard let range = (self.getAttributedText()?.string ?? self.getText())?.range(ofText: text) else { return }
changeTextColor(inRange: range, with: color)
}
func changeTextColor(inRange range: NSRange, with color: UIColor) {
add(attributes: [.foregroundColor: color], inRange: range)
}
private func add(attributes: [NSAttributedString.Key: Any], inRange range: NSRange) {
guard !attributes.isEmpty else { return }
var rangedAttributes: [RangedAttributes] = self.rangedAttributes
var attributedString: NSMutableAttributedString
if let attributedText = getAttributedText() {
attributedString = NSMutableAttributedString(attributedString: attributedText)
} else if let text = getText() {
attributedString = NSMutableAttributedString(string: text)
} else {
return
}
rangedAttributes.append(RangedAttributes(attributes, inRange: range))
rangedAttributes.forEach { (rangedAttributes) in
attributedString.addAttributes(
rangedAttributes.attributes,
range: rangedAttributes.range
)
}
set(attributedText: attributedString)
}
func resetFontChanges() {
guard let text = getText() else { return }
set(attributedText: NSMutableAttributedString(string: text))
}
}
With in the default implementation I use a little helper method for getting the NSRange
of a substring
.
public extension String {
func range(ofText text: String) -> NSRange {
let fullText = self
let range = (fullText as NSString).range(of: text)
return range
}
}
We're done! You can now change parts of the text its font and text color.
titleLabel.text = "Welcome"
titleLabel.font = UIFont.systemFont(ofSize: 70, weight: .bold)
titleLabel.textColor = UIColor.black
titleLabel.changeFont(ofText: "lc", with: UIFont.systemFont(ofSize: 60, weight: .light))
titleLabel.changeTextColor(ofText: "el", with: UIColor.blue)
titleLabel.changeTextColor(ofText: "co", with: UIColor.red)
titleLabel.changeTextColor(ofText: "m", with: UIColor.green)
You can use NSMutableAttributedString and NSAttributedString to create customized string. The function below makes given boldString bold in given string.
Swift 3
func attributedText(withString string: String, boldString: String, font: UIFont) -> NSAttributedString {
let attributedString = NSMutableAttributedString(string: string,
attributes: [NSFontAttributeName: font])
let boldFontAttribute: [String: Any] = [NSFontAttributeName: UIFont.boldSystemFont(ofSize: font.pointSize)]
let range = (string as NSString).range(of: boldString)
attributedString.addAttributes(boldFontAttribute, range: range)
return attributedString
}
Example usage
authorLabel.attributedText = attributedText(withString: String(format: "Author : %@", user.name), boldString: "Author", font: authorLabel.font)
Swift 4
func attributedText(withString string: String, boldString: String, font: UIFont) -> NSAttributedString {
let attributedString = NSMutableAttributedString(string: string,
attributes: [NSAttributedStringKey.font: font])
let boldFontAttribute: [NSAttributedStringKey: Any] = [NSAttributedStringKey.font: UIFont.boldSystemFont(ofSize: font.pointSize)]
let range = (string as NSString).range(of: boldString)
attributedString.addAttributes(boldFontAttribute, range: range)
return attributedString
}
Swift 4.2 and 5
func attributedText(withString string: String, boldString: String, font: UIFont) -> NSAttributedString {
let attributedString = NSMutableAttributedString(string: string,
attributes: [NSAttributedString.Key.font: font])
let boldFontAttribute: [NSAttributedString.Key: Any] = [NSAttributedString.Key.font: UIFont.boldSystemFont(ofSize: font.pointSize)]
let range = (string as NSString).range(of: boldString)
attributedString.addAttributes(boldFontAttribute, range: range)
return attributedString
}