1

在测验的这一点上,应用程序应该计算最终分数。目前,它在选择最后一个问题的答案后崩溃,标题错误出现在第三行:

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)

}

}
4

2 回答 2

2

它崩溃了,因为question.selectedAnswerIndexis nil。通常,您不应该尽可能使用隐式展开运算符 ( !),因为如果可选的是 ,它会使您的应用程序崩溃nil。相反,在一个if let语句中解开它并优雅地处理错误,如果它恰好是nil.

似乎您并不希望question.selectedAnswerIndex如此,nil所以我希望您的代码中的其他地方可能会出现逻辑错误。

于 2018-04-26T18:27:55.890 回答
1

该问题是由于您使用视图控制器在导航控制器viewControllers数组中的位置来确定问题索引引起的。“开始”视图控制器位于位置 0,而您的第一个“问题”视图控制器位于位置 1。

这意味着您永远不会问关于用户的食物偏好的问题“0”。

然后,当您遍历问题并强制展开时selectedAnswerIndex,您会崩溃。

一般来说,除非:

  1. 你知道价值不是nil
  2. 反正你也无能为力

第二点是不要太聪明。您可以将一个简单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 行。

此外,在这种情况下,我看不出结构的questionStringandanswers属性Question是可选的充分理由;一个问题必须有一个问题和一组答案。我可能还会更改questionString为只是question- 将类型放入属性名称是多余的。

您的tableView属性QuestionController是使用隐式展开的可选选项的好地方 - 您知道将会有 tableview,因为您的代码所做的第一件事就是创建它。使用隐式展开的可选(UITableView!而不是UITableView?)将使您不必不断地展开它。

于 2018-04-27T02:56:15.993 回答