目次
概要
前回でAlamofireを使用してサーバーからデータを取得したが、エンコードされていてそのまま使えなかったりしていた。
今回は、取得したjsonデータを自分で作成したクラスに格納することをしていく。
これで実際の実装で使えるようになるるはずだ。
ソースコード
アプリ実行部分
import SwiftUI
@main
struct TestApp: App {
@UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
var body: some Scene {
WindowGroup {
CallZipCodeView()
}
}
}
JSONのデータを格納するクラス
import Foundation
struct ZipCodeResponseBase: Decodable {
var message: String?
var results: [Address]
var status: Int
struct Address: Decodable {
var address1: String
var address2: String
var address3: String
var kana1: String
var kana2: String
var kana3: String
var prefcode: String
var zipcode: String
}
}
View表示部分
import SwiftUI
struct CallZipCodeView: View {
@ObservedObject private var viewModel = CallZipCodeViewModel()
@FocusState private var focusState: Bool
var body: some View {
ZStack {
VStack {
zipcodeView()
resultTextField()
jsonTextView()
}
if viewModel.isShownProgressView {
progressView()
}
}
}
private func progressView() -> some View {
return ZStack {
Color.gray.opacity(0.2)
ProgressView()
.progressViewStyle(.circular)
.padding()
.tint(Color.white)
.background(Color.black)
.cornerRadius(8)
.scaleEffect(1.2)
}
.edgesIgnoringSafeArea(.all)
}
private func zipcodeView() -> some View {
return VStack {
Text("郵便番号を入力してください")
Text("(ハイフン不要)")
TextField("郵便番号", text: $viewModel.zipCode)
.padding(EdgeInsets(top: 10, leading: 20, bottom: 10, trailing: 20))
.textFieldStyle(.roundedBorder)
.focused($focusState)
.keyboardType(.numberPad)
Button {
focusState = false
viewModel.pushedSearchButton()
} label: {
Text("郵便番号取得")
}
}
.onTapGesture {
focusState = false
}
}
private func resultTextField() -> some View {
return VStack(alignment: .leading, spacing: 0) {
HStack(alignment: .lastTextBaseline) {
Text("〒")
.padding(EdgeInsets(top: 10, leading: 20, bottom: 0, trailing: 0))
TextField("郵便番号", text: $viewModel.zipText)
.textFieldStyle(.roundedBorder)
.disabled(true)
.frame(width: 120)
}
Text("住所")
.padding(EdgeInsets(top: 10, leading: 20, bottom: 0, trailing: 20))
TextField("住所", text: $viewModel.addressText)
.textFieldStyle(.roundedBorder)
.disabled(true)
.padding()
Text("住所カナ")
.padding(EdgeInsets(top: 10, leading: 20, bottom: 0, trailing: 20))
TextField("住所カナ", text: $viewModel.addressKana)
.textFieldStyle(.roundedBorder)
.disabled(true)
.padding()
}
}
private func jsonTextView() -> some View {
return VStack {
TextEditor(text: $viewModel.jsonData)
.disabled(true)
.padding()
}
}
}
処理部分
import Alamofire
import Foundation
class CallZipCodeViewModel: ObservableObject {
@Published var zipCode: String = ""
@Published var jsonData = ""
@Published var isShownProgressView = false
@Published var zipText: String = ""
@Published var addressText: String = ""
@Published var addressKana: String = ""
private let baseUrl = "https://zipcloud.ibsnet.co.jp/api/search"
func pushedSearchButton() {
isShownProgressView = true
jsonData = ""
zipText = ""
addressText = ""
addressKana = ""
let params: [String : String] = ["zipcode" : zipCode]
AF.request(baseUrl, method: .get, parameters: params).responseData { [weak self] response in
guard let self else {
return
}
self.isShownProgressView = false
guard let data = response.data else {
print("No Data")
return
}
do {
let decoder: JSONDecoder = JSONDecoder()
let zipCodeResponseBase: ZipCodeResponseBase = try decoder.decode(ZipCodeResponseBase.self, from: data)
guard let zipCodeData = zipCodeResponseBase.results.first else {
print("results transfer error")
return
}
self.jsonData = "\(zipCodeResponseBase)"
self.zipText = zipCodeData.zipcode
self.addressText = zipCodeData.address1 + zipCodeData.address2 + zipCodeData.address3
self.addressKana = zipCodeData.kana1 + zipCodeData.kana2 + zipCodeData.kana3
} catch {
print(error.localizedDescription)
}
}
}
}
デモ動画
詳細
レスポンスクラスの作成
ただデータを受け取っただけでは実装で利用するためのデータとしては使えない。
今回の例で使用した郵便番号検索APIでは以下のようなデータが返ってくる。
{
"message": null,
"results": [
{
"address1": "北海道",
"address2": "美唄市",
"address3": "上美唄町協和",
"kana1": "ホッカイドウ",
"kana2": "ビバイシ",
"kana3": "カミビバイチョウキョウワ",
"prefcode": "1",
"zipcode": "0790177"
}
],
"status": 200
}
jsonデータは上記のように「:」の左にあるキーの部分と「:」の右にある値の羅列になっている。
そして、「[ ]」で囲まれている箇所が配列部分になる。
構造は以下のようになっている。
レスポンスデータ
キー名(型名) | 内容 |
message(String?型) | メッセージ文字列 |
results(住所データ型(仮)) | データの中身 |
status(Int型) | 通信のステータスコード |
住所データ型(仮)
キー名(型名) | 内容 |
address1(String型) | 住所(都道府県) |
address2(String型) | 住所(市区町村) |
address3(String型) | 住所(町名) |
kana1(String型) | 住所フリガナ(都道府県) |
kana2(String型) | 住所フリガナ(市区町村) |
kana3(String型) | 住所フリガナ(町名) |
prefcode(String型) | 都道府県の番号 |
zipcode(String型) | 郵便番号 |
上記の表を元に、自作のクラスに格納するには以下のように作成する。
struct ZipCodeResponseBase: Decodable {
var message: String?
var results: [Address]
var status: Int
struct Address: Decodable {
var address1: String
var address2: String
var address3: String
var kana1: String
var kana2: String
var kana3: String
var prefcode: String
var zipcode: String
}
}
変数名はキー名と同じに、そして、型名もjsonのデータから推測する。基本的に、数字と数値の違いは「”」(ダブルクォーテーション)に囲まれているかどうかで判別する。
そして、今回の配列の内部のように、オリジナルの型がまた別にあれば新たに作成する必要がある。
加えて、jsonデータを格納するクラスはDecodableを継承しておく必要がある。
では、今度はレスポンス部分を見ていこう。
レスポンス処理部分でjsonファイルをカスタムクラスに格納する
jsonデータをカスタムクラスに格納する場合は以下のように実装する。
do {
let decoder: JSONDecoder = JSONDecoder()
let zipCodeResponseBase: ZipCodeResponseBase = try decoder.decode(ZipCodeResponseBase.self, from: data)
guard let zipCodeData = zipCodeResponseBase.results.first else {
print("results transfer error")
return
}
self.jsonData = "\(zipCodeResponseBase)"
self.zipText = zipCodeData.zipcode
self.addressText = zipCodeData.address1 + zipCodeData.address2 + zipCodeData.address3
self.addressKana = zipCodeData.kana1 + zipCodeData.kana2 + zipCodeData.kana3
} catch {
print(error.localizedDescription)
}
- JSONDecoderのインスタンスを作成
- JSONDecoderクラスのdecodeメソッドを使用する
第一引数にカスタムクラスの型を、第二引数にjsonデータを設定する。
今回、jsonのデータの中身はZipCodeResponseBaseクラスのresults部分のため、その部分だけ抽出する。
また、results部分は配列のため、先頭のデータを使用する。
あとは、取得したデータを元にデータを格納している。