Transforming the Case of Strings in Swift

In this blog post, I will explain the three built-in Swift options for case transformations on Strings, their locale-aware variants, and what support is offered in SwiftUI.

I will also explain why capitalized is equivalent to titlecase related functions in other programming languages and why it should not be mixed up with "Title Case" in natural languages.

This blog post is NOT about the various case styles (camelCase, PascalCase, snake_case or kebab-case) for naming variables.

Let's get started.

A natural language has a variety of case styles. Here are common case styles in the English language.

Case styleExample
Sentence caseThe quick brown fox jumps over the lazy dog
All uppercase / All capsTHE QUICK BROWN FOX JUMPS OVER THE LAZY DOG
All lowercasethe quick brown fox jumps over the lazy dog
Start caseThe Quick Brown Fox Jumps Over The Lazy Dog
Title CaseThe Quick Brown Fox Jumps over the Lazy Dog

Programming languages may have built-in support for some but not all of these cases. In Swift we have:

Swift APICase style
uppercased()All uppercase / All caps
lowercased()All lowercase
capitalizedStart case
let example = "The Quick Brown Fox Jumps over the Lazy Dog"
example.lowercased() // the quick brown fox jumps over the lazy dog
example.uppercased() // THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG
example.capitalized // The Quick Brown Fox Jumps Over The Lazy Dog

Let's ignore the confusing fact that lowercased() and uppercased() are functions and capitalized is a property.

What I do want to address is the confusion that other programming languages may use different names for Swift's capitalized. Kotlin has titlecase. VBA has something called "proper case".

In text processing, title case usually involves the capitalization of all words irrespective of their part of speech.

What programmers call "title case" (or in Swift: capitalized) is known as Start Case by authors which is a simplified variant.

As software programmers, we mean the mapping that is applied to the initial character in a word according to the mapping rules defined in the Unicode Standard.

The titlecase mapping in Unicode differs from the uppercase mapping in that a number of characters require special handling. These are chiefly ligatures and digraphs such as 'fl', 'dz', and 'lj', plus a number of polytonic Greek characters. For example, U+01C7 (LJ) maps to U+01C8 (Lj) rather than to U+01C9 (lj).

By the way, the topic of case mapping is so complicated and important that it has its own FAQ.

Some case mappings depend on language or locale. Therefore Swift provides variants for case transformation considering the locale.

"i".uppercased(with: Locale(identifier: "tr_TR")) // returns "İ"
"ı".uppercased(with: Locale(identifier: "tr_TR")) // returns "I"

For convenience reasons Swift also provides APIs to get the case transformation in the current locale

When working with text that’s presented to the user, use locale-aware APIs

Another important fact to know is that case transformations aren’t guaranteed to be symmetrical or to produce strings of the same lengths as the originals.

"Straße".count // 6
"Straße".uppercased().count // 7

"Straße".uppercased() // STRASSE

Let's talk about SwiftUI.

You have support for lowercase and uppercase transformation with textCase(_:) introduced with iOS 14. But no capitalized option.

struct ContentView: View {
    var example = "heLLo woRLd"
    var body: some View {
        VStack {
            Text(example) // heLLo woRLd
            Text(example).textCase(.lowercase) // hello world
            Text(example).textCase(.uppercase) // HELLO WORLD
        }
        .padding()
    }
}

You can build a custom Swift property wrapper as explained by John Sundell.

@propertyWrapper struct Capitalized {
    var wrappedValue: String {
        didSet { wrappedValue = wrappedValue.capitalized }
    }

    init(wrappedValue: String) {
        self.wrappedValue = wrappedValue.capitalized
    }
}

Then use it in your SwiftUI view.

struct ContentView: View {
    @Capitalized
    var example = "heLLo woRLd"

    var body: some View {
        VStack {
            Text(example) // Hello World
        }
        .padding()
    }
}

You can also build a more complicated Swift property wrapper that accepts the case for transformation as shown by Andy Ibanez.

Did you find this article valuable?

Support Swifty Tech by Marco Eidinger by becoming a sponsor. Any amount is appreciated!