如何在Swift中创建范围?

vic*_*hai 95 range swift

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

NSRange range;
Run Code Online (Sandbox Code Playgroud)

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

Sur*_*gch 237

针对Swift 4进行了更新

Swift范围比NSRangeSwift 3 更复杂,并且它们在Swift 3中没有变得更容易.如果你想尝试理解一些复杂性背后的原因,请阅读本文此内容.我将向您展示如何创建它们以及何时可以使用它们.

封闭范围: a...b

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

1. ClosedRange

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

let myRange: ClosedRange = 1...3

let myArray = ["a", "b", "c", "d", "e"]
myArray[myRange] // ["b", "c", "d"]
Run Code Online (Sandbox Code Playgroud)

但是,a 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])
}
Run Code Online (Sandbox Code Playgroud)

半开放范围: a..<b

此范围运算符包含元素a包含元素b.如上所述,有两种不同类型的半开放范围:RangeCountableRange.

1. Range

与此同时ClosedRange,您可以使用a访问集合的元素Range.例:

let myRange: Range = 1..<3

let myArray = ["a", "b", "c", "d", "e"]
myArray[myRange] // ["b", "c"]
Run Code Online (Sandbox Code Playgroud)

但是,再次,你不能迭代a,Range因为它只是可比较的,而不是可怕的.

2. 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])
}
Run Code Online (Sandbox Code Playgroud)

NSRange

您可以(必须)仍然NSRange在Swift中使用(例如,在制作属性字符串时),因此知道如何制作它是有帮助的.

let myNSRange = NSRange(location: 3, length: 2)
Run Code Online (Sandbox Code Playgroud)

请注意,这是位置和长度,而不是起始索引和结束索引.这里的例子与Swift系列的含义相似3..<5.但是,由于类型不同,它们不可互换.

字符串范围

.....<范围内经营者创建范围的简便方法.例如:

let myRange = 1..<3
Run Code Online (Sandbox Code Playgroud)

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

let myRange = CountableRange<Int>(uncheckedBounds: (lower: 1, upper: 3)) // 1..<3
Run Code Online (Sandbox Code Playgroud)

你可以看到这里的索引类型是Int.String但是,这不起作用,因为字符串由字符组成,并非所有字符都是相同的大小.(阅读此内容可获得更多信息.)例如,表情符号比字母"b"占用更多空间.

NSRange的问题

试着用实验NSRangeNSString使用的表情符号,你就会明白我的意思.头痛.

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

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

let myNSString2: NSString = "acde"
myNSString2.substring(with: myNSRange) // "c"    Where is the "d"!?
Run Code Online (Sandbox Code Playgroud)

笑脸需要两个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 = "acde"
let start2 = myString.index(myString.startIndex, offsetBy: 1)
let end2 = myString.index(myString.startIndex, offsetBy: 4)
let myRange2 = start2..<end2
myString[myRange2] // "cd"
Run Code Online (Sandbox Code Playgroud)

单面范围: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...
Run Code Online (Sandbox Code Playgroud)

这也适用于String范围.如果您使用str.startIndexstr.endIndex在一端制作范围,则可以将其关闭.编译器会推断它.

特定

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

let myRange = ..<index    // Hello
Run Code Online (Sandbox Code Playgroud)

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

var str = "Hello, playground"
let index = str.index(str.endIndex, offsetBy: -10)
let myRange = index...        // playground
Run Code Online (Sandbox Code Playgroud)

也可以看看:

笔记

  • 您不能将在一个字符串上创建的范围用于不同的字符串.
  • 正如您所看到的,字符串范围在Swift中很痛苦,但它们确实可以更好地处理表情符号和其他Unicode标量.

进一步研究


Leo*_*bus 25

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
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

或者干脆

let myString = "Hello World"
let myRange = myString.startIndex..<myString.startIndex.advancedBy(5)
let mySubString = myString.substringWithRange(myRange)   // Hello
Run Code Online (Sandbox Code Playgroud)


mat*_*att 5

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

手头有一些转换实用程序很有用,这样我们在与字符串对话时就可以像 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
}
Run Code Online (Sandbox Code Playgroud)

例如,"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
Run Code Online (Sandbox Code Playgroud)

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

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