Apple has an old example how to get the range of a whole line, given a particular character range:
In order to obtain the full line range of the first line, they call the following Objective-C function:
[string lineRangeForRange:NSMakeRange(0, 0)]
I tried to implement the same thing in Swift, but I can't make it work because the method signature has changed:
string.lineRange(for: NSRange(location: 0, length: 0))
throws a compiler error:
Argument type 'NSRange' (aka '_NSRange') does not conform to expected type 'RangeExpression'
RangeExpression
is some weird protocol I haven't really understood in its entirety. However, I figured that Range<Bound>
conforms to it, so I tried the following:
let range = NSRange(location: 0, length: 0)
textView.string.lineRange(for: Range<Int>(range)!)
This time I get another compiler error:
Generic parameter 'R' could not be inferred
I couldn't find any generic parameter R
, neither in Range
, nor in RangeExpression
.
lineRange(for:)
(essentially) expects a range of String.Index
, not a range of integers. Here is a simple example:
let string = "Line1\nLine2"
// Find the full range of the line containing the first "1":
if let range = string.range(of: "1") {
let lineRange = string.lineRange(for: range)
print(string[lineRange]) // Line1
}
The actual parameter is a generic parameter of type R : RangeExpression, R.Bound == String.Index
, which means that you can also pass partial ranges like string.startIndex...
or ..<string.endIndex
.
The Swift version of the Objective-C sample code
NSString *string;
unsigned numberOfLines, index, stringLength = [string length];
for (index = 0, numberOfLines = 0; index < stringLength; numberOfLines++)
index = NSMaxRange([string lineRangeForRange:NSMakeRange(index, 0)]);
would be
let string = "Line1\nLine2"
var index = string.startIndex
var numberOfLines = 0
while index != string.endIndex {
let range = string.lineRange(for: index..<index)
numberOfLines += 1
index = range.upperBound
}
print(numberOfLines)
Here index..<index
takes the role of NSMakeRange(index, 0)
.
If the purpose is just to count (or enumerate) the total number of lines then an alternative is to use
string.enumerateLines(invoking: { (line, _) in
// ...
})
instead (compare How to split a string by new lines in Swift).