iOS
Swift
swift4

Swift4 Stringのsubstring周りが変わっていた

More than 1 year has passed since last update.

2017/11/07 追記

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

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

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

var hoge : String = "aaaa"

hoge.characters.length
↓
hoge.count

本文

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

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

Swift3 < 4までの書き方

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. 1から5まで

let one2five = 1...5

2. 1から5未満

let one2lessthanfive = 1..<5

3. 1以上

let morethanOne = 1...

4. 5以下

let lessthanFive = ...5

5. 5未満

let lessthanFive = ..<5

参考: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)]

2.2文字目から末尾まで

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]

3.2〜4文字目

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]

別の書き方

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

1.先頭からx文字

let text1 = text.prefix(x)

2.末尾からx文字

let text1 = text.suffix(x)

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

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)
    }
}