I'm new to Swift. I'm trying to make a quiz app using the API, I can show a question and answers, but I can't move on to the next questions. I can access different questions in the api with the index, but when I click on the options, I can't go to the next question. I looked at similar applications and watched a few how to make quiz app videos from youtube, but I couldn't do it.
my QuizManager
import Foundation
protocol quizManagerDelegate {
func didUpdateQuiz(_ Quizmanager: QuizManager ,quiz: QuizModel)
}
struct QuizManager {
var index : Int = 0
var maxQuestion = 14
mutating func nextQuestion(result: Bool) -> Int{
if result == true{
return index 1
} else {
return index
}
}
var delegate: quizManagerDelegate?
func performRequest(){
let urlString = "https://opentdb.com/api.php?amount=15&type=multiple"
if let url = URL(string: urlString){
let session = URLSession(configuration: .default)
let task = session.dataTask(with: url) { data, response, error in
if error != nil {
print(error!)
return
}
if let safeData = data{
if let quiz = self.parseJSON(quizdata: safeData){
delegate?.didUpdateQuiz(self, quiz: quiz)
}
}
}
task.resume()
}
}
func handle(data: Data?, response: URLResponse?, error: Error?) -> Void {
}
func parseJSON(quizdata: Data) -> QuizModel? {
let decoder = JSONDecoder()
do{
let decodedData = try decoder.decode(Welcome.self, from: quizdata)
let correct = decodedData.results?[index].correct_answer ?? "error"
let quest = decodedData.results?[index].question ?? "error"
let incorrect = decodedData.results?[index].incorrect_answers ?? ["error"]
let question = QuizModel(correctAnswer: correct, question: quest, falseAnswer: incorrect)
return question
} catch {
print(error)
return nil
}
}
mutating func nextQuestion(){
if index 1 < maxQuestion {
index = 1
}else {
index = 0
}
}
}
my QuizData
import Foundation
// MARK: - Welcome
struct Welcome: Codable {
let results: [Result]?
}
// MARK: - Result
struct Result: Codable {
let category: String?
let question, correct_answer: String?
let incorrect_answers: [String]?
}
my QuizModel
import Foundation
struct QuizModel {
let correctAnswer : String
let question : String
let falseAnswer : [String]
}
my ViewController
import UIKit
class ViewController: UIViewController {
@IBOutlet weak var ScoreLabel: UILabel!
@IBOutlet weak var ChoiceButton4: UIButton!
@IBOutlet weak var ChoiceButton3: UIButton!
@IBOutlet weak var ChoiceButton2: UIButton!
@IBOutlet weak var ChoiceButton1: UIButton!
@IBOutlet weak var QuestionTextView: UITextView!
var quizMangager = QuizManager()
var score = 0
var theQuiz: QuizModel?
@IBAction func OptionsButtonPressed(_ sender: UIButton) {
guard let thisQuiz = theQuiz,
let btnTitle = sender.currentTitle
else { return }
if btnTitle == thisQuiz.correctAnswer {
score = 1
ScoreLabel.text = "SCORE: \(score)"
quizMangager.nextQuestion()
sender.setTitleColor(.systemGreen, for: [])
} else {
sender.setTitleColor(.systemRed, for: [])
quizMangager.nextQuestion()
}
Timer.scheduledTimer(timeInterval: 0.35, target: self, selector: #selector(updateUI), userInfo: nil, repeats: false)
QuestionTextView.text = thisQuiz.question
}
@objc func updateUI() {
}
override func viewDidLoad() {
super.viewDidLoad()
QuestionTextView.layer.cornerRadius = 15
quizMangager.delegate = self
quizMangager.performRequest()
}
}
extension ViewController : quizManagerDelegate{
func didUpdateQuiz(_ Quizmanager: QuizManager, quiz: QuizModel) {
DispatchQueue.main.async { [self] in
self.theQuiz = quiz
self.QuestionTextView.text = quiz.question
var allOptions = []
allOptions.append(quiz.falseAnswer[0])
allOptions.append(quiz.falseAnswer[1])
allOptions.append(quiz.falseAnswer[2])
allOptions.append(quiz.correctAnswer)
let generatedValue = Array(allOptions.shuffled().prefix(4))
print(generatedValue)
print(quiz.correctAnswer)
ChoiceButton1.setTitle(generatedValue[0] as? String, for: .normal)
ChoiceButton2.setTitle(generatedValue[1] as? String, for: .normal)
ChoiceButton3.setTitle(generatedValue[2] as? String, for: .normal)
ChoiceButton4.setTitle(generatedValue[3] as? String, for: .normal)
}
}
}
CodePudding user response:
Because you are "new to Swift" I'll skip critique of your code... but a couple notes...
Since you are using the "new" UIButton
styles, you should use the button configuration instead of setTitle()
/ .setTitleColor()
, etc.
To "move on to the next question" add quizManager.performRequest()
to your updateUI()
function.
Take a look at this modified version of your ViewController
class:
class ViewController: UIViewController {
@IBOutlet weak var ScoreLabel: UILabel!
@IBOutlet weak var ChoiceButton4: UIButton!
@IBOutlet weak var ChoiceButton3: UIButton!
@IBOutlet weak var ChoiceButton2: UIButton!
@IBOutlet weak var ChoiceButton1: UIButton!
@IBOutlet weak var QuestionTextView: UITextView!
var quizMangager = QuizManager()
var score = 0
var theQuiz: QuizModel?
@IBAction func OptionsButtonPressed(_ sender: UIButton) {
guard let thisQuiz = theQuiz
else { return }
// disable buttons so user cannot tap more than one
let btns: [UIButton] = [ChoiceButton1, ChoiceButton2, ChoiceButton3, ChoiceButton4]
btns.forEach { btn in
btn.isUserInteractionEnabled = false
}
var cfg = sender.configuration
if let btnTitle = cfg?.title {
if btnTitle == thisQuiz.correctAnswer {
score = 1
ScoreLabel.text = "SCORE: \(score)"
cfg?.baseForegroundColor = .systemGreen
} else {
cfg?.baseForegroundColor = .systemRed
}
}
sender.configuration = cfg
Timer.scheduledTimer(timeInterval: 0.35, target: self, selector: #selector(updateUI), userInfo: nil, repeats: false)
}
@objc func updateUI() {
quizMangager.nextQuestion()
quizMangager.performRequest()
}
override func viewDidLoad() {
super.viewDidLoad()
QuestionTextView.layer.cornerRadius = 15
quizMangager.delegate = self
quizMangager.performRequest()
}
}
extension ViewController : quizManagerDelegate{
func didUpdateQuiz(_ Quizmanager: QuizManager, quiz: QuizModel) {
DispatchQueue.main.async { [self] in
self.theQuiz = quiz
self.QuestionTextView.text = quiz.question
var allOptions: [String] = []
allOptions.append(quiz.falseAnswer[0])
allOptions.append(quiz.falseAnswer[1])
allOptions.append(quiz.falseAnswer[2])
allOptions.append(quiz.correctAnswer)
let generatedValue = Array(allOptions.shuffled().prefix(4))
print(generatedValue)
print(quiz.correctAnswer)
let btns: [UIButton] = [ChoiceButton1, ChoiceButton2, ChoiceButton3, ChoiceButton4]
// we want to reset buttons
// enabled and title color
// and set the new titles
for (str, btn) in zip(generatedValue, btns) {
var cfg = btn.configuration
cfg?.baseForegroundColor = .white
cfg?.title = str
btn.configuration = cfg
btn.isUserInteractionEnabled = true
}
}
}
}
CodePudding user response:
Your code has lots of problems need to be solved.
I will explain as detail as possible and you will need to try it yourself.
First of all, you have QuizManager
to control Quiz
but you don't store QuizData here you delegate data back to ViewController
. So when you update answer you call quizMangager.nextQuestion()
is pointless because QuizManager
don't control or has any data.
For this you first need to refactor your QuizManager
.
- Change
protocol quizManagerDelegate
->protocol QuizManagerDelegate
for right naming convention. - When you
performRequest
you get yoursafeData
just parse one time and get the list Result. You can convert to listQuizModel
if you like and save it in yourQuizManager
- In
QuizManagerDelegate
, just notice to the view controller the quizModel in order for updating
protocol QuizManagerDelegate {
func didUpdateQuiz(quiz: QuizModel)
}
And in your performRequest
code will be like this
self.parseJSON(quizdata: safeData)
delegate?.didUpdateQuiz(quiz:listQuizData[index]) // remember to check if has index
func parseJSON(quizdata: Data) {
let decoder = JSONDecoder()
do {
let decodedData = try decoder.decode(Welcome.self, from: quizdata)
let decodedResult = decodedData.results // list result
// you can convert to list quizmodel and save it
// If you do like this just get only one quizModel as each parseJson.
let correct = decodedData.results?[index].correct_answer ?? "error" // remove
let quest = decodedData.results?[index].question ?? "error" // remove
let incorrect = decodedData.results?[index].incorrect_answers ?? ["error"] // remove
let question = QuizModel(correctAnswer: correct, question: quest, falseAnswer: incorrect) // remove
return question // remove
} catch {
print(error)
return nil
}
}
with listQuizData
is the list result you parse and convert from Json
in performRequest
- Final in your
nextQuestion
after you update the index just need to calldelegate?.didUpdateQuiz(quiz:listQuizData[index])
Come to the ViewController
You just need to take the QuizModel
and view it
func didUpdateQuiz(quiz: QuizModel) {
self.theQuiz = quiz
var allOptions : [String] = []
allOptions.append(contentsOf: quiz.falseAnswer) // combine to append only one time
allOptions.append(quiz.correctAnswer)
let generatedValue = Array(allOptions.shuffled().prefix(4))
print(generatedValue)
print(quiz.correctAnswer)
ChoiceButton1.setTitle(generatedValue[0], for: .normal)
ChoiceButton2.setTitle(generatedValue[1], for: .normal)
ChoiceButton3.setTitle(generatedValue[2], for: .normal)
ChoiceButton4.setTitle(generatedValue[3], for: .normal)
}