NEW: Nominations are now open for the 2019 Swift Community Awards! >>

Adding to a list of words

Paul Hudson    @twostraws   

The user interface for this app will be made up of three main SwiftUI views: a NavigationView showing the word they are spelling from, a TextField where they can enter one answer, and a List showing all the words they have entered previously.

For now, every time users enter a word into the text field, we’ll automatically add it to the list of used words. Later, though, we’ll add some validation to make sure the word hasn’t been used before, can actually be produced from the root word they’ve been given, and is a real word and not just some random letters.

Let’s start with the basics: we need an array of words they have already used, a root word for them to spell other words from, and a string we can bind to a text field. So, add these three properties to ContentView now:

@State private var usedWords = [String]()
@State private var rootWord = ""
@State private var newWord = ""

As for the body of the view, we’re going to start off as simple as possible: a NavigationView with rootWord for its title, then a VStack with a text field and a list:

var body: some View {
    NavigationView {
        VStack {
            TextField("Enter your word", text: $newWord)

            List(usedWords, id: \.self) {
                Text($0)
            }
        }
        .navigationBarTitle(rootWord)
    }
}

By giving usedWords to List directly, we’re asking it to make one row for every word in the array, uniquely identified by the word itself. This would cause problems if there were lots of duplicates in usedWords, but soon enough we’ll be disallowing that so it’s not a problem.

If you run the program, you’ll see the text field doesn’t look great – it’s not really even visible next to the navigation bar or the list. Fortunately, we can fix that by asking SwiftUI to draw a light gray border around its edge using the textFieldStyle() modifier. This usually looks best with a little padding around the edges so it doesn’t touch the edges of the screen, so add these two modifiers to the text field now:

.textFieldStyle(RoundedBorderTextFieldStyle())
.padding()

That styling looks a little better, but the text view still has a problem: although we can type into the text box, we can’t submit anything from there – there’s no way of adding our entry to the list of used words.

To fix that we’re going to write a new method called addNewWord() that will:

  1. Lowercase newWord and remove any whitespace
  2. Check that it has at least 1 character otherwise exit
  3. Insert that word at position 0 in the usedWords array
  4. Set newWord back to be an empty string

Later on we’ll add some extra validation between steps 2 and 3 to make sure the word is allowable, but for now this method is straightforward:

func addNewWord() {
    // lowercase and trim the word, to make sure we don't add duplicate words with case differences
    let answer = newWord.lowercased().trimmingCharacters(in: .whitespacesAndNewlines)

    // exit if the remaining string is empty
    guard answer.count > 0 else {
        return
    }

    // extra validation to come

    usedWords.insert(answer, at: 0)
    newWord = ""
}

We want to call addNewWord() when the user presses return on the keyboard, and in SwiftUI we can do that by providing an on commit closure for the text field. I know that sounds fancy, but in practice it’s just a matter of providing a trailing closure to TextField that will be called whenever return is pressed.

In fact, because the closure’s signature – the parameters it needs to accept and its return type – exactly matches the addNewWord() method we just wrote, we can pass that in directly:

TextField("Enter your word", text: $newWord, onCommit: addNewWord)

Run the app now and you’ll see that things are starting to come together already: we can now type words into the text field, press return, and see them appear in the list.

Inside addNewWord() we used usedWords.insert(answer, at: 0) for a reason: if we had used append(answer) the new words would have appeared at the end of the list where they would probably be off screen, but by inserting words at the start of the array they automatically slide in at the top of the list – much better.

Before we put a title up in the navigation view, I’m going to make two small changes to our layout.

First, when we call addNewWord() it lowercases the word the user entered, which is helpful because it means the user can’t add “car”, “Car”, and “CAR”. However, it looks odd in practice: the text field automatically capitalizes the first letter of whatever the user types, so when they submit “Car” what they see in the list is “car”.

To fix this, we can disable capitalization for the text field with another modifier: autocapitalization(). Please add this to the text field now:

.autocapitalization(.none)

The second thing we’ll change, just because we can, is to use Apple’s SF Symbols icons to show the length of each word next to the text. SF Symbols provides numbers in circles from 0 through 50, all named using the format “x.circle.fill” – so 1.circle.fill, 20.circle.fill.

In this program we’ll be showing eight-letter words to users, so if they rearrange all those letters to make a new word the longest it will be is also eight letters. As a result, we can use those SF Symbols number circles just fine – we know that all possible word lengths are covered.

If we use a second view inside a List row, SwiftUI will automatically create an implicit horizontal stack for us so that everything in the row sits neatly side by side. What this means is we can just add Image(systemName:) directly inside the list and we’re done:

List(usedWords, id: \.self) {
    Image(systemName: "\($0.count).circle")
    Text($0)
}

If you run the app now you’ll see you can type words in the text field, press return, then see them slide into the list with their length icon to the side. Nice!

LEARN SWIFTUI FOR FREE I have a massive, free SwiftUI video collection on YouTube teaching you how to build complete apps with SwiftUI – check it out!

MASTER SWIFT NOW
Buy Testing Swift Buy Practical iOS 12 Buy Pro Swift Buy Swift Design Patterns Buy Swift Coding Challenges Buy Server-Side Swift (Vapor Edition) Buy Server-Side Swift (Kitura Edition) Buy Hacking with macOS Buy Advanced iOS Volume One Buy Advanced iOS Volume Two Buy Hacking with watchOS Buy Hacking with tvOS Buy Hacking with Swift Buy Dive Into SpriteKit Buy Swift in Sixty Seconds Buy Objective-C for Swift Developers Buy Beyond Code

Was this page useful? Let us know!

Average rating: 5.0/5