Используем SVG в Xamarin.Forms или долгая история о простой задаче

В этой статье рассматриваются такие темы, как отображение svg изображений в Xamarin-Forms, какие библиотеки можно использовать для выполнения этой задачи, преимущества использования svg, и некоторые другие связанные темы. Также в статье приведены подводные камни, с которыми я столкнулся в процессе реализации задачи.


Советую сразу набраться терпения, так как статья будет длинной 😀.

Почему стоит отдавать предпочтение SVG?

Когда речь идет об иконках (и не только) важно понимать, что они будут отображаться на устройствах пользователей с разным разрешением (DPI - Dots Per Inch количество точек на дюйм). Следовательно, ваше изображение должно выглядеть одинаково хорошо на практически всех возможных разрешениях экранов.

Немного разбирающийся в особенностях Android и IOS читатель может возразить: "Этого можно достичь и без использования данного формата!".

 - Да, можно (Android, IOS). Если вы прочитали эти 2 статьи то заметили некоторое неудобство, ведь вам нужно подготовить с десяток вариантов каждой иконки, что не очень то и удобно.

Тут на помощь приходят SVG изображения. Так как SVG изображения векторные, они не подвержены потере качества при масштабировании:
Рекомендую почитать эту статью на википедии. Там подробно изложены достоинства и недостатки данного формата.


Прежде чем продолжить хочу сказать: статью стоит рассматривать не как инструкцию по решению задачи, а как историю поиска ее решения.

Итак, создадим папку "Resources" в проекте PCL библиотеки, где будем хранить наши иконки.

Вполне уместно будет прочитать эту статью по использованию изображений в документации к Xamarin. Сделав это, может показаться что вы уже знаете как использовать изображения. Достаточно просто добавить картинку в проект, установить Build Action на панели Properties файла в значение "Embedded Resource", а затем указать путь к иконке в свойстве "Source" объекта "Image" (не забываем про имя сборки и имена иерархии папок, ведущих к изображению, разделяемые символом "." точка).

Так я и поступил:

List1 = new StackLayout();
List1.Children.Add(new Image
{
    Source = ImageSource.FromResource("SvgTests.Resources.phone.svg")
});

Но... страница как была белой, так и осталась, изображение не появилось. 
Возникли подозрения, что путь к папке прописан неверно, или изображение не добавилось в сборку, и еще много разных предположений, которые я поочередно проверил. В конечном итоге, я решил попробовать добавить изображение в другом формате, например jpg. И вот удивление, изображение появилось!

Тут и вылезает наш первый подводный камень, о котором в документации не сказано ни слова: стандартный объект Image не поддерживает изображения в формате svg. Но использовать десятки растровых изображений под разные разрешения уж очень не хотелось, и я начал поиск решения данной проблемы.

Попытка №1 (библиотека Xamarin.Forms.Plugins/SVG)

Данный пункт можно пропустить, так как эта попытка не увенчалась успехом.

Эту библиотеку я нашел на первой же странице гугла. Мельком пролистал readme, и начал подключать ее к своему проекту.

Думаю процедура установки библиотеки с помощью менеджера пакетов NuGet не нуждается в объяснении. Однако стоит подметить один нюанс: при установке важно отметить галочками все проекты (PCL, IOS и Android).


И сразу же ошибка установки пакета:


Could not install package 'Xam.Plugins.Forms.Svg 1.0.0.27'. You are trying to install this package into a project that targets '.NETPortable,Version=v4.5,Profile=Profile259', but the package does not contain any assembly references or content files that are compatible with that framework. For more information, contact the package author.

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

Решение 1.1

Первое решение я нашел тут, на StackOverflow. Его "основой" является предположение, что не установлены (или установлены не все) PCL библиотеки.

Перевод:

  1. Скачайте расширение Portable Tools 2 и установите его, используя аргумент командной строки "/buildmachine".
  2. Скачайте и установите Portable Class Libraries v4.6.
  3. Скопируйте содержимое (директории v4.0, v4.5 и v4.6) из папки по адресу C:\Program Files (x86)\Microsoft .NET Portable Library Reference Assemblies 4.6 в папку по адресу C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETPortable, заменяя существующие файлы. Не забудьте сделать резервную копию файлов, если хотите быть на светлой безопасной стороне силы.

Скажу сразу, данное решение не работает, и продемонстрировано в статье для исключительной полноты материала.

Решение 1.2

Продолжая поиски переходим к следующему, рабочему решению, которое я нашел тут (комментарий, который оставил пользователь VisionFlare 26 апреля 2017 года).

Перевод:

  1. Закройте Visual Studio.
  2. Откройте .csproj файл вашего PCL проекта с помощью любого текстового редактора.
  3. В открывшемся файле найдите строку с текстом "<TargetFrameworkProfile>ProfileXXX</TargetFrameworkProfile>" где XXX - любые числа.
  4. Замените "ProfileXXX" на "Profile78", сохраните файл.
  5. Откройте Visual Studio.
  6. Установите необходимый пакет.
  7. Закройте VisualStudio.
  8. Верните файл .csproj PCL проекта в исходное состояние.
  9. Откройте Visual Studio.
  10. Используйте пакет (библиотеку).
Как упоминалось ранее, данное решение действительно работает, и библиотека успешно устанавливается во все проекты solution (решения).

Пробуем использовать установленную библиотеку. Не забываем читать readme репозитория, в котором описан процесс подключения:

В ваших Android, IOS и Windows Phone проектах вызываем метод:

Xamarin.Forms.Init(); // platform specific init

SvgImageRenderer.Init(); // метод, который нужно вызвать


Теперь все готово к использованию, настало время создать объект класса SvgImage.

Для этого нужно:

  • Установить ширину и высоту изображения
  • Установить Build Action для изображения в значение Embedded Resource для ващей svg картинки
  • Путь к изображению прописывается по правилу: {ИмяПроекта}.{ИмяПапки}.{ИмяФайла}.{Расширение}
  • Установить свойство SvgAssembly на сборку, содержащую файл изображения.
  • Windows Phone имеет дополнительные требования, прочитать о которых можно все в том же readme файле.

Пример (C#):

new SvgImage { 
SvgPath = "PluginSampleApp.Images.hipster.svg",
SvgAssembly = typeof(App).GetTypeInfo().Assembly, 
HeightRequest = 100,
WidthRequest = 100

};

Пример (XAML):


<abstractions:SvgImage Grid.Row="0" Grid.Column="0" SvgAssembly="{Binding SvgAssembly}" SvgPath="{Binding CoolMaskSvgPath}" HeightRequest="50" WidthRequest="50" BackgroundColor="White" HorizontalOptions="Center" VerticalOptions="Center"/>

где abstractions - псевдоним пространства имен из C#.



Но... после столь долгих манипуляций снова ошибка, на этот раз "Unhandled Exception". Она происходит во время выполнения приложения, и к сожалению не дает нам никакой информации о себе. Для получения такой информации, временно поставим глобальный обработчик ошибок в Android либо IOS приложении:

[Activity(Label = "SvgTests", Icon = "@drawable/icon", Theme = "@style/MainTheme", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation)]
    public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity
    {
        protected override void OnCreate(Bundle bundle)
        {
            AppDomain.CurrentDomain.UnhandledException += (sender, args) =>
            {
                // вывод информации об ошибке в консоль, либо можно поставить тут точку останова, и посмотреть детали в отладчике

            };
...


Запускаем проект, опять получаем Unhandled exception. Нажимаем кнопку "Continue" чтобы попасть в глобальный обработчик ошибок. В отладчике движемся вниз по иерархии классов исключений:




... и находим поле с текстом "Value cannot be null. Parameter name: name". Можем еще посмотреть StackTrace, но особо нам это ничего не даст. Как будет рассказано дальше, это ошибка в библиотеке. На момент написания статьи, последний коммит в репозитории данной библиотеки был сделан 2 года назад, так что вполне возможно что она больше не совместима с последней версией Xamarin.

Попытка №1 провалилась, а мы продолжаем 😁.

Попытка №2 (библиотека TwinTechsFormsLib)

Перед тем как продолжить, удалите библиотеку, используемую в предыдущем разделе.

Продолжив поиски я наткнулся на статью, в которой приводятся разные библиотеки для отображения SVG изображений, и краткая информация об каждой из них. 

Перевод:
Одним из первых элементов управления для Xamarin-Forms был контрол под названием "SvgImage от paulpatarinski" (я рассматривал его в разделе "Попытка №1" выше). Он был разработан до появления NGraphics (которая предлагает действительно мощную поддержку SVG), поэтому использует некий кастомный fork наподобие NGraphics. 
С помощью NGraphics поддержка SVG была значительно улучшена. Автор статьи разработал fork "SvgImage от paulpatarinski", который уже использует NGraphics. Но к сожалению, эта библиотека не поддерживает платформы Windows Phone и UWP.

Найти данную библиотеку можно по этой ссылке (репозиторий). Инициализация ничем не отличается от оригинальной библиотеки, поэтому дублировать инструкцию смысла нет. Можно почитать этот readme файл.

Итак, приступаем к установке. Находим пакет TwinTechsForms.SvgImage в NuGet, отмечаем все проекты решения, и нажимаем "Install":


Установка проходит успешно. Пробуем добавить объект Svg контрола на страницу:


     List1.Children.Add(new SvgImage()

            {

                SvgAssembly = typeof(App).GetTypeInfo().Assembly,
                SvgPath = "SvgTests.Resources.pdf.svg",
                WidthRequest = 200,
                HeightRequest = 300
            });


На этапе выполнения этот код приводит к ошибке "System.NotSupportedException: Path Operation c". Снова разочаровавшись, мне пришлось вернуться к поиску следующей библиотеки...


Данная библиотека рабочая. Причина ошибки кроется в одном из подводных камней, речь о котором пойдет в следующем разделе (подраздел "Подводные камни").

Попытка №3: да прибудет с нами решение!

А продолжил я поиск библиотеки все в той же статье. На этот раз было решено использовать библиотеку "NControl.Controls". Данная библиотека помимо SvgImage включает некоторые другие элементы управления, которые в этой статье я не рассматриваю. Пример по использованию смотрим тут. Впрочем, техника применения ничем не отличается от рассматриваемых ранее библиотек. Не забываем инициализировать библиотеку перед использованием (см. раздел "Попытка №1").

Пакет устанавливается без ошибок. В отличие от предыдущей библиотеки, в этой путь к изображению задается в свойстве "SvgResource". Запускаем и... снова ошибка во время выполнения: "System.Exception: An error occured when reading the Svg Resource: Path Operation c". По сути это та же ошибка, которая происходила и при использовании предыдущей библиотеки.
А не пора ли забросить эту затею, и использовать растровые картинки? 
- Нет, продолжаем.
После недолгих поисков по issues спискам разных репозиториев, я наткнулся на вот эту страницу. Внимание стоит обратить на комментарий пользователя johnnysbug, который он оставил 8 сентября 2016 года.

Подводные камни

Данная ошибка происходит по той причине, что используемое мной svg изображение находилось в сжатом виде, что к сожалению не поддерживается NGraphics. И пока библиотека не обзаведется поддержкой сжатого формата, в качестве временного решения используем несжатые svg изображения.

Как выяснилось немного позже, NControl.Controls по непонятонй причине портит изображение так, если оно было бы растровым:

По этой причине возвращаем библиотеку из раздела "Попытка №2", и пользуемся. Если конечно вы не разрабатываете под Windows Phone 😀.

Но на этом подводные камни не заканчиваются. Если для Android задавать ширину и высоту картинки не обязательно, то для отображения картинки на iOS это необходимо. Помимо этого, для отображения svg на последней платформе, инициализировать библиотеку в классе AppDelegate необходимо до инициализации Xamarin-Forms:

[Register("AppDelegate")]
    public class AppDelegate : FormsApplicationDelegate
    {
        public override bool FinishedLaunching(UIApplication app, NSDictionary options)
        {
            SvgImageViewRenderer.Init();
            Forms.Init();
...

Работающая программа на Android эмуляторе:



Спасибо за внимание 😀.

Комментарии

  1. Используем Svg В Xamarin.Forms Или Долгая История О Простой Задаче >>>>> Download Now

    >>>>> Download Full

    Используем Svg В Xamarin.Forms Или Долгая История О Простой Задаче >>>>> Download LINK

    >>>>> Download Now

    Используем Svg В Xamarin.Forms Или Долгая История О Простой Задаче >>>>> Download Full

    >>>>> Download LINK O2

    ОтветитьУдалить

Отправить комментарий

Популярные сообщения из этого блога

Разработка на IOS из-под Windows - ставим Hackintosh на виртуальную машину и подключаем к Visual Studio

Анонсы статей