SF SymbolsのsystemNameでタイポを防ぐために....

# 環境
- Swift 5.3.2

# 実装

```swift
enum SFSymbol {
    case rectangleAndPencilAndEllipsis
    case ...

    var image: UIImage? {
        switch self {
        case .rectangleAndPencilAndEllipsis: return UIImage(systemName: "rectangle.and.pencil.and.ellipsis")
        case ...
        }
    }
}

let image = SFSymbol.rectangleAndPencilAndEllipsis.image
```

たったこれだけのことなのに、
よく分からない寄り道をしました。


その作業工程で
`正規表現を使わずにlowerCamelCaseをsnake_case等に変換するStringのExtension`
という副産物ができたので合わせてご覧ください。

# 経緯
`SF Symbols`の画像を複数箇所で利用する場合

```swift
let image = UIImage(systemName: "rectangle.and.pencil.and.ellipsis")
```

毎回文字列指定するのはタイポ起きそうだし長いしなんだかなぁと思っていました。

# 過ち
### その一
早速足を踏み外します。

```swift
enum SFSymbol {
    case rectangleAndPencilAndEllipsis
    case ...
    
    var name: String {
        switch self {
        case .rectangleAndPencilAndEllipsis: return "rectangle.and.pencil.and.ellipsis"
        case ...
        }
    }
}

let image = UIImage(systemName: SFSymbol.rectangleAndPencilAndEllipsis.name)

```
この時点でなんで気づかないんですかね。

### その二
微調整が入ります。

```swift
enum SFSymbol {
    case rectangleAndPencilAndEllipsis
    case ...
    
    var name: String {
        switch self {
        case .rectangleAndPencilAndEllipsis: return "rectangle.and.pencil.and.ellipsis"
        case ...
        }
    }
}

extension UIImage {
    convenience init?(sfSymbol: SFSymbol) {
        self.init(systemName: sfSymbol.name)
    }
}

let image = UIImage(sfSymbol: .rectangleAndPencilAndEllipsis)
```
何がしたかったんですかね。

### その三
過去の自分：「nameで返してる文字列も無くしてしまえばいいのでは？」

副産物が出来上がります。

```swift
extension String {
    var splitLowercaseStrings: [String] {
        guard self.includesUppercaseChar else { return [self] }
        let (lowercaseString, otherString) = self.dividedLowercaseStringBeforeUppercaseCharAndOther
        let replacedOtherString = otherString.replacedFirstCharWithLowercaseChar
        guard replacedOtherString.includesUppercaseChar else { return [lowercaseString, replacedOtherString] }
        return [[lowercaseString],
                replacedOtherString.splitLowercaseStrings]
            .flatMap { $0 }
    }

    private var includesUppercaseChar: Bool {
        filter { $0.isUppercase }.count != 0
    }

    private var dividedLowercaseStringBeforeUppercaseCharAndOther: (prefixString: String, otherString: String) {
        guard let uppercaseCharIndex = firstIndex(where: { $0.isUppercase }) else { return (self, "") }
        let targetRange = startIndex..<uppercaseCharIndex
        let firstLowercaseString = String(self[targetRange])
        var otherString = self
        otherString.removeSubrange(targetRange)
        return (firstLowercaseString, otherString)
    }

    private var replacedFirstCharWithLowercaseChar: String {
        var string = self
        let firstUppercaseChar = string.first!
        let firstLowercaseChar = firstUppercaseChar.lowercased()
        let secondIndex = string.index(after: string.startIndex)
        string.insert(contentsOf: firstLowercaseChar, at: secondIndex)
        return String(string.dropFirst())
    }
}

enum SystemSymbol: String {
    case rectangleAndPencilAndEllipsis
    case ...
}

let systemImageName = SystemSymbol.rectangleAndPencilAndEllipsis.rawValue.splitLowercaseStrings.joined(separator: ".")
let image = UIImage(systemName: systemImageName)
```

地獄ですね。


# 最終的に
そもそも`SF Symbols`を複数箇所で利用していませんでした。

# 余談
副産物について

```swift
let ordinalNumbers = "firstSecondThirdFourthFifthSixth"

print("dot: \(ordinalNumbers.splitLowercaseStrings.joined(separator: "."))")
// dot: first.second.third.fourth.fifth.sixth
print("underScore: \(ordinalNumbers.splitLowercaseStrings.joined(separator: "_"))")
// underScore: first_second_third_fourth_fifth_sixth
```
lowerCamelCaseの文字列を任意の文字列で連結できます。

(※)先頭の大文字を小文字に差し替える
`var replacedFirstCharWithLowercaseChar: String` では、
[replacingOccurrences(of:with:)](https://developer.apple.com/documentation/foundation/nsstring/1412937-replacingoccurrences)も試しましたが、
後続している大文字始まりの文字列の塊があると、その大文字も差し代わってしまうので
[insert(contentsOf: at:)](https://developer.apple.com/documentation/swift/string/3129039-insert) と [dropFirst(_:)](https://developer.apple.com/documentation/swift/string/2893343-dropfirst)を用いて処理しています。