1

I have a string like this:

Your score is %@.

I want the string to become :

Your score is perfect.

I was thinking to use:

let attrs = [NSAttributedString.Key.font : UIFont.boldSystemFont(ofSize: 15)]
let boldAttributedString = NSMutableAttributedString(string: "perfect".localized(), attributes: attrs)
String(format: "Your string is %@.", boldAttributedString) 

I know my way around NSMutableString and attributed strings, but I'm not sure if it's possible because I have no way of doing a count of words, because in other languages it might be shorter or in a different order... The value for “perfect” is coming from an enum value.

Any ideas on how to approach this issue?

Rob
  • 415,655
  • 72
  • 787
  • 1,044
BMatthys
  • 36
  • 4
  • 3
    Take a look at this: https://stackoverflow.com/questions/55643869/ios-swift-is-there-a-way-to-make-a-specific-portion-of-text-bold/55644090#55644090 – Anand Apr 15 '19 at 15:02
  • 1
    I would recommend using a 3rd party framework like [SwiftyAttributes](https://github.com/eddiekaiger/SwiftyAttributes), it'll make working with attributed strings in swift a lot easier. – EmilioPelaez Apr 15 '19 at 15:22
  • 1
    There is not `NSAttributed(format:)` like `String(format:)`. The easiest way (and might be CPU consuming), would be to use HTML: `You string is %@`, do `let htmlString = String(format: "Your string is %@.", "perfect".localized())` and interpret it transforming it into NSAttributedString. – Larme Apr 15 '19 at 15:28
  • What I did in this scenario was I had to separate the strings since we support multiple languages, and as you said, are a different order for some. You can set the range something like: let range = (text as NSString).range(of: "perfect") then: attributedString.addAttributes(attributedText, range: range). You can also use HTML tags to make this easier – DavidA Apr 15 '19 at 15:30
  • @DavidA Only issue in your idea, is that there is that is doesn't work correctly with that kind of case: `"Your perfect string is %@."`. You'll detect the first "perfect". – Larme Apr 15 '19 at 15:32
  • @Larme ahh right, good catch :) – DavidA Apr 15 '19 at 15:35
  • I really wish I had time to dig into this question right now; it's a great one, but I just popped on while waiting for a build to finish. Here's what you want to do: find or write a markup toolkit that lets you turn something like `Your score is perfect` into an attributed string. Localize `Your score is %@`, and then run that through the markup engine. I'd love to spend 20 minutes working out the details because this is a nice problem, but I can't right now, so comment. – Rob Napier Apr 15 '19 at 19:08

1 Answers1

0

You can use NSAttributedString and append text and attributes:

    let normalAttrs = [NSAttributedString.Key.font : UIFont.systemFont(ofSize: 15)]
    let boldAttrs = [NSAttributedString.Key.font : UIFont.boldSystemFont(ofSize: 15)]

    let partOne = NSMutableAttributedString(string: "Your score is ", attributes: normalAttrs)
    let partTwo = NSMutableAttributedString(string: "perfect".localized(), attributes: boldAttrs)
    let partThree = NSMutableAttributedString(string: ".", attributes: normalAttrs)

    partOne.append(partTwo)
    partOne.append(partThree)

    myLabel.attributedText = partOne

Edit:

Here's a more flexible approach using NSMutableAttributedString's

replaceCharacters(in range: NSRange, with attrString: NSAttributedString)

This will make it easy to replace "%@" in your localized string with a localized version of the "score word", regardless of length or position:

class TestViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        // simulate localized strings
        let localizedStrings = [
            "Your score is %@.",
            "You got a %@ score.",
            "%@ is your final score.",
            ]

        let localizedPerfects = [
            "Perfect",
            "Perfekt",
            "Perfectamente",
            ]

        let normalAttrs = [NSAttributedString.Key.font : UIFont.systemFont(ofSize: 15)]
        let boldAttrs = [NSAttributedString.Key.font : UIFont.boldSystemFont(ofSize: 15)]

        var y = 40

        for (scoreString, perfectString) in zip(localizedStrings, localizedPerfects) {

            // add a label
            let label = UILabel()
            view.addSubview(label)
            label.frame = CGRect(x: 20, y: y, width: 280, height: 40);
            y += 40

            if let rangeOfPctAt = scoreString.range(of: "%@") {

                let attStr = NSMutableAttributedString(string: scoreString, attributes: normalAttrs)
                let attPerfect = NSAttributedString(string: perfectString, attributes: boldAttrs)

                let nsRange = NSRange(rangeOfPctAt, in: attStr.string)
                attStr.replaceCharacters(in: nsRange, with: attPerfect)

                label.attributedText = attStr

            } else {

                // "%@" was not in localized score string
                label.text = "invalid score string format"

            }

        }

    }
}

Result:

enter image description here

DonMag
  • 69,424
  • 5
  • 50
  • 86
  • This won't work, just as the OP states, when localizing the format string and the `%@` isn't at the end. – rmaddy Apr 15 '19 at 16:53
  • @rmaddy - This suggestion avoids the use of `%@` to insert the localized "perfect". – DonMag Apr 15 '19 at 17:18
  • But what if `"Your string is %@."` gets localized to `"Wotgy %@ htreye dfg."`? The OP brings this up in the question. – rmaddy Apr 15 '19 at 17:23
  • @rmaddy - In that case, the question is too vague: ***"I want to make part of a string bold, but I don't know what the string is or what word(s) I want to make bold."*** Presumably, the OP has determined his approach to localizing the string and will therefore know which "part(s)" should be normal and which "part(s)" should be bold. – DonMag Apr 15 '19 at 17:49