こんにちは、怠けるのが好きで SwiftUI が大好きな aBiteエンジニア、けわしです!
#TL;DR
- 交通系ICカード、Suica、PASMO、ICOCA の残高を読み取りましょう🚃
- Xcode 11 で導入された最新技術、SwiftUI と Swift Package Manager (SPM) を使って、爆速開発します🏇😝
- 私は30分でアプリ完成しました⏱
- WAON, nanaco, 楽天Edy の電子マネー や運転免許証の読み取りの練習にご活用ください💶💰
- iPhone で IC 使うには TRETJapanNFCReader が最高です! (@treastrain 氏開発のOSSライブラリ)
#完成するアプリ
Suica、PASMO、ICOCA タッチで、残高が見れます!
#Xcode プロジェクトの作成
Create a new Xcode project から iOS の Single View App を作成します。
User Interface は忘れず SwiftUI にしましょう!
プロジェクト設定 と Info.plist
プロジェクト設定で、capabilities を選択します。
NFC を追加します!
Info.plist に次の設定を記入してください。
Privacy - NFC Scan Usage Description に、何か文字列を与えるのはとても大事で、ないとアプリがクラッシュします!逆にクラッシュしたら、これで直ること、多いですね!
0003 は交通系ICカード用のFeliCa システムコードですね。これがないとタッチしても反応しません。
ファイルツリー
赤字のファイルを触ります!Swift Package Manager でライブラリインストール
あとは、指示に従い、インストール。
無事インストール完了すると、次のようになります。
#コーディング
##コード1: Suica読み取り部分
import Foundation
import TRETJapanNFCReader
final class UserData: NSObject, ObservableObject, FeliCaReaderSessionDelegate {
@Published var balance: Int? = nil
var reader: TransitICReader!
override init() {
super.init()
self.reader = TransitICReader(delegate: self)
self.reader.get(itemTypes: [.balance])
}
func feliCaReaderSession(didRead feliCaCard: FeliCaCard) {
let transitICCard = feliCaCard as! TransitICCard
DispatchQueue.main.async {
self.balance = transitICCard.data.balance! // カード残高
}
}
// ライブラリを使う上で、FelicaReaderSessionDelegate の要求する型に合わせる為、記述しております。
func japanNFCReaderSession(didInvalidateWithError error: Error) {
}
}
コード2: アプリ画面部分
import SwiftUI
struct ContentView: View {
@EnvironmentObject var userData: UserData
var body: some View {
VStack {
Text("FeliCa の残高")
if (self.userData.balance != nil) {
Text("¥\(String(self.userData.balance!))")
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
let userData = UserData()
userData.balance = 1500 // プレビュー用残高
return ContentView().environmentObject(userData)
}
}
コード3: 仕上げ
ContentView に UserData をくっつけます。
SceneDelegate.swift の23行目のコード
let contentView = ContentView()
を、次のように変更します。
let contentView = ContentView().environmentObject(UserData())
これだけで、アプリ起動時にスキャナが起動します。そして、読み取り成功後に、残高をアプリ画面(ContentView) に渡してくれます☺️
コード解説
アプリ画面部分:ContentView.swift
EnvironmentObject
@EnvironmentObject var userData: UserData
UserData 側の残高変更を監視する為、"@EnvironmentObject" をつけます。
残高表示
var body: some View {
// 縦に2つ並べる
VStack {
Text("FeliCa の残高")
// 残高情報がある場合に、残高を表示する
if (self.userData.balance != nil) {
Text("¥\(String(self.userData.balance!))")
}
}
}
プレビュー
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
let userData = UserData()
userData.balance = 1500 // プレビュー用残高
return ContentView().environmentObject(userData)
}
}
こうすることで、プレビューがでます。
プレビューの良いところは、要素を選択して右のペインでフォント色などを変更すると、コードが自動で変わるところですね😍
Suica読み取り部分:UserData.swift
スキャン開始
アプリ起動時に、SceneDelegate.swift
で UserData()
が呼ばれた時に読み取りを開始します。
override init() {
super.init()
self.reader = TransitICReader(delegate: self)
self.reader.get(itemTypes: [.balance])
}
Suica 残高読み取り
func feliCaReaderSession(didRead feliCaCard: FeliCaCard) {
let transitICCard = feliCaCard as! TransitICCard
DispatchQueue.main.async {
self.balance = transitICCard.data.balance! // カード残高
}
}
ポイントは、self.balance = transitICCard.data.balance!
を DispatchQueue.main.async {}
で囲うところですね。
これは、UserData と ContentView が別スレッドで走っています。これで囲うと、別スレッドのContentViewに残高が送信できます。
参考、おすすめ文献
iPhone で交通系IC(Suica、PASMO、ICOCA、…etc.)を読み取ってみよう!
treastrain/TRETJapanNFCReader 今回用いているOSSライブラリ。とてもよいライブラリです。