NCMBでは公式SDKとしてSwift/Objective-C/Kotlin/Java/Unity/JavaScript SDKを用意しています。また、それ以外にもコミュニティSDKとして、非公式ながらFlutter/React Native/Google Apps Script/C#/Ruby/Python/PHPなど幅広い言語向けにSDKが開発されています。
今回は公式SDKの一つ、Swift SDKを使って地図検索アプリを作ってみます。前回は画面の仕様とSDKの初期化について解説しました。今回は位置情報をデータストアにインポートする流れを解説します。
完成版のコード
作成したデモアプリのコードはNCMBMania/swift_map_appにアップロードしてあります。
インポート画面について
インポート画面はContentViewのタブで読み込まれています。 ImportView
がそうです。
// インポート画面
ImportView()
.tabItem {
VStack {
Image(systemName: "gear")
Text("Import")
}
}.tag(2)
インポート画面の構築
まず ImportView
のStateについてです。文字列の配列が入る logs と データをインポート(または削除)するNCMBのデータストアクラス名を入れた _className があります。
struct ImportView: View {
@State private var logs: [String] = []
private var _className = "Station"
UIは次のようになります。インポート実行タンをタップすると _execute
が実行されます。また、logs が更新されると List を使ってログが表示されます。
var body: some View {
VStack(spacing: 16
) {
Text("駅一覧を読み込みます").padding()
// ボタンを押したら駅登録処理開始
Button(action: _execute, label: { Text("インポート実行")})
if logs.count > 0 {
// ログ表示用
Text("ログ")
List {
ForEach(Array(logs.enumerated()), id: \.element) { index, log in
Text(logs[index])
}
}
}
}
}
データのインポートについて
データをインポートする際には繰り返し処理できるように、まず既存データを削除しています。もし既存データがなかった場合でも(データストアのクラスがなくとも)エラーにはならず、空の配列が返ってくるだけです。
private func _execute() {
// まずデータを削除する
_removeStations()
// JSONファイルを読み込む
let stations = _loadStations()
// JSONファイルの内容をデータストアに登録する
registerDataStore(stations: stations)
}
_removeStations
は以下のようになります。NCMBQueryを使ってデータを取得し、各データ(NCMBObject)のdeleteメソッドを使ってデータを削除します。
// すでにデータストアにある駅名データを削除する処理(何度も繰り返せる用)
private func _removeStations() {
// 検索対象のデータストアのクラス(DBで言うテーブル名相当)
var query = NCMBQuery.getQuery(className: _className)
// 100件対象とする
query.limit = 100
// 検索実行
query.findInBackground(callback: { result in
// 処理が成功している場合
if case let .success(ary) = result {
// すべてのデータを削除
ary.forEach{ station in
// 削除処理(非同期)
station.deleteInBackground(callback: {_ in
// 特に処理なし
})
}
}
})
}
データを削除したら、山手線の各駅の位置情報が入ったJSONファイルを読み込みます。
// アセットにあるJSONファイルを読み込む
private func _loadStations() -> [Station] {
// インポートする
guard let url = Bundle.main.url(forResource: "yamanote", withExtension: "json") else {
fatalError("ファイルが見つからない")
}
// 読み込み
guard let data = try? Data(contentsOf: url) else {
fatalError("ファイル読み込みエラー")
}
// JSONデコード
let decoder = JSONDecoder()
guard let stations = try? decoder.decode([Station].self, from: data) else {
fatalError("JSON読み込みエラー")
}
return stations
}
この時利用している Station
という構造体は上記のようになっています。
struct Station: Codable {
var name: String
var latitude: Double
var longitude: Double
}
読み込んだデータをデータストアに登録する registerDataStore
関数は以下のようになります。JSONファイルのパラメータを渡して、Stationクラスにデータを作成します。NCMBGeoPointを使って位置情報を保存するのがポイントです。
// 駅名の配列をデータストアに登録する
private func registerDataStore(stations: [Station]) {
stations.forEach{ params in
// 登録するデータストアのクラス(DBで言うテーブル名相当)
let station = NCMBObject(className: _className)
// 駅名をセット
station["name"] = params.name
// 位置情報はNCMBGeoPointを利用する
let geo = NCMBGeoPoint(latitude: params.latitude, longitude: params.longitude)
// 位置情報をセット
station["geo"] = geo
// 保存処理(非同期処理)
station.saveInBackground(callback: { result in
// 成功していればログに追記
if case .success(_) = result {
let log = "保存しました -> \(params.name)"
logs.append(log)
}
})
}
}
全体のコード
ImportViewの内容は次のようになります。
struct Station: Codable {
var name: String
var latitude: Double
var longitude: Double
}
struct ImportView: View {
@State private var logs: [String] = []
private var _className = "Station"
// すでにデータストアにある駅名データを削除する処理(何度も繰り返せる用)
private func _removeStations() {
// 検索対象のデータストアのクラス(DBで言うテーブル名相当)
var query = NCMBQuery.getQuery(className: _className)
// 100件対象とする
query.limit = 100
// 検索実行
query.findInBackground(callback: { result in
// 処理が成功している場合
if case let .success(ary) = result {
// すべてのデータを削除
ary.forEach{ station in
// 削除処理(非同期)
station.deleteInBackground(callback: {_ in
// 特に処理なし
})
}
}
})
}
// アセットにあるJSONファイルを読み込む
private func _loadStations() -> [Station] {
// インポートする
guard let url = Bundle.main.url(forResource: "yamanote", withExtension: "json") else {
fatalError("ファイルが見つからない")
}
// 読み込み
guard let data = try? Data(contentsOf: url) else {
fatalError("ファイル読み込みエラー")
}
// JSONデコード
let decoder = JSONDecoder()
guard let stations = try? decoder.decode([Station].self, from: data) else {
fatalError("JSON読み込みエラー")
}
return stations
}
// 駅名の配列をデータストアに登録する
private func registerDataStore(stations: [Station]) {
stations.forEach{ params in
// 登録するデータストアのクラス(DBで言うテーブル名相当)
let station = NCMBObject(className: _className)
// 駅名をセット
station["name"] = params.name
// 位置情報はNCMBGeoPointを利用する
let geo = NCMBGeoPoint(latitude: params.latitude, longitude: params.longitude)
// 位置情報をセット
station["geo"] = geo
// 保存処理(非同期処理)
station.saveInBackground(callback: { result in
// 成功していればログに追記
if case .success(_) = result {
let log = "保存しました -> \(params.name)"
logs.append(log)
}
})
}
}
private func _execute() {
// まずデータを削除する
_removeStations()
// JSONファイルを読み込む
let stations = _loadStations()
// JSONファイルの内容をデータストアに登録する
registerDataStore(stations: stations)
}
var body: some View {
VStack(spacing: 16
) {
Text("駅一覧を読み込みます").padding()
// ボタンを押したら駅登録処理開始
Button(action: _execute, label: { Text("インポート実行")})
if logs.count > 0 {
// ログ表示用
Text("ログ")
List {
ForEach(Array(logs.enumerated()), id: \.element) { index, log in
Text(logs[index])
}
}
}
}
}
}
まとめ
今回はJSONファイルをインポートし、それを使ってNCMBObjectを作成しました。次回は地図コンポーネントと合わせて、位置情報検索を実装します。