Swift String Methods: contains, hasPrefix, and More

Swift strings come with many built-in methods that help you search text, check prefixes and suffixes, change letter case, replace parts of a string, and clean up user input. In this article, you will learn how common Swift String methods such as contains(), hasPrefix(), hasSuffix(), lowercased(), and replacingOccurrences(of:with:) work, when to use them, and what mistakes to avoid.

Quick answer: Swift String methods let you inspect and transform text without writing low-level character loops yourself. Use contains() to look for text, hasPrefix() and hasSuffix() to test the start or end of a string, and methods like lowercased() or replacingOccurrences(of:with:) to create a modified copy.

Difficulty: Beginner

Helpful to know first: basic Swift syntax, how variables store values, and that String is a value type used to hold text.

1. What Is String Methods?

String methods are built-in functions provided by Swift’s String type. You call them on a string value to ask questions about the text or to create a changed version of it.

These methods solve a very common problem: working with user input, file names, search text, messages, URLs, and formatted output safely and clearly.

A common beginner confusion is expecting String methods to mutate the original value automatically. In Swift, many string operations return a new string, so you usually need to assign the result to a variable or constant.

2. Why String Methods Matters

Strings appear almost everywhere in Swift programs. You use them when validating forms, checking commands, filtering search results, formatting display text, or cleaning imported data.

Without these methods, even simple tasks would require manual loops and character-by-character logic. Built-in methods make code shorter, easier to read, and less error-prone.

You should not use these methods blindly, though. String matching is case-sensitive by default, and a method returning true does not always mean the text is valid in a business-rule sense.

3. Basic Syntax or Core Idea

Calling a method on a String

In Swift, you call a String method with dot syntax. The string value comes first, then the method name and any arguments.

let title = "Swift String Guide"
let hasSwift = title.contains("Swift")
let startsWithSwift = title.hasPrefix("Swift")
let endsWithGuide = title.hasSuffix("Guide")

This code asks three questions about the same string. Each method returns a Bool, so the result is either true or false.

Methods that return a changed string

Some methods return a new String instead of a Boolean.

let name = "  Alice Smith  "
let cleanName = name.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
let lowercaseName = cleanName.lowercased()

The original name does not change. Instead, each method call produces a new string value that you can store and use later.

4. Step-by-Step Examples

Example 1: Using contains() to search for text

Use contains() when you want to know whether a string includes a substring anywhere inside it.

let message = "Your order has shipped"
let didShip = message.contains("shipped")

print(didShip)

This prints true because the word shipped appears in the message.

Example 2: Using hasPrefix() for command checks

hasPrefix() is useful when input must begin with a known pattern, such as a slash command.

let input = "/help"

if input.hasPrefix("/") {
    print("This looks like a command.")
}

This checks only the beginning of the string. It does not search the entire value like contains() does.

Example 3: Using hasSuffix() for file extensions

hasSuffix() is often the simplest way to test whether a filename ends with a known extension.

let fileName = "report.pdf"

if fileName.hasSuffix(".pdf") {
    print("This is a PDF file.")
}

This works well for simple extension checks, though you may still want stronger validation in production code.

Example 4: Using lowercased() for case-insensitive comparison

String matching in Swift is case-sensitive by default. If you want a basic case-insensitive check, normalize both strings first.

let userInput = "SWIFT"
let keyword = "swift"

if userInput.lowercased() == keyword.lowercased() {
    print("The values match.")
}

This is a common and practical beginner pattern for case-insensitive matching.

Example 5: Replacing text with replacingOccurrences(of:with:)

Use this method when you want to generate a modified copy of a string.

let template = "Hello, NAME!"
let finalMessage = template.replacingOccurrences(of: "NAME", with: "Maya")

print(finalMessage)

This prints Hello, Maya!. The original template string remains unchanged.

5. Practical Use Cases

6. Common Mistakes

Mistake 1: Expecting contains() to ignore letter case

A very common mistake is assuming that contains() treats uppercase and lowercase letters as the same.

Problem: This code searches for lowercase text inside a string that contains uppercase letters, so the result is false even though the words look similar to a human reader.

let title = "Learn Swift"
let found = title.contains("swift")

print(found)

Fix: Normalize both values to the same case before comparing when a simple case-insensitive check is appropriate.

let title = "Learn Swift"
let found = title.lowercased().contains("swift".lowercased())

print(found)

The corrected version works because both strings are converted to lowercase before the search.

Mistake 2: Thinking a method changes the original string automatically

Many String methods return a new value instead of mutating the existing one.

Problem: This code calls replacingOccurrences(of:with:) but ignores the returned string, so the original value is still unchanged.

var text = "red, green, blue"
text.replacingOccurrences(of: "green", with: "yellow")

print(text)

Fix: Assign the returned string back to a variable or store it in a new constant.

var text = "red, green, blue"
text = text.replacingOccurrences(of: "green", with: "yellow")

print(text)

The corrected version works because the new string replaces the old value stored in text.

Mistake 3: Using hasSuffix() on mixed-case file extensions

File names and user input are often inconsistent in capitalization.

Problem: This check fails for uppercase file extensions, even though the file is still a PNG image.

let imageName = "photo.PNG"

if imageName.hasSuffix(".png") {
    print("PNG image")
}

Fix: Normalize the string before checking the suffix when case should not matter.

let imageName = "photo.PNG"

if imageName.lowercased().hasSuffix(".png") {
    print("PNG image")
}

The corrected version works because the comparison is performed on a lowercase copy of the filename.

Mistake 4: Calling String methods on an optional String without unwrapping

You often receive text as an optional, especially from input parsing or dictionary lookups.

Problem: This code tries to call a String method on String?, which can produce the compile-time error Value of optional type 'String?' must be unwrapped to refer to member 'contains' of wrapped base type 'String'.

let searchText: String? = "swift guide"

if searchText.contains("swift") {
    print("Match found")
}

Fix: Unwrap the optional first with if let before calling String methods.

let searchText: String? = "swift guide"

if let searchText = searchText, searchText.contains("swift") {
    print("Match found")
}

The corrected version works because the method is called only after the optional has been safely unwrapped.

7. Best Practices

Practice 1: Normalize text before user-facing comparisons

User input is often inconsistent. A less reliable approach is comparing raw strings directly when capitalization should not matter.

let input = "Admin"
let allowedRole = "admin"

// Preferred for simple case-insensitive checks
let matches = input.lowercased() == allowedRole.lowercased()

This makes simple text validation more predictable for real users.

Practice 2: Use the most specific method for the job

If you only care about the start or end of a string, prefer hasPrefix() or hasSuffix() over broader searches.

let path = "/api/users"

// More specific and clearer than a general substring search
if path.hasPrefix("/api/") {
    print("API route detected")
}

This improves readability because future readers immediately understand what part of the string matters.

Practice 3: Store transformed results when you will reuse them

If you need multiple comparisons, avoid repeating the same transformation many times.

let email = "  [email protected]  "
let cleanEmail = email
    .trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
    .lowercased()

print(cleanEmail)

This keeps the code simpler and avoids repeating the same cleanup logic in multiple places.

8. Limitations and Edge Cases

9. Practical Mini Project

This small program cleans a list of filenames, checks for image files, and prints a normalized result. It combines trimming, case normalization, suffix checks, and replacement.

let rawFiles = [
    "  profile.PNG  ",
    "notes.txt",
    "banner.jpg",
    "archive.ZIP"
]

for file in rawFiles {
    let cleanFile = file
        .trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
        .lowercased()

    let displayName = cleanFile.replacingOccurrences(of: "_", with: " ")

    if cleanFile.hasSuffix(".png") || cleanFile.hasSuffix(".jpg") {
        print("Image file: \(displayName)")
    } else {
        print("Other file: \(displayName)")
    }
}

This example shows a realistic workflow: normalize incoming text, perform consistent checks, and produce output based on the result. These exact steps are common in file handling, imported data cleanup, and search filtering.

10. Key Points

11. Practice Exercise

Try building a small text checker that validates a username string before using it.

Expected output: a cleaned username and a Boolean result such as true or false.

Hint: Use trimmingCharacters(in:), lowercased(), hasPrefix(), and replacingOccurrences(of:with:).

let username = "  Admin User  "

let cleanUsername = username
    .trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)

let startsWithAdmin = cleanUsername.lowercased().hasPrefix("admin")
let finalUsername = cleanUsername.replacingOccurrences(of: " ", with: "_")

print("Final username: \(finalUsername)")
print("Starts with admin: \(startsWithAdmin)")

12. Final Summary

Swift String methods make everyday text processing much easier. Instead of manually looping through characters, you can use methods like contains(), hasPrefix(), and hasSuffix() to inspect strings quickly, and methods like lowercased(), trimmingCharacters(in:), and replacingOccurrences(of:with:) to create cleaner, more predictable values.

The most important beginner lessons are that String matching is usually case-sensitive, many methods return a new string instead of mutating the original one, and optional strings must be unwrapped before use. Once you are comfortable with these basics, a strong next step is learning Swift string indexing, splitting strings, and working with substrings for more advanced text processing.