Imanuel Pardosi
Imanuel Pardosi

Reputation: 331

Replace just bold word in string, Swift

I have a UILabel with text "hello world, hello". There are 2 hello words. And I want to replace the only 'bold hello' to 'thanks' without bold.

I use this code:

uiLabel1.text = "hello world, hello"

let target = "hello"

let replace = "thanks"

uiLabel1.text.replacingOccurrences(of: target, with: replace, options: NSString.CompareOptions.literal, range: nil)

And the result is: "thanks world, thanks"
The result I want: "hello world, thanks"

Upvotes: 0

Views: 1176

Answers (2)

MadProgrammer
MadProgrammer

Reputation: 347314

Okay, so there's probably an easier way to do this...

So, I went through the API (like super quick) and looked for something like lastIndexOf, which lead me on a little trail to String#range(of:options), which allows you to search backwards, hmmm, interesting.

This returns a Range<String.Index> ... okay, so how can I use that?! Hmm, maybe String#replacingOccurrences(of:with:options:range:) 🤔

So, crack open a play ground and...

var str = "hello world, hello"
let lastIndexOf = str.range(of: "hello", options: .backwards)
str = str.replacingOccurrences(of: "hello", with: "thanks", options: .caseInsensitive, range: lastIndexOf)

str now equals "hello world, thanks"

Hi @MadProgrammer, your code is to replace the last hello word to thanks, right? But my question is to replace hello with the bold attribute, it may in the first, middle or at the end of a string.

Okay, so clearly we're missing some context...

Assuming, now, you're using a NSAttributedString, it becomes slightly more complicated

Building the string itself is not hard, figuring out how to find string components by attribute, a little more difficult.

Lucky for us, we have the Internet. So, the following is based on ideas I got from:

One of the important things to remember when trying to solve an issue, you'll be lucky to find a single answer which does it all, instead, you need to break your issue down and focus on solving individual elements, and be prepared to go back to the start 😉

So, once again, unto the play ground...

import UIKit

var str = "hello world, "
//let lastIndexOf = str.range(of: "hello", options: .backwards)
//str = str.replacingOccurrences(of: "hello", with: "thanks", options: .caseInsensitive, range: lastIndexOf)

extension UIFont {
    var isBold: Bool {
        return fontDescriptor.symbolicTraits.contains(.traitBold)
    }

    var isItalic: Bool {
        return fontDescriptor.symbolicTraits.contains(.traitItalic)
    }
}

// Just so I can see that the style ;)    
let fontSize = CGFloat(24.0)
let boldAttrs = [
    NSAttributedString.Key.font: UIFont.boldSystemFont(ofSize: fontSize),
    NSAttributedString.Key.foregroundColor: UIColor.white // Playground
]
// Playground only
let plainAttrs = [
    NSAttributedString.Key.foregroundColor: UIColor.white // Playground
]

let boldText = NSMutableAttributedString(string: "hello", attributes: boldAttrs)
let styledText = NSMutableAttributedString(string: str, attributes: plainAttrs)
let someMoreBoldText = NSMutableAttributedString(string: "not to be replaced", attributes: boldAttrs)

// Attributes can be combined with their appear together ;)
styledText.append(boldText)
styledText.append(NSMutableAttributedString(string: " ", attributes: plainAttrs))
styledText.append(someMoreBoldText)
styledText.append(NSMutableAttributedString(string: " ", attributes: plainAttrs))
styledText.append(boldText)

styledText.enumerateAttribute(NSAttributedString.Key.font, in: NSRange(0..<styledText.length)) { (value, range, stop) in
    guard let font = value as? UIFont, font.isBold else {
        return;
    }
    let subText = styledText.attributedSubstring(from: range)
    guard subText.string == "hello" else {
        return
    }
    styledText.replaceCharacters(in: range, with: "thanks")
}
styledText

Which outputs...

Output

The important things for me are:

  1. The style has not be changed
  2. Only the individual "hello" values, which are bolded, have been changed

Upvotes: 2

Lal Krishna
Lal Krishna

Reputation: 16180

Here is the code. But actually this is hardcoded. If the target enclosed in between <b></b>, it will work.

var text = "hello world, <b>hello</b>"
let target = "hello"
let replace = "thanks"

text = text.replacingOccurrences(of: "<b>\(target)</b>", with: replace, options: .literal, range: nil) //hello world, thanks

Upvotes: 0

Related Questions