Help us understand the problem. What is going on with this article?

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

こんにちは、怠けるのが好きで 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ライブラリ。とてもよいライブラリです。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした