Make part of a UILabel bold in Swift

后端 未结 9 1979
面向向阳花
面向向阳花 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)
    

提交回复
热议问题