[iOS] NFC

2 minute read

NFC (Near Field Communication)

Type

nfctype.png

Mode

NFC card emulation NFC reader/writer NFC peer-to-peer
NFC를 탑재한 기기가 기존의 비접촉식 카드와 같이 동작 NFC를 탑재한 기기가 RFID 태그 리더기로 동작 NFC 기기 간 데이터 송수신

HCE (Host Card Emulation)

  • 호스트 카드 에뮬레이션(Host card emulation, HCE)은 소프트웨어만을 사용하여 다양한 전자 식별자(접근, 수송, 은행업무)의 정확한 가상 표현을 제공하는 소프트웨어 구조

iOS NFC

NFC Type

NDEF (NFC Data Exchange Format)

  • https://smartits.tistory.com/206

    iso14443

  • ISO14443-4 type A / B tag with ISO7816 communication
  • MiFare technology tag (MIFARE Plus, UltraLight, DESFire)

    iso15693

  • ISO15693 tag

    iso18092

  • FeliCa tag

    pace

  • Password Authenticated Connection Establishment (PACE) with ISO7816 communication

    VAS (Value Added Service)

  • Apple

지원 모드

NFC card emulation NFC reader/writer NFC peer-to-peer
애플페이(월렛)에서만 지원 개발 가능하도록 지원 로드맵상 지원 예정 없음

SampleCode

iso7816 (T-Money)

  • Project Setting
    • Capability : Near Field Communication Tag Reading
    • Adds the Near Field Communication Tag Reader Session Formats Entitlement to the entitlements file
      <key>com.apple.developer.nfc.readersession.formats</key>
      <array>
          <string>TAG</string>
      </array>
    
    • add the NFCReaderUsageDescription key as a string item to the Info.plist file
      <key>NFCReaderUsageDescription</key>
      <string>NFC tag to read Tags Info into the application</string>
    
    • add the list of the application identifiers supported in your app (T-Money AID : D4100000030001)
      <key>com.apple.developer.nfc.readersession.iso7816.select-identifiers</key>
      <array>
          <string>D4100000030001</string>
      </array>
    
import CoreNFC

class NFCTagReader: NFCTagReaderSessionDelegate {
    var session: NFCReaderSession?

    // MARK: - BeginScanning
    func beginScanning(_ sender: Any) {
        session = NFCTagReaderSession(pollingOption: .iso14443, delegate: self)
        session?.alertMessage = "Hold your iPhone near the item to learn more about it."
        session?.begin()
    }

    // MARK: - NFCTagReaderSessionDelegate

    func tagReaderSessionDidBecomeActive(_ session: NFCTagReaderSession) {
    }

    func tagReaderSession(_ session: NFCTagReaderSession, didInvalidateWithError error: Error) {
        // Check the invalidation reason from the returned error.
        if let readerError = error as? NFCReaderError {
            // Show an alert when the invalidation reason is not because of a
            // successful read during a single-tag read session, or because the
            // user canceled a multiple-tag read session from the UI or
            // programmatically using the invalidate method call.
            if ((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)
                }
            }
        }

        // To read new tags, a new session instance is required.
        self.session = nil
    }

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

        if tags.count > 1 {
            // Restart polling in 500ms
            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
        }

        if case let NFCTag.iso7816(tag) = tags.first! {

            session.connect(to: tags.first!) { (error: Error?) in

                guard error == nil else {
                    session.alertMessage = "Application failure : \(String(describing: error))"
                    session.invalidate(errorMessage: "Application failure")
                    return
                }

                // Application Data Unit (APDU).
                let myAPDU = NFCISO7816APDU(data: "00a4040007d4100000030001".hexaData)!
                tag.sendCommand(apdu: myAPDU) { (response: Data, sw1: UInt8, sw2: UInt8, error: Error?) in

                    guard error == nil else {
                        session.alertMessage = "Application failure (\(String(describing: error)))"
                        session.invalidate(errorMessage: "Application failure (\(String(describing: error)))")
                        return
                    }

                    session.alertMessage = "\(response.hexEncodedString())"
                    session.invalidate()
                }
            }
        }
    }
}

extension StringProtocol {
    var hexaData: Data { .init(hexa) }
    var hexaBytes: [UInt8] { .init(hexa) }
    private var hexa: UnfoldSequence<UInt8, Index> {
        sequence(state: startIndex) { startIndex in
            guard startIndex < self.endIndex else { return nil }
            let endIndex = self.index(startIndex, offsetBy: 2, limitedBy: self.endIndex) ?? self.endIndex
            defer { startIndex = endIndex }
            return UInt8(self[startIndex..<endIndex], radix: 16)
        }
    }
}

extension Data {
    struct HexEncodingOptions: OptionSet {
        let rawValue: Int
        static let upperCase = HexEncodingOptions(rawValue: 1 << 0)
    }

    func hexEncodedString(options: HexEncodingOptions = []) -> String {
        let format = options.contains(.upperCase) ? "%02hhX" : "%02hhx"
        return self.map { String(format: format, $0) }.joined()
    }
}

NDEF

Tags: ,

Categories:

Updated:

Leave a comment