Ivan442769011
Ivan442769011

Reputation: 69

Swift string replace and get new range

Any better way to retrieve the Range after string replace?

Here is the current version:

let username = "apple"
msgString = "{{user}} has an apple"
guard let range = msgString.range(of: "{{user}}") else { return }
msgString.replaceSubrange(range, with username)
userRange = msgString.range(of: username)

Does any better way to get the user name range

Upvotes: 2

Views: 4391

Answers (4)

matt
matt

Reputation: 535304

Let's start with a corrected version of the code you gave:

let username = "apple"
var msgString = "{{user}} has an apple"
guard let range = msgString.range(of: "{{user}}") else { return }
msgString.replaceSubrange(range, with: username)

Here, within msgString we have replaced "{{user}}" with "apple". The question is: what is the range of this "apple" within msgString?

The solution is simple when you realize that we already know the answer. It comes in two parts:

  • The new range starts in the same place the old range did. That is the same as saying that the lower bound of the new range is the same as the lower bound of the old range. We already know what that is: it's range.lowerBound.

  • The new range's length is the length of the replacement string. This is expressed its count.

Hence the desired range is:

let newRange = 
    range.lowerBound..<msgString.index(range.lowerBound, offsetBy: username.count)

If we're going to be doing a lot of this sort of thing, it might be nice to extend String. For example:

extension String {
    mutating func replaceSubrangeAndReportNewRange (
        _ bounds: Range<String.Index>, with s: String
        ) -> Range<String.Index> {
            self.replaceSubrange(bounds, with:s)
            return bounds.lowerBound..<self.index(bounds.lowerBound, offsetBy:s.count)
    }
}

Upvotes: 1

Mojtaba Hosseini
Mojtaba Hosseini

Reputation: 119340

Two functions:

  • replacingOccurrences... returns a new string and informs about the change ranges
  • replaceOccurrences... changes the value of the current variable and informs about the change ranges

The best place to implement those is in String extension:

extension String {

    func replacingOccurrences<T: StringProtocol>(of toReplace: T,
                                                 with newString: T,
                                                 options: String.CompareOptions = [],
                                                 range searchRange: Range<T.Index>? = nil,
                                                 completion: ((Range<T.Index>?, Range<T.Index>?) -> Void)) -> String {

        let oldRange = range(of: toReplace)

        let replacedString = replacingOccurrences(of: toReplace,
                                                  with: newString,
                                                  options: options,
                                                  range: searchRange)

        let newRange = replacedString.range(of: newString)

        completion(oldRange, newRange)

        return replacedString
    }

    mutating func replaceOccurrences<T: StringProtocol>(of toReplace: T,
                                                        with newString: T,
                                                        options: String.CompareOptions = [],
                                                        range searchRange: Range<T.Index>? = nil,
                                                        completion: ((Range<T.Index>?, Range<T.Index>?) -> Void)) {

        self = replacingOccurrences(of: toReplace,
                                    with: newString,
                                    options: options,
                                    range: searchRange,
                                    completion: completion)
    }
}

Usage:

let username = "apple"
var msgString = "{{user}} has an apple"

msgString.replaceOccurrences(of: "{{user}}", with: username) { (oldRange, newRange) in
    print(oldRange)
    print(newRange)
}

Upvotes: 1

Mohammad Reza Koohkan
Mohammad Reza Koohkan

Reputation: 1734

i suggest you to create a class for doing stuff that you do multiple times

in this class you can get stringRange and newReplacedString with replace method :

class StringService {

  /** create an object to access every method because this class provides you services */

  static let shared = StringService()


  /** Service classes should not be available for creating instances */

  private init () {}


  func replace(mySting: String ,by: String, with: String) -> (String,Range<String.Index>?) {

     var newString = ""

     if let range = myString.range(of:by) {

        newString.replaceSubrange(range,with:with)

        return (newString,range)

      }esle{

        return ("",nil)

      }
  }

Usage :

let replacedString = StringService.shared.replace(mySting: "myString is",
                                                  by: "is",
                                                  with: "was").0

if let replacedStringRange = StringService.shared.replace(mySting: "myString is",
                                                  by: "is",
                                                  with: "was").1 {

}

if you wanted to use native library you can use :

"myString is".replacingOccurrences(of: "is",
                                   with: "was",
                                   options: .literal,
                                   range: nil)

Upvotes: 1

Jaydeep Vora
Jaydeep Vora

Reputation: 6213

Swift 4.0

You can use replacingOccurrences method to replace the text.

  msgString.replacingOccurrences(of: "{{user}}", with: "apple", options: .literal, range: nil)

output:

apple has an apple

Upvotes: -1

Related Questions