'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:[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:[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:[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
}
}
Solution 4:[4]
I might be late but I want to share a little improvement to Augustine P A answer or Leo Dabus comment.
Basically, that code won't work properly if we are using upper camel case
notation (like "DuckDuckGo") because it will add a space at the beginning of the string.
To address this issue, this is a slightly modified version of the code, using Swift 3.x, and it's compatible with both upper and lower came case:
extension String {
func camelCaseToWords() -> String {
return unicodeScalars.reduce("") {
if CharacterSet.uppercaseLetters.contains($1) {
if $0.count > 0 {
return ($0 + " " + String($1))
}
}
return $0 + String($1)
}
}
}
Solution 5:[5]
extension String {
func titlecased() -> String {
return self
.replacingOccurrences(of: "([a-z])([A-Z](?=[A-Z])[a-z]*)", with: "$1 $2", options: .regularExpression)
.replacingOccurrences(of: "([A-Z])([A-Z][a-z])", with: "$1 $2", options: .regularExpression)
.replacingOccurrences(of: "([a-z])([A-Z][a-z])", with: "$1 $2", options: .regularExpression)
.replacingOccurrences(of: "([a-z])([A-Z][a-z])", with: "$1 $2", options: .regularExpression)
}
}
In
"ThisStringHasNoSpacesButItDoesHaveCapitals"
"IAmNotAGoat"
"LOLThatsHilarious!"
"ThisIsASMSMessage"
Out
"This String Has No Spaces But It Does Have Capitals"
"I Am Not A Goat"
"LOL Thats Hilarious!"
"This Is ASMS Message" // (Difficult tohandle single letter words when they are next to acronyms.)
Solution 6:[6]
I can do this extension in less lines of code (and without a CharacterSet), but yes, you basically have to enumerate each String if you want to insert spaces in front of capital letters.
extension String {
var differentCamelCaps: String {
var newString: String = ""
for eachCharacter in self {
if "A"..."Z" ~= eachCharacter {
newString.append(" ")
}
newString.append(eachCharacter)
}
return newString
}
}
print("ÄnotherCamelCaps".differentCamelCaps) // Änother Camel Caps
Solution 7:[7]
Swift 5 solution
extension String {
func camelCaseToWords() -> String {
return unicodeScalars.reduce("") {
if CharacterSet.uppercaseLetters.contains($1) {
if $0.count > 0 {
return ($0 + " " + String($1))
}
}
return $0 + String($1)
}
}
}
Solution 8:[8]
If you want to make it more efficient, you can use Regular Expressions
.
extension String {
func replace(regex: NSRegularExpression, with replacer: (_ match:String)->String) -> String {
let str = self as NSString
let ret = str.mutableCopy() as! NSMutableString
let matches = regex.matches(in: str as String, options: [], range: NSMakeRange(0, str.length))
for match in matches.reversed() {
let original = str.substring(with: match.range)
let replacement = replacer(original)
ret.replaceCharacters(in: match.range, with: replacement)
}
return ret as String
}
}
let camelCaps = "aCamelCaps" // there are 3 Capital character
let pattern = "[A-Z]"
let regular = try!NSRegularExpression(pattern: pattern)
let camelCapped:String = camelCaps.replace(regex: regular) { " \($0)" }
print("Uppercase characters replaced: \(camelCapped)")
Solution 9:[9]
Here's what I came up with using Unicode character classes: (Swift 5)
extension String {
var titleCased: String {
self
.replacingOccurrences(of: "(\\p{UppercaseLetter}\\p{LowercaseLetter}|\\p{UppercaseLetter}+(?=\\p{UppercaseLetter}))",
with: " $1",
options: .regularExpression,
range: range(of: self)
)
.capitalized
}
}
Output:
fillPath ? Fill Path ThisStringHasNoSpaces ? This String Has No Spaces IAmNotAGoat ? I Am Not A Goat LOLThatsHilarious! ? Lol Thats Hilarious! ThisIsASMSMessage ? This Is Asms Message
Solution 10:[10]
Swift way:
extension String {
var titlecased: String {
map { ($0.isUppercase ? " " : "") + String($0) }
.joined(separator: "")
.trimmingCharacters(in: .whitespaces)
}
}
Solution 11:[11]
Swift 5+
Small style improvements on previous answers
import Foundation
extension String {
func camelCaseToWords() -> String {
unicodeScalars.reduce("") {
guard CharacterSet.uppercaseLetters.contains($1),
$0.count > 0
else { return $0 + String($1) }
return ($0 + " " + String($1))
}
}
}
Using guard let
statements is usually recommended, as they provide an "early exit" for non matching cases and decrease the overall nesting levels of your code (which usually improves readability quite a lot... and remember, readability counts!)
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow