在测验的这一点上,应用程序应该计算最终得分。目前,在最后一个问题上选择答案后崩溃,标题错误出现在第三行:
var score = 0
for question in QuestionController.questionsList {
score += question.selectedAnswerIndex!
以下该文件的完整代码。它在以前的应用程序迭代中运行良好,但今天就开始了。我知道强制解包选项存在问题,但不清楚为什么它只发生在这种情况下:
import UIKit
class ViewController: UIViewController {
var window: UIWindow?
override func viewDidLoad() {
super.viewDidLoad()
self.title="Quiz"
self.view.backgroundColor=UIColor.white
setupViews()
}
@objc func btnGetStartedAction() {
let v=QuestionController()
self.navigationController?.pushViewController(v, animated: true)
}
func setupViews() {
self.view.addSubview(lblTitle)
lblTitle.topAnchor.constraint(equalTo: self.view.topAnchor, constant: 150).isActive=true
lblTitle.centerXAnchor.constraint(equalTo: self.view.centerXAnchor).isActive=true
lblTitle.widthAnchor.constraint(equalToConstant: 250).isActive=true
lblTitle.heightAnchor.constraint(equalToConstant: 250).isActive=true
self.view.addSubview(btnGetStarted)
btnGetStarted.topAnchor.constraint(equalTo: lblTitle.bottomAnchor, constant: 20).isActive=true
btnGetStarted.heightAnchor.constraint(equalToConstant: 50).isActive=true
btnGetStarted.widthAnchor.constraint(equalToConstant: 150).isActive=true
btnGetStarted.centerXAnchor.constraint(equalTo: self.view.centerXAnchor).isActive=true
btnGetStarted.centerYAnchor.constraint(equalTo: self.view.centerYAnchor, constant: 0).isActive=true
}
let lblTitle: UILabel = {
let lbl=UILabel()
lbl.text="Have you ever wondered which character you are on Friends? Answer the question on this quiz."
lbl.textColor=UIColor.black
lbl.textAlignment = .center
lbl.font = UIFont.systemFont(ofSize: 30)
lbl.adjustsFontSizeToFitWidth = true
lbl.numberOfLines=0
lbl.sizeToFit()
lbl.translatesAutoresizingMaskIntoConstraints=false
return lbl
}()
let btnGetStarted: UIButton = {
let btn=UIButton()
btn.setTitle("Get Started", for: .normal)
btn.setTitleColor(UIColor.white, for: .normal)
btn.backgroundColor=UIColor.blue
btn.layer.cornerRadius=5
btn.layer.masksToBounds=true
btn.translatesAutoresizingMaskIntoConstraints=false
btn.addTarget(self, action: #selector(btnGetStartedAction), for: .touchUpInside)
return btn
}()
}
struct Question {
var questionString: String?
var answers: [String]?
var selectedAnswerIndex: Int?
}
class QuestionController: UIViewController, UITableViewDelegate, UITableViewDataSource {
let cellId = "cellId"
let headerId = "headerId"
var tableView: UITableView?
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
self.tableView?.frame = CGRect(x: 0, y: 64, width: self.view.frame.width, height: self.view.frame.height - 64)
}
static var questionsList: [Question] = [Question(questionString: "What is your favorite type of food?", answers: ["Sandwiches", "Pizza", "Seafood", "Unagi"], selectedAnswerIndex: nil), Question(questionString: "What do you do for a living?", answers: ["Paleontologist", "Actor", "Chef", "Waitress"], selectedAnswerIndex: nil), Question(questionString: "Were you on a break?", answers: ["Yes", "No"], selectedAnswerIndex: nil)]
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.title = "Question"
navigationController?.navigationBar.tintColor = UIColor.white
navigationItem.backBarButtonItem = UIBarButtonItem(title: "Back", style: .plain, target: nil, action: nil)
tableView = UITableView()
tableView?.dataSource = self
tableView?.delegate = self
tableView?.estimatedRowHeight = 140
tableView?.sectionHeaderHeight = 100
self.tableView?.rowHeight = UITableViewAutomaticDimension
self.view.addSubview(self.tableView!)
tableView?.register(AnswerCell.self, forCellReuseIdentifier: cellId)
tableView?.register(QuestionHeader.self, forHeaderFooterViewReuseIdentifier: headerId)
tableView?.tableFooterView = UIView()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if let index = navigationController?.viewControllers.index(of: self) {
let question = QuestionController.questionsList[index]
if let count = question.answers?.count {
return count
}
}
return 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath as IndexPath) as! AnswerCell
if let index = navigationController?.viewControllers.index(of: self) {
let question = QuestionController.questionsList[index]
cell.nameLabel.text = question.answers?[indexPath.row]
cell.nameLabel.numberOfLines = 0
cell.nameLabel.lineBreakMode = .byWordWrapping
}
return cell
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let header = tableView.dequeueReusableHeaderFooterView(withIdentifier: headerId) as! QuestionHeader
if let index = navigationController?.viewControllers.index(of: self) {
let question = QuestionController.questionsList[index]
header.nameLabel.text = question.questionString
header.nameLabel.numberOfLines = 0
header.nameLabel.lineBreakMode = .byWordWrapping
}
return header
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if let index = navigationController?.viewControllers.index(of: self) {
QuestionController.questionsList[index].selectedAnswerIndex = indexPath.item
if index < QuestionController.questionsList.count - 1 {
let questionController = QuestionController()
navigationController?.pushViewController(questionController, animated: true)
} else {
let controller = ResultsController()
navigationController?.pushViewController(controller, animated: true)
}
}
}
}
class ResultsController: UIViewController {
let resultsLabel: UILabel = {
let label = UILabel()
label.text = "Congratulations! You'd make a great Ross!"
label.contentMode = .scaleToFill
label.numberOfLines = 0
label.translatesAutoresizingMaskIntoConstraints = false
label.textAlignment = .center
label.font = UIFont.boldSystemFont(ofSize: 30)
return label
}()
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Done", style: .plain, target: self, action: #selector(done(sender:)))
navigationItem.title = "Results"
view.backgroundColor = UIColor.white
view.addSubview(resultsLabel)
view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|[v0]|", options: NSLayoutFormatOptions(), metrics: nil, views: ["v0": resultsLabel]))
view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|[v0]|", options: NSLayoutFormatOptions(), metrics: nil, views: ["v0": resultsLabel]))
let names = ["Ross", "Joey", "Chandler", "Monica", "Rachel", "Phoebe"]
var score = 0
for question in QuestionController.questionsList {
score += question.selectedAnswerIndex!
}
let result = names[score % names.count]
resultsLabel.text = "Congratulations! \(result)."
}
@objc func done(sender: UIBarButtonItem) {
navigationController?.popToRootViewController(animated: true)
}
}
答案 0 :(得分:2)
因为question.selectedAnswerIndex
是nil
而崩溃了。通常,您不应该尽可能使用隐式展开运算符(!
),因为如果可选项为nil
,它将导致应用程序崩溃。而是将其打包在if let
语句中,如果它恰好是nil
,则会正常处理错误。
虽然您似乎并不期望question.selectedAnswerIndex
为nil
所以我希望您的代码中的其他位置可能会出现逻辑错误。
答案 1 :(得分:1)
问题是由于您在导航控制器的viewControllers
数组中使用视图控制器的位置来确定问题索引。 “get started”视图控制器位于0位置,您的第一个“问题”视图控制器位于位置1。
这意味着您永远不会对用户的食物偏好提出问题'0'。
然后,当您遍历问题并强制解开selectedAnswerIndex
时,您会崩溃。
一般情况下,除非:
,否则不应强行打开包装nil
第二点是不要试图太聪明。您可以将当前问题索引的简单int
传递给视图控制器。虽然这不像使用viewControllers
数组来确定问题索引那样“聪明”,但是如果堆栈中的视图控制器数量发生变化,则更容易看到发生了什么并且不会中断。
class QuestionController: UIViewController, UITableViewDelegate, UITableViewDataSource {
let cellId = "cellId"
let headerId = "headerId"
var tableView: UITableView?
var questionIndex = 0
....
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return QuestionController.questionsList[index].answers?.count ?? 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath as IndexPath) as! AnswerCell
let question = QuestionController.questionsList[questionIndex]
cell.nameLabel.text = question.answers?[indexPath.row]
cell.nameLabel.numberOfLines = 0
cell.nameLabel.lineBreakMode = .byWordWrapping
return cell
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let header = tableView.dequeueReusableHeaderFooterView(withIdentifier: headerId) as! QuestionHeader
let question = QuestionController.questionsList[questionIndex]
header.nameLabel.text = question.questionString
header.nameLabel.numberOfLines = 0
header.nameLabel.lineBreakMode = .byWordWrapping
return header
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
QuestionController.questionsList[questionIndex].selectedAnswerIndex = indexPath.item
if questionIndex < QuestionController.questionsList.count - 1 {
let questionController = QuestionController()
questionController.questionIndex = questionIndex+1
navigationController?.pushViewController(questionController, animated: true)
} else {
let controller = ResultsController()
navigationController?.pushViewController(controller, animated: true)
}
}
}
这个代码不仅可以工作,而且更容易看到发生了什么,而且代码行数更少,因为您不需要从视图控制器数组中展开可选索引;例如,numberOfRowsInSection
从7行变为1。
此外,在这种情况下,我看不出questionString
结构的answers
和Question
属性是可选的充分理由;一个问题必须有一个问题和一组答案。我可能也会将questionString
更改为question
- 将属性名称中的类型设置为多余。
tableView
的{{1}}属性是使用隐式展开的可选项的好地方 - 您知道会有tableview,因为您的代码所做的第一件事就是创建它。使用隐式展开的可选(QuestionController
而不是UITableView!
)将使您不必再继续打开它。