P.Festus P.Festus - 4 years ago 244
iOS Question

UITextView attributedText not updating font and/or font size

I have a tableview. The purpose of the tableview is to show search results originating from UISearchBar user input. The tableview has a custom tableview cell in which there is a Label and UITextView. A function passes in the user search text, matches said search text against some base text and attributes only the matched text using a specific font, font color and size. It serves the purpose of highlighting the matched characters so that the app user can see the search text in the Label and UITextView of the tableview cells. Of note, the base font of the Label and UITextView are set in IB. The matched search text font, color and size are set through the function referenced above.

On execution, everything works perfectly with respect to the text appearing in the Label. The matched search text is changed to a color, text and size as intended and the unmatched text stays unchanged.

The latter is not the case with the text displayed in the UITextView. The color, size and font of the matched search text changes as intended. For example, if the app user enters "the" into the UISearchBar, any instance of "the" shown in the UITextView has the intended text color, font type and size change. However, the font of the text in the UITextView (unmatched text) is changed to something completely different. (EDIT - my original post stated that matched and unmatched did not change). As to the unmatched text, the text is changed to a font type that is completely different than anything set in IB or set via the function and the font size is small. The font type and size seem immutable despite my tinkering.

Here is the code applied to the UITextView object:

// class function used to match search text and set attribute

class func attributeSearchedText (searchString: String, baseString: String) -> NSMutableAttributedString {

let attributedString:NSMutableAttributedString = NSMutableAttributedString(string: baseString)

let regex = try! NSRegularExpression(pattern: searchString, options: .caseInsensitive)
for match in regex.matches(in: baseString, options: NSRegularExpression.MatchingOptions(), range: NSRange(location: 0, length: (baseString.characters.count))) as [NSTextCheckingResult]
{
attributedString.addAttribute(NSForegroundColorAttributeName, value: UIColor.orange, range: match.range)
attributedString.addAttribute(NSFontAttributeName, value: UIFont(name:"HelveticaNeue-Bold", size: 14.0)!, range: match.range)
}

return attributedString
}

// function applied to the tableview cell

cell.ruleTextBody.attributedText = SearchObject.attributeSearchedText2(searchString: searchString, baseString: filteredData[indexPath.row].ruleTxtBody)


If I remove the attributedText function then the font originally set in IB is shown as intended:

cell.ruleTextBody.text = filteredData[indexPath.row].ruleTxtBody


In view of the foregoing, it is clear that the issue has something to do with attributedText and UITextView. I need the UITextView as I want to keep the scroll feature.

Any thoughts on how to fix this issue?

Answer Source

Because you forgot to set the attributes for the non-highlighted text. NSAttributedString will default to 12pt system font (San Francisco for iOS 10 / Helvetica for earlier) if you do not specify it.

Try this:

class ViewController: UIViewController {
    @IBOutlet weak var textView: UITextView!
    var defaultAttributes: [String: Any]!

    override func viewDidLoad() {
        super.viewDidLoad()

        // This is how you designed the text view in IB
        self.defaultAttributes = textView.attributedText.attributes(at: 0, effectiveRange: nil)
        self.textView.attributedText = ViewController.attributeSearchedText(searchString: "a", baseString: self.textView.text, defaultAttributes: self.defaultAttributes)
    }

    class func attributeSearchedText (searchString: String, baseString: String, defaultAttributes: [String: Any]) -> NSMutableAttributedString {
        let attributedString = NSMutableAttributedString(string: baseString, attributes: defaultAttributes)

        let regex = try! NSRegularExpression(pattern: searchString, options: .caseInsensitive)
        for match in regex.matches(in: baseString, options: NSRegularExpression.MatchingOptions(), range: NSRange(location: 0, length: (baseString.characters.count))) as [NSTextCheckingResult]
        {
            attributedString.addAttribute(NSForegroundColorAttributeName, value: UIColor.orange, range: match.range)
            attributedString.addAttribute(NSFontAttributeName, value: UIFont(name:"HelveticaNeue-Bold", size: 14.0)!, range: match.range)
        }

        return attributedString
    }

}

Result:

enter image description here


Edit:

If your text is inside a UITableView and does not require editability, it's more efficient to use UILabel instead of UITextView to hold the textual content. An example of a custom table cell (remember to drop a UILabel into the prototype cell and connect the outlet from Interface Builder)

import UIKit

class MyTableViewCell: UITableViewCell {
    @IBOutlet weak var contentLabel: UILabel!

    class func attributeSearchedText (searchString: String, baseString: String, defaultAttributes: [String: Any]?) -> NSMutableAttributedString {
        let attributedString = NSMutableAttributedString(string: baseString, attributes: defaultAttributes)

        let regex = try! NSRegularExpression(pattern: searchString, options: .caseInsensitive)
        for match in regex.matches(in: baseString, options: NSRegularExpression.MatchingOptions(), range: NSRange(location: 0, length: (baseString.characters.count))) as [NSTextCheckingResult]
        {
            attributedString.addAttribute(NSForegroundColorAttributeName, value: UIColor.orange, range: match.range)

            // You may consider dropping this line so that it uses whatever you designed in IB
            // attributedString.addAttribute(NSFontAttributeName, value: UIFont(name:"HelveticaNeue-Bold", size: 14.0)!, range: match.range)
        }

        return attributedString
    }
}

And the view controller:

class ViewController: UITableViewController {
    var defaultAttributes: [String: Any]?
    let cellContents = [
        "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer pulvinar velit ut dignissim iaculis. Vivamus sit amet justo auctor, fermentum nunc quis, facilisis odio. Nullam vitae tempor diam. Aliquam ultrices bibendum cursus. Fusce non est eros. In aliquet viverra dui, sed porttitor magna dignissim volutpat. Etiam aliquam mauris ut ligula feugiat finibus",
        "Sed efficitur ut nunc in vehicula. Duis fringilla, ligula vel lacinia commodo, mauris dui consectetur dui, egestas rhoncus nulla justo sit amet elit. Sed sollicitudin iaculis mi, eu commodo libero dictum ac. Morbi lobortis aliquet quam ac egestas. In in suscipit eros. Vivamus vel mauris pretium, sollicitudin mi nec, iaculis elit. Mauris venenatis ullamcorper elementum. Aenean in sapien vel felis venenatis placerat.",
        "Fusce placerat vestibulum massa quis congue. Mauris rutrum sagittis pellentesque. Integer pulvinar a massa vel mattis. Duis vehicula porta nibh, nec gravida dui. Sed facilisis, nisi in efficitur tempus, nibh ex rutrum purus, eu volutpat lorem dui nec neque. Curabitur et iaculis turpis. Vivamus cursus massa quis tincidunt pellentesque. Nunc interdum justo eu viverra dictum. Nulla quis sagittis dui, vel hendrerit diam. Duis vel fermentum neque, sit amet euismod sapien. Curabitur sagittis justo vitae feugiat feugiat. Nullam nunc magna, convallis vel dui in, fringilla lacinia nibh. Aenean euismod commodo est, at congue felis."
    ]


    override func viewDidLoad() {
        super.viewDidLoad()

        self.tableView.rowHeight = UITableViewAutomaticDimension
        self.tableView.estimatedRowHeight = 44

        if let cell = self.tableView.dequeueReusableCell(withIdentifier: "myCell") as? MyTableViewCell {
            self.defaultAttributes = cell.contentLabel.attributedText?.attributes(at: 0, effectiveRange: nil)
        }
    }

    override func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return self.cellContents.count
    }

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "myCell") as! MyTableViewCell

        cell.contentLabel.attributedText = MyTableViewCell.attributeSearchedText(searchString: "a", baseString: cellContents[indexPath.row], defaultAttributes: self.defaultAttributes)
        return cell
    }
}

enter image description here

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download