How to get range of substring in swift3?
let string = "Please Click Here"
if let range = string.range(of: "Click") {
print(range)
}
How does String substring work in Swift
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 then, I am choosing to use their API. It helps me to remember that String manipulations are not just simple Int
index lookups.
Swift find all occurrences of a substring
You just keep advancing the search range until you can't find any more instances of the substring:
extension String {
func indicesOf(string: String) -> [Int] {
var indices = [Int]()
var searchStartIndex = self.startIndex
while searchStartIndex < self.endIndex,
let range = self.range(of: string, range: searchStartIndex..<self.endIndex),
!range.isEmpty
{
let index = distance(from: self.startIndex, to: range.lowerBound)
indices.append(index)
searchStartIndex = range.upperBound
}
return indices
}
}
let keyword = "a"
let html = "aaaa"
let indicies = html.indicesOf(string: keyword)
print(indicies) // [0, 1, 2, 3]
How do you use String.substringWithRange? (or, how do Ranges work in Swift?)
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.
How to get substring with specific ranges in Swift 4?
You can search for substrings using range(of:)
.
import Foundation
let greeting = "Hello there world!"
if let endIndex = greeting.range(of: "world!")?.lowerBound {
print(greeting[..<endIndex])
}
outputs:
Hello there
EDIT:
If you want to separate out the words, there's a quick-and-dirty way and a good way. The quick-and-dirty way:
import Foundation
let greeting = "Hello there world!"
let words = greeting.split(separator: " ")
print(words[1])
And here's the thorough way, which will enumerate all the words in the string no matter how they're separated:
import Foundation
let greeting = "Hello there world!"
var words: [String] = []
greeting.enumerateSubstrings(in: greeting.startIndex..<greeting.endIndex, options: .byWords) { substring, _, _, _ in
if let substring = substring {
words.append(substring)
}
}
print(words[1])
EDIT 2: And if you're just trying to get the 7th through the 11th character, you can do this:
import Foundation
let greeting = "Hello there world!"
let startIndex = greeting.index(greeting.startIndex, offsetBy: 6)
let endIndex = greeting.index(startIndex, offsetBy: 5)
print(greeting[startIndex..<endIndex])
Index of a substring in a string with Swift
edit/update:
Xcode 11.4 • Swift 5.2 or later
import Foundation
extension StringProtocol {
func index<S: StringProtocol>(of string: S, options: String.CompareOptions = []) -> Index? {
range(of: string, options: options)?.lowerBound
}
func endIndex<S: StringProtocol>(of string: S, options: String.CompareOptions = []) -> Index? {
range(of: string, options: options)?.upperBound
}
func indices<S: StringProtocol>(of string: S, options: String.CompareOptions = []) -> [Index] {
ranges(of: string, options: options).map(\.lowerBound)
}
func ranges<S: StringProtocol>(of string: S, options: String.CompareOptions = []) -> [Range<Index>] {
var result: [Range<Index>] = []
var startIndex = self.startIndex
while startIndex < endIndex,
let range = self[startIndex...]
.range(of: string, options: options) {
result.append(range)
startIndex = range.lowerBound < range.upperBound ? range.upperBound :
index(range.lowerBound, offsetBy: 1, limitedBy: endIndex) ?? endIndex
}
return result
}
}
usage:
let str = "abcde"
if let index = str.index(of: "cd") {
let substring = str[..<index] // ab
let string = String(substring)
print(string) // "ab\n"
}
let str = "Hello, playground, playground, playground"
str.index(of: "play") // 7
str.endIndex(of: "play") // 11
str.indices(of: "play") // [7, 19, 31]
str.ranges(of: "play") // [{lowerBound 7, upperBound 11}, {lowerBound 19, upperBound 23}, {lowerBound 31, upperBound 35}]
case insensitive sample
let query = "Play"
let ranges = str.ranges(of: query, options: .caseInsensitive)
let matches = ranges.map { str[$0] } //
print(matches) // ["play", "play", "play"]
regular expression sample
let query = "play"
let escapedQuery = NSRegularExpression.escapedPattern(for: query)
let pattern = "\\b\(escapedQuery)\\w+" // matches any word that starts with "play" prefix
let ranges = str.ranges(of: pattern, options: .regularExpression)
let matches = ranges.map { str[$0] }
print(matches) // ["playground", "playground", "playground"]
How to figure out the range of a substring in one string and then use it in another
String (or generally, collection) indices must only be used with the collection that they were created with. In order to find the same positions in another string, the indices must be converted to (integer) offsets and back to indices of the target string:
func foobar(input: String) -> String? {
let s1 = "0123456789"
let s2 = "一二三四五六七八九";
guard let range = s1.range(of: input) else {
return nil
}
let pos = s1.distance(from: s1.startIndex, to: range.lowerBound)
let len = s1.distance(from: range.lowerBound, to: range.upperBound)
guard
let lo = s2.index(s2.startIndex, offsetBy: pos, limitedBy: s2.endIndex),
let hi = s2.index(lo, offsetBy: len, limitedBy: s2.endIndex)
else {
return nil
}
return String(s2[lo..<hi])
}
print(foobar(input: "1") as Any) // Optional("一")
print(foobar(input: "123") as Any) // Optional("一二三")
print(foobar(input: "124") as Any) // nil
Your Objective-C code works as long as all characters in the string consume a single UTF-16 code unit (because that is what NSRange
counts). It will not work correctly emojis, flags, and other characters which are represented as UTF-16 surrogate pairs, e.g. with
NSString *anotherString = @"一二三四五六七八九";
IOS How to get String.range(of:string) multiple in swift
If you want to find all ranges you can try to use solution from this question get all ranges of a substring in a string in swift
but if your main purpose of that is replacing the occurrence of some string/pattern you can add an extension like that:
extension String {
func replacing(pattern: String, withTemplate: String) throws -> String {
let regex = try NSRegularExpression(pattern: pattern, options: .caseInsensitive)
return regex.stringByReplacingMatches(in: self,
options: [],
range: NSRange(0 ..< utf16.count),
withTemplate: withTemplate)
}
}
Related Topics
Uitableview Has Unwanted Animation When Reloaddata Is Called
How to Include Openssl on an iOS Project in a Way That Works
Pass a Nsdictionary as Parameter to UItapgesturerecognizer
How to Fetch Images from Photo Library Within Range of Two Dates in iOS
Spritekit Swift Node Count Issues
How to "Rerequest" Email Permission Using Facebook iOS Sdk 4.X
Restkit 0.20 JSON Mapping Along with Additional Offline Data
App Crashes After Updating Coredata Model That Is Being Displayed in a UItableview
How to Print Out(Nslog) The Properties of a Custom Object Added to a Nsmutablearray
Error When Instantiating a UIfont in an Text Attributes Dictionary
Is History API Broken on iOS? (Location Bar Doesn't Update on Pushstate)
Invalid Update: Invalid Number of Sections in Uitableview