반응형
/*
See the LICENSE.txt file for this sample’s licensing information.
Abstract:
The view controller that scans and displays NDEF messages.
*/
import UIKit
import CoreNFC
/// - Tag: MessagesTableViewController
class MessagesTableViewController: UITableViewController, NFCNDEFReaderSessionDelegate, NFCTagReaderSessionDelegate {
// MARK: - Properties
let reuseIdentifier = "reuseIdentifier"
var detectedMessages = [NFCNDEFMessage]()
var session: NFCNDEFReaderSession?
var tagSession: NFCTagReaderSession?
var detectedTagInfo = [String]() // 일반 태그 정보 저장
// MARK: - View Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
// 왼쪽에 "카드 스캔" 버튼 추가
let cardScanButton = UIBarButtonItem(title: "카드 스캔", style: .plain, target: self, action: #selector(beginGeneralTagScanning(_:)))
navigationItem.leftBarButtonItem = cardScanButton
// 오른쪽 "Scan" 버튼은 Storyboard에 이미 있음
}
// MARK: - Actions
/// - Tag: beginScanning
@IBAction func beginScanning(_ sender: Any) {
guard NFCNDEFReaderSession.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
}
session = NFCNDEFReaderSession(delegate: self, queue: nil, invalidateAfterFirstRead: false)
session?.alertMessage = "Hold your iPhone near the item to learn more about it."
session?.begin()
}
// MARK: - NFCNDEFReaderSessionDelegate
/// - Tag: processingTagData
func readerSession(_ session: NFCNDEFReaderSession, didDetectNDEFs messages: [NFCNDEFMessage]) {
DispatchQueue.main.async {
// Process detected NFCNDEFMessage objects.
self.detectedMessages.append(contentsOf: messages)
self.tableView.reloadData()
}
}
/// - Tag: processingNDEFTag
func readerSession(_ session: NFCNDEFReaderSession, didDetect tags: [NFCNDEFTag]) {
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
}
// Connect to the found tag and perform NDEF message reading
let tag = tags.first!
session.connect(to: tag, completionHandler: { (error: Error?) in
if nil != error {
session.alertMessage = "Unable to connect to tag."
session.invalidate()
return
}
tag.queryNDEFStatus(completionHandler: { (ndefStatus: NFCNDEFStatus, capacity: Int, error: Error?) in
if .notSupported == ndefStatus {
session.alertMessage = "Tag is not NDEF compliant"
session.invalidate()
return
} else if nil != error {
session.alertMessage = "Unable to query NDEF status of tag"
session.invalidate()
return
}
tag.readNDEF(completionHandler: { (message: NFCNDEFMessage?, error: Error?) in
var statusMessage: String
if nil != error || nil == message {
statusMessage = "Fail to read NDEF from tag"
} else {
statusMessage = "Found 1 NDEF message"
DispatchQueue.main.async {
// Process detected NFCNDEFMessage objects.
self.detectedMessages.append(message!)
self.tableView.reloadData()
}
}
session.alertMessage = statusMessage
session.invalidate()
})
})
})
}
/// - Tag: sessionBecomeActive
func readerSessionDidBecomeActive(_ session: NFCNDEFReaderSession) {
}
/// - Tag: endScanning
func readerSession(_ session: NFCNDEFReaderSession, 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 != .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)
}
}
}
// To read new tags, a new session instance is required.
self.session = nil
}
// MARK: - addMessage(fromUserActivity:)
func addMessage(fromUserActivity message: NFCNDEFMessage) {
DispatchQueue.main.async {
self.detectedMessages.append(message)
self.tableView.reloadData()
}
}
// MARK: - General Tag Reading (ISO7816, MiFare, FeliCa)
@IBAction func beginGeneralTagScanning(_ sender: Any) {
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
}
tagSession = NFCTagReaderSession(pollingOption: [.iso14443, .iso15693, .iso18092], delegate: self)
tagSession?.alertMessage = "카드나 태그를 iPhone 상단에 가까이 대주세요."
tagSession?.begin()
}
// MARK: - NFCTagReaderSessionDelegate
func tagReaderSessionDidBecomeActive(_ session: NFCTagReaderSession) {
print("Tag reader session became active")
}
func tagReaderSession(_ session: NFCTagReaderSession, didInvalidateWithError error: Error) {
if let readerError = error as? NFCReaderError {
if (readerError.code != .readerSessionInvalidationErrorFirstNDEFTagRead)
&& (readerError.code != .readerSessionInvalidationErrorUserCanceled) {
DispatchQueue.main.async {
let alertController = UIAlertController(
title: "세션 종료",
message: error.localizedDescription,
preferredStyle: .alert
)
alertController.addAction(UIAlertAction(title: "확인", style: .default, handler: nil))
self.present(alertController, animated: true, completion: nil)
}
}
}
self.tagSession = nil
}
func tagReaderSession(_ session: NFCTagReaderSession, didDetect tags: [NFCTag]) {
if tags.count > 1 {
session.alertMessage = "태그가 2개 이상 감지되었습니다. 하나만 남겨주세요."
DispatchQueue.global().asyncAfter(deadline: .now() + .milliseconds(500)) {
session.restartPolling()
}
return
}
let tag = tags.first!
session.connect(to: tag) { (error: Error?) in
if error != nil {
session.invalidate(errorMessage: "태그에 연결할 수 없습니다.")
return
}
var tagInfo = ""
var tagType = ""
switch tag {
case .iso7816(let iso7816Tag):
tagType = "ISO7816 (신용카드/스마트카드)"
tagInfo = self.readISO7816Tag(iso7816Tag)
case .miFare(let miFareTag):
tagType = "MiFare (출입카드/교통카드)"
tagInfo = self.readMiFareTag(miFareTag)
case .feliCa(let feliCaTag):
tagType = "FeliCa (교통카드)"
tagInfo = self.readFeliCaTag(feliCaTag)
case .iso15693(let iso15693Tag):
tagType = "ISO15693"
tagInfo = self.readISO15693Tag(iso15693Tag)
@unknown default:
tagType = "Unknown Tag Type"
tagInfo = "알 수 없는 태그 타입입니다."
}
let fullInfo = "[\(tagType)]\n\(tagInfo)"
DispatchQueue.main.async {
// NDEF 메시지 형태로 변환하여 표시
let record = NFCNDEFPayload(
format: .nfcWellKnown,
type: "T".data(using: .utf8)!,
identifier: Data(),
payload: fullInfo.data(using: .utf8)!
)
let message = NFCNDEFMessage(records: [record])
self.detectedMessages.insert(message, at: 0)
self.tableView.reloadData()
}
session.alertMessage = "태그 읽기 완료"
session.invalidate()
}
}
// MARK: - Tag Reading Helpers
private func readISO7816Tag(_ tag: NFCISO7816Tag) -> String {
var info = ""
info += "UID: \(tag.identifier.map { String(format: "%02X", $0) }.joined(separator: " "))\n"
info += "Historical Bytes: \(tag.historicalBytes?.map { String(format: "%02X", $0) }.joined(separator: " ") ?? "없음")\n"
info += "Application Data: \(tag.applicationData?.map { String(format: "%02X", $0) }.joined(separator: " ") ?? "없음")\n"
info += "\n* 암호화된 데이터는 읽을 수 없습니다"
return info
}
private func readMiFareTag(_ tag: NFCMiFareTag) -> String {
var info = ""
info += "UID: \(tag.identifier.map { String(format: "%02X", $0) }.joined(separator: " "))\n"
switch tag.mifareFamily {
case .plus:
info += "타입: MiFare Plus\n"
case .ultralight:
info += "타입: MiFare Ultralight\n"
case .desfire:
info += "타입: MiFare DESFire\n"
case .unknown:
info += "타입: Unknown MiFare\n"
@unknown default:
info += "타입: Unknown\n"
}
info += "Historical Bytes: \(tag.historicalBytes?.map { String(format: "%02X", $0) }.joined(separator: " ") ?? "없음")\n"
info += "\n* 출입카드/교통카드 데이터는 암호화되어 있습니다"
return info
}
private func readFeliCaTag(_ tag: NFCFeliCaTag) -> String {
var info = ""
info += "UID: \(tag.currentIDm.map { String(format: "%02X", $0) }.joined(separator: " "))\n"
info += "System Code: \(tag.currentSystemCode.map { String(format: "%02X", $0) }.joined(separator: " "))\n"
info += "\n* 교통카드 잔액 등은 암호화되어 읽을 수 없습니다"
return info
}
private func readISO15693Tag(_ tag: NFCISO15693Tag) -> String {
var info = ""
info += "UID: \(tag.identifier.map { String(format: "%02X", $0) }.joined(separator: " "))\n"
info += "IC Manufacturer Code: \(String(format: "%02X", tag.icManufacturerCode))\n"
info += "IC Serial Number: \(tag.icSerialNumber.map { String(format: "%02X", $0) }.joined(separator: " "))\n"
return info
}
}
-------
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.developer.associated-domains</key>
<array>
<string>applinks:example.com</string>
</array>
<key>com.apple.developer.nfc.readersession.formats</key>
<array>
<string>NDEF</string>
<string>TAG</string>
<string>ISO7816</string>
<string>FELICA</string>
</array>
</dict>
</plist>
--------
반응형
'Swift' 카테고리의 다른 글
| iOS | iOS 13 웹뷰 페이지 스크롤 안되는 현상(WebView Scroll error) (1) | 2024.12.13 |
|---|---|
| Swift + RxSwift | UITableView/ UICollectionView 에서 작동하는 델리게이트 메서드 willDisplayCell (1) | 2024.12.11 |
| Using existentials and generics(익스텐셜 / 제네릭 타입) (0) | 2024.08.06 |
| Swift | Optional(value) 원론적으로 왜 출력될까? (0) | 2024.07.01 |
| iOS/Siwft | XCode - SwiftLint 적용 (0) | 2024.06.25 |