マンガ読書管理アプリの設計メモ(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を使うと、色々なデータを使ったアプリが作れるようになるので、楽しいですね!
さて次は、最低限作成しないといけない画面である「読んだ漫画の一覧画面」をやりたいと思います。
また、今回の部分で、この辺りはもっと詳しく、という話があれば、コメントいただければと思います!
素敵なアプリやサービスが作れるようにひとりで開発を頑張っています。応援してくれると嬉しいです!