Cannot increment beyond endIndex
You can write something like this
extension String {
func substring(from offset: Int) -> String {
let fromIndex = index(self.startIndex, offsetBy: offset)
return substring(from: fromIndex)
}
}
Examples
"Hello world".substring(from: 0) // "Hello world"
"Hello world".substring(from: 1) // "ello world"
"Hello world".substring(from: 2) // "llo world"
What does happen if you pass the wrong param?
Something like this will generate a fatal error.
"Hello world".substring(from: 12)
fatal error: cannot increment beyond endIndex
You can make you code safer adding a guard statement like this
extension String {
func substring(from: Int) -> String? {
guard from < self.characters.count else { return nil }
let fromIndex = index(self.startIndex, offsetBy: from)
return substring(from: fromIndex)
}
}
Spaces in string causing fatal error: cannot increment beyond endIndex
Your code is working with the given string:
var str = "PIXEL STUDIOS - TEST1"
let c = str.characters
let r = c.index(c.startIndex, offsetBy: 6)..<c.index(c.endIndex, offsetBy: 0)
let substring = str[r]
print(substring)
prints:
STUDIOS - TEST1
Conclusion: label.stringValue
is fishy.
Print it out for further investigations.
String.endIndex incrementing error
There are two problems:
fieldName.endIndex
is the "one past the end" position of the string,
it has no successor.- You must not use the index of one string as the subscript for
a different string. That may work in some cases, but can
crash with a runtime exception if the strings contain
characters outside of the "basic multilingual plane" (Emojis, flags, ...).
A working variant would be (Swift 2.2):
let fieldName = "VERSION:"
let versionField = "VERSION:4.1"
if versionField.hasPrefix(fieldName) {
let version = versionField.substringFromIndex(
versionField.startIndex.advancedBy(fieldName.characters.count))
print(version) // 4.1
} else {
print("No version found")
}
or alternatively:
if let range = versionField.rangeOfString(fieldName)
where range.startIndex == versionField.startIndex {
let version = versionField.substringFromIndex(range.endIndex)
print(version) // 4.1
} else {
print("No version found")
}
You can remove the constraint
where range.startIndex == versionField.startIndex
if the field should be found anywhere in the string.
Swift 3:
if versionField.hasPrefix(fieldName) {
let version = versionField.substring(
from: versionField.index(versionField.startIndex, offsetBy: fieldName.characters.count))
print(version) // 4.1
} else {
print("No version found")
}
or alternatively,
if let range = versionField.range(of: fieldName),
range.lowerBound == versionField.startIndex {
let version = versionField.substring(from: range.upperBound)
print(version) // 4.1
} else {
print("No version found")
}
Can't advance past endIndex Swift
You are modifying the string you are iterating over, so your indices become invalid. Instead, you could add a skipChar
boolean that says that you've already handled the next character and then skip that character by executing continue
:
func romanToInt(_ s: String) -> Int {
let romanDigits = ["I" : 1,
"V" : 5,
"X" : 10,
"L" : 50,
"C" : 100,
"D" : 500,
"M" : 1000]
let romanSums = ["IV" : 4,
"IX" : 9,
"XL" : 40,
"XC" : 90,
"CD" : 400,
"CM" : 900]
var sum = 0
var str = s
var charIndex = str.startIndex
var skipChar = false
for index in str.indices {
if skipChar {
skipChar = false
continue
}
if index != str.index(before: str.endIndex) {
charIndex = str.index(after: index)
} else {
charIndex = str.index(before: str.endIndex)
}
let chars = String(str[index]) + String(str[charIndex])
if romanSums[chars] != nil {
print(chars)
skipChar = true
sum += romanSums[chars]!
print(sum)
} else {
let char = String(str[index])
print(char)
sum += romanDigits[char]!
print(sum)
}
print(str)
}
return sum
}
let check = romanToInt("MCMXCIV")
print(check)
1994
Conforming String.CharacterView.Index to Strideable: fatal error when using stride(to:by:): cannot increment endIndex
Simply declaring the protocol conformance
extension String.CharacterView.Index : Strideable { }
compiles because String.CharacterView.Index
conforms to BidirectionalIndexType
, and ForwardIndexType/BidirectionalIndexType
have default method implementations for advancedBy()
and distanceTo()
as required by Strideable
.
Strideable
has the default protocol method implementation
for stride()
:
extension Strideable {
// ...
public func stride(to end: Self, by stride: Self.Stride) -> StrideTo<Self>
}
So the only methods which are "directly" implemented forString.CharacterView.Index
are – as far as I can see - the successor()
and predecessor()
methods from BidirectionalIndexType
.
As you already figured out, the default method implementation ofstride()
does not work well with String.CharacterView.Index
.
But is is always possible to define dedicated methods for a concrete type. For the problems of making String.CharacterView.Index
conform to Strideable
see
Vatsal Manot's answer below and the discussion in the comments – it took me a while to get what he meant :)
Here is a possible implementation of a stride(to:by:)
method for String.CharacterView.Index
:
extension String.CharacterView.Index {
typealias Index = String.CharacterView.Index
func stride(to end: Index, by stride: Int) -> AnySequence<Index> {
precondition(stride != 0, "stride size must not be zero")
return AnySequence { () -> AnyGenerator<Index> in
var current = self
return AnyGenerator {
if stride > 0 ? current >= end : current <= end {
return nil
}
defer {
current = current.advancedBy(stride, limit: end)
}
return current
}
}
}
}
This seems to work as expected:
let str = "01234"
str.startIndex.stride(to: str.endIndex, by: 2).forEach {
print($0,str.characters[$0])
}
Output
0 0
2 2
4 4
Swift 4 Substring Crash
If you use the variation with limitedBy
parameter, that will return an optional value:
if let start = str.index(str.startIndex, offsetBy: 7, limitedBy: str.endIndex) {
...
}
That will gracefully detect whether the offset moves the index past the endIndex
. Obviously, handle this optional however best in your scenario (if let
, guard let
, nil coalescing operator, etc.).
Different behavior of index(after:) in Collection and String
If we go to the source code of Array
, we can find:
public func index(after i: Int) -> Int {
// NOTE: this is a manual specialization of index movement for a Strideable
// index that is required for Array performance. The optimizer is not
// capable of creating partial specializations yet.
// NOTE: Range checks are not performed here, because it is done later by
// the subscript function.
return i + 1
}
In this case, we can rewrite code like this and finally get the crash:
let a = [1]
let index = a.index(after: a.endIndex)
print(a[index])
So, all works "as expected" for Array
type, but we must check the result index
by yourself, if we don't want to get crash at runtime
P.S. Useful link by @MartinR: https://forums.swift.org/t/behaviour-of-collection-index-limitedby/19083/3
How to offset a RangeT in Swift?
There’s a second version of advance
that takes a maximum index not to go beyond:
let s = "Hello, I must be going."
let range = s.startIndex..<s.endIndex
let startIndex = advance(range.startIndex, 50, s.endIndex)
let endIndex = advance(range.endIndex, 50, s.endIndex)
var newRange = startIndex..<endIndex
if newRange.isEmpty {
println("new range out of bounds")
}
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]
Insert Charc into String in a Range where character X exist at Index - swift 2.0
The following finds the first space in the range 15..<END_OF_YOUR_STRING
, and replaces it with a new line (\n
). In your question you stated you explicitly wanted to look for a space in range 15...20
, and also insert a new line after the space. Below I have assumed that you actually want:
- To replace the space by a new line, since you'll otherwise have a trailing space on the line following the line break.
- To search for the first space starting at index
15
, but continuing until you find one (otherwise: if you find no space within range15...20
, no line break should be inserted?).
Both of these deviations from your question can be quite easily reverted, so tell me if you'd prefer me to follow your instructions to specifically to the point (rather than including my own reason), and I'll update this answer.
Solution as follows:
var foo = "This is my somewhat long test string"
let bar = 15 /* find first space " " starting from index 'bar' */
if let replaceAtIndex = foo[foo.startIndex.advancedBy(bar)..<foo.endIndex]
.rangeOfString(" ")?.startIndex.advancedBy(bar) {
foo = foo.stringByReplacingCharactersInRange(
replaceAtIndex...replaceAtIndex, withString: "\n")
}
print(foo)
/* This is my somewhat
long test string */
Note that there is a off-by-one difference between finding a space in the range of 15 to 20 and the 15:th to 20:th character (the latter is in the range 14...19
). Above, we search for the first space starting at the 16th character (index 15
).
Related Topics
Adding a UIvisualeffectview to Button
Binary Search: Error in Passing Array
How to Use a Protocol with a Typealias as a Func Parameter
Social Framework Is Deprecated in iOS11
How to Change an UIimageview into a Custom Cell
Make Statement More Clear: Checking Object for Key in Dictionary
Swift Generics and Protocol Extensions
When to Detach Firebase Listeners in Tableview Cell
Uipageviewcontroller with Previous and Next Buttons
Swiftui Index Out of Range in Foreach
Use The Same View for Adding and Editing Coredata Objects
Encode Struct and Convert to Dictionary [String: Any]
Mapping a Dictionary to an Array of Structs in Swift
Nevpnmanager with L2Tp Protocol
How to Segue an Image to Another Viewcontroller and Display It Within an Imageview