前提・実現したいこと
teratailのAPIを叩き、質問一覧をTableViewに表示させたいです(Alamofire
SwiftyJSON
を使用)。
MVPの練習をしたく、アーキテクチャはMVPを採用しています。
QuestionModel内でAPIを叩き、配列questionsに追加しています。
そして、処理結果をQuestionListViewPresenterへ渡し、NewArrivalQuestionListViewControllerでTableViewに表示させるといった流れです。
発生している問題・エラーメッセージ
データ自体はTableViewに表示されて問題ないのですが、TableViewを下に引っ張った時にUIRefreshControl
でデータ更新をしたいです。
現状だと下に引っ張ったときに下記エラーが出ています。
UIRefreshControl
のインスタンスには関数updateQuestions()
をaddTarget
で指定しています。
このupdateQuestions()
はNewArrivalQuestionListViewControllerのviewDidLoad
でも使用しています。
viewDidLoadの時には問題ないのに、なぜ下に引っ張った時にエラーになるのか疑問です。
QuestionListViewPresenterのfunc entity()
でエラーが発生していますが、データを格納している配列questions
を見るとデータが入っていないため、エラーになっているようです。
そのため、恐らくAlamofire
の非同期通信が影響しているのではと思っています。
しかし、QuestionModel内の関数fetchQuestions()
ではNotificationCenterを使用し、QuestionListViewPresenterへ通知を行っているため、その影響は無いはずです(実際にviewDidLoadで表示できているため)。
色々と考えてみましたが、原因が分からずお手上げの状況のため、ご教示いただけませんでしょうか?
補足ですが、fetchQuestions()
の中のprint("通知")
は下に引っ張った時に出力されませんでした。
(つまり通知する前にエラーになっている?)
エラー内容:Fatal error: Index out of range QuestionListViewPresenterのこの箇所で。 エラー発生箇所 : func entity(at indexPath: IndexPath) -> QuestionEntity { return questionModel.questions[indexPath.row] }
該当のソースコード
QuestionEntity
1struct QuestionEntity { 2 var id: Int 3 var title: String 4 var tags: [String] 5 var displayName: String 6 var photo: String 7 var created: String 8 var isAccepted: Bool 9 10 init(id: Int, title: String, tags: [String], displayName: String, photo: String, created: String, isAccepted: Bool) { 11 self.id = id 12 self.title = title 13 self.tags = tags 14 self.displayName = displayName 15 self.photo = photo 16 self.created = created 17 self.isAccepted = isAccepted 18 } 19}
QuestionModel
1class QuestionModel { 2 3 var questions: [QuestionEntity] = [] 4 var notificationName: Notification.Name { 5 return Notification.Name(rawValue: "questions") 6 } 7 8 func fetchQuestions() { 9 print("fetch") 10 Alamofire.request("https://teratail.com/api/v1/questions").responseJSON { response in 11 guard let object = response.result.value else { return } 12 let json = JSON(object) 13 json["questions"].forEach { (_, json) in 14 let id = json["id"].intValue 15 let title = json["title"].stringValue 16 let tags = json["tags"].arrayObject 17 let displayName = json["user"]["display_name"].stringValue 18 let photo = json["user"]["photo"].stringValue 19 let created = json["created"].stringValue 20 let isAccepted = json["isAccepted"].boolValue 21 self.questions.append(QuestionEntity(id: id, title: title, tags: tags as! [String], displayName: displayName, photo: photo, created: created, isAccepted: isAccepted)) 22 } 23 self.notify() 24 print("通知") 25 } 26 } 27 28 func refreshQuestions() { 29 questions = [] 30 print("refresh") 31 } 32 33 func addObserver(_ observer: Any, selector: Selector) { 34 NotificationCenter.default.addObserver(observer, selector: selector, name: notificationName, object: nil) 35 } 36 37 func removeObserver(_ observer: Any) { 38 NotificationCenter.default.removeObserver(observer) 39 } 40 41 func notify() { 42 NotificationCenter.default.post(name: notificationName, object: nil) 43 } 44} 45
QuestionListViewPresenter
1class QuestionListViewPresenter { 2 3 private weak var view: ListViewInterface! 4 5 var questionModel: QuestionModel! 6 7 var numberOfQuestions: Int { 8 return questionModel?.questions.count ?? 10 9 } 10 11 init(view: ListViewInterface) { 12 self.view = view 13 self.questionModel = QuestionModel() 14 self.questionModel.addObserver(self, selector: #selector(self.updated)) 15 } 16// 17// deinit { 18// questionModel.removeObserver(self) 19// } 20// 21 @objc func updateQuestions() { 22 questionModel.refreshQuestions() 23 questionModel.fetchQuestions() 24 print("presenter") 25 } 26 27 func entity(at indexPath: IndexPath) -> QuestionEntity { 28 return questionModel.questions[indexPath.row] //Fatal error: Index out of range 29 } 30 31 @objc func updated() { 32 view?.reloadData() 33 } 34} 35
NewArrivalQuestionListViewController
1protocol ListViewInterface: class { 2 func reloadData() 3} 4 5class NewArrivalQuestionListViewController: UIViewController, ListViewInterface { 6 7 @IBOutlet weak var newArrivalQuestionListTableView: UITableView! 8 9 var presenter: QuestionListViewPresenter! 10 11 var itemInfo: IndicatorInfo = "新着" 12 13 override func viewDidLoad() { 14 super.viewDidLoad() 15 initializePresenter() 16 initializeTableView() 17 newArrivalQuestionListTableView.refreshControl = UIRefreshControl() 18 newArrivalQuestionListTableView.refreshControl?.addTarget(self, action: #selector(self.updateQuestions), for: .valueChanged) 19 updateQuestions() 20 } 21 22 func initializeTableView() { 23 newArrivalQuestionListTableView.delegate = self 24 newArrivalQuestionListTableView.dataSource = self 25 newArrivalQuestionListTableView.register(UINib(nibName: "QuestionListTableViewCell", bundle: nil), forCellReuseIdentifier: "QuestionListTableViewCell") 26 } 27 28 func initializePresenter() { 29 presenter = QuestionListViewPresenter(view: self) 30 } 31 32 func reloadData() { 33 newArrivalQuestionListTableView.refreshControl?.endRefreshing() 34 newArrivalQuestionListTableView.reloadData() 35 print("リロード") 36 } 37 38 @objc func updateQuestions() { 39 presenter.updateQuestions() 40 newArrivalQuestionListTableView.refreshControl?.beginRefreshing() 41 } 42} 43 44extension NewArrivalQuestionListViewController: UITableViewDelegate, UITableViewDataSource { 45 func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 46 return presenter.numberOfQuestions 47 } 48 49 func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 50 let cell = newArrivalQuestionListTableView.dequeueReusableCell(withIdentifier: "QuestionListTableViewCell", for: indexPath) as! QuestionListTableViewCell 51 cell.setQuestion(entity: presenter.entity(at: indexPath)) 52 return cell 53 } 54 55 func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 56 newArrivalQuestionListTableView.deselectRow(at: indexPath, animated: true) 57 } 58}
参考記事
回答1件
あなたの回答
tips
プレビュー
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2020/02/09 13:27