前提・実現したいこと
iOSアプリを作成しています。
ライブハウスの名前を検索して地図上に周辺のコインロッカーを表示させるアプリです。
場所の情報(検索ワード、経度、緯度、詳細)のデータベースを仮想サーバに作り、
サーバー側にPHPファイルを起いて自作APIを作り、JSON形式で出力するようにしています。
発生している問題・エラーメッセージ
swift側でJSONのパースが上手くできないのか、それとも構造体への格納ができないのか
do try catch エラーハンドリングで必ずエラーをキャッチしてしまいます。
http://192.168.33.10:8000/api.php/?livehouse=%E6%9C%AC%E5%85%AB%E5%B9%A1
2019-02-21 17:41:49.762064+0900 WhereistheCoinLocker[10697:390643] TIC Read Status [1:0x600000ae1380]: 1:57
php output: Optional("[{\"latitude\":35.720825,\"longitude\":139.927428,\"keyword\":\"JR本八幡駅改札付近北口方面\"},{\"latitude\":35.7211,\"longitude\":139.927267,\"keyword\":\"都営新宿線本八幡駅 地下1階 駅長事務室の左右\"},略 検索結果が続きます]")
エラーがでました
該当のソースコード
//jsonの中身を受け取るデータ構造
struct Coinlocker: Codable {
let latitude : Double?
let longitude : Double?
let keyword : String?
}
//jsonのデータ構造
struct ResultJson: Codable {
//複数要素
let item:[Coinlocker]?
}
//ロッカーを検索するためPHPにリクエストするメソッド
func searchLocker (keyword: String) {
//検索キーワードをURLエンコードする
guard let keyword_encode = keyword.addingPercentEncoding(withAllowedCharacters: sNSCharacterSet.urlQueryAllowed) else {
return
}
//リクエストURLの組み立て
guard let req_url = URL(string: "http://192.168.33.10:8000/api.php/?livehouse=\(keyword_encode)") else {
return
}
print(req_url)
//リクエストに必要な情報を生成
let req = URLRequest(url: req_url)
//データ転送を管理するためのセッションを生成
let session = URLSession(configuration: .default, delegate: nil
, delegateQueue: OperationQueue.main)
//リクエストをタスクとして登録
let task = session.dataTask(with: req, completionHandler: {
(data , response , error) in
//セッションを終了
session.finishTasksAndInvalidate()
//do try catch エラーハンドリング
do {
//data にちゃんと検索結果が入っているらしい
let phpOutput = String(data: data!, encoding: .utf8)
print("php output: \(String(describing: phpOutput))")
//JsonDecoderのインスタンス取得
let decoder = JSONDecoder()
//受け取ったJSONデータをパース(解析)して格納
let json = try decoder.decode(ResultJson.self, from: data!)
print(json)
} catch {
//エラー処理
print("エラーがでました")
}
})
//ダウンロード開始
task.resume()
}
<?php
if(isset($_GET['livehouse'])) {
$livehouse = htmlspecialchars($_GET['livehouse']);
define("USERNAME","xxxxx");
define("PASSWORD", "xxxxxx");
try{
$dbh = new PDO("mysql:host=192.168.33.10;dbname=coinlockerdb;
charset=utf8", USERNAME, PASSWORD);
$dbh-> setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$dbh-> setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$stmt = $dbh->prepare("select latitude, longitude, keyword from coinlockers where livehouse like '%$livehouse%'");
$stmt-> setFetchMode(PDO::FETCH_ASSOC);
$stmt-> execute();
$rows = array();
while($row = $stmt->fetch(PDO::FETCH_ASSOC)){
$rows[] = $row;
}
# 接続成功ならjson形式で吐き出す
header('Content-Type: application/json; charset=UTF-8');
echo $json = json_encode($rows, JSON_UNESCAPED_UNICODE);
return $json;
} catch (PDOException $e){
echo "JSON吐き出し失敗";
echo $e->getMessage();
}
$dbh =null;
}else{
echo "失敗";
}
試したこと
swift側の構造体の変数名とデータベースの各項目のスペルが間違っていないか確認しました。
デバッグエリアに出ている TIC Read Status [1:0x600000ae1380]: 1:57についても検索してみましたがこれはエラーではないのでしょうか。調べてもよくわかりませんでした。
初心者でどこがおかしいのか、何を調べていいのかもわからない状態です。
補足情報(FW/ツールのバージョンなど)
データベースのテーブルの情報は下記の通りです。
+-----------+------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-----------+------------------+------+-----+---------+----------------+
| id | int(10) unsigned | NO | PRI | NULL | auto_increment |
| latitude | double | NO | UNI | NULL | |
| longitude | double | NO | UNI | NULL | |
| livehouse | varchar(50) | NO | | NULL | |
| keyword | text | NO | | NULL | |
+-----------+------------------+------+-----+---------+----------------+
参考にしたものはこちらです。(たった2日でマスターできるiPhoneアプリ開発集中講座 Xcode 10 Swift 4.2対応 より)
//Jsonのitem内のデータ構造
struct ItemJson: Codable {
//お菓子の名前
let name: String?
//メーカー
let maker: String?
//掲載URL
let url: URL?
//画像のURL
let image: URL?
}
//JSONのデータ構造
struct ResultJson: Codable {
//複数要素
let item:[ItemJson]?
}
//searchokashiメソッド
func searchOkashi (keyword: String) {
//お菓子の検索キーワードをURLエンコードする
guard let keyword_encode = keyword.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) else {
return
}
//リクエストURLの組み立て
guard let req_url = URL(string:
"http://www.sysbird.jp/toriko/api/?apikey=guest&format=json&keyword=\(keyword_encode)&max=10&prder=r") else {
return
}
print(req_url)
//リクエストに必要な情報を生成
let req = URLRequest(url: req_url)
//データ転送を管理するためのセッションを生成
let session = URLSession(configuration: .default, delegate: nil, delegateQueue: OperationQueue.main)
//リクエストをタスクとして登録
let task = session.dataTask(with: req, completionHandler: {
(data, response, error) in
//セッション終了
session.finishTasksAndInvalidate()
//do try catcyエラーハンドリング
do{
//JSONDecoderのインスタンス取得
let decoder = JSONDecoder()
//受け取ったJSONデータをパース(解析)して格納
let json = try decoder.decode(ResultJson.self, from:data!)
//お菓子の情報が取得できているか確認
if let items = json.item {
//お菓子のリストを初期化
self.okashiList.removeAll()
//取得したお菓子の数だけ処理
for item in items {
//お菓子の名前、メーカ名、掲載URL,画像URLをアンラップ
if let name = item.name, let maker = item.maker, let link = item.url, let image = item.image {
//1つのお菓子をタプルでまとめて管理
let okashi = (name, maker, link, image)
//お菓子の配列へ追加
self.okashiList.append(okashi)
}
}
//TableView を更新する
self.tableView.reloadData()
if let okashidbg = self.okashiList.first {
print ("-----------------")
print ("okashiList[0] = \(okashidbg)")
}
}
} catch {
//エラー処理
print("エラーが出ました")
}
})
//ダウンロード開始
task.resume()
}
//Cellに値を設定するdatasourceメソッド。必ず記述。
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
//お菓子リストの総数
return okashiList.count
}
//Cellに値を設定するdatasourceメソッド。必ず記述。
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
//今回表示を行う、Cellオブジェクト(1行)を取得
let cell = tableView.dequeueReusableCell(withIdentifier: "okashiCell", for: indexPath)
//お菓子のタイトル設定
cell.textLabel?.text = okashiList[indexPath.row].name
//お菓子の画像を取得
if let imageData = try? Data(contentsOf: okashiList[indexPath.row].image) {
//正常に取得できた場合は、UIImageで画像オブジェクトを生成してCellにお菓子画像を設定
cell.imageView?.image = UIImage(data: imageData)
}
//設定済みのCellオブジェクトを画像に反映
return cell
}
//Cellが洗濯された際に呼び出されるdelegateメソッド
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// ハイライト解除
tableView.deselectRow(at: indexPath, animated: true)
//SFSafariViewを開く
let safariViewController = SFSafariViewController(url: okashiList[indexPath.row].link)
//dekegate の通知先を自分自身
safariViewController.delegate = self
//SafariViewが開かれる
present(safariViewController, animated: true, completion: nil)
}
// SafariViewが閉じられた時によばれるdelegateメソッド
func safariViewControllerDidFinish(_ controller: SFSafariViewController) {
// SafariViewを閉じる
dismiss(animated: true, completion: nil)
}
-
気になる質問をクリップする
クリップした質問は、後からいつでもマイページで確認できます。
またクリップした質問に回答があった際、通知やメールを受け取ることができます。
クリップを取り消します
-
良い質問の評価を上げる
以下のような質問は評価を上げましょう
- 質問内容が明確
- 自分も答えを知りたい
- 質問者以外のユーザにも役立つ
評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。
質問の評価を上げたことを取り消します
-
評価を下げられる数の上限に達しました
評価を下げることができません
- 1日5回まで評価を下げられます
- 1日に1ユーザに対して2回まで評価を下げられます
質問の評価を下げる
teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。
- プログラミングに関係のない質問
- やってほしいことだけを記載した丸投げの質問
- 問題・課題が含まれていない質問
- 意図的に内容が抹消された質問
- 過去に投稿した質問と同じ内容の質問
- 広告と受け取られるような投稿
評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。
質問の評価を下げたことを取り消します
この機能は開放されていません
評価を下げる条件を満たしてません
質問の評価を下げる機能の利用条件
この機能を利用するためには、以下の事項を行う必要があります。
- 質問回答など一定の行動
-
メールアドレスの認証
メールアドレスの認証
-
質問評価に関するヘルプページの閲覧
質問評価に関するヘルプページの閲覧
checkベストアンサー
+1
そのコードだと
JSONは、
{
"item": [{
"latitude": 35.720825,
"longitude": 139.927428,
"keyword": "JR本八幡駅改札付近北口方面"
}, {
"latitude": 35.7211,
"longitude": 139.927267,
"keyword": "都営新宿線本八幡駅 地下1階 駅長事務室の左右"
}]
}
になります。
そのJSONだと
コードは、
//let json = try decoder.decode(ResultJson.self, from: data!)
let json = try decoder.decode([Coinlocker].self, from: data!)
になります。
オマケ
エラー表示しましょう。キッカケくらいは掴めるかも知れません。
//print("エラーがでました")
print(error)
余談
ライブハウスだからCoin Rockerとか...
投稿
-
回答の評価を上げる
以下のような回答は評価を上げましょう
- 正しい回答
- わかりやすい回答
- ためになる回答
評価が高い回答ほどページの上位に表示されます。
-
回答の評価を下げる
下記のような回答は推奨されていません。
- 間違っている回答
- 質問の回答になっていない投稿
- スパムや攻撃的な表現を用いた投稿
評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。
15分調べてもわからないことは、teratailで質問しよう!
- ただいまの回答率 88.33%
- 質問をまとめることで、思考を整理して素早く解決
- テンプレート機能で、簡単に質問をまとめられる
質問への追記・修正、ベストアンサー選択の依頼
t_obara
2019/02/21 18:25
catchする際に、catch let error { print(error); } として、エラー原因を表示してみましょう。また、参考にしたものがあるのであれば、それを提示してください。参考にしたものと違う部分があるはずなので、その違いを確認してみましょう。
FKM
2019/02/21 18:40 編集
Swiftは知らないので他の有識者に委ねるところですが、PHPを使ったjson取得なら、
グーグルのConsole > ネットワークでjsonの出力データ確認ができます。
そこを確認したら、どんな値が吐き出されているか確認するといいでしょう。
きちんとjson形式のフォーマットになっていなかったら、エラーを吐くはずです。
Tajiko
2019/02/21 18:45
ありがとうございます。
早速catch let error { print(error); }としてみましたら、下記エラーが出ました。
typeMismatch(Swift.Dictionary<Swift.String, Any>, Swift.DecodingError.Context(codingPath: [], debugDescription: "Expected to decode Dictionary<String, Any> but found an array instead.", underlyingError: nil))
構造体の中のl keyword が String型 ではおかしいということでしょうか。
また、参考にしたものは「たった2日でマスターできるiPhoneアプリ開発集中講座 Xcode 10 Swift 4.2対応」という書籍です。質問に追加いたします。
Tajiko
2019/02/21 19:12
FKMさま
ありがとうございます。
ご指摘の通り確認しましたところ、JSONの形式は正しいようでした。
FKM
2019/02/21 19:27 編集
あと、PHPで少し変な文章があったので修正しました。そして自分も勘違いがあったんですが
catch(PDO::Exception $e) は名の通り、PDOの接続エラーしか返してくれません。ですので、
json_encode()の不備を調べるためには二重にtry-catchが必要ですね
try{
$data = json_encode($rows, JSON_UNESCAPED_UNICODE);
}catch( Exception $err ){
$data = $err -> getMessage();
}
echo $data;
//return $json; いらない