Сканирование тегов NFC с помощью .NET MAUI на устройствах iOS

Я разрабатываю приложение .NET MAUI, и сканирование тегов NFC является одним из требований. Я уже завершил разработку для Android, которая была простой и понятной. В Android все работает: от обнаружения тега NFC, получения полезных данных до их обработки.

В iOS я не могу обнаружить тег NFC. Вот что я сделал.

Служба чтения NFC iOS

Я реализовал очень простой сервис NFC для iOS, который выглядит следующим образом.

using CoreFoundation;
using CoreNFC;
using Foundation;
using MSS.App.Services.Interfaces;
using System;
using UIKit;

namespace OurAppNamespace.Platforms.iOS.Services
{
    public class IosNfcService : NFCTagReaderSessionDelegate, INfcService
    {
        private readonly ILogService? _logService;

        private NFCTagReaderSession? _nfcSession;

        public IosNfcService(ILogService? logService)
        {
            _logService = logService;
        }

        public async void Enable()
        {
            _logService?.LogInfo($"{this}.Enable");

            try
            {
                var isNfcAvailable = UIDevice.CurrentDevice.CheckSystemVersion(13, 0);

                _logService?.LogInfo($"{this}.Enable > {nameof(isNfcAvailable)} = {isNfcAvailable}");

                if (isNfcAvailable)
                {
                    _nfcSession = new NFCTagReaderSession(NFCPollingOption.Iso14443 | NFCPollingOption.Iso15693, this, DispatchQueue.CurrentQueue)
                    {
                        AlertMessage = "Test123"
                    };
                    _nfcSession.BeginSession();

                    _logService?.LogInfo($"{this}.Enable > Session started.");
                }
            }
            catch (Exception exception)
            {
                _logService?.LogError($"{this}.Enable > Starting Reader Session failed.", exception);
            }
        }

        public override void DidBecomeActive(NFCTagReaderSession session)
        {
            _logService?.LogInfo($"{this}.DidBecomeActive");
        }

        public override void DidInvalidate(NFCTagReaderSession session, NSError error)
        {
            _logService?.LogInfo($"{this}.DidInvalidate");
        }

        public override void DidDetectTags(NFCTagReaderSession session, INFCTag[] tags)
        {
            _logService?.LogInfo($"{this}.DidDetectTags");
        }

        public void Disable()
        {
            _logService?.LogInfo($"{this}.Disable");

            _nfcSession?.InvalidateSession();
        }
    }
}

Он основан на нескольких примерах кода, которые я нашел в Интернете. Основная идея (насколько я понимаю) заключается в том, что вы создаете экземпляр NFCTagReaderSession, если версия iOS 13 или выше, запускаете сеанс чтения, пока представление открыто, и как только что-то обнаружено, вызывается соответствующая функция. Насколько я понимаю, мне должен вызвать DidDetectTags, как только я прикреплю свой телефон к метке NFC, но я этого не делаю.

Инфо.plist

Я отредактировал файл Info.plist, расположенный в разделе «Платформы/iOS» в моем проекте .NET MAUI, чтобы добавить разрешения для тегов NFC. Строки, которые я добавил в файл:

<key>NFCReaderUsageDescription</key>
<string>NFC tag to read NDEF messages into the application</string>
<key>com.apple.developer.nfc.readersession.iso7816.select-identifiers</key>
<array>
    <string>com.apple.developer.nfc.readersession.iso7816.select-identifiers</string>
    <string>D2760000850100</string>
</array>

Права.plist

Это мое первое приложение, предназначенное для iOS, и я узнал о правах, необходимых для iOS. Я выполнил все шаги официального руководства .

  1. Я добавил пустой файл Entitlements.plist в свой проект рядом с моим Info.plist в разделе «Платформы/iOS».
  2. Я открыл настройки проекта, перешел в категорию «iOS», а затем «Подписание пакета» и добавил только что добавленный файл Entitlements.plist в раздел «Пользовательские права».

Это добавило следующие тексты в конфигурацию моего проекта.

<PropertyGroup Condition = "'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|net8.0-ios|AnyCPU'">
  <CodesignEntitlements>Platforms\iOS\Entitlements.plist</CodesignEntitlements>
</PropertyGroup>

<PropertyGroup Condition = "'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|net8.0-ios|AnyCPU'">
  <CodesignEntitlements>Platforms\iOS\Entitlements.plist</CodesignEntitlements>
</PropertyGroup>

Результат

Я удалил папки bin и obj своего решения, удалил ранее развернутую тестовую версию приложения с устройства iOS, перезапустил устройство, а затем пересобрал все решение. Если я открою приложение сейчас, я смогу его открыть, но не получу никаких событий для NFC. Я вижу вывод журнала из функции Enable, но не вызов DidDetectTags.

Я добавил Entitlements.plist, но не установил флажок «Чтение тегов беспроводной связи ближнего радиуса действия» в файле. Если я это сделаю, я больше не смогу запустить приложение. Вывод Visual Studio мало что мне говорит, но файлы журналов Xamarin указывают на проблему с профилем обеспечения. Я исследовал ошибку, упомянутую ниже, но не был уверен, как действовать дальше. Если я снимаю флажок из Entitlements.plist, приложение работает нормально, но я не обнаруживаю никаких тегов NFC.

Xamarin.Messaging.IDB.Local.DeployAppMessageHandler Error: 0 : An error occurred while trying to deploy the app '[APP_NAME]'. Details: Could not install the application '[PATH]\[APP_NAME]\out\[APP].ipa' on the device [TEST_DEVICE]. Details: ApplicationVerificationFailed|0xE8008015 - Failed to verify code signature of /var/installd/Library/Caches/com.apple.mobile.installd.staging/temp.59k3xO/extracted/Payload/[APP_NAME] : 0xe8008015 (A valid provisioning profile for this executable was not found.)
Xamarin.iOS.Windows.WindowsiOSException: Could not install the application '[LOCAL_PATH]\[APP_NAME]\out\[APP].ipa' on the device [TEST_DEVICE]. Details: ApplicationVerificationFailed|0xE8008015 - Failed to verify code signature of /var/installd/Library/Caches/com.apple.mobile.installd.staging/temp.59k3xO/extracted/Payload/[APP_NAME] : 0xe8008015 (A valid provisioning profile for this executable was not found.)
   at Xamarin.iOS.Windows.Installer.ApplicationSession.InstallApp(String appPath, String appBundleId) in D:\a\_work\1\s\src\Tools\Xamarin.iOS.Windows.Client\Installer\ApplicationSession.cs:line 276
   at Xamarin.iOS.Windows.Installer.ApplicationSession.Deploy(String appRootFolder, String appBundleId, String appName) in D:\a\_work\1\s\src\Tools\Xamarin.iOS.Windows.Client\Installer\ApplicationSession.cs:line 95
   at Xamarin.iOS.Windows.HotRestartClient.Deploy(AppleDevice nativeDevice, String appBundleId, String appBundleName, Boolean& incremental) in D:\a\_work\1\s\src\Tools\Xamarin.iOS.Windows.Client\HotRestartClient.cs:line 250
   at Xamarin.Messaging.IDB.Local.DeployAppMessageHandler.<ExecuteAsync>d__5.MoveNext() in D:\a\_work\1\s\src\Messaging\Xamarin.Messaging.IDB.Local\Handlers\DeployAppMessageHandler.cs:line 43: 04/04/2024 16:20:09Z
    DateTime=2024-04-04T16:20:09.7343180Z: 04/04/2024 16:20:09Z
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
0
593
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Если вы посмотрите мой блог здесь: https://medium.com/stackademic/nfc-ndef-communication-with-maui-and-xamarin-forms-fae28632c2d3

Я показал, как создать собственный сеанс, позволяющий получать обратные вызовы:

 public class SessionDelegate : NFCNdefReaderSessionDelegate
{
    private readonly byte[] bytes;
    public TaskCompletionSource<NfcTransmissionStatus> WasDataTransmitted { get; set; }

    public SessionDelegate(byte[] bytes)
    {
        this.bytes = bytes;
        WasDataTransmitted = new TaskCompletionSource<NfcTransmissionStatus>();
    }

    public override void DidDetect(NFCNdefReaderSession session, NFCNdefMessage[] messages)
    {

    }

    public override void DidDetectTags(NFCNdefReaderSession session, INFCNdefTag[] tags)
    {
        MainThread.BeginInvokeOnMainThread(() =>
        {
            session.AlertMessage = "Tag detected";
            try
            {
                if (tags.Length != 1)
                {
                    session.InvalidateSession(errorMessage: "Cannot write on multiple tags at the same time");
                    WasDataTransmitted.TrySetResult(NfcTransmissionStatus.Failed);
                    return;
                }
                var NdefTag = tags.First();
                session.ConnectToTag(NdefTag, (error) =>
                {
                    if (error != null)
                    {
                        WasDataTransmitted.TrySetResult(NfcTransmissionStatus.Failed);
                        session.InvalidateSession("There was an error while making this request");
                        return;
                    }
                });
                NdefTag?.QueryNdefStatus((status, capacity, error) =>
                {
                    if (error != null)
                    {
                        WasDataTransmitted.TrySetResult(NfcTransmissionStatus.Failed);
                        session.InvalidateSession("Could not query status of tag");
                    }
                    switch (status)
                    {
                        case NFCNdefStatus.NotSupported:
                            WasDataTransmitted.TrySetResult(NfcTransmissionStatus.Failed);
                            session.InvalidateSession("This is an unsupported tag");
                            break;
                        case NFCNdefStatus.ReadOnly:
                            WasDataTransmitted.TrySetResult(NfcTransmissionStatus.Failed);
                            session.InvalidateSession("This tag is readonly");
                            break;
                        case NFCNdefStatus.ReadWrite:
                            var isNfcWriteAvailable = UIDevice.CurrentDevice.CheckSystemVersion(13, 0);
                            if (isNfcWriteAvailable)
                            {
                                var chunkString = Encoding.UTF8.GetString(bytes);
                                var textPayload = NFCNdefPayload.CreateWellKnownTypePayload(chunkString);
                                var ndefpayloadArray = new NFCNdefPayload[] { textPayload };
                                var ndefMessage = new NFCNdefMessage(ndefpayloadArray);
                                NdefTag.WriteNdef(ndefMessage, (tagError) =>
                                {
                                    if (tagError != null)
                                    {
                                        WasDataTransmitted.TrySetResult(NfcTransmissionStatus.Failed);
                                        session.InvalidateSession("Falied to write this message");
                                    }
                                    else
                                    {
                                        session.AlertMessage = "Write successful";
                                        session.InvalidateSession();
                                        WasDataTransmitted.TrySetResult(NfcTransmissionStatus.Success);
                                    }
                                });
                            }
                            else
                            {
                                WasDataTransmitted.TrySetResult(NfcTransmissionStatus.Failed);
                                session.InvalidateSession("There was an error while trying to write on this tag");
                            }
                            break;
                        default:
                            WasDataTransmitted.TrySetResult(NfcTransmissionStatus.Unknown);
                            session.InvalidateSession("Tag status unkoown");
                            break;
                    }
                });
            }
            catch
            {
                session.InvalidateSession();
                WasDataTransmitted.TrySetResult(NfcTransmissionStatus.Failed);
            }
        });
    }

    public override void DidInvalidate(NFCNdefReaderSession session, NSError error)
    {
        if (error.Code == (int)NFCReaderError.ReaderSessionInvalidationErrorSessionTimeout)
        {
            MainThread.BeginInvokeOnMainThread(async () =>
            {
                await Application.Current.MainPage.DisplayAlert("Error ", "Tag search timed out", "Ok");
            });
        }
    }
}

Затем используйте это с TaskCompletionSource, и вы сможете получить свои обратные вызовы.

Что вам нужно было сделать, чтобы запустить Entitlements? Как я писал в своей теме, как только я устанавливаю флажок NFC на экране «Права» (или добавляю строки вручную), я больше не могу запустить приложение на iOS.

BeRo 05.04.2024 13:48

Есть ли у вашего профиля обеспечения разрешения NFC?

FreakyAli 06.04.2024 01:32

Я использую автоматическую подготовку, и, согласно MSDN, она поддерживает NFC. Я работаю над машиной Windows для создания своего программного обеспечения. Как вы думаете, это может быть проблемой?

BeRo 08.04.2024 19:53

Компьютер с Windows не должен вызывать никаких проблем. Можете ли вы просмотреть связанный блог и посмотреть, не пропустили ли вы какие-либо шаги из упомянутых?

FreakyAli 09.04.2024 15:03

Мой код выглядит немного иначе, но все упомянутые вами конфигурации для Info.plist или Entitlements.plist я выполнил. Мне также удалось запустить Mac и подключить к нему свою Visual Studio. Но даже это мне не помогло. Я все еще получаю ту же ошибку, о которой написал в конце исходного сообщения. Я использую автоматическую подготовку и использую учетную запись разработчика Apple нашей компании.

BeRo 09.04.2024 17:26

Я смог решить эту проблему и опубликовал ответ. Большое спасибо вам за вашу поддержку.

BeRo 09.04.2024 23:02
Ответ принят как подходящий

Я смог решить проблему. Как оказалось, проблема заключалась не в том, что я разрабатывал программу на ПК с Windows, в наличии плохого кода, пропущенных конфигурациях и т. д., а в учетной записи, используемой при автоматической подготовке. Убедившись, что я все сделал правильно, я создал новый ключ команды в Apple App Store Connect API и использовал эти учетные данные в Visual Studio для автоматической подготовки. Я создал их так же, как и в прошлый раз, но на этот раз это сработало. Я не могу сказать, в чем разница, но теперь я, наконец, могу развернуть свое приложение на iOS даже с разрешениями NFC, и я смог завершить всю функцию в течение нескольких часов после того, как развертывание на iOS сработало.

Спасибо FreakyAli за поддержку в моих усилиях по выяснению этой проблемы.

Другие вопросы по теме