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
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}
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.blacktitleLabel.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)