Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

'subscript' is unavailable: cannot subscript String with a CountableClosedRange<Int>, see the documentation comment for discussion

In Swift 4, I'm getting this error when I try to take a Substring of a String using subscript syntax.

'subscript' is unavailable: cannot subscript String with a CountableClosedRange, see the documentation comment for discussion

For example:

let myString: String = "foobar"
let mySubstring: Substring = myString[1..<3]

Two questions:

  1. How can I resolve this error?
  2. Where is "the documentation comment for discussion" that was referred to in the error?
like image 812
Barry Jones Avatar asked Aug 04 '17 03:08

Barry Jones


3 Answers

  1. If you want to use subscripts on Strings like "palindrome"[1..<3] and "palindrome"[1...3], use these extensions.

Swift 4

extension String {
    subscript (bounds: CountableClosedRange<Int>) -> String {
        let start = index(startIndex, offsetBy: bounds.lowerBound)
        let end = index(startIndex, offsetBy: bounds.upperBound)
        return String(self[start...end])
    }

    subscript (bounds: CountableRange<Int>) -> String {
        let start = index(startIndex, offsetBy: bounds.lowerBound)
        let end = index(startIndex, offsetBy: bounds.upperBound)
        return String(self[start..<end])
    }
}

Swift 3

For Swift 3 replace with return self[start...end] and return self[start..<end].

  1. Apple didn't build this into the Swift language because the definition of a 'character' depends on how the String is encoded. A character can be 8 to 64 bits, and the default is usually UTF-16. You can specify other String encodings in String.Index.

This is the documentation that Xcode error refers to.

More on String encodings like UTF-8 and UTF-16

like image 187
p-sun Avatar answered Nov 12 '22 22:11

p-sun


Your question (and self-answer) has 2 problems:

Subscripting a string with Int has never been available in Swift's Standard Library. This code has been invalid for as long as Swift exists:

let mySubstring: Substring = myString[1..<3]

The new String.Index(encodedOffset: ) returns an index in UTF-16 (16-bit) encoding. Swift's string uses Extended Grapheme Cluster, which can take between 8 and 64 bits to store a character. Emojis make for very good demonstration:

let myString = "πŸ‡ΊπŸ‡ΈπŸ‡¨πŸ‡¦πŸ‡¬πŸ‡§πŸ‡«πŸ‡·"
let lowerBound = String.Index(encodedOffset: 1)
let upperBound = String.Index(encodedOffset: 3)
let mySubstring = myString[lowerBound..<upperBound]

// Expected: Canadian and UK flags
// Actual  : gibberish
print(mySubstring)

In fact, getting the String.Index has not changed at all in Swift 4, for better or worse:

let myString = "πŸ‡ΊπŸ‡ΈπŸ‡¨πŸ‡¦πŸ‡¬πŸ‡§πŸ‡«πŸ‡·"
let lowerBound = myString.index(myString.startIndex, offsetBy: 1)
let upperBound = myString.index(myString.startIndex, offsetBy: 3)
let mySubstring = myString[lowerBound..<upperBound]

print(mySubstring)
like image 28
Code Different Avatar answered Nov 13 '22 00:11

Code Different


You could just convert your string to an array of characters...

let aryChar = Array(myString)

Then you get all the array functionality...

like image 19
Ronk Avatar answered Nov 12 '22 23:11

Ronk