简体   繁体   中英

Swift: How do I use String.Index to work with replaceSubrange

I can't figure out String.Index vs. Int

error is...

Instance method 'replaceSubrange(_:with:)' requires the types 'String.Index' and 'Int' be equivalent

func MID( destin: String, start_pos: Int, length: Int, source: String )
{
    var destin_string = destin
    let dest_index = Int( start_pos - 1 )
    let dest_end_index = Int( dest_index + length )
    destin_string.replaceSubrange(  dest_index...dest_end_index,    <<<<<< error
                                    with: source )
}

The index type of String is not Int -compatible because a character can contain multiple bytes.

A workaround to convert Int indices to String.Index is NSRange which matches the startPos and length parameters and which can be reliably converted to Range<String.Index> .

By the way to modify the string inside the function and never use it makes no sense. A better way is an inout parameter

func MID(destin: inout String, startPos: Int, length: Int, source: String )
{
    let nsRange = NSRange(location: startPos, length: length)
    guard let range = Range(nsRange, in: destin) else { return }
    destin.replaceSubrange(range, with: source)
}

The best way is to work with indices all the way and specially the function index(:offsetBy:)

Something like this

func MID(destin: String, start_pos: Int, length: Int, source: String ) -> String {
    var destin_string = destin
    let dest_index = destin.index(destin.startIndex, offsetBy: start_pos)
    let dest_end_index = destin.index(destin.startIndex, offsetBy: start_pos + length)
    destin_string.replaceSubrange(  dest_index...dest_end_index,
                                    with: source )

    return destin_string
}

You might need to adjust some calculations depending on what this function is supposed to do.

Here is a variant of the above with what I consider a bit better naming and some basic error handling

func replace(in string: String, start: Int, length: Int, with replacement: String ) -> String {
    if string.isEmpty || start >= string.count { return string }
    var input = string
    let startIndex = string.index(string.startIndex, offsetBy: start)
    var endIndex = string.index(string.startIndex, offsetBy: start + length)
    if endIndex >= string.endIndex { endIndex = string.endIndex }
    input.replaceSubrange(startIndex..<endIndex, with: replacement)

    return input
}

In Swift String is a collection with its own Index type, which doesn't coincide with Int type as per Swift's Array collection.

You'd better read the the official String documentation at the section "Manipulating Indices".

Anyway, in your case rather than that method you wrote, you'd better just write an helper in an extension to get the range of indices from a String instance and then use directly the replaceSubrange(_:with:) method.

Something like:

extension String {
    func indicesRange(position: Int, length: Int) -> Range<String.Index>? {
          assert(position >= 0 && length >= 0, "position and length must not be negative.")
          guard 
              let start = index(startIndex, offsetBy: position, limitedBy: endIndex),
              start < endIndex,
              let end = index(start, offsetBy: length, limitedBy: endIndex),
              end > start
          else { return nil }
          
          return start..<end
    }

}

// usage assuming previously defined destination and source strings, 
// as well as startPos and length non-negative Int values:
if let range = destination.indicesRange(position: startPos, length: length) {
     destination.replaceSubrange(range, with: source)
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM