Answers:
为Swift 4更新
Swift范围比复杂NSRange
,并且在Swift 3中并没有变得更容易。如果您想尝试了解这种复杂性背后的原因,请阅读this和this。我将仅向您展示如何创建它们以及何时使用它们。
a...b
此范围运算符创建一个Swift范围,其中既包含element a
又包含element b
,即使b
该类型是某个类型(例如Int.max
)的最大可能值。封闭范围有两种不同类型:ClosedRange
和CountableClosedRange
。
ClosedRange
Swift中所有范围的元素都是可比较的(即,它们符合Comparable协议)。这样您就可以访问集合中范围内的元素。这是一个例子:
let myRange: ClosedRange = 1...3
let myArray = ["a", "b", "c", "d", "e"]
myArray[myRange] // ["b", "c", "d"]
但是,a ClosedRange
是不可数的(即,它不符合Sequence协议)。这意味着您不能使用循环来遍历元素for
。为此,您需要CountableClosedRange
。
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
此范围运算符包括element,a
但不包括element b
。像上面一样,有两种不同类型的半开范围:Range
和CountableRange
。
Range
与一样ClosedRange
,您可以使用来访问集合的元素Range
。例:
let myRange: Range = 1..<3
let myArray = ["a", "b", "c", "d", "e"]
myArray[myRange] // ["b", "c"]
同样,您不能对a进行迭代,Range
因为它仅是可比较的而不是可跨越的。
CountableRange
A 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中使用(例如,在制作属性字符串时),因此了解如何制作一个字符串很有帮助。
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
但是,这不起作用,因为字符串是由字符组成的,并且并非所有字符的大小都相同。(有关更多信息,请阅读此内容。)例如😀之类的表情符号比字母“ 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”的意外结果。
快速解决方案
正因为如此,随着斯威夫特字符串使用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.startIndex
或str.endIndex
另一端建立范围,则可以不设置范围。编译器将推断出它。
给定
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
笔记
NSString
在内部以UTF-16编码存储其字符。完整的unicode标量为21位。咧嘴笑脸的字符(U+1F600
)不能存储在单个16位代码单元中,因此它NSRange
基于16位代码单元分布在2个计数上。在此示例中,3个代码单元恰好只代表2个字符
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
这样使用
var start = str.startIndex // Start at the string's start index
var end = advance(str.startIndex, 5) // Take start index and advance 5 characters forward
var range: Range<String.Index> = Range<String.Index>(start: start,end: end)
let firstFiveDigit = str.substringWithRange(range)
print(firstFiveDigit)
输出:你好
我感到惊讶的是,即使在Swift 4中,仍然没有使用Int来表达String范围的简单本地方法。允许您提供Int来按范围获取子字符串的唯一String方法是prefix
and suffix
。
拥有一些转换实用程序很有用,这样我们在与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"
。
对于Range<String.Index>
必须与Cocoa交流的那些时刻(例如,在处理NSAttributedString属性范围时),将a转换为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)
}
}
我创建了以下扩展名:
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
你可以这样使用
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
我想做这个:
print("Hello"[1...3])
// out: Error
但不幸的是,我无法编写自己的下标,因为讨厌的人占用了名称空间。
我们可以这样做:
print("Hello"[range: 1...3])
// out: ell
只需将其添加到您的项目中:
extension String {
subscript(range: ClosedRange<Int>) -> String {
get {
let start = String.Index(utf16Offset: range.lowerBound, in: self)
let end = String.Index(utf16Offset: range.upperBound, in: self)
return String(self[start...end])
}
}
}
Range<String.Index>
,但有时需要使用NSString
和NSRange
,因此更多上下文将很有用。–但是请看一下stackoverflow.com/questions/24092884/…。