【WWDC19】Core NFC で FeliCa(Suica) を読み取るサンプル【iOS 13 以降】

本記事は2019年06月06日に Qiita に投稿した記事を移行したものです。

iOS 13 以降の Core NFC では FeliCa の読み取りに対応したことがわかったので、読み取るためのサンプルコードを載せます。

サンプルコード

//  Created by treastrain on 2019/06/06.  
//  Copyright © 2019 treastrain / Tanaka Ryoga. All rights reserved.  
//  

import UIKit  
import CoreNFC  

class ViewController: UIViewController, NFCTagReaderSessionDelegate {  

    var session: NFCTagReaderSession?  

    override func viewDidLoad() {  
        super.viewDidLoad()  
        // Do any additional setup after loading the view.  
    }  

    @IBAction func beginScanning(_ sender: UIButton) {  
        guard NFCTagReaderSession.readingAvailable else {  
            let alertController = UIAlertController(  
                title: "Scanning Not Supported",  
                message: "This device doesn't support tag scanning.",  
                preferredStyle: .alert  
            )  
            alertController.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))  
            self.present(alertController, animated: true, completion: nil)  
            return  
        }  

        self.session = NFCTagReaderSession(pollingOption: .iso18092, delegate: self)  
        self.session?.alertMessage = "Hold your iPhone near the item to learn more about it."  
        self.session?.begin()  
    }  

    func tagReaderSessionDidBecomeActive(_ session: NFCTagReaderSession) {  
        print("tagReaderSessionDidBecomeActive(_:)")  
    }  

    func tagReaderSession(_ session: NFCTagReaderSession, didInvalidateWithError error: Error) {  
        if let readerError = error as? NFCReaderError {  
            if (readerError.code != .readerSessionInvalidationErrorFirstNDEFTagRead)  
                && (readerError.code != .readerSessionInvalidationErrorUserCanceled) {  
                let alertController = UIAlertController(  
                    title: "Session Invalidated",  
                    message: error.localizedDescription,  
                    preferredStyle: .alert  
                )  
                alertController.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))  
                DispatchQueue.main.async {  
                    self.present(alertController, animated: true, completion: nil)  
                }  
            }  
        }  

        self.session = nil  
    }  

    func tagReaderSession(_ session: NFCTagReaderSession, didDetect tags: [NFCTag]) {  
        print("tagReaderSession(_:didDetect:)")  

        if tags.count > 1 {  
            let retryInterval = DispatchTimeInterval.milliseconds(500)  
            session.alertMessage = "More than 1 tag is detected, please remove all tags and try again."  
            DispatchQueue.global().asyncAfter(deadline: .now() + retryInterval, execute: {  
                session.restartPolling()  
            })  
            return  
        }  

        let tag = tags.first!  

        session.connect(to: tag) { (error) in  
            if nil != error {  
                session.invalidate(errorMessage: "Connection error. Please try again.")  
                return  
            }  

            guard case .feliCa(let feliCaTag) = tag else {  
                let retryInterval = DispatchTimeInterval.milliseconds(500)  
                session.alertMessage = "A tag that is not FeliCa is detected, please try again with tag FeliCa."  
                DispatchQueue.global().asyncAfter(deadline: .now() + retryInterval, execute: {  
                    session.restartPolling()  
                })  
                return  
            }  

            print(feliCaTag)  

            let idm = feliCaTag.currentIDm.map { String(format: "%.2hhx", $0) }.joined()  
            let systemCode = feliCaTag.currentSystemCode.map { String(format: "%.2hhx", $0) }.joined()  

            print("IDm: \(idm)")  
            print("System Code: \(systemCode)")  

            session.alertMessage = "Read success!\nIDm: \(idm)\nSystem Code: \(systemCode)"  
            session.invalidate()  
        }  
    }  


}  

設定事項

Core NFC を使うにあたって、コードだけでなく Xcode プロジェクトの設定もいくつか必要です。
特に、読み取りたい FeliCa のシステムコードをあらかじめ全て指定しておく必要があります。
こちらの記事を参照してください。

動作確認

FeliCa カードを読み取ることはもちろん、Apple Watch の Suica であっても(エミュレートカード)読み取ることができます。

サンプルプロジェクト

このコードが使用されているサンプルプロジェクトを GitHub にて公開しております。
treastrain/ios13-felica-reader: Sample project to read FeliCa on iOS 13 and later - GitHub