Swift: приложение вылетает с ошибкой EXC_BAD_INSTRUCTION? - PullRequest
0 голосов
/ 26 апреля 2018

В этот момент в викторине приложение должно рассчитать итоговую оценку. В данный момент происходит сбой после выбора ответа на последний вопрос, в третьей строке появляется ошибка заголовка:

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)

}

}

Ответы [ 2 ]

0 голосов
/ 27 апреля 2018

Проблема вызвана тем, что вы используете позицию контроллера вида в массиве 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)
       }
    }
}

Мало того, что этот код работает, легче увидеть, что происходит, и меньше строк кода, потому что вам не нужно продолжать развертывание необязательного индекса из массива view controllers; например, numberOfRowsInSection идет от 7 строк до 1.

Кроме того, в этом случае я не вижу веской причины, по которой свойства questionString и answers структуры Question являются необязательными; Вопрос должен иметь вопрос и набор ответов. Я бы, вероятно, также изменил бы questionString на question - указание типа в имени свойства является излишним.

Свойство tableView вашего QuestionController является хорошим местом для использования неявно развернутого необязательного параметра. Вы знаете, что будет просмотр таблицы, потому что в первую очередь ваш код создает ее. Использование неявно развернутого необязательного (UITableView! вместо UITableView?) избавит вас от необходимости постоянно разворачивать его.

0 голосов
/ 26 апреля 2018

Это сбой, потому что question.selectedAnswerIndex это nil. Как правило, вы не должны использовать неявный оператор развертки (!), когда это возможно, поскольку это приведет к сбою вашего приложения, если необязательный параметр nil. Вместо этого разверните его в операторе if let и корректно обработайте ошибку, если она окажется nil.

Кажется, однако, что вы не ожидаете, что question.selectedAnswerIndex будет nil, поэтому я ожидаю, что у вас может быть логическая ошибка где-то еще в вашем коде.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...