How to get the range of the first line in a string

2019-07-30 15:14发布

I would like to change the formatting of the first line of text in an NSTextView (give it a different font size and weight to make it look like a headline). Therefore, I need the range of the first line. One way to go is this:

guard let firstLineString = textView.string.components(separatedBy: .newlines).first else {
    return
}
let range = NSRange(location: 0, length: firstLineString.count)

However, I might be working with quite long texts so it appears to be inefficient to first split the entire string into line components when all I need is the first line component. Thus, it seems to make sense to use the firstIndex(where:) method:

let firstNewLineIndex = textView.string.firstIndex { character -> Bool in
    return CharacterSet.newlines.contains(character)
}
// Then: Create an NSRange from 0 up to firstNewLineIndex. 

This doesn't work and I get an error:

Cannot convert value of type '(Unicode.Scalar) -> Bool' to expected argument type 'Character'

because the contains method accepts not a Character but a Unicode.Scalar as a parameter (which doesn't really make sense to me because then it should be called a UnicodeScalarSet and not a CharacterSet, but nevermind...).

My question is:

How can I implement this in an efficient way, without first slicing the whole string?

(It doesn't necessarily have to use the firstIndex(where:) method, but appears to be the way to go.)

2条回答
爷的心禁止访问
2楼-- · 2019-07-30 16:07

You can use rangeOfCharacter, which returns the Range<String.Index> of the first character from a set in your string:

extension StringProtocol where Index == String.Index {
    var partialRangeOfFirstLine: PartialRangeUpTo<String.Index> {
        return ..<(rangeOfCharacter(from: .newlines)?.lowerBound ?? endIndex)
    }
    var rangeOfFirstLine: Range<Index> {
        return startIndex..<partialRangeOfFirstLine.upperBound
    }
    var firstLine: SubSequence {
        return self[partialRangeOfFirstLine]
    }
}

You can use it like so:

var str = """
    some string 
    with new lines
"""
var attributedString = NSMutableAttributedString(string: str)
let firstLine = NSAttributedString(string: String(str.firstLine))
// change firstLine as you wish

let range = NSRange(str.rangeOfFirstLine, in: str)
attributedString.replaceCharacters(in: range, with: firstLine)
查看更多
太酷不给撩
3楼-- · 2019-07-30 16:14

A String.Index range for the first line in string can be obtained with

let range = string.lineRange(for: ..<string.startIndex)

If you need that as an NSRange then

let nsRange = NSRange(range, in: string)

does the trick.

查看更多
登录 后发表回答