Реализация чтения/записи NFC-меток через мобильное приложение
NFC-метки — это не про «тапнул и работает». На практике в 30-40% случаев первый запрос к метке возвращает NFCReaderError.readerTransceiveErrorTagConnectionLost на iOS или IOException: Tag lost на Android раньше, чем успевает прочитать даже заголовок NDEF-записи. Причина — слишком короткое удержание телефона над меткой, interference от металлических поверхностей или неправильный тайм-аут сессии.
Где чаще всего ломается интеграция
iOS: ограничения CoreNFC и типы тегов
Apple добавила CoreNFC в iOS 11, но полноценный read/write NDEF появился только в iOS 13. До сих пор встречаются проекты, которые таргетируют iOS 12 и не могут объяснить, почему у половины пользователей метки «не читаются».
Второй камень — NFCNDEFReaderSession против NFCTagReaderSession. Первый сессионный тип работает только с NDEF-совместимыми метками. Если клиент принёс Mifare Classic — он просто не заработает: Apple не поддерживает этот протокол из соображений безопасности. Нужно заранее проверить тип метки и использовать NFCTagReaderSession с NFCMiFareTag для Mifare Ultralight или NFCISO7816Tag для смарт-карт с APDU-командами.
Ещё болезненное место — Background Tag Reading, доступный с iOS 13 на iPhone XS/XR и новее. Приложение не запущено, пользователь подносит телефон к метке — система читает NDEF-URI и запускает приложение. Выглядит магически. Но требует com.apple.developer.nfc.readersession.formats в entitlements с NDEF, плюс URL-схему или Universal Link. Если забыть добавить домен в apple-app-site-association — deep link просто не откроется.
Android: фрагментация NFC-стека
На Android NfcAdapter.ACTION_NDEF_DISCOVERED, ACTION_TAG_DISCOVERED, ACTION_TECH_DISCOVERED — три разных интента с разным приоритетом диспетчеризации. Если объявить только NDEF_DISCOVERED, то метка формата NDEF будет перехвачена корректно, но «сырая» метка без NDEF-структуры уйдёт в другое приложение или вообще не придёт.
Foreground dispatch через NfcAdapter.enableForegroundDispatch() решает это, но требует чёткого жизненного цикла: включать в onResume, отключать в onPause. Один пропущенный disableForegroundDispatch — и приложение начинает получать NFC-интенты даже когда оно не активно, что ломает пользовательский опыт.
Для записи NDEF на Android: если метка write-protected или отформатирована под другой тип — tag.connect() подвиснет или выбросит IOException. Нужен явный тайм-аут через tag.setTimeout() для конкретного tech-класса и retry-логика с экспоненциальным backoff.
Как мы реализуем
iOS — стек и подход
Работаем с CoreNFC через NFCNDEFReaderSession для стандартных сценариев и NFCTagReaderSession для нестандартных форматов. Для записи — создаём NFCNDEFMessage с нужными NFCNDEFPayload, используем NFCNDEFPayload.wellKnownTypeURIPayload() для URI-записей вместо ручного кодирования байт, что исключает ошибки TNF-заголовков.
Сессию оборачиваем в async/await через Continuation, чтобы не тянуть delegate-цепочку через весь ViewModel. Ошибки маппим на понятные пользователю состояния — sessionTimeout, tagNotCompatible, writeProtected — и показываем через нативный NFCReaderSession alert или собственный UI.
Android — стек и подход
Используем Ndef и NdefFormatable tech-классы через Tag.getTechList(). Перед записью проверяем ndef.isWritable() и ndef.maxSize() — типичная ошибка: пытаться записать 500 байт на 144-байтную Ntag213. Для форматирования чистых меток используем NdefFormatable.format() с минимальным начальным сообщением.
Весь NFC-код выносим в NfcRepository с Flow<NfcEvent>, UI-слой подписывается через collectLatest. При смене конфигурации (поворот экрана) сессия не прерывается — Activity переоткрывается, а foreground dispatch восстанавливается в onResume.
Поддерживаемые форматы меток
| Тип метки | iOS | Android | Примечания |
|---|---|---|---|
| NDEF (Ntag213/215/216) | ✓ | ✓ | Наиболее распространённые |
| Mifare Ultralight | ✓ (ISO7816) | ✓ | Требует NFCTagReaderSession на iOS |
| Mifare Classic | ✗ | ✓ | Apple не поддерживает |
| ISO 15693 | ✓ (iOS 14+) | ✓ | Для промышленных меток |
| FeliCa | ✓ (только Япония) | ✓ | Транспортные карты |
Процесс работы
Начинаем с аудита: какие типы меток используются, какой объём данных записывается, нужен ли background reading, нужна ли запись или только чтение. От этого зависит выбор tech-классов и права в entitlements/manifest.
Дальше — разработка с unit-тестами на mock-тегах через NFCNDEFReaderSessionMock (iOS) и MockNdefTag (Android). Финальное тестирование — на реальных метках разных производителей: NXP, Broadcom, ST Microelectronics ведут себя немного по-разному даже в рамках одного стандарта.
Сроки
Базовая интеграция чтения/записи NDEF на одной платформе — 3–5 рабочих дней. Если нужны нестандартные форматы, background reading и поддержка нескольких типов меток — от 2 недель. Оценка после анализа требований.







