Перейти к содержанию
Fire Monkey от А до Я

Martifan

Пользователи
  • Постов

    396
  • Зарегистрирован

  • Посещение

  • Победитель дней

    16

Весь контент Martifan

  1. Вот обновленный и адаптированный вариант вашего кода для изменения приоритета потока на платформе POSIX в Delphi: uses Posix.Sched, Posix.Pthread, Posix.Errno; procedure SetThreadPriority; var Params: sched_param; ThreadId: pthread_t; Res: Integer; begin ThreadId := pthread_self(); Params.sched_priority := 10; // устанавливаем приоритет 10 Res := pthread_setschedparam(ThreadId, SCHED_FIFO, @Params); if Res <> 0 then raise Exception.CreateFmt('Error setting thread priority: %d', [Res]); end; Вы правильно указали, что для изменения приоритета потока может потребоваться выполнение приложения с правами администратора (root). Если ваше приложение должно работать без таких прав, убедитесь, что вы обрабатываете ошибки, возникающие при изменении приоритета, и рассмотрите возможность использования альтернативных методов изменения приоритета или оптимизации производительности. Также стоит учесть, что установка очень высокого приоритета потока может привести к проблемам с производительностью и стабильностью системы. Всегда тестируйте свое приложение на разных конфигурациях и устройствах, чтобы убедиться, что оно работает корректно и не вызывает нежелательных побочных эффектов.
  2. На Android 11 (API уровень 30) и выше, чтобы иметь полный доступ к внешнему хранилищу, вам нужно использовать разрешение MANAGE_EXTERNAL_STORAGE. Предоставление READ_EXTERNAL_STORAGE уже не даст полного доступа к файлам на устройстве. Чтобы запросить разрешение MANAGE_EXTERNAL_STORAGE, выполните следующие шаги: В вашем файле AndroidManifest.xml, добавьте разрешение: <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" /> Запросите разрешение у пользователя. Для этого создайте новый Intent и установите соответствующее действие: var Intent: JIntent; begin Intent := TJIntent.JavaClass.init; Intent.setAction(TJSettings.JavaClass.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION); TAndroidHelper.Activity.startActivity(Intent); end; Проверьте, предоставлено ли разрешение, используя метод Environment.isExternalStorageManager(): uses ..., Androidapi.JNI.App, Androidapi.JNI.JavaTypes, Androidapi.JNI.Os; function IsExternalStorageManager: Boolean; begin Result := TJEnvironment.JavaClass.isExternalStorageManager(TAndroidHelper.Context); end; Обратите внимание, что MANAGE_ALL_FILES_ACCESS_PERMISSION доступно только на уровне API 30 и выше. Если ваше приложение также должно поддерживать более ранние версии Android, убедитесь, что вы проверяете версию Android перед вызовом этого кода: uses ..., Androidapi.JNI.Os; if TBuild_VERSION.JavaClass.SDK_INT >= 30 then begin // Запросите разрешение MANAGE_EXTERNAL_STORAGE end else begin // Запросите разрешение READ_EXTERNAL_STORAGE или WRITE_EXTERNAL_STORAGE end; По поводу отсутствия активити для действия android.settings.MANAGE_ALL_FILES_ACCESS_PERMISSION, возможно, это связано с конкретным устройством или версией Android. Вам может потребоваться использовать другой метод для запроса разрешения или обработать эту ситуацию соответствующим образом в вашем коде.
  3. Похоже, что вы пытаетесь пересоздать ресурсы DirectX после возникновения ошибки DXGI в Delphi с использованием FireMonkey (FMX). Ваш подход в целом верный, но возможно, вы упускаете некоторые важные шаги при пересоздании ресурсов. Вот несколько предложений, как можно улучшить ваш код: Обязательно убедитесь, что все объекты, использующие DirectX ресурсы, освобождают их перед вызовом DisposeResources. Это может включать текстуры, шейдеры, буферы и т.д. Может потребоваться вызов Device.Reset перед пересозданием общих ресурсов. Это зависит от того, каким образом вы обрабатываете ошибки DXGI в вашем коде. Вот пример, как это может выглядеть: procedure ResetDxEngine; var c: TCanvasD2D; begin // Освобождаем ресурсы для каждой канвы for c in Canvases do c.DisposeResources; // Сброс устройства if Assigned(TCanvasD2D.Device) then TCanvasD2D.Device.Reset; // Уничтожаем и создаем общие ресурсы TCanvasD2D.DestroySharedResources; TCanvasD2D.CreateSharedResources; // Создаем ресурсы для каждой канвы for c in Canvases do c.CreateResources; end; Если после выполнения этих шагов ваш код все еще не работает, возможно, проблема заключается в другой части вашего кода, связанной с обработкой ошибок DXGI. Проверьте, что вы корректно обрабатываете ошибки DXGI и вызываете ResetDxEngine только при необходимости. Обратите внимание, что некоторые ошибки DXGI могут быть связаны с оборудованием, и пересоздание ресурсов может не решить проблему. В таких случаях может потребоваться использование других методов восстановления, таких как переключение на программное устройство. Помимо этого, убедитесь, что ваш код корректно обрабатывает ошибки и исключения, возникающие при работе с DirectX, чтобы вы могли предоставить информацию пользователю или предпринять дополнительные действия для восстановления работы приложения.
  4. procedure TForm1.FormCreate(Sender: TObject); var LTextHeight: Single; LLines: Integer; begin LTextHeight := MeasureTextHeight(Memo1.TextSettings.Font, Memo1.Text); LLines := Round(Memo1.ContentBounds.Height / LTextHeight); ShowMessage('Количество строк: ' + IntToStr(LLines)); end;
  5. Если у вас отсутствует модуль Androidapi.JNI.Support.Compat в вашей версии Delphi, то, вероятно, это связано с тем, что вы используете более старую версию Delphi, которая не включает этот модуль. В этом случае вы можете попробовать использовать классы, доступные в модуле Androidapi.JNI.Support, например, TJContextCompatMarshmallow для проверки разрешения в Android 6.0 и выше. uses Androidapi.Helpers, Androidapi.JNI.GraphicsContentViewText, Androidapi.JNI.JavaTypes, Androidapi.JNI.Support; procedure CheckAndRequestStoragePermission; begin if TJContextCompatMarshmallow.JavaClass.checkSelfPermission(TAndroidHelper.Context, JStringToString(TJManifest_permission.JavaClass.READ_EXTERNAL_STORAGE)) <> TJPackageManager.JavaClass.PERMISSION_GRANTED then begin TJActivityCompatMarshmallow.JavaClass.requestPermissions(TAndroidHelper.Activity, TJavaObjectArray<JString>.Create([TJManifest_permission.JavaClass.READ_EXTERNAL_STORAGE]), 0); end else TDialogService.ShowMessage('Permission granted'); end; Обратите внимание, что класс TJActivityCompatMarshmallow используется для запроса разрешения на чтение внешнего хранилища. Если вы используете другое разрешение, вам нужно использовать соответствующий класс из Androidapi.JNI.Support
  6. Причиной вылета приложения может быть ошибка в коде, которая вызывает неопределенное поведение при выполнении некоторых действий с TMemo. Ошибка может быть связана с попыткой доступа к освобожденной памяти или нарушением границ массива. Чтобы выявить причину ошибки, вы можете использовать отладчик в Delphi, который позволяет исследовать состояние программы во время выполнения и отслеживать переменные, значения и вызовы функций. Если вы не можете использовать отладчик для какой-либо причины, попробуйте добавить в ваш код обработку исключений, чтобы увидеть, какая ошибка возникает и в каком месте. Например, вы можете использовать следующий код: try // код, который может вызвать ошибку except on E: Exception do ShowMessage('Error: ' + E.Message); end; Этот код позволит отлавливать исключения и выводить сообщения об ошибках в приложении. Кроме того, вы можете попробовать использовать инструменты трассировки, такие как Logcat или сторонние инструменты для отслеживания ошибок в приложении на устройстве Android. Они могут помочь вам идентифицировать проблему и выявить причину вылета приложения.
  7. Код, который вы привели, предназначен для открытия экрана настроек разрешений в Android 11 и выше, где пользователь может предоставить разрешение на доступ к внешнему хранилищу. Однако, если у пользователя уже есть необходимые разрешения, экран настроек разрешений не будет открываться. Поэтому, если экран настроек разрешений не открывается, это может означать, что у пользователя уже есть необходимые разрешения. Если вы хотите убедиться в этом, вы можете добавить проверку разрешения в ваш код, как я показал в предыдущем ответе. if TJContextCompat.JavaClass.checkSelfPermission(TAndroidHelper.Context, JStringToString(TJManifest_permission.JavaClass.READ_EXTERNAL_STORAGE)) <> TJPackageManager.JavaClass.PERMISSION_GRANTED then begin // Разрешение не выдано, запрашиваем его // ... end else begin // Разрешение уже выдано // ... end; Если после проверки разрешения экран настроек разрешений все еще не открывается, проверьте, что вы используете правильное действие Intent. Вместо MANAGE_ALL_FILES_ACCESS_PERMISSION можно использовать другое действие, которое открывает экран настроек разрешений. Например, вы можете попробовать использовать ACTION_APPLICATION_DETAILS_SETTINGS.
  8. В версии Android API 28 (Android 9.0) и более новых версиях, класс TJContextCompat доступен в модуле Androidapi.JNI.Support.Compat, поэтому, для использования этого класса, вам нужно добавить этот модуль в uses секцию вашего кода. Пример использования TJContextCompat для проверки разрешения: uses Androidapi.Helpers, Androidapi.JNI.GraphicsContentViewText, Androidapi.JNI.JavaTypes, Androidapi.JNI.Support.Compat, FMX.DialogService; procedure CheckAndRequestStoragePermission; begin if TJContextCompat.JavaClass.checkSelfPermission(TAndroidHelper.Context, JStringToString(TJManifest_permission.JavaClass.READ_EXTERNAL_STORAGE)) <> TJPackageManager.JavaClass.PERMISSION_GRANTED then begin TJActivityCompat.JavaClass.requestPermissions(TAndroidHelper.Activity, TJavaObjectArray<JString>.Create([TJManifest_permission.JavaClass.READ_EXTERNAL_STORAGE]), 0); end else TDialogService.ShowMessage('Permission granted'); end; Обратите внимание, что в этом примере мы также используем TJActivityCompat, чтобы запросить разрешение на чтение внешнего хранилища, если разрешение еще не выдано. Этот класс также находится в модуле Androidapi.JNI.Support.Compat.
  9. TJSettings.JavaClass.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION и android.settings.MANAGE_ALL_FILES_ACCESS_PERMISSION - это одно и то же действие Intent, которое открывает настройки разрешений для приложения на Android 11 и выше. Вы можете использовать оба варианта в своем коде, в зависимости от того, что вам удобнее использовать.
  10. TJContextCompat - это статический класс из модуля Androidapi.JNI.Support. Он содержит статические методы, которые обеспечивают совместимость с разными версиями Android, в том числе методы для проверки и запроса разрешений. ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION - это строковая константа, которая определяет действие Intent для отображения настроек разрешений приложения в Android 11 и выше. Она определена в классе android.provider.Settings и доступна через Androidapi.JNI.Provider в Delphi. В вашем случае эта константа используется для запуска настроек разрешений на чтение и запись во внешнее хранилище приложения на Android 11 и выше. После запуска настроек пользователь может выдать необходимые разрешения для приложения.
  11. uses Androidapi.Helpers, Androidapi.JNI.GraphicsContentViewText, Androidapi.JNI.JavaTypes, Androidapi.JNI.Support, FMX.DialogService; procedure RequestStoragePermission; var Intent: JIntent; begin if TJBuild_VERSION.JavaClass.SDK_INT >= 30 then begin if not TJEnvironment.JavaClass.isExternalStorageManager then begin Intent := TJIntent.Create; Intent := TJIntent.JavaClass.init(TJSettings.JavaClass.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION); intent.setData(TJnet_Uri.JavaClass.parse(StringToJString('package:').concat(TAndroidHelper.Context.getPackageName))); if intent.resolveActivity(TAndroidHelper.Context.getPackageManager) <> nil then TAndroidHelper.Context.startActivity(Intent); end; end else begin PermissionsService.RequestPermissions([JStringToString(TJManifest_permission.JavaClass.READ_EXTERNAL_STORAGE), JStringToString(TJManifest_permission.JavaClass.WRITE_EXTERNAL_STORAGE)], procedure(const APermissions: TArray<string>; const AGrantResults: TArray<TPermissionStatus>) begin if (Length(AGrantResults) = 2) and (AGrantResults[0] = TPermissionStatus.Granted) and (AGrantResults[1] = TPermissionStatus.Granted) then TDialogService.ShowMessage('Permission granted') else TDialogService.ShowMessage('Permission denied'); end); end; end; procedure CheckAndRequestStoragePermission; begin if TJBuild_VERSION.JavaClass.SDK_INT >= 30 then begin if TJContextCompat.JavaClass.checkSelfPermission(TAndroidHelper.Context, JStringToString(TJManifest_permission.JavaClass.READ_EXTERNAL_STORAGE)) <> TJPackageManager.JavaClass.PERMISSION_GRANTED then RequestStoragePermission else TDialogService.ShowMessage('Permission granted'); end else begin if (PermissionsService.IsPermissionGranted(JStringToString(TJManifest_permission.JavaClass.READ_EXTERNAL_STORAGE)) and PermissionsService.IsPermissionGranted(JStringToString(TJManifest_permission.JavaClass.WRITE_EXTERNAL_STORAGE))) then TDialogService.ShowMessage('Permission granted') else RequestStoragePermission; end; end;
  12. В FMX для работы с диалогами следует использовать FMX.DialogService. В данном случае проблема возникает из-за того, что в библиотеке используется FMX.Dialogs, который может работать некорректно с главным потоком приложения. uses FMX.DialogService; procedure Get_F; safecall; begin TDialogService.ShowMessage('+++'); end; Также обратите внимание, что в вашем примере вы вызываете FreeLibrary(hDll); в блоке условия if, что может привести к проблемам, если библиотека уже была загружена ранее. Вы можете перенести вызов FreeLibrary за пределы блока условия, чтобы быть уверенным, что библиотека будет освобождена, когда ее больше не нужно.
  13. Проблема заключается в том, что вызов функции Sleep блокирует главный поток приложения, что приводит к остановке обработки сообщений в очереди сообщений Windows. В результате функция SendMessage не сможет отправить сообщение до тех пор, пока главный поток не будет освобожден и сможет продолжить обработку очереди сообщений. Для решения этой проблемы необходимо использовать асинхронный подход и выполнять задержку в отдельном потоке, чтобы не блокировать главный поток приложения. Например, можно использовать класс TTask из библиотеки Delphi для выполнения задержки в отдельном потоке. Пример использования TTask для выполнения задержки в отдельном потоке: uses System.Threading; procedure SendResult(ResultCode: Integer); var Message: TMessage<Integer>; begin // отправляем результат в другой intent // ... // отправляем результат в главную форму Message := TMessage<Integer>.Create(ResultCode); TMessageManager.DefaultManager.SendMessage(nil, Message); end; procedure ScanResultHandler(const AData: TArray<TScanResult>); var ResultCode: Integer; begin // обработка сканера // ... // задержка в отдельном потоке TTask.Run( procedure begin Sleep(10000); SendResult(ResultCode); end); end; В этом примере мы использовали анонимную процедуру, чтобы выполнить задержку в отдельном потоке. Задержка выполняется с помощью функции Sleep, а затем мы вызываем функцию SendResult, чтобы отправить результат в другой intent и в главную форму через TMessageManager.DefaultManager.SendMessage. Поскольку задержка выполняется в отдельном потоке, главный поток приложения не блокируется, и сообщения в очереди обрабатываются нормально.
  14. Для получения информации о том, какой метод блокировки экрана используется на устройстве Android, можно воспользоваться классом KeyguardManager. Этот класс позволяет проверить, заблокирован ли экран на устройстве и какой именно метод блокировки экрана используется. Пример использования KeyguardManager в Delphi: uses Androidapi.JNI.GraphicsContentViewText, Androidapi.JNI.Os, Androidapi.JNI.Provider, Androidapi.JNI.Widget, Androidapi.Helpers, Androidapi.JNI.JavaTypes; function IsDeviceLocked: Boolean; var KeyguardManager: JKeyguardManager; begin KeyguardManager := TJKeyguardManager.Wrap ((SharedActivityContext.getSystemService(TJContext.JavaClass.KEYGUARD_SERVICE) as ILocalObject).GetObjectID); Result := KeyguardManager.isKeyguardLocked(); end; function GetKeyguardType: Integer; var KeyguardManager: JKeyguardManager; begin KeyguardManager := TJKeyguardManager.Wrap ((SharedActivityContext.getSystemService(TJContext.JavaClass.KEYGUARD_SERVICE) as ILocalObject).GetObjectID); if KeyguardManager.isKeyguardSecure() then begin if KeyguardManager.isDeviceSecure() then Result := 2 // Блокировка по биометрическим данным else Result := 1; // Блокировка по паролю, пин-коду или рисунку end else Result := 0; // Блокировка не настроена end; Функция IsDeviceLocked проверяет, заблокирован ли экран на устройстве в настоящее время. Если экран заблокирован, функция возвращает True, в противном случае возвращает False. Функция GetKeyguardType позволяет получить тип блокировки экрана на устройстве. Если на устройстве настроена блокировка экрана по биометрическим данным, функция возвращает 2. Если на устройстве настроена блокировка экрана по паролю, пин-коду или рисунку, функция возвращает 1. Если блокировка экрана не настроена, функция возвращает 0. Обратите внимание, что для использования KeyguardManager необходимо включить соответствующее разрешение в файле манифеста вашего приложения: <uses-permission android:name="android.permission.USE_BIOMETRIC"/>
  15. Для установки приоритета потока в Linux в Delphi можно использовать функцию pthread_setschedparam(). Эта функция позволяет задать приоритет потока в соответствии с POSIX-стандартом. Пример использования функции pthread_setschedparam() для установки приоритета потока можно найти в следующем коде: uses Posix.Sched; var Params: sched_param; ThreadId: pthread_t; begin ThreadId := pthread_self(); Params.sched_priority := 10; // устанавливаем приоритет 10 if pthread_setschedparam(ThreadId, SCHED_FIFO, @Params) <> 0 then raise Exception.Create('Error setting thread priority'); end; В этом примере мы устанавливаем приоритет потока на уровне 10 с помощью структуры sched_param. Далее мы используем функцию pthread_setschedparam() для установки приоритета нашему потоку, и если функция возвращает значение отличное от 0, то возникает исключение. Обратите внимание, что установка приоритета потока может быть опасной, поскольку это может привести к проблемам с производительностью системы или блокировке других потоков. Поэтому необходимо использовать эту функцию осторожно и только в случае крайней необходимости.
  16. Здравствуйте! Для разбора звукового файла WAV в Delphi можно использовать стандартные средства работы с аудиофайлами из библиотеки Windows API или сторонние библиотеки, такие как BASS или FMOD. Пример чтения и воспроизведения звукового файла WAV с использованием Windows API можно найти в следующем коде: var WaveFormat: TWAVEFORMATEX; MMResult: MMRESULT; HWaveOut: HWAVEOUT; WaveHeader: TWAVEHDR; WaveData: Pointer; WaveSize: DWORD; begin MMResult := waveInOpen(@HWaveOut, WAVE_MAPPER, @WaveFormat, 0, 0, CALLBACK_NULL); if MMResult <> MMSYSERR_NOERROR then raise Exception.CreateFmt('Error opening audio device. Error code: %d', [MMResult]); try WaveData := LoadWaveData('audio.wav', WaveSize); // функция загрузки данных из WAV файла ZeroMemory(@WaveHeader, SizeOf(TWAVEHDR)); WaveHeader.dwBufferLength := WaveSize; WaveHeader.lpData := WaveData; MMResult := waveOutPrepareHeader(HWaveOut, @WaveHeader, SizeOf(TWAVEHDR)); if MMResult <> MMSYSERR_NOERROR then raise Exception.CreateFmt('Error preparing audio data. Error code: %d', [MMResult]); try MMResult := waveOutWrite(HWaveOut, @WaveHeader, SizeOf(TWAVEHDR)); if MMResult <> MMSYSERR_NOERROR then raise Exception.CreateFmt('Error playing audio. Error code: %d', [MMResult]); while waveOutUnprepareHeader(HWaveOut, @WaveHeader, SizeOf(TWAVEHDR)) = WAVERR_STILLPLAYING do Sleep(10); finally FreeMem(WaveData); end; finally waveOutClose(HWaveOut); end; end; Для анализа звукового файла можно использовать сторонние библиотеки, такие как BASS или FMOD. Например, для работы с BASS можно использовать следующий код: uses Bass; var Stream: HSTREAM; Info: BASS_CHANNELINFO; Frequency: Integer; begin if not BASS_Init(-1, 44100, 0, 0, nil) then raise Exception.Create('Error initializing audio system'); try Stream := BASS_StreamCreateFile(False, 'audio.wav', 0, 0, BASS_SAMPLE_FLOAT or BASS_STREAM_DECODE); if Stream = 0 then raise Exception.Create('Error creating audio stream'); try BASS_ChannelGetInfo(Stream, Info); Frequency := Info.freq; // далее можно использовать функции BASS для анализа звукового файла finally BASS_StreamFree(Stream); end; finally BASS_Free(); end; end;
  17. Martifan

    iOS Push Notification

    https://github.com/DelphiWorlds/Kastri посмотри эту библиотеку там все есть
  18. Всем доброго времени сутки. Просьба помогите протестировать шифрацию, то есть я дам вам шифровку кто расшифрует заплачу 500$ вот зашифрованы текст: ba3xDmsH/xHtIPa7KO3uDQHnuZzXAxS0fEfXmLgt7ISasdETm9Oj9s/VGpXH//egpN6Rs0vJmZnONQtf73p1k8sYYuNO7sC5/LNvSfwucMw1EYiujj2Nrya34lORM7pp/vsR2PYGeaLqbeSFk6RtQq0X5Kj2n97bagRxlLAJNzh2VTkEESWn/W5YIhzucdNdLlWmz/5F2zltM0NkzOeBwprugxU0GQ0tHcKh4fiQKCD8DZyitZcRIgbuYYwoGEmJddedm+GhUhN8RB6ljCxKuwpXz2QdmAyiW97bAbOgUK/oVHsEZ5gLIr7NmdgDQ9t8XhrgmaZcO/bsYkkcBUJzLTUbqh4l+Sq4zWwFaCMyJTsuQvrDAEbBqY8MCvPmNeD1aS/ey79SAnKrQp2T170lIAGQwZq7iP7YQs8VWsYsP2QlY0vTM86T852bEHL/Oon9grmUptD4WTIGUakSNxJ70jySuOKQgZOME3S+9BcbpPx/c8lO32wH6FPe9RlMZaYf9RHKUsaPOMFPV+xFGJ2OhNeItJkEd7qvp9aCj/h7zvdrQTCi5IFdsEWxEhRz2f1dxGciI5qc3k3Onvor55zCyA== всем спасибо за помощь
  19. https://en.delphipraxis.net/topic/3308-problem-with-delphi-rio-1033-and-google-api-level-29/
  20. Привет, подскажите как можно реализовать авторизацию с помощью Apple ID? любые идеи. заранее спасибо
  21. Martifan

    Delphi Sydney iOS Bug

    Всем привет. Установил Delphi Sydney, запустил пустую приложению под iOS и у меня запускается так: как убрать эти черные панели верх и внизу? P.S.. под Delphi Rio все хорошо работает Спасибо за любой ответ
  22. Martifan

    Push Notification Sound

    Здравствуйте, Сделал в аппликацию пуш уведомление все работает но есть одна проблема, когда доходит уведомление нету звука, хотя настройках показывает что звук включен, скрипт для отправки уведомления: { "to": "fetrOVKDF8Q:*****", "notification" : { "body" : "Hey!? Checkout my custom notification", "title" : "Hey!? Checkout my custom notification", "Sound" : "default", "badge": "1" } } может кто знает как включить звук заранее спасибо
  23. Всем привет. Помогите перевести код на Delphi C# (Xamarin): public override void RegisteredForRemoteNotifications(UIApplication application, NSData deviceToken) { //DeviceToken = Regex.Replace(deviceToken.ToString(), "[^0-9a-zA-Z]+", ""); //Replace the above line whick worked up to iOS12 with the code below: byte[] bytes = deviceToken.ToArray<byte>(); string[] hexArray = bytes.Select(b => b.ToString("x2")).ToArray(); DeviceToken = string.Join(string.Empty, hexArray); } let deviceTokenString = deviceToken.map { String(format: "%02x", $0) }.joined() заранее спасибо
×
×
  • Создать...