[Разработка под iOS, Разработка под Android, C#, Xamarin] Экраны отсутствующего контента в мобильном приложении на примере Xamarin
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
С чего все началосьОчень часто, работая с Enterprise приложениями, приходится сталкиваться с подобными экранами при отсутствии получаемых данных с бекенда, когда нам на экране попросту нужно отобразить список.
Связано это обычно с тем, что либо дизайнер отсутствует вовсе, и все варфреймы идут в виде набросков от заказчика, либо же дизайнер есть, но этот элемент для заказчика считается не столь важным, и его попросту игнорируют.Первые шаги к улучшению ситуацииСтолкнувшись с подобной ситуацией в очередной раз, я понял, что меня это раздражает даже как разработчика. Потому я направился к дизайнеру с этим вопросом, и по итогу мы пришли к более информативному и приятному для глаз варианту.
Сделали для этого вью-модель, и соответствующие вьюхи под каждую платформу. Ниже приведу код вью-модели, платформенный UI-код не буду постить из-за громоздкости.
public class EmptyStateViewModel : ViewModel
{
public EmptyStateViewModel(string image, string title, string description)
{
Image = image;
Title = title;
Description = description;
}
public string Image { get; }
public string Title { get; }
public string Description { get; }
}
Следующим шагом на платформах(или в xaml в случае для Xamarin Forms) нужно прописать Bindings на проперти вью модели, в зависимости от того, какой mvvm-фреймворк используется.А что дальше?А далее возник вопрос - что делать, если по каким-то причинам наш запрос на бекенд фейлится. И тут пришла мысль - переиспользовать уже готовый EmptyStateView, но добавить внизу кнопку Retry, для возможности повторной отправки запроса. Потому мы просто наследовались от EmptyStateViewModel, но добавили поля с текстом кнопки, и командой на клик.
public class ErrorStateViewModel : EmptyStateViewModel
{
public ErrorStateViewModel(string image, string title, string description, string actionTitle, Command actionCommand)
: base(image, title, description)
{
ActionTitle = actionTitle;
ActionCommand = actionCommand;
}
public string ActionTitle { get; }
public Command ActionCommand { get; }
}
И как все это использовать?После этого пришло время задуматься - как это менеджить. И тут неплохим показался вот такой вариант. Создаем фабрику, которая будет нам возвращать нужную вью-модель. А так же метод None, который будет возвращать null.
public static class OverlayFactory
{
public static T None<T>()
where T : EmptyStateViewModel
{
return null;
}
public static EmptyStateViewModel CreateCustom(string image, string title, string description)
{
return new EmptyStateViewModel(image, title, description);
}
public static ErrorStateViewModel CreateCustom(string image, string title, string description, string actionTitle, Command actionCommand)
{
return new ErrorStateViewModel(image, title, description, actionTitle, actionCommand);
}
}
Как результат - мы можем во вью-модели, где мы грузим список, легко обрабатывать отображение пустой вьюхи, либо же вьюхи ошибки
public class SomeViewModel : BaseViewModel
{
private IItemsLoadingService _itemsLoadingService;
public SomeViewModel(IItemsLoadingService itemsLoadingService)
{
_itemsLoadingService = itemsLoadingService;
}
public ObservableCollection<ItemViewModel> Items { get; } = new ObservableCollection<ItemViewModel>();
public EmptyStateViewModel EmptyState { get; protected set; }
public ErrorStateViewModel ErrorState { get; protected set; }
public override async Task InitializeAsync()
{
await base.InitializeAsync();
await LoadItemsAsync();
}
private async Task LoadItemsAsync()
{
try
{
var result = await _itemsLoadingService.GetItemsAsync();
var items = result.ToList();
ErrorState = OverlayFactory.None<ErrorStateViewModel>();
if (items.Count == 0)
{
EmptyState = OverlayFactory.CreateCustom("img_empty_state", "Title", "Description");
}
else
{
EmptyState = OverlayFactory.None<ErrorStateViewModel>();
// Add items to list
}
}
catch
{
ErrorState = OverlayFactory.CreateCustom("img_error_state", "Title", "Description", "Retry", new Command(() => LoadItemsAsync));
}
}
}
На платформах же нам необходимо прописать кастомный Binding для EmptyState/ErrorState вьюх на соответствующие вью модели, в зависимости от используемого mvvm-фреймворка, и проверять, если у нас EmptyStateViewModel/ErrorStateViewModel null, то скрывать соответствующую вьюху. Для этого в нашем случае использовался простой метод SetViewModel.Для андроида тут все просто, при задании для View ее ViewModel мы установим View уже существующий ViewState из коробки. Если ViewModel null - тогда попросту задаем ViewState Gone, если существует - то Visible:
public void SetViewModel(EmptyStateViewModel viewModel)
{
ViewModel = viewModel;
View.Visibility = viewModel != null ? ViewStates.Visible : ViewStates.Gone;
}
Для iOS немного сложнее - необходимо деактивировать constraints для вьюхи, а только потом - прятать ее. Для начала добавим enum, аналогичный стандартному из Android.
public void SetViewModel(EmptyStateViewModel viewModel)
{
ViewModel = viewModel;
View.SetVisibility(viewModel != null ? ViewStates.Visible : ViewStates.Gone);
}
Нам потребуется несколько extension методов
public static void SetVisibility(this UIView view, ViewVisibility visibility)
{
var constraints = GetViewConstraints(view) ?? new NSLayoutConstraint[] {};
if (visibility == ViewVisibility.Gone)
{
SaveViewConstraints(view, constraints);
NSLayoutConstraint.DeactivateConstraints(constraints);
view.Hidden = true;
return;
}
if (visibility == ViewVisibility.Visible)
{
SaveViewConstraints(view, null);
NSLayoutConstraint.ActivateConstraints(constraints);
view.Hidden = false;
return;
}
}
Тут мы в случае установки ViewVisibility.Gone предварительно сохраняем constraints нашей view и деактивируем их, а при включении видимости - наоборот достаем предварительно сохраненные constraints, обнуляем сохранение, а затем активируем их.
private static NSLayoutConstraint[] GetViewConstraints(UIView view)
{
return view.GetAssociatedObject<NSMutableArray<NSLayoutConstraint>>(Key)?.ToArray() ??
view.Superview?.Constraints
.Where(constraint => (constraint.FirstItem?.Equals(view) == true) || constraint.SecondItem.Equals(view))
.ToArray();
}
private static void SaveViewConstraints(UIView view, NSLayoutConstraint[] constraints)
{
NSMutableArray<NSLayoutConstraint> viewConstraints = null;
if (constraints.Length > 0)
{
viewConstraints = new NSMutableArray<NSLayoutConstraint>();
viewConstraints.AddObjects(constraints);
}
view.SetAssociatedObject(Key, viewConstraints, AssociationPolicy.RetainNonAtomic);
}
Первый метод позволяет достать предварительно сохраненные constraints, если такие есть, либо же, если нет, получить текущие. Если же родительский view отсутствует, то вернется null.Второй метод сохранит текущие constraints, чтоб затем их можно было восстановить.Таким образом получилось сделать более приятные экраны с отсутствующими данными, либо же экраны состояния ошибок.PS - первая статья на Хабре, потому не судите строго. Но нужно же с чего-то начать.
===========
Источник:
habr.com
===========
Похожие новости:
- [Java, Разработка мобильных приложений, Разработка под Android] Android Bluetooth Low Energy (BLE) — готовим правильно, часть #1 (перевод)
- [Java, C++, Разработка под Android] Android interop with SWIG (a guide). From simple to weird. Part 1 — simple
- [Системное администрирование, Разработка под iOS, Разработка под MacOS] Безопасный downgrade macOS Big Sur (без 1008F)
- [Разработка игр, C#, Unity, Дизайн игр] Домино на Unity
- [Разработка под iOS, Разработка мобильных приложений, Swift, Аналитика мобильных приложений] Почему я не могу найти Яндекс.Такси через системный поиск на iPhone?
- [Python, C++, C#, Математика, Профессиональная литература] С каких книг можно начать изучать программирование (Python, C#, C++, Java, Lua, …)
- [Разработка под iOS, Objective C, Swift] Memory Management: ARC vs MRC в iOS
- [Программирование, Разработка игр, WebGL, Прототипирование, Godot] Как собрать паука в Godot, Unigine или PlayCanvas
- [Ненормальное программирование, .NET, C#] «Duck typing» и C#
- [JavaScript, Программирование, C#, Rust] Вышла версия 1.0 библиотеки для управления секс-игрушками Buttplug
Теги для поиска: #_razrabotka_pod_ios (Разработка под iOS), #_razrabotka_pod_android (Разработка под Android), #_c#, #_xamarin, #_empty_screen, #_xamarin, #_pustoj_ekran (Пустой экран), #_razrabotka_pod_ios (
Разработка под iOS
), #_razrabotka_pod_android (
Разработка под Android
), #_c#, #_xamarin
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 25-Ноя 07:51
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
С чего все началосьОчень часто, работая с Enterprise приложениями, приходится сталкиваться с подобными экранами при отсутствии получаемых данных с бекенда, когда нам на экране попросту нужно отобразить список. Связано это обычно с тем, что либо дизайнер отсутствует вовсе, и все варфреймы идут в виде набросков от заказчика, либо же дизайнер есть, но этот элемент для заказчика считается не столь важным, и его попросту игнорируют.Первые шаги к улучшению ситуацииСтолкнувшись с подобной ситуацией в очередной раз, я понял, что меня это раздражает даже как разработчика. Потому я направился к дизайнеру с этим вопросом, и по итогу мы пришли к более информативному и приятному для глаз варианту. Сделали для этого вью-модель, и соответствующие вьюхи под каждую платформу. Ниже приведу код вью-модели, платформенный UI-код не буду постить из-за громоздкости. public class EmptyStateViewModel : ViewModel
{ public EmptyStateViewModel(string image, string title, string description) { Image = image; Title = title; Description = description; } public string Image { get; } public string Title { get; } public string Description { get; } } public class ErrorStateViewModel : EmptyStateViewModel
{ public ErrorStateViewModel(string image, string title, string description, string actionTitle, Command actionCommand) : base(image, title, description) { ActionTitle = actionTitle; ActionCommand = actionCommand; } public string ActionTitle { get; } public Command ActionCommand { get; } } public static class OverlayFactory
{ public static T None<T>() where T : EmptyStateViewModel { return null; } public static EmptyStateViewModel CreateCustom(string image, string title, string description) { return new EmptyStateViewModel(image, title, description); } public static ErrorStateViewModel CreateCustom(string image, string title, string description, string actionTitle, Command actionCommand) { return new ErrorStateViewModel(image, title, description, actionTitle, actionCommand); } } public class SomeViewModel : BaseViewModel
{ private IItemsLoadingService _itemsLoadingService; public SomeViewModel(IItemsLoadingService itemsLoadingService) { _itemsLoadingService = itemsLoadingService; } public ObservableCollection<ItemViewModel> Items { get; } = new ObservableCollection<ItemViewModel>(); public EmptyStateViewModel EmptyState { get; protected set; } public ErrorStateViewModel ErrorState { get; protected set; } public override async Task InitializeAsync() { await base.InitializeAsync(); await LoadItemsAsync(); } private async Task LoadItemsAsync() { try { var result = await _itemsLoadingService.GetItemsAsync(); var items = result.ToList(); ErrorState = OverlayFactory.None<ErrorStateViewModel>(); if (items.Count == 0) { EmptyState = OverlayFactory.CreateCustom("img_empty_state", "Title", "Description"); } else { EmptyState = OverlayFactory.None<ErrorStateViewModel>(); // Add items to list } } catch { ErrorState = OverlayFactory.CreateCustom("img_error_state", "Title", "Description", "Retry", new Command(() => LoadItemsAsync)); } } } public void SetViewModel(EmptyStateViewModel viewModel)
{ ViewModel = viewModel; View.Visibility = viewModel != null ? ViewStates.Visible : ViewStates.Gone; } public void SetViewModel(EmptyStateViewModel viewModel)
{ ViewModel = viewModel; View.SetVisibility(viewModel != null ? ViewStates.Visible : ViewStates.Gone); } public static void SetVisibility(this UIView view, ViewVisibility visibility)
{ var constraints = GetViewConstraints(view) ?? new NSLayoutConstraint[] {}; if (visibility == ViewVisibility.Gone) { SaveViewConstraints(view, constraints); NSLayoutConstraint.DeactivateConstraints(constraints); view.Hidden = true; return; } if (visibility == ViewVisibility.Visible) { SaveViewConstraints(view, null); NSLayoutConstraint.ActivateConstraints(constraints); view.Hidden = false; return; } } private static NSLayoutConstraint[] GetViewConstraints(UIView view)
{ return view.GetAssociatedObject<NSMutableArray<NSLayoutConstraint>>(Key)?.ToArray() ?? view.Superview?.Constraints .Where(constraint => (constraint.FirstItem?.Equals(view) == true) || constraint.SecondItem.Equals(view)) .ToArray(); } private static void SaveViewConstraints(UIView view, NSLayoutConstraint[] constraints) { NSMutableArray<NSLayoutConstraint> viewConstraints = null; if (constraints.Length > 0) { viewConstraints = new NSMutableArray<NSLayoutConstraint>(); viewConstraints.AddObjects(constraints); } view.SetAssociatedObject(Key, viewConstraints, AssociationPolicy.RetainNonAtomic); } =========== Источник: habr.com =========== Похожие новости:
Разработка под iOS ), #_razrabotka_pod_android ( Разработка под Android ), #_c#, #_xamarin |
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 25-Ноя 07:51
Часовой пояс: UTC + 5