Swift: Capitalize and decapitalize a list
Video: Swift: Capitalize and decapitalize a list by Taught by Celeste AI - AI Coding Coach
Swift with Copilot: Capitalize and Decapitalize a List
names.map { $0.capitalized }— title-case each.names.map { $0.lowercased() }— lowercase.String.uppercased()for SHOUT. Themap+ string-method pattern is the bread and butter of array transformations.
A two-line warmup. Pulls together arrays, closures, and string methods.
The Copilot prompt
// Capitalize and decapitalize a list of names
let names = ["alice", "BOB", "Charlie"]
Copilot completes:
let names = ["alice", "BOB", "Charlie"]
let capitalized = names.map { $0.capitalized }
let lowered = names.map { $0.lowercased() }
print(capitalized) // ["Alice", "Bob", "Charlie"]
print(lowered) // ["alice", "bob", "charlie"]
String case methods
"hello world".capitalized // "Hello World" — first letter of each word
"hello world".uppercased() // "HELLO WORLD"
"HELLO WORLD".lowercased() // "hello world"
Note: capitalized is a property (no parens). uppercased() and lowercased() are methods (with parens). It's an inconsistency in the standard library — just memorize.
Why capitalized differs from "title case"
"the quick brown fox".capitalized
// "The Quick Brown Fox"
"a tale of two cities".capitalized
// "A Tale Of Two Cities" ← "of" should be lowercase in most title-case styles
capitalized is per-word capitalization: first letter up, rest down. For real title case (which keeps articles, prepositions, conjunctions lowercase), no built-in. Use a third-party or write rules.
Locale-aware
"İstanbul".lowercased() // "i̇stanbul" — wrong for Turkish
"İstanbul".lowercased(with: Locale(identifier: "tr_TR")) // "istanbul" — correct
Turkish has a dotted/dotless I rule different from English. For user-facing transforms in non-English locales, use lowercased(with:)/uppercased(with:).
For machine processing (logs, identifiers), the default (Unicode-default) is fine.
map: applying any transformation
map works for any function/closure that takes one argument and returns one value:
names.map(\.uppercased) // ❌ uppercased is a method, not KeyPath
names.map { $0.uppercased() } // ✓
KeyPaths work for properties:
names.map(\.count) // [5, 3, 7] — character counts
names.map(\.capitalized) // because `capitalized` is a property
For methods, you need the closure form { $0.method() }.
Trim whitespace
A common companion:
let raw = [" alice ", "BOB", " Charlie\n"]
let cleaned = raw.map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }
// ["alice", "BOB", "Charlie"]
let normalized = cleaned.map { $0.lowercased() }
// ["alice", "bob", "charlie"]
trimmingCharacters(in:) removes the specified characters from both ends.
Multiple transforms in one pipeline
let pipeline = names
.map { $0.trimmingCharacters(in: .whitespaces) }
.map { $0.lowercased() }
.filter { !$0.isEmpty }
Multiple maps are clean; Swift's compiler optimizes them. For very large arrays, you might collapse:
let result = names.compactMap { name -> String? in
let trimmed = name.trimmingCharacters(in: .whitespaces).lowercased()
return trimmed.isEmpty ? nil : trimmed
}
compactMap lets you both transform and filter (drop nils).
Mutate in place: var + index loop
var names = ["alice", "BOB"]
for i in names.indices {
names[i] = names[i].capitalized
}
In-place mutation. Rarely needed — map is usually preferred (immutable output).
Capitalize first letter only
capitalized does each word. For only the first letter:
extension String {
var firstCapitalized: String {
guard let first = self.first else { return self }
return first.uppercased() + self.dropFirst()
}
}
"hello world".firstCapitalized
// "Hello world"
first returns Character?, dropFirst() returns Substring. The + between String and Substring works because of StringProtocol.
Filtering by case
let onlyShouts = names.filter { $0 == $0.uppercased() && $0 != $0.lowercased() }
// strings that are entirely uppercase letters
For "starts with uppercase":
let titled = names.filter { $0.first?.isUppercase == true }
first returns optional Character; isUppercase checks if it's an uppercase letter.
A cleanup utility
extension String {
func normalizedName() -> String {
self.trimmingCharacters(in: .whitespacesAndNewlines)
.capitalized
}
}
let raw = [" alice ", "BOB", "charlie brown"]
let clean = raw.map(\.normalizedName)
// Wait — `\.normalizedName` doesn't work because it's a method.
let clean2 = raw.map { $0.normalizedName() }
// ["Alice", "Bob", "Charlie Brown"] — note multi-space preserved
For stricter cleaning (collapse whitespace too):
extension String {
func normalizedName() -> String {
let trimmed = self.trimmingCharacters(in: .whitespacesAndNewlines)
let collapsed = trimmed.split(whereSeparator: \.isWhitespace).joined(separator: " ")
return collapsed.capitalized
}
}
Common stumbles
capitalized is a property; uppercased() is a method. Different syntax. Memorize.
capitalized does every word. "iOS" → "Ios". For name preservation, write your own.
Locale issues. Turkish/Azeri have special I rules. Use lowercased(with:) for non-English.
Mutating let. let arr = [...]; arr.uppercased() — the array doesn't have a method like that, only Strings inside. Use arr.map { $0.uppercased() }.
KeyPath for methods. \.uppercased doesn't work — it's a method, not a property. Use { $0.uppercased() }.
Empty string. "".capitalized == "", no crash. Same for the others.
What's next
Episode 10: Random list of 5 numbers 1–100. Covered ground for episode 2; this episode is the more polished version.
Recap
names.map { $0.capitalized } and names.map { $0.lowercased() }. capitalized is a property (no parens); uppercased()/lowercased() are methods (with parens). For Turkish/Azeri, use lowercased(with: Locale(identifier: "tr_TR")). KeyPaths only work for properties; use closures for methods. compactMap for transform + filter.
Next episode: random list 1-100.