如何在Swift中创建范围?

129

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

NSRange range;

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


3
你需要使用这个范围吗?Swift字符串使用Range<String.Index>,但有时需要使用NSStringNSRange,因此提供更多上下文会更有帮助。但是可以查看https://dev59.com/wGAf5IYBdhLWcg3w9mts。 - Martin R
10个回答

302

已更新到Swift 4

Swift范围比NSRange更加复杂,在Swift 3中也没有变得更简单。如果您想尝试理解一些复杂性背后的原因,请阅读thisthis。我将向您展示如何创建它们以及何时可以使用它们。

闭合范围:a...b

这个范围操作符创建一个Swift范围,其中包括元素a 元素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"]

然而,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(例如创建属性字符串),因此了解如何创建一个是有用的。

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"占用更多的空间。(阅读this获取更多信息。) NSRange存在的问题 尝试使用带有表情符号的NSStringNSRange进行实验,你会明白我的意思。头痛。
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"!?

The smiley face takes two UTF-16 code units to store, so it gives the unexpected result of not including the "d".
Swift 解决方案
因此,在 Swift 字符串中,您使用 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"

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

在 Swift 4 中,有些事情变得更简单了。每当范围的起点或终点可以被推断出时,您可以将其省略。

Int

您可以使用单侧整数范围来迭代集合。以下是一些来自文档的示例。

// 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创建范围,则可以省略其中的一个端点。编译器将会自动推断。

给定

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

参见:

注释

  • 你不能在不同的字符串上使用你创建的范围。
  • 正如你所看到的,Swift中的String范围很麻烦,但它们确实使处理表情符号和其他Unicode标量变得更容易。

进一步学习


我的评论涉及“NSRange问题”子部分。 NSString在内部以UTF-16编码存储其字符。完整的Unicode标量为21位。露齿笑脸字符(U+1F600)无法存储在单个16位代码单元中,因此它被分成2个。 NSRange基于16位代码单元进行计数。在这个例子中,3个代码单元恰好代表2个字符。 - Code Different

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

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

5
我觉得令人惊讶的是,即使在Swift 4中,仍然没有一种简单的本地方法来使用Int表示String范围。唯一允许您提供一个Int以通过范围获取子字符串的String方法是prefix和suffix。
拥有一些转换实用程序非常有用,这样我们可以像NSRange一样与String交流。这是一个实用程序,它接受位置和长度(就像NSRange一样),并返回一个Range:
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)" 是包含 "hello" 第一个字符的 Range<String.Index>。作为奖励,我允许负位置: "hello".range(-1,1)" 是包含 "hello" 最后一个字符的 Range<String.Index>
Range<String.Index> 转换为 NSRange 也很有用,因为有时您需要与 Cocoa 进行交互(例如,在处理 NSAttributedString 属性范围时)。Swift 4 提供了一种本地方法来实现这一点:
let nsrange = NSRange(range, in:s) // where s is the string

因此,我们可以编写另一个实用程序,它可从字符串位置和长度直接转换为NSRange:
extension String {
    func nsRange(_ start:Int, _ length:Int) -> NSRange {
        return NSRange(self.range(start,length), in:self)
    }
}

2

使用方法如下

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)

输出:你好


我看到它们有“范围”数据类型。那么我该如何使用它? - vichhai
@vichhai,您能详细说明您想在哪里使用吗? - Dharmbir Singh
就像在Objective-C中一样: NSRange范围; - vichhai

2
(1..<10)返回...

范围 = 1..<10


这个回答如何解决问题? - Machavity

1
如果有人想创建NSRange对象,可以按如下方式创建:
let range: NSRange = NSRange.init(location: 0, length: 5)

这将创建一个起始位置为0,长度为5的范围。

0

我创建了以下扩展:

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

0
func replace(input: String, start: Int,lenght: Int, newChar: Character) -> String {
    var chars = Array(input.characters)

    for i in start...lenght {
        guard i < input.characters.count else{
            break
        }
        chars[i] = newChar
    }
    return String(chars)
}

0

你可以像这样使用

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

0
我想要做这个:
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])
        }
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接