如何从完整字符串 iOS swift 中找到字符串的多个 NSRange

Kar*_*ren 2 nsstring nsrange swift

let fullString = "Hello world, there are \(string(07)) continents and \(string(195)) countries."
let range = [NSMakeRange(24,2), NSMakeRange(40,3)]
Run Code Online (Sandbox Code Playgroud)

需要找到整个完整字符串中数字的 NSRange,并且两个数字有可能相同。目前如上所示进行硬编码,消息可以是动态的,而硬编码值将出现问题。

我已经分割了字符串并尝试获取,NSRange因为可能存在相同的值。比如 stringOne 和 stringTwo。

func findNSMakeRange(initialString:String, fromString: String) {
        let fullStringRange = fromString.startIndex..<fromString.endIndex
        fromString.enumerateSubstrings(in: fullStringRange, options: NSString.EnumerationOptions.byWords) { (substring, substringRange, enclosingRange, stop) -> () in
            let start = distance(fromString.startIndex, substringRange.startIndex)
            let length = distance(substringRange.startIndex, substringRange.endIndex)
            let range = NSMakeRange(start, length)

            if (substring == initialString) {
                print(substring, range)
            }
        })
    }
Run Code Online (Sandbox Code Playgroud)

收到类似错误Cannot invoke distance with an argument list of type (String.Index, String.Index)

大家有更好的解决办法吗?

Rob*_*Rob 9

您说您想要迭代NSRange字符串中的匹配项,以便可以将粗体属性应用于相关子字符串。

在 Swift 5.7 及更高版本中,您可以使用新的Regex

string.ranges(of: /\d+/)
    .map { NSRange($0, in: string) }
    .forEach {
        attributedString.setAttributes(attributes, range: $0)
    }
Run Code Online (Sandbox Code Playgroud)

或者,如果您发现传统的正则表达式太神秘,您可以导入RegexBuilder,并且可以使用新的正则表达式 DSL:

string.ranges(of: Regex { OneOrMore(.digit) })
    .map { NSRange($0, in: string) }
    .forEach {
        attributedString.setAttributes(attributes, range: $0)
    }
Run Code Online (Sandbox Code Playgroud)

现在,只有当您确实需要 an 时才需要这种将 映射Range<String.Index>到 a的模式,而现在很少需要这种模式。例如,如果您只想打印子范围,您可以这样做:NSRangeNSRange

string.ranges(of: /\d+/)
    .forEach { print(string[$0]) }
Run Code Online (Sandbox Code Playgroud)

AttributedString或者,如果您需要在原始 中查找范围的基础上更新可变的String,您可以映射到Range<AttributedString.Index>

string.ranges(of: /\d+/)
    .compactMap { Range($0, in: attributedString) }
    .forEach { attributedString[$0].foregroundColor = .blue }
Run Code Online (Sandbox Code Playgroud)

但关键的一点是,我们现在可以使用字符Regex包围的本机文字/,如上所示。


在 5.7 之前的 Swift 版本中,人们会使用NSRegularExpression. 例如:

let range = NSRange(location: 0, length: string.count)
try! NSRegularExpression(pattern: "\\d+").enumerateMatches(in: string, range: range) { result, _, _ in
    guard let range = result?.range else { return }
    attributedString.setAttributes(attributes, range: range)
}
Run Code Online (Sandbox Code Playgroud)

因为正则表达式反斜杠位于字符串内部,所以必须用另一个反斜杠对其进行转义(这就是其中有两个反斜杠的原因)。或者您可以使用扩展字符串分隔符,替换"\\d+"#"\d+"#.


就个人而言,在 Swift 5.7 之前,我发现有一个返回 Swift 范围数组的方法很有用,即[Range<String.Index>]

extension StringProtocol {
    func ranges<T: StringProtocol>(of string: T, options: String.CompareOptions = []) -> [Range<Index>] {
        var ranges: [Range<Index>] = []
        var start: Index = startIndex
        
        while let range = range(of: string, options: options, range: start ..< endIndex) {
            ranges.append(range)
            
            if !range.isEmpty {
                start = range.upperBound               // if not empty, resume search at upper bound
            } else if range.lowerBound < endIndex {
                start = index(after: range.lowerBound) // if empty and not at end, resume search at next character
            } else {
                break                                  // if empty and at end, then quit
            }
        }
        
        return ranges
    }
}
Run Code Online (Sandbox Code Playgroud)

然后你可以像这样使用它:

let string = "Hello world, there are 09 continents and 195 countries."
let ranges = string.ranges(of: "[0-9]+", options: .regularExpression)
Run Code Online (Sandbox Code Playgroud)

然后你就可以map到. 回到最初的示例,如果您想在某些属性字符串中将这些数字设为粗体:RangeNSRange

let string = "Hello world, there are 09 continents and 195 countries."
let ranges = string.ranges(of: "[0-9]+", options: .regularExpression)
Run Code Online (Sandbox Code Playgroud)

资源: