Separating CamelCase string into space-separated words in Swift
I would like to separate a CamelCase string into space-separated words in a new string. Here is what I have so far:
var camelCaps: String {
guard self.count > 0 else { return self }
var newString: String = ""
let uppercase = CharacterSet.uppercaseLetters
let first = self.unicodeScalars.first!
newString.append(Character(first))
for scalar in self.unicodeScalars.dropFirst() {
if uppercase.contains(scalar) {
newString.append(" ")
}
let character = Character(scalar)
newString.append(character)
}
return newString
}
let aCamelCaps = "aCamelCaps"
let camelCapped = aCamelCaps.camelCaps // Produce: "a Camel Caps"
let anotherCamelCaps = "ÄnotherCamelCaps"
let anotherCamelCapped = anotherCamelCaps.camelCaps // "Änother Camel Caps"
I'm inclined to suspect that this may not be the most efficient way to convert to space-separated words, if I call it in a tight loop, or 1000's of times. Are there more efficient ways to do this in Swift?
[Edit 1:] The solution I require should remain general for Unicode scalars, not specific to Roman ASCII "A..Z".
[Edit 2:] The solution should also skip the first letter, i.e. not prepend a space before the first letter.
[Edit 3:] Updated for Swift 4 syntax, and added caching of uppercaseLetters, which improves performance in very long strings and tight loops.
Solution 1:
extension String {
func camelCaseToWords() -> String {
return unicodeScalars.dropFirst().reduce(String(prefix(1))) {
return CharacterSet.uppercaseLetters.contains($1)
? $0 + " " + String($1)
: $0 + String($1)
}
}
}
print("ÄnotherCamelCaps".camelCaseToWords()) // Änother Camel Caps
May be helpful for someone :)
Solution 2:
One Line Solution
I concur with @aircraft, regular expressions can solve this problem in one LOC!
// Swift 5 (and probably 4?)
extension String {
func titleCase() -> String {
return self
.replacingOccurrences(of: "([A-Z])",
with: " $1",
options: .regularExpression,
range: range(of: self))
.trimmingCharacters(in: .whitespacesAndNewlines)
.capitalized // If input is in llamaCase
}
}
Props to this JS answer.
P.S. I have a gist for snake_case → CamelCase
here.
P.P.S. I updated this for New Swift (currently 5.1), then saw @busta's answer, and swapped out my startIndex..<endIndex
for his range(of: self)
. Credit where it's due y'all!
Solution 3:
a better full swifty solution... based on AmitaiB answer
extension String {
func titlecased() -> String {
return self.replacingOccurrences(of: "([A-Z])", with: " $1", options: .regularExpression, range: self.range(of: self))
.trimmingCharacters(in: .whitespacesAndNewlines)
.capitalized
}
}