Convert first letter of every word to uppercase
A few observations on improving the code snippet, but using the same basic logic:

If you split the string with components, you can use joined to put them back together.
The ...str.startIndex syntax will crash if you supply a zero length string. I would suggest using prefix and dropFirst to simplify the code.
When writing routines like this, I’d generally suggest using StringProtocol so that it works for both string and substrings. In this case, I would make it an extension of StringProtocol.
This method of capitalizing the first character of every word (regardless of part of speech) is sometimes called “start case” or “proper case”. I would suggest renaming the method accordingly.

Thus:
extension StringProtocol {
    func startcased() -&gt; String {
        components(separatedBy: &quot; &quot;)
            .map { $0.prefix(1).uppercased() + $0.dropFirst() }
            .joined(separator: &quot; &quot;)
    }
}

let string = &quot;this is a test&quot;
print(string.startcased())                                  // This Is A Test


If the in the intent was to achieve “title case”, we often wouldn't capitalize certain parts of speech, e.g., articles and conjunctions. The NaturalLanguage library can be used for more refined capitalization logic:
import NaturalLanguage

extension StringProtocol {
    func titlecased() -&gt; String {
        var capitalizeWordRanges: [Range&lt;String.Index&gt;] = []

        var result = String(self)

        // find words other than conjunctions and determiners

        let tagger = NLTagger(tagSchemes: [.lexicalClass])
        tagger.string = result
        let options: NLTagger.Options = [.omitWhitespace, .joinContractions]
        var isStartOfSentence = true

        tagger.enumerateTags(in: startIndex..&lt;endIndex, unit: .word, scheme: .lexicalClass, options: options) { tag, range in
            guard let tag = tag else { return true }

            print(tag.rawValue, self[range])

            switch tag {
            case .sentenceTerminator:               // see if we're potentially starting a new sentence
                isStartOfSentence = true
                return true

            case .punctuation, .openQuote, .closeQuote, .openParenthesis, .closeParenthesis:
                return true

            case .conjunction where self[range].count &lt; 4,
                 .determiner where self[range].count &lt; 4,
                 .preposition where self[range].count &lt; 4:         // only capitalize conjunctions or articles at the start of a sentence
                if isStartOfSentence { fallthrough }

            default:
                capitalizeWordRanges.append(range)            // otherwise, add this to the array of ranges to t
            }

            isStartOfSentence = false

            return true
        }

        // loop through, doing replacements; do it in reverse order because sometimes the capitalized string isn't the same length, e.g. German ß -&gt; SS

        for range in capitalizeWordRanges.reversed() {
            result.replaceSubrange(range.lowerBound...range.lowerBound, with: result[range.lowerBound].uppercased())
        }

        return result
    }
}

let string2 = &quot;this is a test&quot;
print(string2.titlecased())                                 // This Is a Test

let string3 = #&quot;The sign said, &quot;don't feed the animals.&quot;&quot;#
print(string3.titlecased())                                 // The Sign Said, &quot;Don't Feed the Animals.&quot;

let string4 = &quot;¿donde está la biblioteca?&quot;
print(string4.titlecased())                                 // ¿Donde Está la Biblioteca?


Note, enumerating through the words solves another problem, namely situations where words may not preceded immediately by a space, but rather, perhaps punctuation, such as the quotation marks before “don't” in string3, above. Just splitting and joining by spaces will only work in the simplest of scenarios. Also, some languages have punctuation at the start of a sentence, e.g. the Spanish “¡Ay, caramba!” or “¿Donde está la biblioteca?” If you iterate through the actual words, rather than relying on spaces, you solve that problem.
This admittedly is not perfect “title case” logic. E.g. it is taking care of the capitalizing of the first character, but not shifting the rest of the word to lowercase (e.g., if the input string was entirely in uppercase). But if you are only worried about the first letter, this is probably better than the naive start/proper capitalization routine. And if you want to implement a richer title case rendition, this routine might be a good starting point.