skip to Main Content

I have a string let’s say " my name is %@ and i study in class %@" now I want to bold the placeholder text which i will be inserting , so that the result will look something like this:" My name is Harsh and i study in class 10" and i will display it on a label

I have already tried using NSAttributedString but since the string will be localised i am not able to use the range parameter of attributed string to make it bold.

3

Answers


  1. I can offer a naive solution using NSRegularExpression without any complex / scary regex. I am sure there are more optimal solutions than this.

    The steps are quite close to what meaning matters has posted above

    1. Store the string to inject (Harsh, 13) etc in an array
    2. Have a localized string which has placeholders
    3. Use REGEX to find the location of the placeholders and store these locations in a locations array
    4. Update the localized string by replacing the placeholders with values from the string array
    5. Create an NSMutableAttributedString from the updated localized string
    6. Loop through the strings to inject array and update the regions of NSMutableAttributedString defined by the locations array

    Here is the code I used with some comments to explain:

    // This is not needed, just part of my UI
    // Only the inject part is relevant to you
    @objc
    private func didTapSubmitButton()
    {
        if let inputText = textField.text
        {
            let input = inputText.components(separatedBy: ",")
            let text = "My name is %@ and I am %@ years old"
            inject(input, into: text)
        }
    }
    
    // The actual function
    private func inject(_ strings: [String],
                        into text: String)
    {
        let placeholderString = "%@"
        
        // Store all the positions of the %@ in the string
        var placeholderIndexes: [Int] = []
        
        // Locate the %@ in the original text
        do
        {
            let regex = try NSRegularExpression(pattern: placeholderString,
                                                options: .caseInsensitive)
            
            // Loop through all the %@ found and store their locations
            for match in regex.matches(in: text,
                                       options: NSRegularExpression.MatchingOptions(),
                                       range: NSRange(location: 0,
                                                      length: text.count))
                as [NSTextCheckingResult]
            {
                // Append your placeholder array with the location
                placeholderIndexes.append(match.range.location)
            }
        }
        catch
        {
            // handle errors
            print("error")
        }
        
        // Expand your string by inserting the parameters
        let updatedText = String(format: text, arguments: strings)
        
        // Configure an NSMutableAttributedString with the updated text
        let attributedText = NSMutableAttributedString(string: updatedText)
        
            // Keep track of an offset
        // Initially when you store the locations of the %@ in the text
        // My name is %@ and my age is %@ years old, the location is 11 and 27
        // But when you add Harsh, the next location should be increased by
        // the difference in length between the placeholder and the previous
        // string to get the right location of the second parameter
        var offset = 0
        
        // Loop through the strings you want to insert
        for (index, parameter) in strings.enumerated()
        {
            // Get the corresponding location of where it was inserted
            // Plus the offset as discussed above
            let locationOfString = placeholderIndexes[index] + offset
            
            // Get the length of the string
            let stringLength = parameter.count
            
            // Create a range
            let range = NSRange(location: locationOfString,
                                length: stringLength)
            
            // Set the bold font
            let boldFont
                = UIFont.boldSystemFont(ofSize: displayLabel.font.pointSize)
            
            // Set the attributes for the given range
            attributedText.addAttribute(NSAttributedString.Key.font,
                                        value: boldFont,
                                        range: range)
            
            // Update the offset as discussed above
            offset = stringLength - placeholderString.count
        }
        
        // Do what you want with the string
        displayLabel.attributedText = attributedText
    }
    

    The end result:

    Bold part of localised string Parameterised string bold Swift NSAttributedString iOS

    This should be flexible enough to work with any number of placeholders that are present in strings and you do not need to keep track of different placeholders.

    NSAttributed string bold part of localised string with parameters format swift iOS

    Login or Signup to reply.
  2. let descriptionString = String(format: "localised_key".localized(), Harsh, 10)
    let description = NSMutableAttributedString(string: descriptionString, attributes: [NSAttributedString.Key.font: UIFont(name: "NotoSans-Regular", size: 15.7)!, NSAttributedString.Key.foregroundColor: UIColor(rgb: 0x000b38), NSAttributedString.Key.kern: 0.5])
    let rangeName = descriptionString.range(of: "Harsh")
    let rangeClass = descriptionString.range(of: "10")
    let nsrangeName = NSRange(rangeName!, in: descriptionString)
    let nsrangeClass = NSRange(rangeClass!, in: descriptionString)
    description.addAttributes([NSAttributedString.Key.font: UIFont(name: "NotoSans-Bold", size: 15.7)!, NSAttributedString.Key.foregroundColor: UIColor(rgb: 0x000b38), NSAttributedString.Key.kern: 0.5], range: nsrangeName)
    description.addAttributes([NSAttributedString.Key.font: UIFont(name: "NotoSans-Bold", size: 15.7)!, NSAttributedString.Key.foregroundColor: UIColor(rgb: 0x000b38), NSAttributedString.Key.kern: 0.5], range: nsrangeClass)
    

    for more references, use this

    Login or Signup to reply.
  3. let withFormat = "my name is %@ and i study in class %@"
    

    There are different ways to do so, but in my opinion, one of the easiest way would be to use tags:

    Use tags around the placeholders (and other parts if needed):

    let withFormat = "my name is <b>%@</b> and i study in class <b>%@</b>"
    let withFormat = "my name is [b]%@[/b] and i study in class [b]%@[/b]"
    let withFormat = "my name is **%@** and i study in class **%@**"
    

    Tags can be HTML, Markdown, BBCode, or any custom you’d like, then, replace the placeholder values:

    let localized = String(format: withFormat, value1, value2)
    

    Now, depending on how you want to do it, or which tag you used, you can use the init of NSAttributedString from HTML, Markdown, etc, or simply using NSAttributedString(string: localized), look yourself for the tags and apply the render effect needed.

    Here’s a little example:

    let tv = UITextView(frame: CGRect(x: 0, y: 0, width: 300, height: 130))
    tv.backgroundColor = .orange
    
    let attributedString = NSMutableAttributedString()
    
    let htmled = String(format: "my name is <b>%@</b> and i study in class <b>%@</b>", arguments: ["Alice", "Wonderlands"])
    let markdowned = String(format: "my name is **%@** and i study in class **%@**", arguments: ["Alice", "Wonderlands"])
    let bbcoded = String(format: "my name is [b]%@[/b] and i study in class [b]%@[/b]", arguments: ["Alice", "Wonderlands"])
    
    let separator = NSAttributedString(string: "nn")
    let html = try! NSAttributedString(data: Data(htmled.utf8), options: [.documentType : NSAttributedString.DocumentType.html], documentAttributes: nil)
    attributedString.append(html)
    attributedString.append(separator)
    
    let markdown = try! NSAttributedString(markdown: markdowned, baseURL: nil) //iO15+
    attributedString.append(markdown)
    attributedString.append(separator)
    
    let bbcode = NSMutableAttributedString(string: bbcoded)
    let regex = try! NSRegularExpression(pattern: "\[b\](.*?)\[\/b\]", options: [])
    let matches = regex.matches(in: bbcode.string, options: [], range: NSRange(location: 0, length: bbcode.length))
    let boldEffect: [NSAttributedString.Key: Any] = [.font: UIFont.boldSystemFont(ofSize: 12)]
    //We use reversed() because if you replace the first one, you'll remove [b] and [/b], meaning that the other ranges will be affected, so the trick is to start from the end
    matches.reversed().forEach { aMatch in
        let valueRange = aMatch.range(at: 1) //We use the regex group
        let replacement = NSAttributedString(string: bbcode.attributedSubstring(from: valueRange).string, attributes: boldEffect)
        bbcode.replaceCharacters(in: aMatch.range, with: replacement)
    }
    attributedString.append(bbcode)
    
    tv.attributedText = attributedString
    

    Output:

    enter image description here

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search