20
21

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Core NFCAdvent Calendar 2019

Day 16

30分で爆速開発 交通系ICリーダー‬ 【iOS, SwiftUI】

Last updated at Posted at 2019-12-14

こんにちは、怠けるのが好きで SwiftUI が大好きな aBiteエンジニア、けわしです!

#TL;DR

  • 交通系ICカード、Suica、PASMO、ICOCA の残高を読み取りましょう🚃
  • Xcode 11 で導入された最新技術、SwiftUISwift Package Manager (SPM) を使って、爆速開発します🏇😝
  • 私は30分でアプリ完成しました⏱
  • WAON, nanaco, 楽天Edy の電子マネー運転免許証の読み取りの練習にご活用ください💶💰
  • iPhone で IC 使うには TRETJapanNFCReader が最高です! (@treastrain 氏開発のOSSライブラリ)

#完成するアプリ
Suica、PASMO、ICOCA タッチで、残高が見れます!
IMG_02589DDD55D5-1.jpegScreenshot 2019-12-15 at 3.03.26.png

#Xcode プロジェクトの作成
Create a new Xcode project から iOS の Single View App を作成します。
User Interface は忘れず SwiftUI にしましょう!
Screenshot 2019-12-15 at 3.08.44.png

プロジェクト設定 と Info.plist

プロジェクト設定で、capabilities を選択します。
Screenshot 2019-12-15 at 2.20.37.png
NFC を追加します!
Screenshot 2019-12-15 at 2.22.47.png
Info.plist に次の設定を記入してください。
Screenshot 2019-12-15 at 3.12.23.png
Privacy - NFC Scan Usage Description に、何か文字列を与えるのはとても大事で、ないとアプリがクラッシュします!逆にクラッシュしたら、これで直ること、多いですね!
0003 は交通系ICカード用のFeliCa システムコードですね。これがないとタッチしても反応しません。

ファイルツリー

Screenshot 2019-12-15 at 3.19.55.png 赤字のファイルを触ります!

Swift Package Manager でライブラリインストール

Swift Package Manager で開き
Screenshot 2019-12-15 at 2.28.28.png

TRETJapanNFCReader を検索します。
Screenshot 2019-12-15 at 2.29.15.png

あとは、指示に従い、インストール。
無事インストール完了すると、次のようになります。
Screenshot 2019-12-15 at 3.28.16.png

#コーディング
##コード1: Suica読み取り部分

UserData.swift
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: アプリ画面部分

ContentView.swift
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行目のコード

SceneDelegate.swift
let contentView = ContentView()

を、次のように変更します。

SceneDelegate.swift
let contentView = ContentView().environmentObject(UserData())

これだけで、アプリ起動時にスキャナが起動します。そして、読み取り成功後に、残高をアプリ画面(ContentView) に渡してくれます☺️

コード解説

アプリ画面部分:ContentView.swift

EnvironmentObject

@EnvironmentObject var userData: UserData

UserData 側の残高変更を監視する為、"@EnvironmentObject" をつけます。

残高表示 Screenshot 2019-12-15 at 3.47.50.png

ContentView.swift
var body: some View {
    // 縦に2つ並べる
    VStack {
       Text("FeliCa の残高")
       // 残高情報がある場合に、残高を表示する
       if (self.userData.balance != nil) {
            Text(\(String(self.userData.balance!))")
       }
    }
}

プレビュー

ContentView.swift
struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        let userData = UserData()
        userData.balance = 1500 // プレビュー用残高
        return ContentView().environmentObject(userData)
    }
}

こうすることで、プレビューがでます。
Screenshot 2019-12-15 at 3.50.56.png
プレビューの良いところは、要素を選択して右のペインでフォント色などを変更すると、コードが自動で変わるところですね😍
Screenshot 2019-12-15 at 3.53.34.png

Suica読み取り部分:UserData.swift

スキャン開始

アプリ起動時に、SceneDelegate.swiftUserData() が呼ばれた時に読み取りを開始します。

UserData.swift
    override init() {
        super.init()
        self.reader = TransitICReader(delegate: self)
        self.reader.get(itemTypes: [.balance])
    }

Suica 残高読み取り

UserData.swift
    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ライブラリ。とてもよいライブラリです。

20
21
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
20
21

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?