100 Days of SwiftUI Day 30

100 Days of SwiftUI – Day 30

It’s day 30 of the 100 Days of SwiftUI! Yesterday, we had our introduction to our latest project called Word Scramble, as well learned about the List view. Today, we’re implementing our game. We’ll see lots of interesting new pieces of code come by, so strap in. It’s time to dive in!

Word Scramble in SwiftUI: implementation

We’re allowing users to enter words. These words need to be saved somewhere and they also need to be displayed to our users. This is where the List comes in. Before we create the list, we’ll set up all the properties we’ll need for our game. I’ve added comments to explain what they’re used for.

 @State private var usedWords = [String]() // array that contains the words a user has entered
    @State private var rootWord = "" // The word we're spelling from
    @State private var newWord = "" // Used to clear the text field by putting in an empty String
    @State private var errorTitle = "" // Used as title for our alert in case of an error
    @State private var errorMessage = "" // Used as message for our alert in case of an error
    @State private var showingError = false // Used to control whether our error alert is shown

Next, we’ll create our List. It consists of a NavigationView and two sections. I’ve added comments just about everywhere that explain what the code does.

var body: some View {
        NavigationView {
            List {
                Section {
                    TextField("Enter your own word", text: $newWord)
                        .autocapitalization(.none) // removes auto capitialization of words for aesthetic purposes
                }
                
                Section {
                    ForEach(usedWords, id: \.self) { word in
                        HStack {
                            Image(systemName: "\(word.count).circle") // adds a number displaying how many letters our word contains in a circle (symbol)
                            Text(word) // loops through our usedWords array and displays it in the section.
                        }
                    }
                }
            }
            
            .navigationTitle(rootWord) // Title shown at the top of our app
            .onSubmit(addNewWord) // onSubmit means, when the user presses the enter key. In this case, when the press enter, run the addNewWord function so that the word that's entered get's added to the array.
            .onAppear(perform: startGame) // When the app starts, run function startGame
            .alert(errorTitle, isPresented: $showingError) {
                Button("OK", role: .cancel) {}
            } message: {
                Text(errorMessage)
            }
        }
    }

Functions for Word Scramble

You’ve already seen a few function calls in the example above. Time to peak behind the curtain and see what they do!

func addNewWord() {
        let answer = newWord.lowercased().trimmingCharacters(in: .whitespacesAndNewlines) // Lowercases answer, trims whitespaces and line breaks
        guard answer.count > 0 else { return } // Checks if the String isn't empty
        
        guard isOriginal(word: answer) else { // calls the isOriginal and wordError function
            wordError(title: "Word used already.", message: "Be more original!")
            return
        }
         
        guard isPossible(word: answer) else { // calls the isPossible and wordError function
            wordError(title: "Word not possible.", message: "You can't spell that word from \(rootWord)")
            return
        }
        
        guard isReal(word: answer) else { // calls the isReal and wordError function
            wordError(title: "Word not recognized.", message: "You can't just make up words, you know!")
            return
        }
        
        withAnimation { // adds an animation
            usedWords.insert((answer), at: 0) // inserts at the start of the array so that it is shown on top of the list in the app
        }
        newWord = ""
    }
    
    func startGame() {
        if let startWorldsUrl = Bundle.main.url(forResource: "start", withExtension: "txt") { // check if our txt file can be found in the app bundle
            if let startWords = try? String(contentsOf: startWorldsUrl) { // if it's found, do something ->
                let allWords = startWords.components(separatedBy: "\n") // create an array of all items that are on a new line
                rootWord = allWords.randomElement() ?? "silkworm" // should never run, but just in case
                return
            }
        }
        
        fatalError("Could not load start.txt from bundle.") // Should not happen, but if it does, it will show this error and crash the app
    }
    
    func isOriginal(word: String) -> Bool {
        !usedWords.contains(word) // checks whether the array already contains a word and if it doesn, returns false
    }
    
    func isPossible(word: String) -> Bool {
        var tempWord = rootWord // make a copy of our rootword in a new variable
        
        for letter in word { // loops over the tempWord and removes a letter if it's found, so that it cannot be used again
            if let pos = tempWord.firstIndex(of: letter) {
                tempWord.remove(at: pos)
            } else {
                return false
            }
        }
        
        return true
    }
    
    func isReal(word: String) -> Bool { // checks whether text (in this case a word) is valid English
        let checker = UITextChecker() // create an instance of a UITextChecker
        let range = NSRange(location: 0, length: word.utf16.count) // The range of our text to be checked. Location 0 means start at the start. The length is the full length of the word variable we'll pass in when our function is called
        let misspelledRange = checker.rangeOfMisspelledWord(in: word, range: range, startingAt: 0, wrap: false, language: "en")
        
        return misspelledRange.location == NSNotFound
    }
    
    func wordError(title: String, message: String) { // function that allows us to easily set up errors depending on the context
        errorTitle = title
        errorMessage = message
        showingError = true
    }

With our functions implemented, our Word Scramble app is now completed.

Screenshots

Here’s a few screenshots of our app as we’ve implemented with the code examples above. We could do a lot more with the design, but this project was focused on teaching us about List as well as the advanced String functions.

And that’s it for day 30! Tomorrow, we’ll review our project as well complete a few challenges. Time to recharge and get ready!

Darryl

Hi! My name is Darryl and this is my personal blog where I write about my journey as I learn programming! You'll also find articles about other things that interest me including games, tech and anime.

Post navigation

Leave a Comment

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.