>> Why would the standard library not include something simpler like this?
Of course, I don't know, but I have a possible explanation that I replay to myself whenever I start cursing at String's awkward syntax.
The explanation is historical. The class NSString is jam-packed with useful functionality, but it's always been weird that the cananical underlying data representation is UTF-16. UTF-16 is fine, but there's plenty of stuff stored as UTF-8, and even now plenty of sources of pure 8-bit character arrays. This means that there are are lots of actual data conversions to and from UTF-16. (At the time NSString adopted UTF-16, around 1990, the text world was quite different. At that time, it looked like 16-bit units for text would obviously "win" over 8-bit units — "wchar clobbers char, photos at 11!". UTF-16 seemed like the forward-looking option, and UTF-8 looked like a compatibibility footnote. Obviously, that's not what happened.)
In practice, concrete subclasses of NSString may actually store data in forms other than UTF-16, which is often great for efficiency, but is hampered by the need to present UTF-16 semantics publicly.
If you think about Swift's String class, you'll realize that it goes to extreme lengths to avoid being specific about underlying data representation. That design avoids the NSString problem of producing "surprising" performance bottlenecks when we write loops with simple-looking code that involves unwanted data conversion.
The abstraction also helps it to define characters in terms of grapheme clusters (which are variable length information units) instead of code units like UTF-16 or UTF-8, thus bridging the remaining difficulties in using Unicode to represent text globally and getting it right.
I don't see any simple syntax representation of String subscripting that doesn't promise to turn into a horrible performance bottleneck in some piece of code or other, or with some particular string's underlying representation oe other. There's no readily-accessible directing-indexing notation that works well without advance knowledge or assumptions about the string implementation.
Even the related classes that conceptualize strings as UTF-8, 16 or 32 code unit arrays are arranged so that they don't assume a particular representation. (I think that's why these classes have changed so often. The original APIs had hidden biases about the relationship of the client's view of the data, as if wanting to access string data as UTF-8 implied the likelihood of its originating as UTF-8, which is obviously not true.)
FWIW.