🏣

SwiftUIで郵便番号検索APIを使う

2024/02/26に公開

読んでほしい人

  • SwiftUIを勉強している人
  • 郵便番号検索APIを使ってみたい人

補足情報

こちらのサイトのAPIを使って今回はアプリを作っていこうと思います。
https://zipcloud.ibsnet.co.jp/doc/api

記事の内容

SwiftUIでAPIを使ったアプリの学習をしていて、アウトプットのために色々作ってみたいなと思い入門レベルの郵便番号検索ができるアプリを作ってみました。

今回はこんな構造のJSONに合わせて、structを定義します。

{
	"message": null,
	"results": [
		{
			"address1": "東京都",
			"address2": "三鷹市",
			"address3": "",
			"kana1": "トウキョウト",
			"kana2": "ミタカシ",
			"kana3": "",
			"prefcode": "13",
			"zipcode": "1810000"
		}
	],
	"status": 200
}

モデルを作成する。構造体をネストして、JSONがresults: [{}]ネストしている構造に合わせて、2個作成する。

struct ZipCode: Codable {
    var message: String?
    var results: [Result]
    var status: Int
}

struct Result: Codable {
    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側に今回は、ロジックも書いてます。練習用のアプリということで、メソッドをクラスに書いて分けるのまではやってないです。検索Formがあって、郵便番号-なしの7桁ではなかったら、エラーメッセージを表示する正規表現を使っております。入力してボタンを押すと、検索をするメソッドが実行されます。

struct ContentView: View {
    @State private var zipCode = ""
    @State private var results = [Result]()
    @State private var errorMessage = ""
    
    var body: some View {
        VStack {
            TextField("郵便番号を入力してください", text: $zipCode)
                .textFieldStyle(RoundedBorderTextFieldStyle())
                .padding()
            
            Button(action: {
                if validate(zipCode: zipCode) {
                    fetchResults()
                }
            }) {
                Text("検索")
            }
            
            if !errorMessage.isEmpty {
                Text(errorMessage)
                    .foregroundColor(.red)
            }
            
            List(results, id: \.zipcode) { result in
                VStack(alignment: .leading) {
                    Text("郵便番号: \(result.zipcode + result.prefcode)")
                    Text("都道府県コード: \(result.prefcode)")
                    Text("住所カナ: \(result.kana1 + result.kana2 + result.kana3)")
                    Text("住所: \(result.address1 + result.address2 + result.address3)")
                }
            }
        }
    }
    
    func validate(zipCode: String) -> Bool {
        let zipCodePattern = "^[0-9]{3}-?[0-9]{4}$"
        let zipCodePredicate = NSPredicate(format: "SELF MATCHES %@", zipCodePattern)
        let isValid = zipCodePredicate.evaluate(with: zipCode)
        if !isValid {
            errorMessage = "郵便番号は-なしで7桁で入力してください!"
        } else {
            errorMessage = ""
        }
        return isValid
    }
    
    func fetchResults() {
        guard let url = URL(string: "https://zipcloud.ibsnet.co.jp/api/search?zipcode=\(zipCode)") else {
            return
        }
        URLSession.shared.dataTask(with: url) { data, response, error in
            if let data = data {
                do {
                    let decodedData = try JSONDecoder().decode(ZipCode.self, from: data)
                    DispatchQueue.main.async {
                        self.results = decodedData.results
                    }
                } catch {
                    print("Failed to decode JSON: \(error.localizedDescription)")
                }
            }
        }.resume()
    }
}

使うときは、東京都の三鷹市なら1810000で目黒区なら、1520000といった感じで入力します。こんな感じですね。もし入力が正しくないとエラーが表示されます!

こちらが全体のコード

import SwiftUI

struct ZipCode: Codable {
    var message: String?
    var results: [Result]
    var status: Int
}

struct Result: Codable {
    var address1: String
    var address2: String
    var address3: String
    var kana1: String
    var kana2: String
    var kana3: String
    var prefcode: String
    var zipcode: String
}

struct ContentView: View {
    @State private var zipCode = ""
    @State private var results = [Result]()
    @State private var errorMessage = ""
    
    var body: some View {
        VStack {
            TextField("郵便番号を入力してください", text: $zipCode)
                .textFieldStyle(RoundedBorderTextFieldStyle())
                .padding()
            
            Button(action: {
                if validate(zipCode: zipCode) {
                    fetchResults()
                }
            }) {
                Text("検索")
            }
            
            if !errorMessage.isEmpty {
                Text(errorMessage)
                    .foregroundColor(.red)
            }
            
            List(results, id: \.zipcode) { result in
                VStack(alignment: .leading) {
                    Text("郵便番号: \(result.zipcode + result.prefcode)")
                    Text("都道府県コード: \(result.prefcode)")
                    Text("住所カナ: \(result.kana1 + result.kana2 + result.kana3)")
                    Text("住所: \(result.address1 + result.address2 + result.address3)")
                }
            }
        }
    }
    
    func validate(zipCode: String) -> Bool {
        let zipCodePattern = "^[0-9]{3}-?[0-9]{4}$"
        let zipCodePredicate = NSPredicate(format: "SELF MATCHES %@", zipCodePattern)
        let isValid = zipCodePredicate.evaluate(with: zipCode)
        if !isValid {
            errorMessage = "郵便番号は-なしで7桁で入力してください!"
        } else {
            errorMessage = ""
        }
        return isValid
    }
    
    func fetchResults() {
        guard let url = URL(string: "https://zipcloud.ibsnet.co.jp/api/search?zipcode=\(zipCode)") else {
            return
        }
        URLSession.shared.dataTask(with: url) { data, response, error in
            if let data = data {
                do {
                    let decodedData = try JSONDecoder().decode(ZipCode.self, from: data)
                    DispatchQueue.main.async {
                        self.results = decodedData.results
                    }
                } catch {
                    print("Failed to decode JSON: \(error.localizedDescription)")
                }
            }
        }.resume()
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

最後に

今回は、郵便番号検索アプリを作ってAPIの使い方について学習してみました。ご興味あるかたは参考にして使ってみてください。

Codableについてですが、どんなものかというと

https://developer.apple.com/documentation/swift/codable

Codable
A type that can convert itself into and out of an external representation.

コード化可能
それ自身を外部表現に変換したり、外部表現から変換したりできる型。

typealias Codable = Decodable & Encodable

Discussion
Codable is a type alias for the Encodable and Decodable protocols. When you use Codable as a type or a generic constraint, it matches any type that conforms to both protocols.

ディスカッション
Codableは、EncodableおよびDecodableプロトコルの型エイリアスです。Codableを型またはジェネリック制約として使用すると、両方のプロトコルに適合するすべての型にマッチします。

今回は、モデルでJSONのデータを扱うときに、エンコードとデコードする機能を提供してくれているプロトコルのようです。構造体はこのプロトコル(お決まりごとに)に準拠(従うってこと)で、郵便番号検索APIのJSONのデータをテキストと数値に変換してくれます。

Discussion