Question

I created the following method which starts by using the built-in convertStringToTitleCase method on NSString but it really just capitalizes the first letter of each word. I see in .NET there is a method for TextInfo.ToTitleCase which attempts what I'd like to do with Objective-C but also falls short.

http://msdn.microsoft.com/en-us/library/system.globalization.textinfo.totitlecase.aspx

The method I wrote to start is below. How would you handle properly casing an uppercase string? Would a database of words to convert to all uppercase/lowercase help?

- (NSString *)convertStringToTitleCase:(NSString *)str {
    NSMutableString *convertedStr = [NSMutableString stringWithString:[str capitalizedString]];

    NSRange range = NSMakeRange(0, convertedStr.length);

    // a list of words to always make lowercase could be placed here
    [convertedStr replaceOccurrencesOfString:@" De " 
                                  withString:@" de " 
                                     options:NSLiteralSearch 
                                       range:range];

    // a list of words to always make uppercase could be placed here
    [convertedStr replaceOccurrencesOfString:@" Tv " 
                                  withString:@" TV " 
                                     options:NSLiteralSearch 
                                       range:range];

    return convertedStr;
}
Was it helpful?

Solution

As noted in comments, the .NET method you refer to doesn't do "proper" title case (that is, follow a list of exception words to be left in either all-caps or all-lowercase), so -[NSString capitalizedString] is as equivalent as you'll get. If you want exception words, you'll have to write your own method (or find someone else who did, as a google search for NSString "title case" might).

How "proper" your title casing gets depends on how many exception words you're willing to throw at it. How much of the English language do you want it to support? What about other languages? It'll also depend on how far you go in analyzing word boundaries -- you might want "TV" to stay all-caps regardless of whether it's in quotes, at the end of a sentence, etc., but you probably also don't want "you've" to come out "You'Ve".

If you want to process exception words, your plan of repeatedly running replaceOccurrencesOfString... will get slower the more exception words you have. (Also, using spaces in your search/replace strings means you aren't considering other word boundaries you might want to.)

It might be useful to consider NSRegularExpression, since regular expressions already have pretty robust notions of case and word boundaries. If that doesn't work well for you, using a scanner to read through the input string while producing a transformed output string would be more efficient than running multiple search/replace operations.

OTHER TIPS

A nice one-liner(not a general solution, probably very inefficient on huge strings):

[[str lowercaseString] capitalizedString];
extension String {

    /**
      Get the title case string.
     */
    var titleCase: String {
        get {
            return getTitleCaseString()
        }
    }

    // MARK: Private methods.

    /**
     Get title case string.

     - returns: The title case string regarding the lowercase words.
     */
    private func getTitleCaseString() -> String {
        struct Holder {
            static let lowercaseWords = ["a", "an", "and", "at", "but", "by", "else", "for",
                                         "from", "if", "in", "into", "is", "nor", "of", "off",
                                         "on", "or", "out", "the", "to", "via", "vs", "with"]
        }
        return replaceToLowercaseAllOccurrencesOfWords(Holder.lowercaseWords).capitalizeFirst
    }

    /**
     Replace to lowercase all occurrences of lowercase words.

     - parameter lowercaseWords: The lowercase words to replace.

     - returns: String with all occurrences replace to the lowercase words.
     */
    private func replaceToLowercaseAllOccurrencesOfWords(lowercaseWords: [String]) -> String {
        let capitalizedSelf = NSMutableString(string: self.capitalizedString)
        for word in lowercaseWords {
            if let lowercaseWordRegex = try? NSRegularExpression(pattern: "\\b\(word)\\b", options: .CaseInsensitive) {
                lowercaseWordRegex.replaceMatchesInString(capitalizedSelf,
                                                          options: NSMatchingOptions(),
                                                          range: NSMakeRange(0, capitalizedSelf.length),
                                                          withTemplate: word)
            }
        }
        return capitalizedSelf as String
    }

/**
     Capitalize first char.
     */
    private var capitalizeFirst: String {
        if isEmpty { return "" }
        var result = self
        result.replaceRange(startIndex...startIndex, with: String(self[startIndex]).uppercaseString)
        return result
    }

}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top