What I'm wanting to do is very simple in C/C++, Java, and so many other languages. All I want to do is be able to specify the width of a string, similar to this:
printf("%-15s", var);
This would create of a field width of 15 characters. I've done a lot of googling. I've tried using COpaquepointeras well as String(format:in various ways with no luck. Any suggestions would be greatly appreciated. I could have missed something when googling.
You can use withCString to quickly convert the string to an array of bytes (technically an UnsafePointer<Int8>):
let str = "Hello world"
let formatted = str.withCString { String(format: "%-15s", $0) }
print("'\(formatted)'")
You are better to do it yourself
let str0 = "alpha"
let length = 20
// right justify
var str20r = String(count: (length - str0.characters.count), repeatedValue: Character(" "))
str20r.appendContentsOf(str0)
// " alpha"
// left justify
var str20l = str0
str20l.appendContentsOf(String(count: (length - str0.characters.count), repeatedValue: Character(" ")))
// "alpha "
if you need something 'more general'
func formatString(str: String, fixLenght: Int, spacer: Character = Character(" "), justifyToTheRigth: Bool = false)->String {
let c = str.characters.count
let start = str.characters.startIndex
let end = str.characters.endIndex
var str = str
if c > fixLenght {
switch justifyToTheRigth {
case true:
let range = start.advancedBy(c - fixLenght)..<end
return String(str.characters[range])
case false:
let range = start..<end.advancedBy(fixLenght - c)
return String(str.characters[range])
}
} else {
var extraSpace = String(count: fixLenght - c, repeatedValue: spacer)
if justifyToTheRigth {
extraSpace.appendContentsOf(str)
return extraSpace
} else {
str.appendContentsOf(extraSpace)
return str
}
}
}
let str = "ABCDEFGH"
let s0 = formatString(str, fixLenght: 3)
let s1 = formatString(str, fixLenght: 3, justifyToTheRigth: true)
let s2 = formatString(str, fixLenght: 10, spacer: Character("-"))
let s3 = formatString(str, fixLenght: 10, spacer: Character("-"), justifyToTheRigth: true)
print(s0)
print(s1)
print(s2)
print(s3)
which prints
ABC
FGH
ABCDEFGH--
--ABCDEFGH
The problem is that Swift strings have variable size elements, so it's ambiguous what "15 characters" is. This is a source of frustration for simple strings — but makes the language more precise when dealing with emoji, regional identifiers, ligatures, etc.
You can convert the Swift string to a C-string and use normal formatters (see Santosh's answer). The "Swift" way to handle strings is to begin at the starting index of the collection of Characters and advance N times. For example:
let alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
let index = alphabet.characters.startIndex.advancedBy(14) // String.CharacterView.Index
let allChars = alphabet.characters.prefixThrough(index) // String.CharacterView
print(String(allChars)) // "ABCDEFGHIJKLMNO\n"
If you want to force padding, you could use an approach like this:
extension String {
func formatted(characterCount characterCount:Int) -> String {
if characterCount < characters.count {
return String(characters.prefixThrough(characters.startIndex.advancedBy(characterCount - 1)))
} else {
return self + String(count: characterCount - characters.count, repeatedValue: " " as Character)
}
}
}
let abc = "ABC"
let alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
print("!\(abc.formatted(characterCount: 15))!")
// "!ABC !\n"
print("!\(alphabet.formatted(characterCount: 15))!")
// "!ABCDEFGHIJKLMNOP!\n"
Did you try this?
let string1 = "string1"
let string2 = "string2"
let formattedString = String(format: "%-15s - %s",
COpaquePointer(string1.cStringUsingEncoding(NSUTF8StringEncoding)!),
COpaquePointer(string2.cStringUsingEncoding(NSUTF8StringEncoding)!)
)
print(formattedString)
//string1 - string2
We've got a ton of interesting answers now. Thank you everyone. I wrote the following:
func formatLeftJustifiedWidthSpecifier(stringToChange: String, width: Int) -> String {
var newString: String = stringToChange
newString = newString.stringByPaddingToLength(width, withString: " ", startingAtIndex: 0)
return newString
}
From one hand %# is used to format String objects:
import Foundation
var str = "Hello"
print(String(format: "%#", str))
But it does not support the width modifier:
print(String(format: "%-15#", str))
Will still print unpadded text:
"Hello\n"
However there is a modifier %s that seems to work with CStrings:
var cstr = (str as NSString).utf8String //iOS10+ or .UTF8String otherwise
print(String(format: "%-15s", cstr!))
Output:
"Hello \n"
One nice thing is that you can use the same format specification with NSLog:
NSLog("%-15s", cstr!)
To augment the answer above by "Code Different" (thank you!) on Jun 29, 2016, and allow to write something like "hello".center(42); "world".alignLeft(42):
extension String {
// note: symbol names match to nim std/strutils lib
func align (_ boxsz: UInt) -> String {
self.withCString { String(format: "%\(boxsz)s", $0) }
}
func alignLeft (_ boxsz: UInt) -> String {
self.withCString { String(format: "%-\(boxsz)s", $0) }
}
func center (_ boxsz: UInt) -> String {
let n = self.count
guard boxsz > n else { return self }
let padding = boxsz - UInt(n)
let R = padding / 2
guard R > 0 else { return " " + self }
let L = (padding%2 == 0) ? R : (R+1)
return " ".withCString { String(format: "%\(L)s\(self)%\(R)s", $0,$0) }
}
}
What I'm wanting to do is very simple in C/C++, Java, and so many other languages. All I want to do is be able to specify the width of a string, similar to this:
printf("%-15s", var);
This would create of a field width of 15 characters. I've done a lot of googling. I've tried using COpaquepointeras well as String(format:in various ways with no luck. Any suggestions would be greatly appreciated. I could have missed something when googling.
You can use withCString to quickly convert the string to an array of bytes (technically an UnsafePointer<Int8>):
let str = "Hello world"
let formatted = str.withCString { String(format: "%-15s", $0) }
print("'\(formatted)'")
You are better to do it yourself
let str0 = "alpha"
let length = 20
// right justify
var str20r = String(count: (length - str0.characters.count), repeatedValue: Character(" "))
str20r.appendContentsOf(str0)
// " alpha"
// left justify
var str20l = str0
str20l.appendContentsOf(String(count: (length - str0.characters.count), repeatedValue: Character(" ")))
// "alpha "
if you need something 'more general'
func formatString(str: String, fixLenght: Int, spacer: Character = Character(" "), justifyToTheRigth: Bool = false)->String {
let c = str.characters.count
let start = str.characters.startIndex
let end = str.characters.endIndex
var str = str
if c > fixLenght {
switch justifyToTheRigth {
case true:
let range = start.advancedBy(c - fixLenght)..<end
return String(str.characters[range])
case false:
let range = start..<end.advancedBy(fixLenght - c)
return String(str.characters[range])
}
} else {
var extraSpace = String(count: fixLenght - c, repeatedValue: spacer)
if justifyToTheRigth {
extraSpace.appendContentsOf(str)
return extraSpace
} else {
str.appendContentsOf(extraSpace)
return str
}
}
}
let str = "ABCDEFGH"
let s0 = formatString(str, fixLenght: 3)
let s1 = formatString(str, fixLenght: 3, justifyToTheRigth: true)
let s2 = formatString(str, fixLenght: 10, spacer: Character("-"))
let s3 = formatString(str, fixLenght: 10, spacer: Character("-"), justifyToTheRigth: true)
print(s0)
print(s1)
print(s2)
print(s3)
which prints
ABC
FGH
ABCDEFGH--
--ABCDEFGH
The problem is that Swift strings have variable size elements, so it's ambiguous what "15 characters" is. This is a source of frustration for simple strings — but makes the language more precise when dealing with emoji, regional identifiers, ligatures, etc.
You can convert the Swift string to a C-string and use normal formatters (see Santosh's answer). The "Swift" way to handle strings is to begin at the starting index of the collection of Characters and advance N times. For example:
let alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
let index = alphabet.characters.startIndex.advancedBy(14) // String.CharacterView.Index
let allChars = alphabet.characters.prefixThrough(index) // String.CharacterView
print(String(allChars)) // "ABCDEFGHIJKLMNO\n"
If you want to force padding, you could use an approach like this:
extension String {
func formatted(characterCount characterCount:Int) -> String {
if characterCount < characters.count {
return String(characters.prefixThrough(characters.startIndex.advancedBy(characterCount - 1)))
} else {
return self + String(count: characterCount - characters.count, repeatedValue: " " as Character)
}
}
}
let abc = "ABC"
let alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
print("!\(abc.formatted(characterCount: 15))!")
// "!ABC !\n"
print("!\(alphabet.formatted(characterCount: 15))!")
// "!ABCDEFGHIJKLMNOP!\n"
Did you try this?
let string1 = "string1"
let string2 = "string2"
let formattedString = String(format: "%-15s - %s",
COpaquePointer(string1.cStringUsingEncoding(NSUTF8StringEncoding)!),
COpaquePointer(string2.cStringUsingEncoding(NSUTF8StringEncoding)!)
)
print(formattedString)
//string1 - string2
We've got a ton of interesting answers now. Thank you everyone. I wrote the following:
func formatLeftJustifiedWidthSpecifier(stringToChange: String, width: Int) -> String {
var newString: String = stringToChange
newString = newString.stringByPaddingToLength(width, withString: " ", startingAtIndex: 0)
return newString
}
From one hand %# is used to format String objects:
import Foundation
var str = "Hello"
print(String(format: "%#", str))
But it does not support the width modifier:
print(String(format: "%-15#", str))
Will still print unpadded text:
"Hello\n"
However there is a modifier %s that seems to work with CStrings:
var cstr = (str as NSString).utf8String //iOS10+ or .UTF8String otherwise
print(String(format: "%-15s", cstr!))
Output:
"Hello \n"
One nice thing is that you can use the same format specification with NSLog:
NSLog("%-15s", cstr!)
To augment the answer above by "Code Different" (thank you!) on Jun 29, 2016, and allow to write something like "hello".center(42); "world".alignLeft(42):
extension String {
// note: symbol names match to nim std/strutils lib
func align (_ boxsz: UInt) -> String {
self.withCString { String(format: "%\(boxsz)s", $0) }
}
func alignLeft (_ boxsz: UInt) -> String {
self.withCString { String(format: "%-\(boxsz)s", $0) }
}
func center (_ boxsz: UInt) -> String {
let n = self.count
guard boxsz > n else { return self }
let padding = boxsz - UInt(n)
let R = padding / 2
guard R > 0 else { return " " + self }
let L = (padding%2 == 0) ? R : (R+1)
return " ".withCString { String(format: "%\(L)s\(self)%\(R)s", $0,$0) }
}
}
I want to convert the index of a letter contained within a string to an integer value. Attempted to read the header files but I cannot find the type for Index, although it appears to conform to protocol ForwardIndexType with methods (e.g. distanceTo).
var letters = "abcdefg"
let index = letters.characters.indexOf("c")!
// ERROR: Cannot invoke initializer for type 'Int' with an argument list of type '(String.CharacterView.Index)'
let intValue = Int(index) // I want the integer value of the index (e.g. 2)
Any help is appreciated.
edit/update:
Xcode 11 • Swift 5.1 or later
extension StringProtocol {
func distance(of element: Element) -> Int? { firstIndex(of: element)?.distance(in: self) }
func distance<S: StringProtocol>(of string: S) -> Int? { range(of: string)?.lowerBound.distance(in: self) }
}
extension Collection {
func distance(to index: Index) -> Int { distance(from: startIndex, to: index) }
}
extension String.Index {
func distance<S: StringProtocol>(in string: S) -> Int { string.distance(to: self) }
}
Playground testing
let letters = "abcdefg"
let char: Character = "c"
if let distance = letters.distance(of: char) {
print("character \(char) was found at position #\(distance)") // "character c was found at position #2\n"
} else {
print("character \(char) was not found")
}
let string = "cde"
if let distance = letters.distance(of: string) {
print("string \(string) was found at position #\(distance)") // "string cde was found at position #2\n"
} else {
print("string \(string) was not found")
}
Works for Xcode 13 and Swift 5
let myString = "Hello World"
if let i = myString.firstIndex(of: "o") {
let index: Int = myString.distance(from: myString.startIndex, to: i)
print(index) // Prints 4
}
The function func distance(from start: String.Index, to end: String.Index) -> String.IndexDistance returns an IndexDistance which is just a typealias for Int
Swift 4
var str = "abcdefg"
let index = str.index(of: "c")?.encodedOffset // Result: 2
Note: If String contains same multiple characters, it will just get the nearest one from left
var str = "abcdefgc"
let index = str.index(of: "c")?.encodedOffset // Result: 2
encodedOffset has deprecated from Swift 4.2.
Deprecation message:
encodedOffset has been deprecated as most common usage is incorrect. Use utf16Offset(in:) to achieve the same behavior.
So we can use utf16Offset(in:) like this:
var str = "abcdefgc"
let index = str.index(of: "c")?.utf16Offset(in: str) // Result: 2
When searching for index like this
⛔️ guard let index = (positions.firstIndex { position <= $0 }) else {
it is treated as Array.Index. You have to give compiler a clue you want an integer
✅ guard let index: Int = (positions.firstIndex { position <= $0 }) else {
Swift 5
You can do convert to array of characters and then use advanced(by:) to convert to integer.
let myString = "Hello World"
if let i = Array(myString).firstIndex(of: "o") {
let index: Int = i.advanced(by: 0)
print(index) // Prints 4
}
To perform string operation based on index , you can not do it with traditional index numeric approach. because swift.index is retrieved by the indices function and it is not in the Int type. Even though String is an array of characters, still we can't read element by index.
This is frustrating.
So ,to create new substring of every even character of string , check below code.
let mystr = "abcdefghijklmnopqrstuvwxyz"
let mystrArray = Array(mystr)
let strLength = mystrArray.count
var resultStrArray : [Character] = []
var i = 0
while i < strLength {
if i % 2 == 0 {
resultStrArray.append(mystrArray[i])
}
i += 1
}
let resultString = String(resultStrArray)
print(resultString)
Output : acegikmoqsuwy
Thanks In advance
Here is an extension that will let you access the bounds of a substring as Ints instead of String.Index values:
import Foundation
/// This extension is available at
/// https://gist.github.com/zackdotcomputer/9d83f4d48af7127cd0bea427b4d6d61b
extension StringProtocol {
/// Access the range of the search string as integer indices
/// in the rendered string.
/// - NOTE: This is "unsafe" because it may not return what you expect if
/// your string contains single symbols formed from multiple scalars.
/// - Returns: A `CountableRange<Int>` that will align with the Swift String.Index
/// from the result of the standard function range(of:).
func countableRange<SearchType: StringProtocol>(
of search: SearchType,
options: String.CompareOptions = [],
range: Range<String.Index>? = nil,
locale: Locale? = nil
) -> CountableRange<Int>? {
guard let trueRange = self.range(of: search, options: options, range: range, locale: locale) else {
return nil
}
let intStart = self.distance(from: startIndex, to: trueRange.lowerBound)
let intEnd = self.distance(from: trueRange.lowerBound, to: trueRange.upperBound) + intStart
return Range(uncheckedBounds: (lower: intStart, upper: intEnd))
}
}
Just be aware that this can lead to weirdness, which is why Apple has chosen to make it hard. (Though that's a debatable design decision - hiding a dangerous thing by just making it hard...)
You can read more in the String documentation from Apple, but the tldr is that it stems from the fact that these "indices" are actually implementation-specific. They represent the indices into the string after it has been rendered by the OS, and so can shift from OS-to-OS depending on what version of the Unicode spec is being used. This means that accessing values by index is no longer a constant-time operation, because the UTF spec has to be run over the data to determine the right place in the string. These indices will also not line up with the values generated by NSString, if you bridge to it, or with the indices into the underlying UTF scalars. Caveat developer.
In case you got an "index is out of bounds" error. You may try this approach. Working in Swift 5
extension String{
func countIndex(_ char:Character) -> Int{
var count = 0
var temp = self
for c in self{
if c == char {
//temp.remove(at: temp.index(temp.startIndex,offsetBy:count))
//temp.insert(".", at: temp.index(temp.startIndex,offsetBy: count))
return count
}
count += 1
}
return -1
}
}
I have not yet been able to figure out how to get a substring of a String in Swift:
var str = “Hello, playground”
func test(str: String) -> String {
return str.substringWithRange( /* What goes here? */ )
}
test (str)
I'm not able to create a Range in Swift. Autocomplete in the Playground isn’t super helpful - this is what it suggests:
return str.substringWithRange(aRange: Range<String.Index>)
I haven't found anything in the Swift Standard Reference Library that helps. Here was another wild guess:
return str.substringWithRange(Range(0, 1))
And this:
let r:Range<String.Index> = Range<String.Index>(start: 0, end: 2)
return str.substringWithRange(r)
I've seen other answers (Finding index of character in Swift String) that seem to suggest that since String is a bridge type for NSString, the "old" methods should work, but it's not clear how - e.g., this doesn't work either (doesn't appear to be valid syntax):
let x = str.substringWithRange(NSMakeRange(0, 3))
Thoughts?
You can use the substringWithRange method. It takes a start and end String.Index.
var str = "Hello, playground"
str.substringWithRange(Range<String.Index>(start: str.startIndex, end: str.endIndex)) //"Hello, playground"
To change the start and end index, use advancedBy(n).
var str = "Hello, playground"
str.substringWithRange(Range<String.Index>(start: str.startIndex.advancedBy(2), end: str.endIndex.advancedBy(-1))) //"llo, playgroun"
You can also still use the NSString method with NSRange, but you have to make sure you are using an NSString like this:
let myNSString = str as NSString
myNSString.substringWithRange(NSRange(location: 0, length: 3))
Note: as JanX2 mentioned, this second method is not safe with unicode strings.
Swift 2
Simple
let str = "My String"
let subStr = str[str.startIndex.advancedBy(3)...str.startIndex.advancedBy(7)]
//"Strin"
Swift 3
let startIndex = str.index(str.startIndex, offsetBy: 3)
let endIndex = str.index(str.startIndex, offsetBy: 7)
str[startIndex...endIndex] // "Strin"
str.substring(to: startIndex) // "My "
str.substring(from: startIndex) // "String"
Swift 4
substring(to:) and substring(from:) are deprecated in Swift 4.
String(str[..<startIndex]) // "My "
String(str[startIndex...]) // "String"
String(str[startIndex...endIndex]) // "Strin"
At the time I'm writing, no extension is perfectly Swift 4.2 compatible, so here is one that covers all the needs I could think of:
extension String {
func substring(from: Int?, to: Int?) -> String {
if let start = from {
guard start < self.count else {
return ""
}
}
if let end = to {
guard end >= 0 else {
return ""
}
}
if let start = from, let end = to {
guard end - start >= 0 else {
return ""
}
}
let startIndex: String.Index
if let start = from, start >= 0 {
startIndex = self.index(self.startIndex, offsetBy: start)
} else {
startIndex = self.startIndex
}
let endIndex: String.Index
if let end = to, end >= 0, end < self.count {
endIndex = self.index(self.startIndex, offsetBy: end + 1)
} else {
endIndex = self.endIndex
}
return String(self[startIndex ..< endIndex])
}
func substring(from: Int) -> String {
return self.substring(from: from, to: nil)
}
func substring(to: Int) -> String {
return self.substring(from: nil, to: to)
}
func substring(from: Int?, length: Int) -> String {
guard length > 0 else {
return ""
}
let end: Int
if let start = from, start > 0 {
end = start + length - 1
} else {
end = length - 1
}
return self.substring(from: from, to: end)
}
func substring(length: Int, to: Int?) -> String {
guard let end = to, end > 0, length > 0 else {
return ""
}
let start: Int
if let end = to, end - length > 0 {
start = end - length + 1
} else {
start = 0
}
return self.substring(from: start, to: to)
}
}
And then, you can use:
let string = "Hello,World!"
string.substring(from: 1, to: 7)gets you: ello,Wo
string.substring(to: 7)gets you: Hello,Wo
string.substring(from: 3)gets you: lo,World!
string.substring(from: 1, length: 4)gets you: ello
string.substring(length: 4, to: 7)gets you: o,Wo
Updated substring(from: Int?, length: Int) to support starting from zero.
NOTE: #airspeedswift makes some very insightful points on the trade-offs of this approach, particularly the hidden performance impacts. Strings are not simple beasts, and getting to a particular index may take O(n) time, which means a loop that uses a subscript can be O(n^2). You have been warned.
You just need to add a new subscript function that takes a range and uses advancedBy() to walk to where you want:
import Foundation
extension String {
subscript (r: Range<Int>) -> String {
get {
let startIndex = self.startIndex.advancedBy(r.startIndex)
let endIndex = startIndex.advancedBy(r.endIndex - r.startIndex)
return self[Range(start: startIndex, end: endIndex)]
}
}
}
var s = "Hello, playground"
println(s[0...5]) // ==> "Hello,"
println(s[0..<5]) // ==> "Hello"
(This should definitely be part of the language. Please dupe rdar://17158813)
For fun, you can also add a + operator onto the indexes:
func +<T: ForwardIndex>(var index: T, var count: Int) -> T {
for (; count > 0; --count) {
index = index.succ()
}
return index
}
s.substringWithRange(s.startIndex+2 .. s.startIndex+5)
(I don't know yet if this one should be part of the language or not.)
SWIFT 2.0
simple:
let myString = "full text container"
let substring = myString[myString.startIndex..<myString.startIndex.advancedBy(3)] // prints: ful
SWIFT 3.0
let substring = myString[myString.startIndex..<myString.index(myString.startIndex, offsetBy: 3)] // prints: ful
SWIFT 4.0
Substring operations return an instance of the Substring type, instead of String.
let substring = myString[myString.startIndex..<myString.index(myString.startIndex, offsetBy: 3)] // prints: ful
// Convert the result to a String for long-term storage.
let newString = String(substring)
It is much more simple than any of the answers here, once you find the right syntax.
I want to take away the [ and ]
let myString = "[ABCDEFGHI]"
let startIndex = advance(myString.startIndex, 1) //advance as much as you like
let endIndex = advance(myString.endIndex, -1)
let range = startIndex..<endIndex
let myNewString = myString.substringWithRange( range )
result will be "ABCDEFGHI"
the startIndex and endIndex could also be used in
let mySubString = myString.substringFromIndex(startIndex)
and so on!
PS: As indicated in the remarks, there are some syntax changes in swift 2 which comes with xcode 7 and iOS9!
Please look at this page
For example to find the first name (up to the first space) in my full name:
let name = "Joris Kluivers"
let start = name.startIndex
let end = find(name, " ")
if end {
let firstName = name[start..end!]
} else {
// no space found
}
start and end are of type String.Index here and are used to create a Range<String.Index> and used in the subscript accessor (if a space is found at all in the original string).
It's hard to create a String.Index directly from an integer position as used in the opening post. This is because in my name each character would be of equal size in bytes. But characters using special accents in other languages could have used several more bytes (depending on the encoding used). So what byte should the integer refer to?
It's possible to create a new String.Index from an existing one using the methods succ and pred which will make sure the correct number of bytes are skipped to get to the next code point in the encoding. However in this case it's easier to search for the index of the first space in the string to find the end index.
Since String is a bridge type for NSString, the "old" methods should work, but it's not clear how - e.g., this doesn't work either (doesn't appear to be valid syntax):
let x = str.substringWithRange(NSMakeRange(0, 3))
To me, that is the really interesting part of your question. String is bridged to NSString, so most NSString methods do work directly on a String. You can use them freely and without thinking. So, for example, this works just as you expect:
// delete all spaces from Swift String stateName
stateName = stateName.stringByReplacingOccurrencesOfString(" ", withString:"")
But, as so often happens, "I got my mojo workin' but it just don't work on you." You just happened to pick one of the rare cases where a parallel identically named Swift method exists, and in a case like that, the Swift method overshadows the Objective-C method. Thus, when you say str.substringWithRange, Swift thinks you mean the Swift method rather than the NSString method — and then you are hosed, because the Swift method expects a Range<String.Index>, and you don't know how to make one of those.
The easy way out is to stop Swift from overshadowing like this, by casting explicitly:
let x = (str as NSString).substringWithRange(NSMakeRange(0, 3))
Note that no significant extra work is involved here. "Cast" does not mean "convert"; the String is effectively an NSString. We are just telling Swift how to look at this variable for purposes of this one line of code.
The really weird part of this whole thing is that the Swift method, which causes all this trouble, is undocumented. I have no idea where it is defined; it is not in the NSString header and it's not in the Swift header either.
The short answer is that this is really hard in Swift right now. My hunch is that there is still a bunch of work for Apple to do on convenience methods for things like this.
String.substringWithRange() is expecting a Range<String.Index> parameter, and as far as I can tell there isn't a generator method for the String.Index type. You can get String.Index values back from aString.startIndex and aString.endIndex and call .succ() or .pred() on them, but that's madness.
How about an extension on the String class that takes good old Ints?
extension String {
subscript (r: Range<Int>) -> String {
get {
let subStart = advance(self.startIndex, r.startIndex, self.endIndex)
let subEnd = advance(subStart, r.endIndex - r.startIndex, self.endIndex)
return self.substringWithRange(Range(start: subStart, end: subEnd))
}
}
func substring(from: Int) -> String {
let end = countElements(self)
return self[from..<end]
}
func substring(from: Int, length: Int) -> String {
let end = from + length
return self[from..<end]
}
}
let mobyDick = "Call me Ishmael."
println(mobyDick[8...14]) // Ishmael
let dogString = "This 🐶's name is Patch."
println(dogString[5..<6]) // 🐶
println(dogString[5...5]) // 🐶
println(dogString.substring(5)) // 🐶's name is Patch.
println(dogString.substring(5, length: 1)) // 🐶
Update: Swift beta 4 resolves the issues below!
As it stands [in beta 3 and earlier], even Swift-native strings have some issues with handling Unicode characters. The dog icon above worked, but the following doesn't:
let harderString = "1:1️⃣"
for character in harderString {
println(character)
}
Output:
1
:
1
️
⃣
In new Xcode 7.0 use
//: Playground - noun: a place where people can play
import UIKit
var name = "How do you use String.substringWithRange?"
let range = name.startIndex.advancedBy(0)..<name.startIndex.advancedBy(10)
name.substringWithRange(range)
//OUT:
You can use this extensions to improve substringWithRange
Swift 2.3
extension String
{
func substringWithRange(start: Int, end: Int) -> String
{
if (start < 0 || start > self.characters.count)
{
print("start index \(start) out of bounds")
return ""
}
else if end < 0 || end > self.characters.count
{
print("end index \(end) out of bounds")
return ""
}
let range = Range(start: self.startIndex.advancedBy(start), end: self.startIndex.advancedBy(end))
return self.substringWithRange(range)
}
func substringWithRange(start: Int, location: Int) -> String
{
if (start < 0 || start > self.characters.count)
{
print("start index \(start) out of bounds")
return ""
}
else if location < 0 || start + location > self.characters.count
{
print("end index \(start + location) out of bounds")
return ""
}
let range = Range(start: self.startIndex.advancedBy(start), end: self.startIndex.advancedBy(start + location))
return self.substringWithRange(range)
}
}
Swift 3
extension String
{
func substring(start: Int, end: Int) -> String
{
if (start < 0 || start > self.characters.count)
{
print("start index \(start) out of bounds")
return ""
}
else if end < 0 || end > self.characters.count
{
print("end index \(end) out of bounds")
return ""
}
let startIndex = self.characters.index(self.startIndex, offsetBy: start)
let endIndex = self.characters.index(self.startIndex, offsetBy: end)
let range = startIndex..<endIndex
return self.substring(with: range)
}
func substring(start: Int, location: Int) -> String
{
if (start < 0 || start > self.characters.count)
{
print("start index \(start) out of bounds")
return ""
}
else if location < 0 || start + location > self.characters.count
{
print("end index \(start + location) out of bounds")
return ""
}
let startIndex = self.characters.index(self.startIndex, offsetBy: start)
let endIndex = self.characters.index(self.startIndex, offsetBy: start + location)
let range = startIndex..<endIndex
return self.substring(with: range)
}
}
Usage:
let str = "Hello, playground"
let substring1 = str.substringWithRange(0, end: 5) //Hello
let substring2 = str.substringWithRange(7, location: 10) //playground
Sample Code for how to get substring in Swift 2.0
(i) Substring from starting index
Input:-
var str = "Swift is very powerful language!"
print(str)
str = str.substringToIndex(str.startIndex.advancedBy(5))
print(str)
Output:-
Swift is very powerful language!
Swift
(ii) Substring from particular index
Input:-
var str = "Swift is very powerful language!"
print(str)
str = str.substringFromIndex(str.startIndex.advancedBy(6)).substringToIndex(str.startIndex.advancedBy(2))
print(str)
Output:-
Swift is very powerful language!
is
I hope it will help you!
Easy solution with little code.
Make an extension that includes basic subStringing that nearly all other languages have:
extension String {
func subString(start: Int, end: Int) -> String {
let startIndex = self.index(self.startIndex, offsetBy: start)
let endIndex = self.index(startIndex, offsetBy: end)
let finalString = self.substring(from: startIndex)
return finalString.substring(to: endIndex)
}
}
Simply call this with
someString.subString(start: 0, end: 6)
This works in my playground :)
String(seq: Array(str)[2...4])
Updated for Xcode 7. Adds String extension:
Use:
var chuck: String = "Hello Chuck Norris"
chuck[6...11] // => Chuck
Implementation:
extension String {
/**
Subscript to allow for quick String substrings ["Hello"][0...1] = "He"
*/
subscript (r: Range<Int>) -> String {
get {
let start = self.startIndex.advancedBy(r.startIndex)
let end = self.startIndex.advancedBy(r.endIndex - 1)
return self.substringWithRange(start..<end)
}
}
}
try this in playground
var str:String = "Hello, playground"
let range = Range(start:advance(str.startIndex,1), end: advance(str.startIndex,8))
it will give you "ello, p"
However where this gets really interesting is that if you make the last index bigger than the string in playground it will show any strings that you defined after str :o
Range() appears to be a generic function so that it needs to know the type it is dealing with.
You also have to give it the actual string your interested in playgrounds as it seems to hold all stings in a sequence one after another with their variable name afterwards.
So
var str:String = "Hello, playground"
var str2:String = "I'm the next string"
let range = Range(start:advance(str.startIndex,1), end: advance(str.startIndex,49))
gives "ello, playground�str���I'm the next string�str2�"
works even if str2 is defined with a let
:)
Rob Napier had already given a awesome answer using subscript. But i felt one drawback in that as there is no check for out of bound conditions. This can tend to crash. So i modified the extension and here it is
extension String {
subscript (r: Range<Int>) -> String? { //Optional String as return value
get {
let stringCount = self.characters.count as Int
//Check for out of boundary condition
if (stringCount < r.endIndex) || (stringCount < r.startIndex){
return nil
}
let startIndex = self.startIndex.advancedBy(r.startIndex)
let endIndex = self.startIndex.advancedBy(r.endIndex - r.startIndex)
return self[Range(start: startIndex, end: endIndex)]
}
}
}
Output below
var str2 = "Hello, World"
var str3 = str2[0...5]
//Hello,
var str4 = str2[0..<5]
//Hello
var str5 = str2[0..<15]
//nil
So i suggest always to check for the if let
if let string = str[0...5]
{
//Manipulate your string safely
}
In Swift3
For ex: a variable "Duke James Thomas", we need to get "James".
let name = "Duke James Thomas"
let range: Range<String.Index> = name.range(of:"James")!
let lastrange: Range<String.Index> = img.range(of:"Thomas")!
var middlename = name[range.lowerBound..<lstrange.lowerBound]
print (middlename)
Taking a page from Rob Napier, I developed these Common String Extensions, two of which are:
subscript (r: Range<Int>) -> String
{
get {
let startIndex = advance(self.startIndex, r.startIndex)
let endIndex = advance(self.startIndex, r.endIndex - 1)
return self[Range(start: startIndex, end: endIndex)]
}
}
func subString(startIndex: Int, length: Int) -> String
{
var start = advance(self.startIndex, startIndex)
var end = advance(self.startIndex, startIndex + length)
return self.substringWithRange(Range<String.Index>(start: start, end: end))
}
Usage:
"Awesome"[3...7] //"some"
"Awesome".subString(3, length: 4) //"some"
This is how you get a range from a string:
var str = "Hello, playground"
let startIndex = advance(str.startIndex, 1)
let endIndex = advance(startIndex, 8)
let range = startIndex..<endIndex
let substr = str[range] //"ello, pl"
The key point is that you are passing a range of values of type String.Index (this is what advance returns) instead of integers.
The reason why this is necessary, is that strings in Swift don't have random access (because of variable length of Unicode characters basically). You also can't do str[1]. String.Index is designed to work with their internal structure.
You can create an extension with a subscript though, that does this for you, so you can just pass a range of integers (see e.g. Rob Napier's answer).
I tried to come up with something Pythonic.
All the subscripts here are great, but the times I really need something simple is usually when I want to count from back, e.g. string.endIndex.advancedBy(-1)
It supports nil values, for both start and end index, where nil would mean index at 0 for start, string.characters.count for end.
extension String {
var subString: (Int?) -> (Int?) -> String {
return { (start) in
{ (end) in
let startIndex = start ?? 0 < 0 ? self.endIndex.advancedBy(start!) : self.startIndex.advancedBy(start ?? 0)
let endIndex = end ?? self.characters.count < 0 ? self.endIndex.advancedBy(end!) : self.startIndex.advancedBy(end ?? self.characters.count)
return startIndex > endIndex ? "" : self.substringWithRange(startIndex ..< endIndex)
}
}
}
}
let dog = "Dog‼🐶"
print(dog.subString(nil)(-1)) // Dog!!
EDIT
A more elegant solution:
public extension String {
struct Substring {
var start: Int?
var string: String
public subscript(end: Int?) -> String {
let startIndex = start ?? 0 < 0 ? string.endIndex.advancedBy(start!) : string.startIndex.advancedBy(start ?? 0)
let endIndex = end ?? string.characters.count < 0 ? string.endIndex.advancedBy(end!) : string.startIndex.advancedBy(end ?? string.characters.count)
return startIndex > endIndex ? "" : string.substringWithRange(startIndex ..< endIndex)
}
}
public subscript(start: Int?) -> Substring {
return Substring(start: start, string: self)
}
}
let dog = "Dog‼🐶"
print(dog[nil][-1]) // Dog!!
First create the range, then the substring. You can use fromIndex..<toIndex syntax like so:
let range = fullString.startIndex..<fullString.startIndex.advancedBy(15) // 15 first characters of the string
let substring = fullString.substringWithRange(range)
here is a example to get video-Id only .i.e (6oL687G0Iso) from the whole URL in swift
let str = "https://www.youtube.com/watch?v=6oL687G0Iso&list=PLKmzL8Ib1gsT-5LN3V2h2H14wyBZTyvVL&index=2"
var arrSaprate = str.componentsSeparatedByString("v=")
let start = arrSaprate[1]
let rangeOfID = Range(start: start.startIndex,end:start.startIndex.advancedBy(11))
let substring = start[rangeOfID]
print(substring)
let startIndex = text.startIndex
var range = startIndex.advancedBy(1) ..< text.endIndex.advancedBy(-4)
let substring = text.substringWithRange(range)
Full sample you can see here
http://www.learnswiftonline.com/reference-guides/string-reference-guide-for-swift/
shows that this works well:
var str = "abcd"
str = str.substringToIndex(1)
Well, I had the same issue and solved with the "bridgeToObjectiveC()" function:
var helloworld = "Hello World!"
var world = helloworld.bridgeToObjectiveC().substringWithRange(NSMakeRange(6,6))
println("\(world)") // should print World!
Please note that in the example, substringWithRange in conjunction with NSMakeRange take the part of the string starting at index 6 (character "W") and finishing at index 6 + 6 positions ahead (character "!")
Cheers.
You can use any of the substring methods in a Swift String extension I wrote https://bit.ly/JString.
var string = "hello"
var sub = string.substringFrom(3) // or string[3...5]
println(sub)// "lo"
If you have an NSRange, bridging to NSString works seamlessly. For example, I was doing some work with UITextFieldDelegate and I quickly wanted to compute the new string value when it asked if it should replace the range.
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
let newString = (textField.text as NSString).stringByReplacingCharactersInRange(range, withString: string)
println("Got new string: ", newString)
}
Simple extension for String:
extension String {
func substringToIndex(index: Int) -> String {
return self[startIndex...startIndex.advancedBy(min(index, characters.count - 1))]
}
}
If you don't care about performance... this is probably the most concise solution in Swift 4
extension String {
subscript(range: CountableClosedRange<Int>) -> String {
return enumerated().filter{$0.offset >= range.first! && $0.offset < range.last!}
.reduce(""){$0 + String($1.element)}
}
}
It enables you to do something like this:
let myStr = "abcd"
myStr[0..<2] // produces "ab"