Make part of a UILabel bold in Swift

后端 未结 9 1928
面向向阳花
面向向阳花 2020-12-02 12:50

I have a UILabel I\'ve made programmatically as:

var label = UILabel()

I\'ve then declared some styling for the label, includi

相关标签:
9条回答
  • 2020-12-02 13:48

    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)
    
    0 讨论(0)
  • 2020-12-02 13:49

    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
    }
    
    0 讨论(0)
  • 2020-12-02 13:49

    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
    
    0 讨论(0)
提交回复
热议问题