Ios – Separating CamelCase string into space-separated words in Swift

camelcasing, ios, macos, string, 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.

Best Solution

Here another method to do the same thing for Swift 2.x

extension String {    func camelCaseToWords() -> String {        return unicodeScalars.reduce("") {            if NSCharacterSet.uppercaseLetterCharacterSet().characterIsMember(uint_least16_t($1.value)) {                return ($0 + " " + String($1))            }            else {                return ($0 + String($1))            }        }    }}

Swift 3.x

extension String {    func camelCaseToWords() -> String {        return unicodeScalars.reduce("") {            if CharacterSet.uppercaseLetters.contains($1) {                return ($0 + " " + String($1))            }            else {                return $0 + String($1)            }        }    }}

May be helpful for someone :)