How to Check Answer in a Quiz when the Button isSelected - ios

I'm new to Swift and Coding. My english is not so good so apologize in advance!
I want to build a Quiz App. Now I have some trouble with checking the correct Answer because my uibuttons can be selected. The Reason for that ist that I would like to have multiple Answers with multiple Choice and multiple correct answers.
I don't know what kind of Informationen of my Code do you need from me but I will post what I mean it could help.
Thank you so much in advance for your help!!!
Class
import Foundation
class Quiz {
let question: String
let options : [String]
let correctAnswer: [String]
init(question: String, options: [String], correctAnswer: [String]) {
self.question = question
self.options = options
self.correctAnswer = correctAnswer
}
func validateOption(_ index: Int) -> Bool {
let answer = options[index]
return answer == correctAnswer[index]
}
deinit {
}
}
My Viewcontroller
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var quenstionLabel: UILabel!
#IBOutlet var answerButton: [UIButton]!
let quizManager = QuizManager()
override func viewDidLoad() {
super.viewDidLoad()
getNewQuiz()
}
#IBAction func checkAnswerButtonTapped(_ sender: UIButton) {
}
#IBAction func checkMarkAnswerClicked(_ sender: UIButton) {
if sender.isSelected {
sender.isSelected = false
} else {
sender.isSelected = true
}
}
and my Quiz Manager
import Foundation
class QuizManager {
private var quiz: Quiz!
private var _totalAnswers = 0
private var _totalCorrectAnswers = 0
var question: String {
return quiz.question
}
var options: [String] {
return quiz.options
}
var totalAnswers: Int {
return _totalAnswers
}
var totalCorrectAnswers: Int {
return _totalCorrectAnswers
}
func refreshQuiz(){
let randomIndex = Int(arc4random_uniform(UInt32(quizes.count)))
let quizData = quizes[randomIndex]
quiz = Quiz(question: quizData.question, options: quizData.options, correctAnswer: quizData.correctAnswer)
}
func validateAnswer(index: Int){
_totalAnswers += 1
if quiz.validateOption(index){
_totalCorrectAnswers += 1
}
}
If there are needed more Informations, please let me know!!
It would be great, if someone could help me.

You can solve this problem following these steps.
Add an array for your answers in Question Class.
Second Add Delegate in your cell class that will notify your view controller
implement that delegate in view controller and append answers to your model.
On check button tapped simply compare array of your tapped answers with correct answers array.
//Step 1
var myAnswers:[String]
protocol CheckAnswerDelegate : class {
func didPressOptionButton(myAnswer: String)
}
#IBAction func buttonPressed(_ sender: UIButton) {
checkAnswerDelegate?.didPressOptionButton(myAnswer: self.answerButton.titleLabel?.text ?? "")
}
class ViewController: UIViewController,UITableViewDelegate, UITableViewDataSource, CheckAnswerDelegate {}
func didPressOptionButton(myAnswer: String) {
if question1.myAnswers.contains(myAnswer) {
if let firstIndex = question1.myAnswers.firstIndex(where: {$0 == myAnswer}) {
question1.myAnswers.remove(at: firstIndex)
}
}else {
question1.myAnswers.append(myAnswer)
}
self.answerView.reloadData()
}

What I would do is put all of your answer buttons in a list. Then, inside checkAnswerButtonTapped you can run through the list and see which ones of them have isSelected set to true.
for button in buttonList {
var answersSelected = []
if button.isSelected {
answersSelected.append(button.text)
// You may want something different here depending on how the buttons store the answer options
}
}
Once you're done with that, compare them with the predefined correct answers from your QuizManager.
One other thing you could do to make your life a little bit easier is using the .toggle() function with your Boolean variables. Basically it just switches the value of your variable. So in your checkMarkAnswerClicked you could just have sender.isSelected.toggle()

Related

The logic of the interaction between UISegmentedControl and GUI doesn't work in my app

The functionality I tried to implement in the quiz game is looks like this:
Here is the SettingsViewController which contains the settings of an app, where user can choose the difficulity of the game and the sequence of the questions the game have using UISegmentedControl.
The SettingsViewController contains these methods using to implement this functionality:
#IBAction func didChooseDifficulty(_ sender: UISegmentedControl) {
difficulityDelegate?.didChooseDifficulity(
difficulity: selectedDifficulty)
UserDefaults.standard.set(
sender.selectedSegmentIndex,
forKey: "chosenDifficulity")
}
private var selectedDifficulty: Difficulty {
switch self.difficultyControl.selectedSegmentIndex {
case 0: return .easy
case 1: return .hard
case 2: return .insane
default: return .hard
}
}
#IBAction func didChooseSequence(_ sender: UISegmentedControl) {
sequenceDelegate?.didChooseSequence(
sequence: selectedSequence)
UserDefaults.standard.set(
sender.selectedSegmentIndex,
forKey: "chosenSequence")
}
private var selectedSequence: Sequence {
switch self.sequenceControl.selectedSegmentIndex {
case 0: return .sequentally
case 1: return .shuffled
default: return .sequentally
}
}
override func viewDidLoad() {
super.viewDidLoad()
setupQuestionsSequence()
setupDifficulty()
}
func setupQuestionsSequence() {
if let sequence = UserDefaults.standard.value(
forKey: "chosenSequence") {
let selectedIndex = sequence as! Int
sequenceControl.selectedSegmentIndex = selectedIndex
}
}
func setupDifficulty() {
if let difficulity = UserDefaults.standard.value(
forKey: "chosenDifficulity") {
let selectedIndex = difficulity as! Int
difficultyControl.selectedSegmentIndex = selectedIndex
}
}
I use the DifficulityDelegate and ChooseQuestionsSequenceDelegate protocols here:
protocol DifficulityDelegate: AnyObject {
func didChooseDifficulity(
difficulity: Difficulty)
}
protocol ChooseQuestionsSequenceDelegate: AnyObject {
func didChooseSequence(
sequence: Sequence)
}
Also here is the GameDelegate protocol, the main protocol of the game I use:
protocol GameDelegate: AnyObject {
func didEndGame(
difficulty: Difficulty,
withScore score: Int,
name: String,
removeTwoUsed: Bool,
callFriendUsed: Bool,
audienceHelpUsed: Bool)
}
Here is the GameSession class using for the main logic of the game:
class GameSession {
var questionNumber: Int = 0
var callFriendUsed = false
var audienceHelpUsed = false
var removeTwoUsed = false
var difficulty: Difficulty = .hard
var sequence: Sequence = .sequentally
}
Here is the Question structure with the questions is there:
struct Question: Codable {
let question: String
let answers: [String]
let rightAnswer: Int
let difficulty: Difficulty
}
And here is the GameViewController, the main controller where is the game itself occurs.
I need to gain some understanding of why it's nothing happens when I change the UISegmentedControl states. There is no any changes of the game difficulty and the sequence of the questions occurs.
Here is the some code from the GameViewController:
weak var delegate: GameDelegate?
weak var sequenceDelegate: ChooseQuestionsSequenceDelegate?
weak var difficulityDelegate: DifficulityDelegate?
private var questions = [Question]()
var gameSession = GameSession()
var score = 0
var questionNumber = Observable<Int>(0)
var difficulty: Difficulty = .hard {
didSet {
gameSession.difficulty = difficulty
}
}
var sequence: Sequence = .sequentally {
didSet {
gameSession.sequence = sequence
}
}
private var chooseDifficultyStrategy: DiffilultyStrategy {
switch difficulty {
case .easy: return EasyModeStrategy()
case .hard: return HardModeStrategy()
case .insane: return InsaneModeStrategy()
}
}
private var setupSelectedSequence: SequenceStrategy {
switch sequence {
case .sequentally: return SequentiallyStrategy()
case .shuffled: return ShuffledStrategy()
}
}
func initialSetup() {
delegate = Game.instance
Game.instance.gameSession = gameSession
questions = chooseDifficultyStrategy.setupGame(
lifelineButtons: lifelineButtonsCollection)
questions = setupSelectedSequence.setupGame()
}
It might be stupid, I'll be very glad if someone will help me understand what am I doing wrong. Thanks!
Speaking in very broad terms, I'd say that the protocol-and-delegate mechanism isn't what's wanted here. The view controllers that interact with the user's preferences, GameViewController and SettingsViewController, don't necessarily exist at the same time, so they cannot communicate with one another.
What's more likely needed is some central repository, as it were, where the SettingsViewController can write down the user's desires and the GameViewController can read them. A common place to keep preferences like this is UserDefaults; it has the advantage that it persists over time, even between launches of the app, and that it is globally available (meaning any of your code anywhere can interact with it).

App crashes at click with message: 'this class is not key value coding-compliant for the key postReplyButton.'

My app crashes when I click a cell in my tableView of recent posts. The click is supposed to segue me to the MainTextView which has the postReplyButton. The segue worked until I started experimenting with creating comments for the posts.
Here is the MainTextView code:
import Foundation
import UIKit
import Firebase
class MainTextView: UIViewController {
#IBOutlet weak var titleText: UILabel!
#IBOutlet weak var mainText: UILabel!
#IBOutlet weak var commentPlaceHolder: UILabel!
#IBOutlet weak var newCommentLabel: UITextView!
var delegate:NewPostVCDelegate?
#IBAction func postReplyButton() {
// Firebase code here
let postRef = Database.database().reference().child("posts").childByAutoId()
let postObject = [
"comment": newCommentLabel.text,
"timestamp": [".sv": "timestamp"]
] as [String : Any]
postRef.setValue(postObject, withCompletionBlock: { error, ref in
if error == nil {
self.delegate!.didUploadPost(withID: ref.key!)
self.dismiss(animated: true, completion: nil)
} else {
// Handle error
}
})
newCommentLabel.text = String()
commentPlaceHolder.isHidden = false
}
var post: Post?
// MARK: - View Controller LifeCycle
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.setMain()
}
override func viewDidLoad() {
super.viewDidLoad()
newCommentLabel.delegate = self as! UITextViewDelegate
}
private func setMain() {
guard let post = self.post else {
return
}
titleText.text = post.text
mainText.text = post.title
}
func textViewDidChange(_commentView: UITextView) {
commentPlaceHolder.isHidden = !newCommentLabel.text.isEmpty
}
}
For reference, here is my Post class code:
import Foundation
class Post {
var id:String
var title: String
var text:String
var createdAt:Date
var comment: [String] = []
init(id: String, title: String,text:String, timestamp:Double, comment: [String] = []) {
self.id = id
self.title = title
self.text = text
self.createdAt = Date(timeIntervalSince1970: timestamp / 1000)
}
static func parse(_ key:String, data:[String:Any]) -> Post? {
if let title = data["text"] as? String,
let text = data["title"] as? String,
let timestamp = data["timestamp"] as? Double {
return Post(id: key, title: title, text: text, timestamp:timestamp, comment: [])
}
return nil
}
}
I suspect the issue may be with the delegate, which was declared as such in my NewPostViewController:
protocol NewPostVCDelegate {
func didUploadPost(withID id:String)
}
I have tried troubleshooting the storyboard, but everything seems to be in place. Is there an issue of the reuse of the protocol or perhaps the change of adding comments to the Post class itself? Maybe the issue is that I do not in fact want to upload a new post, but really I just want to add a comment to an existing post. If this is the case, how would I change the delegate or create a new one? I can provide more detail if needed. Thank you for your help.
This usually happens if you have an IBOutlet that was created previously with the same postReplyButton name. To check if your app has any other Outlet with the same name go to the Search section in your project and search for postReplyButton and see if you get multiple results for that name. If you do then click on the one which you don't need and delete it from the properties section.
If you have any Outlet which has a bad connection you will see something like this in the properties sections when you click on any one of the search result for postReplyButton
If that does not work then try renaming the Outlet entirely and see if that fixes the problem.
EDITED:
For your issue that you mentioned in the comments try this.
Instead of casting your newCommentLabel as an optional type of UITextViewDelegate just extend your viewController to conform to UITextViewDelegate. This should solve the issue.
class MainTextView: UIViewController, UITextViewDelegate {
override func viewDidLoad() {
super.viewDidLoad()
newCommentLabel.delegate = self
}
}
Once you add UITextViewDelegate to your viewController you will no longer get the warning in viewDidLoad to cast newCommentLabel as an optional of type UITextViewDelegate.

How can I pass UITextField string from SecondViewController to array in Model Class?

I am creating a flash card app with a model and two view controllers. In the first scene I display the questions and answers, cycling through the arrays of questions and answers. The questions and answers arrays are stored in the Model class. The second scene/view controller's purpose is t be able to edit the questions and answers by editing the text in the UITextFields and after pressing the OK button, the questions and answers arrays are updated to the text in the TextFields.
When editing the "question" and "answer" text fields in the Second ViewController, changes to those strings have to be applied to the same FlashCardModel object that is used in the First Scene.
How do I update the questions array in the model class with the value of the questionTextField?
// FlashCardModel.swift
// TabbedFlashCards
//
//
//
//
import Foundation
class FlashCardModel {
var currentQuestionIndex = 0;
var questions = ["What is 1*7?", "What is 2*7?", "What is 3*7?"]
var answers = ["7", "14", "21"]
init(){
//Any useful Constructor code would go here
}
func getNextQuestion() -> String{
currentQuestionIndex = currentQuestionIndex + 1;
if(currentQuestionIndex >= questions.count){
currentQuestionIndex = 0;
}
return questions[currentQuestionIndex]
}
func getAnswer() -> String{
return answers[currentQuestionIndex]
}
func getCurrentQuestion() -> String{
return questions[currentQuestionIndex]
}
func setCurrentQuestion(pString : String){
questions[currentQuestionIndex] = pString
}
func setCurrentAnswer(pString : String){
answers[currentQuestionIndex] = pString
}
}//End of model
//
// SecondViewController.swift
// TabbedFlashCards
//
//
import UIKit
class SecondViewController: UIViewController {
// the reference to our AppDelegate:
var appDelegate: AppDelegate?
// the reference to our data model:
var myFlashCardModel: FlashCardModel?
#IBOutlet weak var questionTextField: UITextField!
#IBOutlet weak var answerTextField: UITextField!
#IBAction func buttonOKAction(sender: AnyObject) {
self.appDelegate = UIApplication.shared.delegate as? AppDelegate
self.myFlashCardModel = self.appDelegate?.myFlashCardModel
var text: String = questionTextField.text!
//myFlashCardModel?.setCurrentQuestion(pString: editQuestion)
//print ("self.questionTextField.text = \(self.questionTextField.text)")
//print ("self.answerTextField.text = \(self.answerTextField.text)")
}
override func viewDidLoad() {
super.viewDidLoad()
self.questionTextField.text = "Question"
self.answerTextField.text = "Answer"
}
}

Difference between Swift 2 and 3? How to all a class's method within another?

I believe I have a pretty simple question here, but due to my lack of experience with Swift, I have been unable to find the answer.
Here are two classes I created:
My controller:
import UIKit
class ViewController: UIViewController {
#IBOutlet private weak var display: UILabel!
private var userIsInTheMIddleOfTyping = false
private var brain = CalculatorBrain()
private var displayValue: Double {
get {
return Double(display.text!)!
}
set {
display.text = String(newValue)
}
}
#IBAction private func performOperation(_ sender: UIButton) {
if userIsInTheMIddleOfTyping{
brain.setOperand(displayValue)
userIsInTheMIddleOfTyping = false
}
if let mathematicalSymbol = sender.currentTitle {
brain.performOperation(mathematicalSymbol)
}
displayValue = brain.result
}
}
My model:
import Foundation
class CalculatorBrain{
private var accumulator = 0.0
func setOperand(operand: Double){
accumulator = operand
}
func performOperation(symbol: String){
switch symbol{
case "π": accumulator = M_PI
case "√": accumulator = sqrt(accumulator)
default: break
}
}
var result: Double{
get {
return accumulator
}
}
}
For some reason I get an error in line: brain.setOperand(displayValue)
Any reason why?
I tried your code and everything was fine, excepted two things:
brain.setOperand(operand: displayValue)
brain.performOperation(symbol: mathematicalSymbol)
In Swift 3, the first parameter need a external parameter by default.

How to check if input from UITextField matches Core Data attribute in array?

I have a Core Data object that has two attributes, question and answer. I also have a view that has a label, a UITextField and a button. I want to show the question attribute in the label and then in the UITextField type in the answer attribute for that question to check if it is correct.
So, in viewDidLoad, I ran a fetch on my Card object and put the results into an array. I followed this Stack Overflow post in order to figure out how to iterate through the array to show each question attribute in the label, but now I am unsure how exactly to check the answer attribute for that question. Below is my source code:
var cardFetch = [Card]()
var count = 0
#IBOutlet weak var displayLabel: UILabel!
#IBOutlet weak var inputText: UITextField!
#IBAction func nextButton(sender: AnyObject) {
displayLabel.text = cardFetch[count%cardFetch.count].question
count++
}
Try something like:
#IBAction func nextButton(sender: AnyObject) {
if let questionString = cardFetch[count%cardFetch.count].question as? String {
displayLabel.text = questionString
count++
}
}
#IBAction func checkAnswer(sender: AnyObject) {
if let answerString = cardFetch[count%cardFetch.count].answer as? String {
if let unwrappedTextfieldString = inputText.text as? String {
if answerString == unwrappedTextfieldString {
// Do whatever you want here
}
}
}
}
Note the unwraps that make optional unwrapping crash safe.

Resources