14

I'm looking for the easiest way to convert a string from camelback format to Title Case format.

How do I change 'playerName' into 'Player Name'?

orj
  • 13,234
  • 14
  • 63
  • 73
Evolve
  • 8,939
  • 12
  • 51
  • 63

16 Answers16

21
NSString *str = @"playerName";
NSMutableString *str2 = [NSMutableString string];

for (NSInteger i=0; i<str.length; i++){
    NSString *ch = [str substringWithRange:NSMakeRange(i, 1)];
    if ([ch rangeOfCharacterFromSet:[NSCharacterSet uppercaseLetterCharacterSet]].location != NSNotFound) {
        [str2 appendString:@" "];
    }
    [str2 appendString:ch];
}
NSLog(@"%@", str2.capitalizedString);
webstersx
  • 771
  • 7
  • 10
  • Great work! Yep this is definitely shorter! Im passing the accepted answer across here, unless someone else can work out a shorter way to do it. – Evolve Mar 21 '13 at 04:48
  • I reckon there's still a shorter way to do it... the other ones are far far far too long for such a simple task :/ – webstersx Mar 21 '13 at 04:51
  • This solution does not deal at all with Emoji and it inserts a space before the first character if the first character is a capital. – orj Jun 07 '13 at 07:53
  • @orj just change the character set from [NSCharacterSet uppercaseLetterCharacterSet] to one you've derived for your needs – webstersx Dec 17 '14 at 23:47
15

Here's a simpler Swift version. I've chucked it into an extension

extension String {

    func stringFromCamelCase() -> String {
        var string = self
        string = string.stringByReplacingOccurrencesOfString("([a-z])([A-Z])", withString: "$1 $2", options: NSStringCompareOptions.RegularExpressionSearch, range: Range<String.Index>(start: string.startIndex, end: string.endIndex))
        string.replaceRange(startIndex...startIndex, with: String(self[startIndex]).capitalizedString)
        return string
    }

}

Usage:

var str = "helloWorld"
str = str.stringFromCamelCase()
Stephen Radford
  • 557
  • 6
  • 13
  • 1
    Updated for Swift 3, this can be reduced to `return self.replacingOccurrences(of: "([a-z])([A-Z])", with: "$1 $2", options: .regularExpression, range: self.startIndex ..< self.endIndex).capitalized` – Kevin Owens Feb 24 '17 at 21:42
12

Try using a regex replace

NSString *modified = [input stringByReplacingOccurrencesOfString:@"([a-z])([A-Z])"
                                                      withString:@"$1 $2"
                                                         options:NSRegularExpressionSearch
                                                           range:NSMakeRange(0, input.length)];
emdog4
  • 1,975
  • 3
  • 20
  • 25
  • Wow! One line! Note: this just adds a space between any two letters which are lowercase immediately followed by uppercase, and thus doesn't capitalize the first letter of the first word as the op requested ("playerName" just becomes "player Name"). That said, it is by far the most concise answer here, and perfect for my needs. – George WS Oct 30 '13 at 21:44
  • 1
    Bit late, but you could just add: modified.capitalizedString – MikeH Mar 18 '16 at 19:47
5

A little shorter, using NSCharacterSet:

__block NSString *str = @"myVerySpecialPlayerName" ;

// split at uppercase letters
NSArray *splitString = [str componentsSeparatedByCharactersInSet:
     [NSCharacterSet uppercaseLetterCharacterSet]] ;

// get the uppercase letters
NSArray *upperCaseLetters = [str componentsSeparatedByCharactersInSet:
     [[NSCharacterSet uppercaseLetterCharacterSet] invertedSet]] ;

// join with two spaces
str = [splitString componentsJoinedByString:@"  "] ;
__block NSInteger offset = 0 ;

// replace each second space with the missing uppercase letter
[upperCaseLetters enumerateObjectsUsingBlock:^(NSString *character, NSUInteger idx, BOOL *stop) {
    if( [character length] > 0 ) {
        str = [str stringByReplacingCharactersInRange:NSMakeRange(idx+offset+1, 1) withString:character] ;
        offset += 2 ;
    }
}] ;

// & capitalize the first one
str = [str capitalizedString] ;

NSLog(@"%@", str) ; // "My Very Special Player Name"
notyce
  • 809
  • 7
  • 5
3

Trying to be more unicode compliant

extension String {
    func camelCaseToTitleCase() -> String {
        return unicodeScalars.map(replaceCaptialsWithSpacePlusCapital).joined().capitalized
    }
    private func replaceCaptialsWithSpacePlusCapital(unichar: UnicodeScalar) -> String {
        if CharacterSet.uppercaseLetters.contains(unichar) {
            return " \(unichar)"
        }
        return "\(unichar)"
    }
}
JMFR
  • 799
  • 5
  • 18
  • I like this solution, but you might want to trim the beginning of the string since the first word in the resulting sentence is preceded by a space. – Matt Long Jan 05 '17 at 19:47
2

Although a little long, but this category for NSString should do the trick. It passed all my tests:

- (NSString *)splitOnCapital
{
  // Make a index of uppercase characters
  NSRange upcaseRange = NSMakeRange('A', 26);
  NSIndexSet *upcaseSet = [NSIndexSet indexSetWithIndexesInRange:upcaseRange];

  // Split our camecase word
  NSMutableString *result = [NSMutableString string];
  NSMutableString *oneWord = [NSMutableString string];
  for (int i = 0; i < self.length; i++) {
    char oneChar = [self characterAtIndex:i];
    if ([upcaseSet containsIndex:oneChar]) {
      // Found a uppercase char, now save previous word
      if (result.length == 0) {
        // First word, no space in beginning
        [result appendFormat:@"%@", [oneWord capitalizedString]];
      }else {
        [result appendFormat:@" %@", oneWord];
      }

      // Clear previous word for new word
      oneWord = [NSMutableString string];
    }

    [oneWord appendFormat:@"%c", oneChar];
  }

  // Add last word
  if (oneWord.length > 0) {
    [result appendFormat:@" %@", oneWord];
  }

  return result;
}
Draco
  • 262
  • 5
  • 12
2

I had a similar issue, the answers here helped me create a solution. I had an array that had a list of labels I wanted to display within a UITableView, one label per row.

My issue was I parsed these labels out of an XML returned by a SOAP action and I had not idea over the format of the strings.

Firstly I implemented webstersx answer into a method. This was great but some of these labels began with a capital letter and some where camel case (e.g. some strings where exampleLabel and others where ExampleLabel. So this meant the ones beginning with a capital had a space inserted in front of the string.

I overcame this by trimming whitespaces from the beggining and end of the string using NSString's stringByTrimmingCharactersInSet.

The next issue was any abbreviations used, such as "ID" or "PNR Status", where being displayed as "I D" and "P N R Status" as the capital letters where, and quite rightly, being picked up and a space inserted before it.

I overcame this issue by implementing a regex similar to emdog4's answer into my new method.

Here is my completed solution:

- (NSString *)formatLabel:(NSString *)label
{
    NSMutableString *str2 = [NSMutableString string];

    for (NSInteger i=0; i<label.length; i++){
        NSString *ch = [label substringWithRange:NSMakeRange(i, 1)];
        if ([ch rangeOfCharacterFromSet:[NSCharacterSet uppercaseLetterCharacterSet]].location != NSNotFound) {
            [str2 appendString:@" "];
        }
        [str2 appendString:ch];
    }
    NSString * formattedString = [str2 stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]].capitalizedString;

    formattedString = [formattedString stringByReplacingOccurrencesOfString:@"([A-Z]) (?![A-Z][a-z])" withString:@"$1" options:NSRegularExpressionSearch range:NSMakeRange(0, formattedString.length)];

    return formattedString;
}

I then simply call something like this, for example, that will return my nicely formatted string:

NSString * formattedLabel = [self formatLabel:@"PNRStatus"];
NSLog(@"Formatted Label: %@", formattedLabel);

Will output:

2013-10-10 10:44:39.888 Test Project[28296:a0b] Formatted Label: PNR Status

Rob
  • 1,233
  • 13
  • 24
2

If anyone needs a Swift version:

func camelCaseToTitleCase(s: NSString) -> String {
    var newString = ""
    if s.length > 0 {
        newString = s.substringToIndex(1).uppercaseString
        for i in 1..<s.length {
            let char = s.characterAtIndex(i)
            if NSCharacterSet.uppercaseLetterCharacterSet().characterIsMember(char) {
                newString += " "
            }
            newString += s.substringWithRange(NSRange(location: i, length: 1))
        }
    }
    return newString
}
Travis M.
  • 10,930
  • 1
  • 56
  • 72
2

I think you can tackle this problem with some Regular Expressions. Check out this similar question: iPhone dev: Replace uppercase characters in NSString with space and downcase

Community
  • 1
  • 1
Reed Olsen
  • 9,099
  • 4
  • 37
  • 47
1

while technically shorter, more ineffecient

NSString *challengeString = @"playerName";
NSMutableString *rStr = [NSMutableString stringWithString:challengeString];

while ([rStr rangeOfCharacterFromSet:[NSCharacterSet uppercaseLetterCharacterSet]].location != NSNotFound) {
    [rStr replaceCharactersInRange:[rStr rangeOfCharacterFromSet:[NSCharacterSet uppercaseLetterCharacterSet]] withString:[[NSString stringWithFormat:@" %@", [rStr substringWithRange:[rStr rangeOfCharacterFromSet:[NSCharacterSet uppercaseLetterCharacterSet]]]] lowercaseString]];
}

NSLog(@"%@", rStr.capitalizedString);
webstersx
  • 771
  • 7
  • 10
1

Not sure this is much shorter than websterx, but I find using characterIsMember easier to read and understand. Also added a length check to fix the space before if the string starts with a capital.

NSString *str = @"PlayerNameHowAboutALongerString";
NSMutableString *str2 = [NSMutableString string];

for (NSInteger i=0; i<str.length; i++){
    unichar ch = [str characterAtIndex:i];
    if ( [[NSCharacterSet uppercaseLetterCharacterSet] characterIsMember:ch]) {
        if (str2.length > 0 ) {
            [str2 appendString:@" "];
        }
    }
    [str2 appendString:[NSString stringWithCharacters:&ch length:1]];
}
NSLog(@"--%@--", str2.capitalizedString);
Ron Davis
  • 1,259
  • 11
  • 13
1

The accepted answer didn't work for me because it doesn't capitalize the first letter, and if the first letter is already capitalized, it adds an extraneous space at the beginning. Here is my improved version:

- (NSString *)titleFromCamelCaseString:(NSString *)input
{
    NSMutableString *output = [NSMutableString string];
    [output appendString:[[input substringToIndex:1] uppercaseString]];
    for (NSUInteger i = 1; i < [input length]; i++)
    {
        unichar character = [input characterAtIndex:i];
        if ([[NSCharacterSet uppercaseLetterCharacterSet] characterIsMember:character])
        {
            [output appendString:@" "];
        }
        [output appendFormat:@"%C", character];
    }
    return output;
}
Nick Lockwood
  • 40,865
  • 11
  • 112
  • 103
0

Here is Swift Code (objective c code by webstersx), Thanks !

var str: NSMutableString = "iLoveSwiftCode"

        var str2: NSMutableString = NSMutableString()

        for var i:NSInteger = 0 ; i < str.length ; i++ {

            var ch:NSString = str.substringWithRange(NSMakeRange(i, 1))
            if(ch .rangeOfCharacterFromSet(NSCharacterSet.uppercaseLetterCharacterSet()).location != NSNotFound) {
            str2 .appendString(" ")
            }
            str2 .appendString(ch)
        }
        println("\(str2.capitalizedString)")

    }
0
NSString *input = @"playerName";
NSString *modified = [input stringByReplacingOccurrencesOfString:@"(?<!^)[A-Z]" withString:@" $0" options:NSRegularExpressionSearch range:NSMakeRange(0, input.length)].capitalizedString;
Iulian Onofrei
  • 9,188
  • 10
  • 67
  • 113
0

Another solution under Swift 2.2

extension String {
    var stringFromCamelCase:String {
        return (self as NSString).replacingOccurrences(
            of: "([a-z])([A-Z])",
            with: "$1 $2",
            options: CompareOptions.regularExpressionSearch,
            range: NSMakeRange(0, self.characters.count)
            ).uppercaseFirst
    }

    var uppercaseFirst: String {
        return String(characters.prefix(1)).uppercased() + String(characters.dropFirst()).lowercased()
    }
}
pravdomil
  • 2,961
  • 1
  • 24
  • 38
-8

try using:

string.Split()

then use the cap letter as token

Draken
  • 3,134
  • 13
  • 34
  • 54
mark
  • 1