410

I've been updating some of my old code and answers with Swift 3 but when I got to Swift Strings and Indexing with substrings things got confusing.

Specifically I was trying the following:

let str = "Hello, playground"
let prefixRange = str.startIndex..<str.startIndex.advancedBy(5)
let prefix = str.substringWithRange(prefixRange)

where the second line was giving me the following error

Value of type 'String' has no member 'substringWithRange'

I see that String does have the following methods now:

str.substring(to: String.Index)
str.substring(from: String.Index)
str.substring(with: Range<String.Index>)

These were really confusing me at first so I started playing around index and range. This is a followup question and answer for substring. I am adding an answer below to show how they are used.

Suragch
  • 364,799
  • 232
  • 1,155
  • 1,198
  • 2
    For those who want to get the substring from the string https://stackoverflow.com/q/32305891/468724 – Inder Kumar Rathore Apr 26 '18 at 11:56
  • or subscript string or substring https://stackoverflow.com/questions/24092884/get-nth-character-of-a-string-in-swift-programming-language/38215613 – Leo Dabus Mar 13 '20 at 18:21

19 Answers19

914

enter image description here

All of the following examples use

var str = "Hello, playground"

Swift 4

Strings got a pretty big overhaul in Swift 4. When you get some substring from a String now, you get a Substring type back rather than a String. Why is this? Strings are value types in Swift. That means if you use one String to make a new one, then it has to be copied over. This is good for stability (no one else is going to change it without your knowledge) but bad for efficiency.

A Substring, on the other hand, is a reference back to the original String from which it came. Here is an image from the documentation illustrating that.

No copying is needed so it is much more efficient to use. However, imagine you got a ten character Substring from a million character String. Because the Substring is referencing the String, the system would have to hold on to the entire String for as long as the Substring is around. Thus, whenever you are done manipulating your Substring, convert it to a String.

let myString = String(mySubstring)

This will copy just the substring over and the memory holding old String can be reclaimed. Substrings (as a type) are meant to be short lived.

Another big improvement in Swift 4 is that Strings are Collections (again). That means that whatever you can do to a Collection, you can do to a String (use subscripts, iterate over the characters, filter, etc).

The following examples show how to get a substring in Swift.

Getting substrings

You can get a substring from a string by using subscripts or a number of other methods (for example, prefix, suffix, split). You still need to use String.Index and not an Int index for the range, though. (See my other answer if you need help with that.)

Beginning of a string

You can use a subscript (note the Swift 4 one-sided range):

let index = str.index(str.startIndex, offsetBy: 5)
let mySubstring = str[..<index] // Hello

or prefix:

let index = str.index(str.startIndex, offsetBy: 5)
let mySubstring = str.prefix(upTo: index) // Hello

or even easier:

let mySubstring = str.prefix(5) // Hello

End of a string

Using subscripts:

let index = str.index(str.endIndex, offsetBy: -10)
let mySubstring = str[index...] // playground

or suffix:

let index = str.index(str.endIndex, offsetBy: -10)
let mySubstring = str.suffix(from: index) // playground

or even easier:

let mySubstring = str.suffix(10) // playground

Note that when using the suffix(from: index) I had to count back from the end by using -10. That is not necessary when just using suffix(x), which just takes the last x characters of a String.

Range in a string

Again we simply use subscripts here.

let start = str.index(str.startIndex, offsetBy: 7)
let end = str.index(str.endIndex, offsetBy: -6)
let range = start..<end

let mySubstring = str[range]  // play

Converting Substring to String

Don't forget, when you are ready to save your substring, you should convert it to a String so that the old string's memory can be cleaned up.

let myString = String(mySubstring)

Using an Int index extension?

I'm hesitant to use an Int based index extension after reading the article Strings in Swift 3 by Airspeed Velocity and Ole Begemann. Although in Swift 4, Strings are collections, the Swift team purposely hasn't used Int indexes. It is still String.Index. This has to do with Swift Characters being composed of varying numbers of Unicode codepoints. The actual index has to be uniquely calculated for every string.

I have to say, I hope the Swift team finds a way to abstract away String.Index in the future. But until them I am choosing to use their API. It helps me to remember that String manipulations are not just simple Int index lookups.

Suragch
  • 364,799
  • 232
  • 1,155
  • 1,198
  • 20
    Thx for the desctription. Well deserved uprates. Apple overcomplicated this. Substring should be as easy as string.substring[from...to]. – Teddy May 05 '17 at 15:53
  • Really good explanation . except one little thing `garbage collected` ;-) I hope people here know that there is no garbage collection in Swift. – Christian Anchor Dampf Jan 16 '20 at 16:18
  • @ChristianAnchorDampf, Thanks for taking the time to comment. I took out garbage collecting. How is the new wording? – Suragch Jan 17 '20 at 00:46
  • Nicely put! Thanks for the explanation. – Vinayak Jan 31 '21 at 20:21
242

I'm really frustrated at Swift's String access model: everything has to be an Index. All I want is to access the i-th character of the string using Int, not the clumsy index and advancing (which happens to change with every major release). So I made an extension to String:

extension String {
    func index(from: Int) -> Index {
        return self.index(startIndex, offsetBy: from)
    }

    func substring(from: Int) -> String {
        let fromIndex = index(from: from)
        return String(self[fromIndex...])
    }

    func substring(to: Int) -> String {
        let toIndex = index(from: to)
        return String(self[..<toIndex])
    }

    func substring(with r: Range<Int>) -> String {
        let startIndex = index(from: r.lowerBound)
        let endIndex = index(from: r.upperBound)
        return String(self[startIndex..<endIndex])
    }
}

let str = "Hello, playground"
print(str.substring(from: 7))         // playground
print(str.substring(to: 5))           // Hello
print(str.substring(with: 7..<11))    // play
Code Different
  • 73,850
  • 14
  • 125
  • 146
  • 5
    The indexes are very useful because a *character* can be more than one byte. Try `let str = "Hello"` `print(str.substring(to: 2))` – vadian Sep 24 '16 at 15:15
  • 135
    Yes, I understand that a character (i.e. *extended grapheme cluster*) can take multiple bytes. My frustration is why we have to use the verbose index-advancing method to access the characters of a string. Why can't the Swift team just add some overloads to the Core Library to abstract it away. If I type `str[5]`, I want to access the character at index 5, whatever that character appears to be or how many bytes it takes. Isn't Swift all about developer's productivity? – Code Different Sep 24 '16 at 15:51
  • nice utility. I've added this for NSRange. `func substring(with r: NSRange) -> String { return substring(with: r.location.. – newDeveloper Dec 18 '16 at 18:40
  • @CodeDifferent you say "I want to access the character at index 5, whatever that character appears to be or how many bytes it takes"... but in which encoding? – sunapi386 Jan 06 '17 at 00:50
  • 2
    @vadian, Your example string is [a problem with Unicode and Swift itself](http://stackoverflow.com/q/41608512/), not a problem with the author's extension. However, there are [other issues to consider](https://oleb.net/blog/2016/08/swift-3-strings/) when being temped to abstract the complexity away in an extension. – Suragch Jan 12 '17 at 10:54
  • I agree that Swift's designers made a big mistake in implementing String so that numeric indexing is not possible. They could have supported Unicode with all its bells and whistles and still have provided numeric indexing by adding a character lookup table alongside the string data. This lookup table could actually be non-existent for strings without problematic Unicode characters, and be created on-the-fly when needed. When it did exist it could, with fancy programming, be a segmented table so it only existed for the problematic sections of the string, so the extra storage would be minimal. – RenniePet Feb 23 '17 at 02:12
  • 6
    @RenniePet I believe Apple recognizes the problem and changes are coming. As per the Swift Evolution page on GitHub: "Swift 4 seeks to make strings more powerful and easier-to-use, while retaining Unicode correctness by default". It's vague but let's keep our hopes up – Code Different Feb 23 '17 at 02:17
  • I think there are just too many C and C++ purists on the team who will say "Stirng is a collection of bytes" instead of "String is a collection of characters" and we end up with this mess. I understand there are probably some people who need access to bytes in String, but why not agree that the are minority and just provide them with appropriate and properly named methods to do so? Whoever is coming to Swift [and please admit, Apple invests in Swift to bring NEW developers] they expect someString[20] to be the 20th CHARACTER of the string, not 20th BYTE! – Krystian Apr 10 '17 at 20:44
  • Looking for an opinion (and knowing that Swift 4 has already changed things)... In `index(from:)`, what about `return self.index(from >= 0 ? startIndex : endIndex, offsetBy: from)` to enable negative indices (i.e., offset from the end)? No need to be gentle; I can take it. – Glenn Jul 17 '17 at 23:47
  • +1 I like this but my only gripe is that "to" is non-inclusive. In natural language, we don't say I'm going from A to B and then stop short of B. I feel your "to:" method need to add 1 when creating the index. Hence str.substring(to: 4) == "Hello". – RowanPD Aug 05 '17 at 02:07
  • C-R-A-S-H MAY OCCUR, Will always crash if the requested index is beyond the string endIndex limit. this may be fixed with the following code: func index(from: Int) -> Index { if let index = self.index(startIndex, offsetBy: from, limitedBy: self.endIndex) { return index } return self.endIndex } – Yitzchak Oct 22 '17 at 18:06
  • 3
    @CodeDifferent why apple didn't add subscript character access? So that people understand that it's bad thing to do. Basically if you would do for i in 0..string.count using subscripts that would be double loop, cause under the hood index has to go through each byte of string to find out which is the next character. If you loop using index, you iterate over string only once. Btw, hate this myself, but that's the reasoning behind subscript being not available on string in swift. – Raimundas Sakalauskas Nov 26 '17 at 15:54
  • 5
    @RaimundasSakalauskas that argument doesn't fly by me. C# has both Unicode correctness and integer subscripting, which is really convenient. In Swift 1, Apple wanted developers to use `countElement(str)` to find the length. In Swift 3, Apple made string not conforming to `Sequence` and forced everyone to use `str.characters` instead. These guys are not afraid of making changes. Their stubbornness on integer subscripting in really hard to understand – Code Different Nov 26 '17 at 16:01
  • 1
    @CodeDifferent Excerpt from documentation: "As mentioned above, different characters can require different amounts of memory to store, so in order to determine which Character is at a particular position, you must iterate over each Unicode scalar from the start or end of that String. For this reason, Swift strings can’t be indexed by integer values.". If you add popular string extension, essentially you are doing nested loop accessing particular character. To avoid devs using that, they opted not to add this as language element. Especially to prevent loop within loop. – Raimundas Sakalauskas Nov 26 '17 at 19:26
  • It's not taht it's impossible, but this has some real performance penalty, and if for i in 0..string.count would be available, people would simply do that without giving it a second thought. Now when it's not available SOME read to the point to understand why it isn't available. – Raimundas Sakalauskas Nov 26 '17 at 19:27
  • 1
    Is it possible that every 6 months (each time a new Swift version is released) the geniuses at Apple can come up with yet a new-and-better way of handling something as basic as Strings??? – Ignacio Oroná Feb 03 '18 at 15:52
  • 1
    @IgnacioOroná they have a [String Manifesto](https://github.com/apple/swift/blob/master/docs/StringManifesto.md) for what they want Swift strings to be. Still no integer indexing on the table. And my other gripe: no raw string so regex pattern is so painful. To match a single backslash I'd have to write `\\\\ ` – Code Different Feb 03 '18 at 15:56
  • 2
    Swift is not only not swift, but also not smart regarding strings. That's crazy, that there is no method available to find an index of a substring in a string! Apple, are you OK? – Dmitry May 06 '19 at 22:52
  • This unnecessarily offsets both range's upper and lower bound from the start. This is extremely inefficient. You could simply offset the range count from the start instead of offsetting from the startIndex again. https://stackoverflow.com/questions/24092884/get-nth-character-of-a-string-in-swift-programming-language/38215613. I have already fixed that issue on the accepted answer there as well. Another point is that you should be extending StringProtocol and returning a subsequence – Leo Dabus Mar 13 '20 at 18:19
  • How can anyone defend Apple on this one? This system is so unbelievably cumbersome and is so different than any other language. – aeskreis Mar 20 '20 at 00:40
  • @Krystian Your assessment is totally wrong. C and C++ treats strings as runs of bytes. Swift doesn't. Swift strings are collections of `Character`, which are unicode correct. – Alexander Apr 19 '20 at 02:21
  • @Alexander-ReinstateMonica better check when my comment was made and what it was about. It was made 3 years ago and what it was about before you start commenting my assessments. – Krystian Apr 19 '20 at 12:47
  • @Krystian Yep. April 2017 was around the time of Swift 3.1. The behavior that's now directly on `String` used to be contained in a `characters: CharacterView` property, but the functionality was largely the same. `someString[20] to be the 20th CHARACTER of the string, not 20th BYTE!` Indexing a string doesn't return a byte, or code point, or anything less than a proper extended grapheme cluster (`Character`) and it never has. The Swift 3.1 String subscript operator took a `String.Index` and returned a `Character`: https://swiftdoc.org/v3.1/type/string/#subscript-subscript_-string-index – Alexander Apr 19 '20 at 13:58
  • @Dimitry No apple is not OK. Their `string` methods are beyond insane. – StephenBoesch Jun 19 '20 at 00:46
  • This is half the reason I quit using Swift. It's too complicated to deal with strings in this language, and I don't buy any of these excuses cause in other languages it's a million times easier. – sudo Dec 05 '20 at 21:15
149

Swift 5 Extension:

extension String {
    subscript(_ range: CountableRange<Int>) -> String {
        let start = index(startIndex, offsetBy: max(0, range.lowerBound))
        let end = index(start, offsetBy: min(self.count - range.lowerBound, 
                                             range.upperBound - range.lowerBound))
        return String(self[start..<end])
    }

    subscript(_ range: CountablePartialRangeFrom<Int>) -> String {
        let start = index(startIndex, offsetBy: max(0, range.lowerBound))
         return String(self[start...])
    }
}

Usage:

let s = "hello"
s[0..<3] // "hel"
s[3...]  // "lo"

Or unicode:

let s = ""
s[0..<1] // ""
Lou Zell
  • 4,435
  • 3
  • 23
  • 20
  • 2
    So much better, thank you for posting this extension! I think coming from Python, Swift is much harder than necessary to get used to. It seems for people going in the other direction from Objective C to Swift there is more positive confirmation. – user3064009 Mar 31 '18 at 20:28
  • What's the purpose of the count which returns count? – Leon May 03 '18 at 16:36
  • 1
    @Leon I just removed it. Prior to 4.1, `count` was only available on `self.characters` – Lou Zell May 07 '18 at 23:49
  • Great solution! One wonders why Apple didn't implement anything like that on first instance.. – Michele Dall'Agata Jun 26 '18 at 10:09
  • 1
    Are there any gotchas to watch out with this particular extension? Why didn't Apple do something like this? – Andz Feb 07 '19 at 16:34
  • 1
    @Andz it is very inefficient. It starts from the beginning of the string - twice - and has to parse every character from there to "range" - twice. – kareman Mar 11 '19 at 11:50
  • 3
    You'll also need to add [an extension that takes a `CountableClosedRange`](https://stackoverflow.com/a/46627527/709202) if you'd like to write e.g. `s[0...2]`. – Chris Frederick Apr 26 '19 at 02:05
  • 1
    @ChrisFrederick and `CountablePartialRangeFrom` for s[2...]. – Mr. Ming Oct 25 '19 at 02:11
  • I'd put a guard at the beginning of your subscript to avoid potential crashes. Like so: guard range.lowerBound < count, range.lowerBound >= 0 else { return self } – C0D3 Feb 04 '20 at 22:00
  • This unnecessarily offsets both range's upper and lower bound from the start. This is extremely inefficient. You could simply offset the range count from the start instead of offsetting from the startIndex again. https://stackoverflow.com/questions/24092884/get-nth-character-of-a-string-in-swift-programming-language/38215613. I have already fixed that issue on the accepted answer there as well. Another point is that you should be extending StringProtocol and returning a subsequence – Leo Dabus Mar 13 '20 at 18:17
  • Updated. Thanks for the critique! – Lou Zell Mar 15 '20 at 01:59
30

Swift 4 & 5:

extension String {
  subscript(_ i: Int) -> String {
    let idx1 = index(startIndex, offsetBy: i)
    let idx2 = index(idx1, offsetBy: 1)
    return String(self[idx1..<idx2])
  }

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

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

How to use it:

"abcde"[0] --> "a"

"abcde"[0...2] --> "abc"

"abcde"[2..<4] --> "cd"

Community
  • 1
  • 1
Soufiane ROCHDI
  • 1,253
  • 14
  • 23
21

Swift 4

In swift 4 String conforms to Collection. Instead of substring, we should now use a subscript. So if you want to cut out only the word "play" from "Hello, playground", you could do it like this:

var str = "Hello, playground"
let start = str.index(str.startIndex, offsetBy: 7)
let end = str.index(str.endIndex, offsetBy: -6)
let result = str[start..<end] // The result is of type Substring

It is interesting to know, that doing so will give you a Substring instead of a String. This is fast and efficient as Substring shares its storage with the original String. However sharing memory this way can also easily lead to memory leaks.

This is why you should copy the result into a new String, once you want to clean up the original String. You can do this using the normal constructor:

let newString = String(result)

You can find more information on the new Substring class in the [Apple documentation].1

So, if you for example get a Range as the result of an NSRegularExpression, you could use the following extension:

extension String {

    subscript(_ range: NSRange) -> String {
        let start = self.index(self.startIndex, offsetBy: range.lowerBound)
        let end = self.index(self.startIndex, offsetBy: range.upperBound)
        let subString = self[start..<end]
        return String(subString)
    }

}
Community
  • 1
  • 1
gebirgsbärbel
  • 2,201
  • 1
  • 22
  • 37
  • Your code will crash if range.upperBound is > length of string. Also, a sample usage would have been helpful as well, as I wasn't familiar with subscripts in Swift. You could include something like datePartOnly = "2018-01-04-08:00"[NSMakeRange(0, 10)]. Other than that, very nice answer, +1 :). – dcp Nov 29 '17 at 21:43
  • nowadays it is this weird thing: `text[Range( nsRange , in: text)!]` – Fattie Jan 28 '20 at 19:19
10

Here's a function that returns substring of a given substring when start and end indices are provided. For complete reference you can visit the links given below.

func substring(string: String, fromIndex: Int, toIndex: Int) -> String? {
    if fromIndex < toIndex && toIndex < string.count /*use string.characters.count for swift3*/{
        let startIndex = string.index(string.startIndex, offsetBy: fromIndex)
        let endIndex = string.index(string.startIndex, offsetBy: toIndex)
        return String(string[startIndex..<endIndex])
    }else{
        return nil
    }
}

Here's a link to the blog post that I have created to deal with string manipulation in swift. String manipulation in swift (Covers swift 4 as well)

Or you can see this gist on github

Nikesh Jha
  • 261
  • 3
  • 6
9

I had the same initial reaction. I too was frustrated at how syntax and objects change so drastically in every major release.

However, I realized from experience how I always eventually suffer the consequences of trying to fight "change" like dealing with multi-byte characters which is inevitable if you're looking at a global audience.

So I decided to recognize and respect the efforts exerted by Apple engineers and do my part by understanding their mindset when they came up with this "horrific" approach.

Instead of creating extensions which is just a workaround to make your life easier (I'm not saying they're wrong or expensive), why not figure out how Strings are now designed to work.

For instance, I had this code which was working on Swift 2.2:

let rString = cString.substringToIndex(2)
let gString = (cString.substringFromIndex(2) as NSString).substringToIndex(2)
let bString = (cString.substringFromIndex(4) as NSString).substringToIndex(2)

and after giving up trying to get the same approach working e.g. using Substrings, I finally understood the concept of treating Strings as a bidirectional collection for which I ended up with this version of the same code:

let rString = String(cString.characters.prefix(2))
cString = String(cString.characters.dropFirst(2))
let gString = String(cString.characters.prefix(2))
cString = String(cString.characters.dropFirst(2))
let bString = String(cString.characters.prefix(2))

I hope this contributes...

Rio Bautista
  • 415
  • 5
  • 4
  • 1
    Well, dealing with a complex problem does not mean that the solution could be elegant. Again, I also understand the problem, but the entire String class and dealing with it is just horrible. – inexcitus Apr 09 '19 at 17:14
7

I'm quite mechanical thinking. Here are the basics...

Swift 4 Swift 5

  let t = "abracadabra"

  let start1 = t.index(t.startIndex, offsetBy:0)
  let   end1 = t.index(t.endIndex, offsetBy:-5)
  let start2 = t.index(t.endIndex, offsetBy:-5)
  let   end2 = t.index(t.endIndex, offsetBy:0)

  let t2 = t[start1 ..< end1]
  let t3 = t[start2 ..< end2]                

  //or a shorter form 

  let t4 = t[..<end1]
  let t5 = t[start2...]

  print("\(t2) \(t3) \(t)")
  print("\(t4) \(t5) \(t)")

  // result:
  // abraca dabra abracadabra

The result is a substring, meaning that it is a part of the original string. To get a full blown separate string just use e.g.

    String(t3)
    String(t4)

This is what I use:

    let mid = t.index(t.endIndex, offsetBy:-5)
    let firstHalf = t[..<mid]
    let secondHalf = t[mid...]
t1ser
  • 1,434
  • 16
  • 15
6

Came across this fairly short and simple way of achieving this.

var str = "Hello, World"
let arrStr = Array(str)
print(arrStr[0..<5]) //["H", "e", "l", "l", "o"]
print(arrStr[7..<12]) //["W", "o", "r", "l", "d"]
print(String(arrStr[0..<5])) //Hello
print(String(arrStr[7..<12])) //World
5

Same frustration, this should not be that hard...

I compiled this example of getting positions for substring(s) from larger text:

//
// Play with finding substrings returning an array of the non-unique words and positions in text
//
//

import UIKit

let Bigstring = "Why is it so hard to find substrings in Swift3"
let searchStrs : Array<String>? = ["Why", "substrings", "Swift3"]

FindSubString(inputStr: Bigstring, subStrings: searchStrs)


func FindSubString(inputStr : String, subStrings: Array<String>?) ->    Array<(String, Int, Int)> {
    var resultArray : Array<(String, Int, Int)> = []
    for i: Int in 0...(subStrings?.count)!-1 {
        if inputStr.contains((subStrings?[i])!) {
            let range: Range<String.Index> = inputStr.range(of: subStrings![i])!
            let lPos = inputStr.distance(from: inputStr.startIndex, to: range.lowerBound)
            let uPos = inputStr.distance(from: inputStr.startIndex, to: range.upperBound)
            let element = ((subStrings?[i])! as String, lPos, uPos)
            resultArray.append(element)
        }
    }
    for words in resultArray {
        print(words)
    }
    return resultArray
}

returns ("Why", 0, 3) ("substrings", 26, 36) ("Swift3", 40, 46)

Tall Dane
  • 173
  • 3
  • 9
  • 3
    That is some code, but does not really explain how string indexing and substrings work in swift3. – Robert Dec 11 '16 at 21:10
5

I am new in Swift 3, but looking the String (index) syntax for analogy I think that index is like a "pointer" constrained to string and Int can help as an independent object. Using the base + offset syntax , then we can get the i-th character from string with the code bellow:

let s = "abcdefghi"
let i = 2
print (s[s.index(s.startIndex, offsetBy:i)])
// print c

For a range of characters ( indexes) from string using String (range) syntax we can get from i-th to f-th characters with the code bellow:

let f = 6
print (s[s.index(s.startIndex, offsetBy:i )..<s.index(s.startIndex, offsetBy:f+1 )])
//print cdefg

For a substring (range) from a string using String.substring (range) we can get the substring using the code bellow:

print (s.substring (with:s.index(s.startIndex, offsetBy:i )..<s.index(s.startIndex, offsetBy:f+1 ) ) )
//print cdefg

Notes:

  1. The i-th and f-th begin with 0.

  2. To f-th, I use offsetBY: f + 1, because the range of subscription use ..< (half-open operator), not include the f-th position.

  3. Of course must include validate errors like invalid index.

Suragch
  • 364,799
  • 232
  • 1,155
  • 1,198
5

Swift 4+

extension String {
    func take(_ n: Int) -> String {
        guard n >= 0 else {
            fatalError("n should never negative")
        }
        let index = self.index(self.startIndex, offsetBy: min(n, self.count))
        return String(self[..<index])
    }
}

Returns a subsequence of the first n characters, or the entire string if the string is shorter. (inspired by: https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.text/take.html)

Example:

let text = "Hello, World!"
let substring = text.take(5) //Hello
Peter Kreinz
  • 6,544
  • 1
  • 51
  • 45
3

Swift 4

extension String {
    subscript(_ i: Int) -> String {
        let idx1 = index(startIndex, offsetBy: i)
        let idx2 = index(idx1, offsetBy: 1)
        return String(self[idx1..<idx2])
    }
}

let s = "hello"

s[0]    // h
s[1]    // e
s[2]    // l
s[3]    // l
s[4]    // o
Just a coder
  • 10,809
  • 12
  • 70
  • 112
2

I created a simple extension for this (Swift 3)

extension String {
    func substring(location: Int, length: Int) -> String? {
        guard characters.count >= location + length else { return nil }
        let start = index(startIndex, offsetBy: location)
        let end = index(startIndex, offsetBy: location + length)
        return substring(with: start..<end)
    }
}
2

Heres a more generic implementation:

This technique still uses index to keep with Swift's standards, and imply a full Character.

extension String
{
    func subString <R> (_ range: R) -> String? where R : RangeExpression, String.Index == R.Bound
    {
        return String(self[range])
    }

    func index(at: Int) -> Index
    {
        return self.index(self.startIndex, offsetBy: at)
    }
}

To sub string from the 3rd character:

let item = "Fred looks funny"
item.subString(item.index(at: 2)...) // "ed looks funny"

I've used camel subString to indicate it returns a String and not a Substring.

Leslie Godwin
  • 2,367
  • 22
  • 16
2

Building on the above I needed to split a string at a non-printing character dropping the non-printing character. I developed two methods:

var str = "abc\u{1A}12345sdf"
let range1: Range<String.Index> = str.range(of: "\u{1A}")!
let index1: Int = str.distance(from: str.startIndex, to: range1.lowerBound)
let start = str.index(str.startIndex, offsetBy: index1)
let end = str.index(str.endIndex, offsetBy: -0)
let result = str[start..<end] // The result is of type Substring
let firstStr = str[str.startIndex..<range1.lowerBound]

which I put together using some of the answers above.

Because a String is a collection I then did the following:

var fString = String()
for (n,c) in str.enumerated(){

*if c == "\u{1A}" {
    print(fString);
    let lString = str.dropFirst(n + 1)
    print(lString)
    break
   }
 fString += String(c)
}*

Which for me was more intuitive. Which one is best? I have no way of telling They both work with Swift 5

Jeremy Andrews
  • 697
  • 10
  • 15
1

Swift 4

"Substring" (https://developer.apple.com/documentation/swift/substring):

let greeting = "Hi there! It's nice to meet you! "
let endOfSentence = greeting.index(of: "!")!
let firstSentence = greeting[...endOfSentence]
// firstSentence == "Hi there!"

Example of extension String:

private typealias HowDoYouLikeThatElonMusk = String
private extension HowDoYouLikeThatElonMusk {

    subscript(_ from: Character?, _ to: Character?, _ include: Bool) -> String? {
        if let _from: Character = from, let _to: Character = to {
            let dynamicSourceForEnd: String = (_from == _to ? String(self.reversed()) : self)
            guard let startOfSentence: String.Index = self.index(of: _from),
                let endOfSentence: String.Index = dynamicSourceForEnd.index(of: _to) else {
                return nil
            }

            let result: String = String(self[startOfSentence...endOfSentence])
            if include == false {
                guard result.count > 2 else {
                        return nil
                }
                return String(result[result.index(result.startIndex, offsetBy: 1)..<result.index(result.endIndex, offsetBy: -1)])
            }
            return result
        } else if let _from: Character = from {
            guard let startOfSentence: String.Index = self.index(of: _from) else {
                return nil
            }
            let result: String = String(self[startOfSentence...])
            if include == false {
                guard result.count > 1 else {
                    return nil
                }
                return String(result[result.index(result.startIndex, offsetBy: 1)...])
            }
            return result
        } else if let _to: Character = to {
            guard let endOfSentence: String.Index = self.index(of: _to) else {
                    return nil
            }
            let result: String = String(self[...endOfSentence])
            if include == false {
                guard result.count > 1 else {
                    return nil
                }
                return String(result[..<result.index(result.endIndex, offsetBy: -1)])
            }
            return result
        }
        return nil
    }
}

example of using the extension String:

let source =                                   ">>>01234..56789<<<"
// include = true
var from =          source["3", nil, true]  //       "34..56789<<<"
var to =            source[nil, "6", true]  // ">>>01234..56"
var fromTo =        source["3", "6", true]  //       "34..56"
let notFound =      source["a", nil, true]  // nil
// include = false
from =              source["3", nil, false] //        "4..56789<<<"
to =                source[nil, "6", false] // ">>>01234..5"
fromTo =            source["3", "6", false] //        "4..5"
let outOfBounds =   source[".", ".", false] // nil

let str = "Hello, playground"
let hello = str[nil, ",", false] // "Hello"
CAHbl463
  • 82
  • 6
1

The specificity of String has mostly been addressed in other answers. To paraphrase: String has a specific Index which is not of type Int because string elements do not have the same size in the general case. Hence, String does not conform to RandomAccessCollection and accessing a specific index implies the traversal of the collection, which is not an O(1) operation.

Many answers have proposed workarounds for using ranges, but they can lead to inefficient code as they use String methods (index(from:), index(:offsetBy:), ...) that are not O(1).

To access string elements like in an array you should use an Array:

let array = Array("Hello, world!")
let letter = array[5]

This is a trade-off, the array creation is an O(n) operation but array accesses are then O(1). You can convert back to a String when you want with String(array).

Louis Lac
  • 2,102
  • 12
  • 21
  • This seems like it would be a good option if you are manipulating your own text. However, if it's coming from users, you run into problems with surrogate pairs and grapheme clusters. – Suragch Feb 10 '21 at 01:16
  • Sure, this should be used with caution and the user must know what he is doing. – Louis Lac Feb 10 '21 at 09:45
-1

Swift 5
let desiredIndex: Int = 7 let substring = str[String.Index(encodedOffset: desiredIndex)...]
This substring variable will give you the result.
Simply here Int is converted to Index and then you can split the strings. Unless you will get errors.