見出し画像

マンガ読書管理アプリの設計メモ(16)

今回は、楽天APIを使って漫画情報を取得するところを書きたいと思います。

1.StoryboardでUI部品をポチポチ配置する。完成はこんな感じ。


2.CodableでAPIデータを受け取るデータモデルを作成する。

楽天APIからJSON型でデータをもらってくるので、それを受け取るデータモデルをCodableで定義していきます。構造と物理名、型はしっかり合わせることが大事です。受け取る全データを定義する必要はなく、必要なものだけ定義すれば良いです。

struct SearchBookList : Codable{
   var count: Int
   var Items: [ItemDic]?
}

struct ItemDic : Codable{
   var Item : ItemInfo?
}

struct ItemInfo : Codable{
   var title:String = ""
   var author:String = ""
   var publisherName:String = ""
   var isbn:String = ""
   var itemCaption:String = ""
   var itemPrice:Int = 0
   var largeImageUrl : String = ""
   var affiliateUrl : String = ""
}


3.SearchBarをdelegateした上で、検索時にAPIを実行させる

通信の細かなところは実はあまり理解できてませんが、色々調べて以下のような実装を行なっています。
なお、JSONDecoderというものを使って取得したデータを「2.」で定義したデータ群に自動的にセットしてくれます。(便利!

// 検索ボタン押下時
   func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
       // アンラップ時
       guard let inputText = self.searchBar.text else{ return }
       
       // 1文字の場合は処理しない。楽天APIの仕様
       if inputText.count < 2 {
           ALRT.create(.alert, title: "2文字以上入れて検索してください").addOK().show()
           return
       }
       
       if (inputText.contains(" ") || inputText.contains(" ")){
           ALRT.create(.alert, title: "スペースは除外して検索してください").addOK().show()
           return
       }
       
       // エスケープ文字を行う。
       guard let escapeInputText = inputText.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryAllowed) else{
           return
       }
       
       // 検索するためのキーボードを閉じる
       self.searchBar.endEditing(true)
       
       // rakutenAPIで本を取得
       let listUrl = "https://app.rakuten.co.jp/services/api/BooksBook/Search/20170404?format=json&XXXX"
       guard let url = URL(string: listUrl) else { return }
       let request = URLRequest(url: url)
       let session = URLSession.shared
       
       let task = session.dataTask(with: request) { (data, response, error) in
           if error != nil {
               ALRT.create(.alert, title: "通信にエラーが発生しました。再度やり直してください。").addOK().show()
               print(error!.localizedDescription)
               return
           }
           
           guard let data = data, let response = response as? HTTPURLResponse else {
               ALRT.create(.alert, title: "通信にエラーが発生しました。再度やり直してください。").addOK().show()
               print("no data or no response")
               return
           }
           
           // JSONデータの解析を行う
           do {
               // 検索結果リストデータに登録
               self.searchBookList = try! JSONDecoder().decode(SearchBookList.self, from:data)
               
               // 取得が終わったらテーブルをリロードする
               DispatchQueue.main.sync {
                   
                   if self.searchBookList.count > 0{
                       self.searchMessage = ""
                   }
                   else{
                       self.searchMessage = "すみません。\n投稿するための本の情報が\n見つかりませんでした。\n検索ワードを変えて再度お試しください。"
                   }
                   self.tableView.reloadData()
               }
               
           }catch let error{
               ALRT.create(.alert, title: "検索にエラーが発生しました。再度やり直してください。").addOK().show()
           }
       }
       // 通信開始
       task.resume()
   }


4.取得したデータをテーブルにセットする。

あとは、Cellにセットしていくだけですが、APIには画像リンクのURLが取得されるので、URLを元に画像データを取得する処理を追加しています。たまに画像データが取得できないことがあるので、フリーのNoImage画像を表示させるようにしています。

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
       // #warning Incomplete implementation, return the number of rows
       return self.searchBookList.Items?.count ?? 0
   }
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
       
       guard let cell = tableView.dequeueReusableCell(withIdentifier: "searchBookCell") as? SearchBookTableViewCell else{
           return UITableViewCell()
       }
       
       // 取得したデータを表示
       let searchBookData = self.searchBookList.Items![indexPath.row]
       cell.titleLabel.text = searchBookData.Item!.title
       cell.authorPublisherLabel.text = searchBookData.Item!.author + "/" + searchBookData.Item!.publisherName
       
       // 取得した画像URLからデータを再取得
       if let urlImage = searchBookData.Item?.largeImageUrl{
           let url = URL(string: urlImage)
           let request = URLRequest(url:url!)
           let session = URLSession.shared
           let task = session.dataTask(with: request) { (data, response, error) in
               if error != nil {
                   // エラーが起きたらUIImageはnoimageを表示
                   cell.bookImageView.image = UIImage(named: "noimage")
                   print(error!.localizedDescription)
               }
               
               guard let data = data, let response = response as? HTTPURLResponse else {
                   // エラーが起きたらUIImageはnoimageを表示
                   cell.bookImageView.image = UIImage(named: "noimage")
                   print("no data or no response")
                   return
               }
               
               guard let image = UIImage(data:data) else {
                   // エラーが起きたらUIImageはnoimageを表示
                   cell.bookImageView.image = UIImage(named: "noimage")
                   return
               }
               DispatchQueue.main.sync{
                   cell.bookImageView.image = image
               }
           }
           task.resume()
       }
       else{
           // 画像のURLがなければnoimageを表示
           cell.bookImageView.image = UIImage(named: "noimage")
       }
       
       // セルを返す
       return cell
       
   }
​


5.セルをタップすると、取得したデータを登録画面へ引き渡し

        override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
           
           // アンラップ処理
            guard let identifier = segue.identifier else {
                return
            }
           
           // セルタップの処理
           if(identifier == "toSearchedReadedBook"){
               // モーダル画面の生成
               let next = segue.destination as! InputReadedViewController
               
               // ボタン位置によって、どのデータを引き継ぐか決定する。
               let cell = sender as! SearchBookTableViewCell
               if let row = tableView.indexPath(for: cell)?.row{
                   next.searchedFlag = true
                   next.searchBook = self.searchBookList.Items![row].Item!
                   next.searchBookImage = cell.bookImageView.image!
               }
           }
       }


これを実装するにあたっては、こちらのサイトを参考にしました。
通信周りは初心者向けの書籍を元に作成したが、もっと良いサイトがどこかにあるはず・・。

公開されているWebAPIを使うと、色々なデータを使ったアプリが作れるようになるので、楽しいですね!


さて次は、最低限作成しないといけない画面である「読んだ漫画の一覧画面」をやりたいと思います。

また、今回の部分で、この辺りはもっと詳しく、という話があれば、コメントいただければと思います!

素敵なアプリやサービスが作れるようにひとりで開発を頑張っています。応援してくれると嬉しいです!