環境
- Swift 5.3.2
実装
enum SFSymbol {
case rectangleAndPencilAndEllipsis
case ...
var image: UIImage? {
switch self {
case .rectangleAndPencilAndEllipsis: return UIImage(systemName: "rectangle.and.pencil.and.ellipsis")
case ...
}
}
}
let image = SFSymbol.rectangleAndPencilAndEllipsis.image
たったこれだけのことなのに、
よく分からない寄り道をしました。
その作業工程で
正規表現を使わずにlowerCamelCaseをsnake_case等に変換するStringのExtension
という副産物ができたので合わせてご覧ください。
経緯
SF Symbols
の画像を複数箇所で利用する場合
let image = UIImage(systemName: "rectangle.and.pencil.and.ellipsis")
毎回文字列指定するのはタイポ起きそうだし長いしなんだかなぁと思っていました。
過ち
その一
早速足を踏み外します。
enum SFSymbol {
case rectangleAndPencilAndEllipsis
case ...
var name: String {
switch self {
case .rectangleAndPencilAndEllipsis: return "rectangle.and.pencil.and.ellipsis"
case ...
}
}
}
let image = UIImage(systemName: SFSymbol.rectangleAndPencilAndEllipsis.name)
この時点でなんで気づかないんですかね。
その二
微調整が入ります。
enum SFSymbol {
case rectangleAndPencilAndEllipsis
case ...
var name: String {
switch self {
case .rectangleAndPencilAndEllipsis: return "rectangle.and.pencil.and.ellipsis"
case ...
}
}
}
extension UIImage {
convenience init?(sfSymbol: SFSymbol) {
self.init(systemName: sfSymbol.name)
}
}
let image = UIImage(sfSymbol: .rectangleAndPencilAndEllipsis)
何がしたかったんですかね。
その三
過去の自分:「nameで返してる文字列も無くしてしまえばいいのでは?」
副産物が出来上がります。
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
を複数箇所で利用していませんでした。
余談
副産物について
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:)も試しましたが、
後続している大文字始まりの文字列の塊があると、その大文字も差し代わってしまうので
insert(contentsOf: at:) と dropFirst(_:)を用いて処理しています。