Don't dismiss keyboard when UITextField returns input

I have a class called TestViewController which asks user for the english translation of a foreign word. User taps on a UITextField which brings up the keyboard, and user types in their answer. The answer is checked after user taps the return key. My problem is that the keyboard is dismised each time the return key is tapped and answer is checked. User has to tap inside UITextField each time they want to submit the next answer. My question is, how to keep the keyboard present at all times.


I've tried commenting out line 75, which keeps keyboard up all the time, but the users answer doesn't get checked so they can move on to the next question.


import UIKit
import AVFoundation

class TestViewController: UIViewController, UITextFieldDelegate {
    
    @IBOutlet weak var stackView: UIStackView!
    @IBOutlet weak var prompt: UILabel!
    @IBOutlet weak var synthesizeButton: UIButton!
    @IBOutlet weak var instructions: UILabel!
    @IBOutlet weak var englishAnswer: UITextField!
    
    var words = [String]()
    var testWords = [String]()
    var useHomework: Bool!
    var zeroTimesWrong: Int!
    var homeworkZeroTimesWrong: Int!
    var questionCounter = 0
    var showingQuestion = true
    var chosenLanguage = String()
    var language = String()
    let wordsString = "Words"
    var englishWord = String()
    var foreignWord = String()
    var attempted = Int()
    var homeworkAttempted = Int()
    var homework = Int()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        print("viewDidLoad questionCounter is: \(questionCounter)")
        print("useHomework in viewDidLoad in TestViewController is: \(useHomework!)")

        loadChosenLanguage()
        loadWords()
        print("testWords just before shuffle: \(testWords)")
        testWords.shuffle()
        print("testWords just after shuffle: \(testWords)")

        if useHomework == true {
            navigationItem.title = "Learn Homework"
        } else {
            navigationItem.title = "Learn \(chosenLanguage.capitalized)"
        }
        navigationItem.rightBarButtonItem =
            UIBarButtonItem(title: "Reveal Answer", style: .plain , target: self, action: #selector(answerTapped))
        
        stackView.transform = CGAffineTransform(scaleX: 0.8, y: 0.8)
        stackView.alpha = 0
        
        synthesizeButton.isEnabled = false
        synthesizeButton.alpha = 0.25
        englishAnswer.delegate = self
        englishAnswer.layer.cornerRadius = 0.05 * englishAnswer.bounds.size.width
        let placeholderColor = UIColor.systemGray
        englishAnswer.attributedPlaceholder = NSAttributedString(string: "Tap here to answer", attributes: [NSAttributedString.Key.foregroundColor : placeholderColor])
        askQuestion()
        
        instructions.isHidden = true
    }
    
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)

        //loadWords()
        //print("testWords just before shuffle: \(testWords)")
        //testWords.shuffle()
        //print("testWords just after shuffle: \(testWords)")
        //print("questionCounter in viewWillAppear in TestViewController is: \(questionCounter)")
        englishAnswer.delegate = self
        englishAnswer.returnKeyType = .done
    }
    
    func textFieldShouldReturn(_ englishAnswer: UITextField) -> Bool {
        englishAnswer.resignFirstResponder()
        return true
    }
    
    @IBAction func synthesizeButton(_ sender: UIButton) {
        let utterance = AVSpeechUtterance(string: testWords[questionCounter].components(separatedBy: "::")[0])
        utterance.voice = AVSpeechSynthesisVoice(language: "en-US")
        utterance.rate = 0.5
        
        let synthesizer = AVSpeechSynthesizer()
        synthesizer.speak(utterance)
    }
    
    @IBAction func englishAnswer(_ sender: UITextField) {
        print("Entered englishAnswer")
        let trimmed = sender.text?.trimmingCharacters(in: .whitespacesAndNewlines)
        print("trimmed?capitalized in englishAnswer in TestViewController is: \(trimmed?.capitalized ?? "")")
        if trimmed?.capitalized == testWords[questionCounter].components(separatedBy: "::")[0] {
            resetWordCounters()
            if questionCounter + 1 < testWords.count {
                questionCounter += 1
            } else {
                questionCounter = 0
            }
            prompt.textColor = UIColor(red: 0, green: 0.7, blue: 0, alpha: 1)
            DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {
                self.prepareForNextQuestion()
            }
        } else {
            appendPracticeWord()
            prompt.textColor = UIColor(red: 0.7, green: 0, blue: 0, alpha: 1)
            synthesizeButton.isEnabled = true
            synthesizeButton.alpha = 1
            instructions.isHidden = false
        }
        englishAnswer.text?.removeAll()
        englishAnswer.placeholder = nil
        englishAnswer.isEnabled = false
        englishAnswer.layer.borderColor = UIColor.lightGray.cgColor as CGColor
    }
    
    func loadChosenLanguage() {
        if let defaults = UserDefaults(suiteName: "group.co.uk.tirnaelectronics.hyperpolyglot.todayview") {
            if let savedChosenLanguage = defaults.object(forKey: "languageChosen") as? String {
                print("savedChosen Language in loadChosenLanguage in TestViewController is: \(savedChosenLanguage)")
                chosenLanguage = savedChosenLanguage
            }
        }
    }
    
    func loadWords() {
        print("in loadWords in TestViewController")
        if let defaults = UserDefaults(suiteName: "group.co.uk.tirnaelectronics.hyperpolyglot.todayview") {
            print("after defaults in loadWords in TestViewController")
            if var savedWords = defaults.object(forKey: "words") as? [String] {
                words.removeAll()
                testWords.removeAll()
                words = savedWords
                print("savedWords in loadWords in TestViewcontroller are: \(savedWords)")
                print("words in loadWords in TestViewController are: \(words)")
                for savedWord in savedWords {
                    let split = savedWord.components(separatedBy: "::")
                    if useHomework == false {
                        if split[7] == chosenLanguage {
                            testWords.append(savedWord)
                            print("testWords.append from chosenLanguage in loadWords in TestViewController are: \(testWords)")
                        }
                    } else {
                        if split[6] == "1" {
                            testWords.append(savedWord)
                            print("testWords.append from homework in loadWords in TestViewController are: \(testWords)")
                        }
                    }
                }
                savedWords.removeAll()
            }
        }
    }
    
    override func viewDidLayoutSubviews() {
        //configureButtons()
    }
    
    func configureButtons() {
        synthesizeButton.layer.cornerRadius = 0.5 * synthesizeButton.bounds.size.width
        synthesizeButton.layer.borderColor = UIColor.lightGray.cgColor as CGColor
        synthesizeButton.layer.borderWidth = 4.0
        synthesizeButton.clipsToBounds = true
    }
    
    func resetWordCounters() {
        print("questionCounter in resetWordCounters in TestViewController is: \(questionCounter)")
        let resetCountersWord = testWords[questionCounter].components(separatedBy: "::")[1]
        print("resetCountersWord in resetWordCounters in TestViewController is: \(resetCountersWord)")

        if useHomework == false {
            attempted += 1
        } else {
            homeworkAttempted += 1
        }
        if useHomework == false {
            zeroTimesWrong = 0
        } else {
            homeworkZeroTimesWrong = 0
        }

        var indexForTestWord = 0
        for testWord in testWords {
            if testWord.components(separatedBy: "::")[1] == resetCountersWord {
                print("testWord equals resetCountersWord: \(resetCountersWord)")
                let split = testWord.components(separatedBy: "::")
                let firstWord = split[0]
                let secondWord = split[1]
                testWords.remove(at: indexForTestWord)
                print("testWords.remove in resetWordCounters in TestViewController are: \(testWords)")
            testWords.insert("\(firstWord)"+"::"+"\(secondWord)"+"::"+"\(zeroTimesWrong!)"+"::"+"\(homeworkZeroTimesWrong!)"+"::"+"\(attempted)"+"::"+"\(homeworkAttempted)"+"::"+"\(homework)"+"::"+"\(language)", at: indexForTestWord)
                print("testWords.insert in resetWordCounters in TestViewController: \(testWords)")
                break
            }
            indexForTestWord += 1
        }
        print("indexForTestWord in resetWordCounters in TestViewController is: \(indexForTestWord)")

        removeInsertWord(resetCountersWordPracticeWord: resetCountersWord)
    }
    
    func appendPracticeWord() {
        zeroTimesWrong += 1
        homeworkZeroTimesWrong += 1
        if useHomework == false {
            attempted += 1
        } else {
            homeworkAttempted += 1
        }

        print("questionCounter in appendPracticeWord in TestViewController is: \(questionCounter)")

        let practiceWord = testWords[questionCounter].components(separatedBy: "::")[1]
        print("practiceWord in appendPracticeWord in TestViewController is: \(practiceWord)")

        var indexForPracticeWord = 0
        for testWord in testWords {
            if testWord.components(separatedBy: "::")[1] == practiceWord {
                print("testWord equals practiceWord: \(practiceWord)")
                let split = testWord.components(separatedBy: "::")
                let firstWord = split[0]
                let secondWord = split[1]
            testWords.insert("\(firstWord)"+"::"+"\(secondWord)"+"::"+"\(zeroTimesWrong!)"+"::"+"\(homeworkZeroTimesWrong!)"+"::"+"\(attempted)"+"::"+"\(homeworkAttempted)"+"::"+"\(homework)"+"::"+"\(language)", at: indexForPracticeWord)
                print("testWords.insert in appendPracticeWord in TestViewController: \(testWords)")
                break
            }
            indexForPracticeWord += 1
        }

        print("indexForPracticeWord in appendPracticeWord in TestViewController is: \(indexForPracticeWord)")

        testWords.remove(at: indexForPracticeWord)
        print("testWords.remove in appendPracticeWord in TestViewController: \(testWords)")

        removeInsertWord(resetCountersWordPracticeWord: practiceWord)
    }

    func removeInsertWord(resetCountersWordPracticeWord: String) {
        var indexForWord = 0
        for word in words {
            if word.components(separatedBy: "::")[1] == resetCountersWordPracticeWord {
                print("word equals resetCountersWordPracticeWord: \(word)")
                let split = word.components(separatedBy: "::")
                let firstWord = split[0]
                let secondWord = split[1]
                words.remove(at: indexForWord)
                print("words.remove in removeInsertWord in TestViewController are: \(words)")
            words.insert("\(firstWord)"+"::"+"\(secondWord)"+"::"+"\(zeroTimesWrong!)"+"::"+"\(homeworkZeroTimesWrong!)"+"::"+"\(attempted)"+"::"+"\(homeworkAttempted)"+"::"+"\(homework)"+"::"+"\(language)", at: indexForWord)
                print("words.insert in removeInsertWord in TestViewController after insert: \(words)")
                break
            }
            indexForWord += 1
        }

        print("indexForWord in removeInsertWord in TestViewController is: \(indexForWord)")

        saveWords()
    }
    
    @objc func answerTapped() {
        showEnglishQuestion()
    }
    
    func showEnglishQuestion() {
        showingQuestion = !showingQuestion
        if showingQuestion {
            // we should be showing the question – reset!
            prepareForNextQuestion()
            navigationItem.rightBarButtonItem =
                UIBarButtonItem(title: "Reveal Answer", style: .plain , target: self, action: #selector(answerTapped))

            //englishAnswer.isEnabled = true
        } else {
            // we should be showing the answer – show it now, and set the color to be green
            print("questionCounter before prompt.text in showEnglishQuestion in TestViewController is: \(questionCounter)")
            prompt.text = testWords[questionCounter].components(separatedBy: "::")[0]
            prompt.textColor = UIColor(red: 0, green: 0.7, blue: 0, alpha: 1)
            print("testWords.count in askQuestion in TestViewController is: \(testWords.count)")
            if questionCounter + 1 < testWords.count {
                let showNextLanguage = testWords[questionCounter + 1] .components(separatedBy: "::")[7]
                navigationItem.rightBarButtonItem =
                    UIBarButtonItem(title: "Next \(showNextLanguage.capitalized) Word", style: .plain , target: self, action: #selector(answerTapped))
                print("showNextLanguage in showEnglishQuestion in TestViewController is: \(showNextLanguage)")
                // move the question counter one place
                questionCounter += 1
                print("questionCounter in showEnglishQuestion in TestViewController is: \(questionCounter)")
            } else {
                // wrap it back to 0 if we've gone beyond the size of the array
                questionCounter = 0
                print("questionCounter in questionCounter = 0 else statement in showEnglishQuestion in TestViewController is: \(questionCounter)")
                print("words array in showEnglishQuestion in TestViewController when questionCounter = 0 is: \(testWords)")
                let showNextLanguage = testWords[questionCounter] .components(separatedBy: "::")[7]
                navigationItem.rightBarButtonItem =
                    UIBarButtonItem(title: "Next \(showNextLanguage.capitalized) Word", style: .plain , target: self, action: #selector(answerTapped))
                print("showNextLanguage in if questionCounter == 0 in showEnglishQuestion in TestViewController is: \(showNextLanguage)")
                // move the question counter one place
            }
            englishAnswer.isEnabled = false
        }
        synthesizeButton.isEnabled = false
        synthesizeButton.alpha = 0.25
        instructions.isHidden = true
    }
    
    func askQuestion() {
        // pull out the foreign word at the current question position
        print("questionCounter before prompt.text in askQuestion in TestViewController is: \(questionCounter)")
        prompt.text = testWords[questionCounter].components(separatedBy: "::")[1]
        
        let animation = UIViewPropertyAnimator(duration: 0.5, dampingRatio: 0.5) {
            self.stackView.alpha = 1
            self.stackView.transform = CGAffineTransform.identity
        }
        animation.startAnimation()
        
        englishAnswer.isEnabled = true
        englishAnswer.layer.borderColor = UIColor.black.cgColor as CGColor
        
        englishWord = testWords[questionCounter].components(separatedBy: "::")[0]
        foreignWord = testWords[questionCounter].components(separatedBy: "::")[1]
        zeroTimesWrong = Int(testWords[questionCounter].components(separatedBy: "::")[2])!
        homeworkZeroTimesWrong = Int(testWords[questionCounter].components(separatedBy: "::")[3])!
        attempted = Int(testWords[questionCounter].components(separatedBy: "::")[4])!
        homeworkAttempted = Int(testWords[questionCounter].components(separatedBy: "::")[5])!
        homework = Int(testWords[questionCounter].components(separatedBy: "::")[6])!
        language = testWords[questionCounter].components(separatedBy: "::")[7]
    }
    
    func prepareForNextQuestion() {
        let animation = UIViewPropertyAnimator(duration: 0.5, curve: .easeInOut) { [unowned self] in
            self.stackView.transform = CGAffineTransform(scaleX: 0.8, y: 0.8)
            self.stackView.alpha = 0
        }
        
        animation.addCompletion { [unowned self] position in
            self.prompt.textColor = UIColor.black
            self.askQuestion()
        }
        
        animation.startAnimation()

        englishAnswer.placeholder = "Tap here to answer"
    }

    func saveWords() {
        if let defaults = UserDefaults(suiteName: "group.co.uk.tirnaelectronics.hyperpolyglot.todayview") {
            defaults.set(words, forKey: "words")
        }
    }
}

Replies

Where do you test that answer is correct ? Cannot find the func.

line 88

OK.


What is the sent event associated with englishAnswer IBAction ?

Only just got round to replying!

UITextField (line 10) when return key (done action) is pressed by user after finishing editing.

To be sure I understand, you have connected from IB Connection Inspector the sent event 'Did End On Exit' to the IBAction englishAnswer in code ?


Do you get log as result of:

print("Entered englishAnswer")


Have you set the delegate for the UITextField (in IB) ? I understand yes.


Try to comment out completely textFieldShouldReturn

    func textFieldShouldReturn (_ englishAnswer: UITextField) -> Bool {
        englishAnswer.resignFirstResponder()
        return true
    }

You could try to connect also 'Editing Did End' to the same IBAction.