Swift Stringの切り出し、文字列操作について(Swift4のsubstring周りの挙動変更対応, Swift5対応バージョン, Swift5.3対応バージョン）

## 2020-10-09 追記

自分でも書いてて忘れたのでメモします。

```swift

var text = "Hello, playground"

let from = text.index(text.startIndex, offsetBy:0)
let to = text.index(text.startIndex, offsetBy:5)
let newString = String(text[from..<to])
print(newString)
```

-> Hello


クロージャーで書くとこんな感じ

```swift

let substr : (String, Int, Int) -> String = { text, from, length in
	let to = text.index(text.startIndex, offsetBy:from + length)
	let from = text.index(text.startIndex, offsetBy:from)
	return String(text[from...to])
}

let text = "Hello, playground"
let newString = substr(text, 1, 2)
print(newString)
```



## 2020/03/17 追記

Swift5だとまた変わってます。何回いじるんやほんま…

Swift5からはString()で囲めばOKです。

```swift
let text = text[from...to]
↓
let text = String(text[from...to])
```

## 2017/11/07 追記

XCode9.1になってさらっとSwiftが4.0.2にバージョンアップしました。

``Stirng.characters``がdeperecatedになっていました。
記憶違いかもしれませんが、確か4.0の時はdeprecatedではなかったと思うのですが...

というわけで下記の修正を行っています。

```swift
var hoge : String = "aaaa"

hoge.characters.length
↓
hoge.count
```



## 本文

Swift4でそこまで変更ないと思っていましたが、Stringが再度Collectionになった結果地味に書き換える箇所が発生していました。

warningだけでは判断出来なかったので、調べた内容をメモしました。



## Swift3 < 4までの書き方

```swift
var text1 = text.substring(from: text.index(text.startIndex, offsetBy: 1))
```

この書き方で書いた場合

``'substring(from:)' is deprecated: Please use String slicing subscript with a 'partial range from' operator.``

というwarningが出ます。

swift4からrangeを使ってsubstringをすればよい、ということですね。

## 復習：Swiftでのrangeの書き方

Swiftのrangeの書き方に未だに慣れていないのでまとめます。


１. 1から5まで

``let one2five = 1...5``

２. 1から5未満

``let one2lessthanfive = 1..<5``

３.  1以上

``let morethanOne = 1...``

４. 5以下

``let lessthanFive = ...5``

５. 5未満

``let lessthanFive = ..<5``


参考：[https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/BasicOperators.html](https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/BasicOperators.html)


## substringでの利用法

```
let text : String = "abcdefg"
let text2 = text[1...]
```

こんな書き方が出来るような気がしたのですが、実際にはこれはエラーになります。

Stringでやる場合はString.Indexを使ってrangeを記述する必要があります。


1. 1〜5文字目まで

```
let text1 = text[text.startIndex...text.index(text.startIndex, offsetBy: 4)]
let text2 = text[text.startIndex..<text.index(text.startIndex, offsetBy: 5)]
let text2 = text[..<text.index(text.startIndex, offsetBy: 5)]
```

２．２文字目から末尾まで

```
let text1 = text[text.index(text.startIndex, offsetBy: 1)...]
let text2 = text[text.index(text.startIndex, offsetBy: 1)...text.index(before: text.endIndex)]
let text3 = text[text.index(text.startIndex, offsetBy: 1)..<text.endIndex]
```

３．２〜４文字目

```
let text1 = text[text.index(text.startIndex, offsetBy: 1)...text.index(text.startIndex, offsetBy: 3)]
let text2 = text[text.index(text.startIndex, offsetBy: 1)..<text.index(text.startIndex, offsetBy: 4)]

let from = text.index(text.startIndex, offsetBy: 1)
let to = text.index(from, offsetBy: 2)
let text3 = text[from...to]
```

## 別の書き方

先頭から、又は末尾から限定であればより短い記述で記述することが出来ます。

１．先頭からx文字

```
let text1 = text.prefix(x)
```

２．末尾からx文字

```
let text1 = text.suffix(x)
```

マイナス値を指定することが出来ないので、末尾を１文字削る場合などは以下のように書きます。

```
let text1 = text.prefix(text.characters.count-1)
```


## Range表記とprefix/suffixの違い

Range表記の場合、文字列の範囲外を指定した場合にfatalエラーが出て止まります。
do〜catchでエラーをキャッチすることも出来ないのでコードでチェックする必要がありそうです。

prefix/suffixの場合は長すぎた場合でもエラーが発生しないので、利用箇所は制限されますが、極力こちらを使うのが正解と考えられます。


## Extensionで解決

```
text.substring(1...5)
text.substring(1..<5)
text.substring(1...)
text.substring(...5)
```

直感的にRangeで書くためのExtensionを作ってみました。

```
extension String {
	
	func substring(_ r: CountableRange<Int>) -> String {
		
		let length = self.count
		let fromIndex = (r.startIndex > 0) ? self.index(self.startIndex, offsetBy: r.startIndex) : self.startIndex
		let toIndex = (length > r.endIndex) ? self.index(self.startIndex, offsetBy: r.endIndex) : self.endIndex
		
		if fromIndex >= self.startIndex && toIndex <= self.endIndex {
			return String(self[fromIndex..<toIndex])
		}
		
		return String(self)
	}
	
	func substring(_ r: CountableClosedRange<Int>) -> String {
		
		let from = r.lowerBound
		let to = r.upperBound
		
		return self.substring(from..<(to+1))
	}
	
	func substring(_ r: CountablePartialRangeFrom<Int>) -> String {
		
		let from = r.lowerBound
		let to = self.count
		
		return self.substring(from..<to)
	}
	
	func substring(_ r: PartialRangeThrough<Int>) -> String {
		
		let from = 0
		let to = r.upperBound
		
		return self.substring(from..<to)
	}
}
```