range使用 - swift4 range




如何在Swift中创建范围? (6)

(1 .. <10)

返回...

范围= 1 .. <10

在Objective-c中,我们使用NSRange创建范围

NSRange range;

那么如何在Swift中创建范围呢?


你可以这样使用

let nsRange = NSRange(location: someInt, length: someInt)

let myNSString = bigTOTPCode as NSString //12345678
let firstDigit = myNSString.substringWithRange(NSRange(location: 0, length: 1)) //1
let secondDigit = myNSString.substringWithRange(NSRange(location: 1, length: 1)) //2
let thirdDigit = myNSString.substringWithRange(NSRange(location: 2, length: 4)) //3456

我创建了以下扩展名:

extension String {
    func substring(from from:Int, to:Int) -> String? {
        if from<to && from>=0 && to<self.characters.count {
            let rng = self.startIndex.advancedBy(from)..<self.startIndex.advancedBy(to)
            return self.substringWithRange(rng)
        } else {
            return nil
        }
    }
}

使用示例:

print("abcde".substring(from: 1, to: 10)) //nil
print("abcde".substring(from: 2, to: 4))  //Optional("cd")
print("abcde".substring(from: 1, to: 0))  //nil
print("abcde".substring(from: 1, to: 1))  //nil
print("abcde".substring(from: -1, to: 1)) //nil

我感到惊讶的是,即使在Swift 4中,仍然没有使用Int来表达String范围的简单本地方法。 唯一允许您提供Int作为按范围获取子字符串的方式的String方法是 prefixsuffix

拥有一些转换实用程序很有用,这样我们在与String交谈时可以像NSRange一样交谈。 这是一个实用程序,它需要一个位置和长度,就像NSRange一样,并返回 Range<String.Index>

func range(_ start:Int, _ length:Int) -> Range<String.Index> {
    let i = self.index(start >= 0 ? self.startIndex : self.endIndex,
        offsetBy: start)
    let j = self.index(i, offsetBy: length)
    return i..<j
}

例如, "hello".range(0,1)"Range<String.Index> 包含 "hello" 的第一个字符。作为奖励,我允许使用负位置: "hello".range(-1,1)"Range<String.Index> 包含 "hello" 的最后一个字符。

在必须与Cocoa交谈的那些时刻(例如,在处理NSAttributedString属性范围时),将 Range<String.Index> 转换为NSRange也很有用。 Swift 4提供了一种本地方法:

let nsrange = NSRange(range, in:s) // where s is the string

因此,我们可以编写另一个实用程序,直接从String的位置和长度转到NSRange:

extension String {
    func nsRange(_ start:Int, _ length:Int) -> NSRange {
        return NSRange(self.range(start,length), in:self)
    }
}

为Swift 4更新

Swift范围比 NSRange 更复杂,在Swift 3中并没有那么简单。如果您想尝试了解这种复杂性背后的原因,请阅读 thisthis 。 我将仅向您展示如何创建它们以及何时使用它们。

封闭范围: a...b

即使 b 是类型的最大可能值(例如 Int.max ),此 范围运算符 也会创建一个同时包含元素 a 元素 b 的Swift范围。 封闭范围有两种不同类型: ClosedRangeCountableClosedRange

1. ClosedRange

Swift中所有范围的元素都是可比较的(即,它们符合Comparable协议)。 这样您就可以访问集合中范围内的元素。 这是一个例子:

let myRange: ClosedRange = 1...3

let myArray = ["a", "b", "c", "d", "e"]
myArray[myRange] // ["b", "c", "d"]

但是, ClosedRange 不可计数(即,它不符合Sequence协议)。 这意味着您不能使用 for 循环遍历元素。 为此,您需要 CountableClosedRange

2. CountableClosedRange

这与最后一个相似,除了现在还可以迭代范围。

let myRange: CountableClosedRange = 1...3

let myArray = ["a", "b", "c", "d", "e"]
myArray[myRange] // ["b", "c", "d"]

for index in myRange {
    print(myArray[index])
}

半开范围: a..<b

此范围运算符包括元素 a 包括元素 b 。 像上面一样,有两种不同类型的半开范围: RangeCountableRange

1. Range

ClosedRange ,您可以使用 Range 访问集合的元素。 例:

let myRange: Range = 1..<3

let myArray = ["a", "b", "c", "d", "e"]
myArray[myRange] // ["b", "c"]

同样,您不能在 Range 进行迭代,因为它只是可比较的而不是可跨越的。

2. CountableRange

CountableRange 允许迭代。

let myRange: CountableRange = 1..<3

let myArray = ["a", "b", "c", "d", "e"]
myArray[myRange] // ["b", "c"]

for index in myRange {
    print(myArray[index])
}

NSRange

在Swift中,您仍然可以(必须)有时仍使用 NSRange (例如,在制作 属性字符串 时),因此了解如何制作 NSRange 很有帮助。

let myNSRange = NSRange(location: 3, length: 2)

请注意,这是位置和 长度 ,而不是开始索引和结束索引。 此处的示例在含义上与Swift范围 3..<5 相似。 但是,由于类型不同,因此不能互换。

带字符串的范围

.....< 范围运算符是创建范围的简便方法。 例如:

let myRange = 1..<3

创建相同范围的长期方法是

let myRange = CountableRange<Int>(uncheckedBounds: (lower: 1, upper: 3)) // 1..<3

您可以看到这里的索引类型是 Int 。 但是,这不适用于 String ,因为String是由Characters组成的,并且并非所有字符的大小都相同。 (有关更多信息,请阅读 this 内容。)例如😀之类的表情符号比字母“ b”占用更多空间。

NSRange问​​题

尝试使用 NSRange 和带有表情符号的 NSString 进行试验,您会明白我的意思。 头痛。

let myNSRange = NSRange(location: 1, length: 3)

let myNSString: NSString = "abcde"
myNSString.substring(with: myNSRange) // "bcd"

let myNSString2: NSString = "a😀cde"
myNSString2.substring(with: myNSRange) // "😀c"    Where is the "d"!?

笑脸需要存储两个UTF-16代码单元,因此它给出了不包含“ d”的意外结果。

快速解决方案

因此,在Swift Strings中,您可以使用 Range<String.Index> ,而不是 Range<Int> 。 字符串索引是根据特定的字符串计算得出的,因此它知道是否存在任何表情符号或扩展的字形簇。

var myString = "abcde"
let start = myString.index(myString.startIndex, offsetBy: 1)
let end = myString.index(myString.startIndex, offsetBy: 4)
let myRange = start..<end
myString[myRange] // "bcd"

myString = "a😀cde"
let start2 = myString.index(myString.startIndex, offsetBy: 1)
let end2 = myString.index(myString.startIndex, offsetBy: 4)
let myRange2 = start2..<end2
myString[myRange2] // "😀cd"

单面范围: a......b..<b

在Swift 4中,事情做了一些简化。 只要可以推断范围的起点或终点,就可以将其关闭。

整数

您可以使用单面整数范围对集合进行迭代。 以下是 文档 中的一些示例。

// iterate from index 2 to the end of the array
for name in names[2...] {
    print(name)
}

// iterate from the beginning of the array to index 2
for name in names[...2] {
    print(name)
}

// iterate from the beginning of the array up to but not including index 2
for name in names[..<2] {
    print(name)
}

// the range from negative infinity to 5. You can't iterate forward
// over this because the starting point in unknown.
let range = ...5
range.contains(7)   // false
range.contains(4)   // true
range.contains(-1)  // true

// You can iterate over this but it will be an infinate loop 
// so you have to break out at some point.
let range = 5...

这也适用于字符串范围。 如果要在一端使用 str.startIndexstr.endIndex 进行范围 str.startIndex ,则可以将其关闭。 编译器将推断出它。

给定

var str = "Hello, playground"
let index = str.index(str.startIndex, offsetBy: 5)

let myRange = ..<index    // Hello

您可以使用 ... 从索引转到str.endIndex ...

var str = "Hello, playground"
let index = str.index(str.endIndex, offsetBy: -10)
let myRange = index...        // playground

也可以看看:

  • String.Index如何在Swift中工作
  • 字符串子字符串在Swift中如何工作

笔记

  • 您不能将使用一个字符串创建的范围用于另一个字符串。
  • 如您所见,String范围在Swift中很麻烦,但确实可以更好地处理表情符号和其他Unicode标量。

进一步研究


Xcode 8 beta 2•Swift 3

let myString = "Hello World"
let myRange = myString.startIndex..<myString.index(myString.startIndex, offsetBy: 5)
let mySubString = myString.substring(with: myRange)   // Hello

Xcode 7•Swift 2.0

let myString = "Hello World"
let myRange = Range<String.Index>(start: myString.startIndex, end: myString.startIndex.advancedBy(5))

let mySubString = myString.substringWithRange(myRange)   // Hello

或简单地

let myString = "Hello World"
let myRange = myString.startIndex..<myString.startIndex.advancedBy(5)
let mySubString = myString.substringWithRange(myRange)   // Hello




range