iOS – MvvmCross Presenter dla UISegmentedControl

Cześć!

Jakiś czas temu informowałem o tym, że pracuje nad dwoma rzeczami dla programistów Xamarin. Dzisiaj chcę Ci zaprezentować jedną z nich i będzie ona dotyczyła systemu iOS. Jest to pierwsza wersja, więc standardowo należy spodziewać się problemów. A nikt tak dobrze nie zweryfikuje produktu jak użytkownicy,

No, ale cóż to jest? Framework MvvmCross pozwala nam na używanie atrybutów w widokach, aby bez dodatkowego kodu po naszej stronie, dodać np. taby dla View Pagera na Androidzie, czy do NavigationView na iOS. I to wszystko we współpracy z flow View Modeli. Jakiś czas temu pracowałem nad funkcjonalnością, która miała umożliwiać przełączanie się pomiędzy kilkoma widokami za pomocą UISegmentedControl. Zależało mi jak najbardziej generycznym napisaniu funkcjonalności, ponieważ wiedziałem, że już wkrótce będę miał do stworzenia podobny widok, w którym z powodzeniem znowu wykorzystam UISegmentedControl.

Chciałem wykorzystać potencjał MvvmCross, dlatego też stworzyłem własny prezenter i odpowiednie atrybuty. Chciałem, aby działało to w następujący sposób:

  1. Dodaje widok, który ma zawierać kontrolkę UISegmentedControl. Jest on parentem dla wewnętrznych widoków. Dziedziczy on po specjalnie przygotowany ViewControllerze i posiada dodatkowy kontener dla widoków (UIView).
  2. Każdy z widoków, które mają być przełączane zostają oznaczone dodatkowym atrybutem, a jednym z jego parametrów jest typ modelu widoku, tego widoku, który jest tym, który zawiera UISegmentedControl (punkt 1).
  3. Logika odpowiedniego połączenia widoków jest osadzona w customowym prezenterze.

Jest to funkcjonalność, której nie znajdziesz w standardowej implementacji MvvmCross. Postanowiłem to rozwiązanie udostępnić w ramach biblioteki open source. Sądzę, że znajdą się osoby, które będą chciały z tego skorzystać.

Konfiguracja

Rzeczy, które należy wykonać nie są trudne. Poniżej opisuje krok po kroku z czego musisz skorzystać. W razie problemów, w repozytorium projektu dodałem sample, z którego możesz skorzystać – link na końcu artykułu.

1. Zainstaluj paczkę NuGet

Zainstaluj paczkę NuGet w projekcie iOS swojego rozwiązania.

https://www.nuget.org/packages/KrzBB.SegmentedControlPresenter_MVVMCross.iOS/

2. Dodaj widok, w którym osadzisz UISegmentedControl

Dodaj UIViewController, a w jego definicji widoku dodaj UISegmentedControl oraz zwykły UIView, który będzie służył za kontener dla widoków. Oznacz go atrybutem [SegmentedControlPresentation] oraz dodaj dziedziczenie po SegmentedTabBarViewControllerBase<[VM]>. Środowisko wymusi na Tobie implementację dwóch metod abstrakcyjnych. Twoim zadaniem jest zwrócenie UISegmentedButton oraz kontenera dla widoków. W poniższym listingu w metodzie ViewDidLoad wywołuje komendę, której zadaniem jest odpalenie akcji w modelu widoku odpowiedzialnej za nawigację do widoków, które chcemy osadzić wewnątrz UIView. 

[MvxRootPresentation(WrapInNavigationController = true)]
[SegmentedControlPresentation]
public partial class RootView : SegmentedTabBarViewControllerBase
{
    private bool _isPresentedFirstTime = true;

    public RootView() : base("RootView", null)
    {
    }

    public override void ViewDidLoad()
    {
        base.ViewDidLoad();

        if (ViewModel != null && _isPresentedFirstTime)
        {
            _isPresentedFirstTime = false;
            ViewModel.ShowInitialViewModelsCommand.ExecuteAsync(null);
        }
    }

    protected override UIView GetContentView()
    {
        return ContentView;
    }

    protected override UISegmentedControl GetSegmentedControl()
    {
        return SegmentControl;
    }
}

3. Dodaj własny prezenter widoku, jeżeli jeszcze go nie posiadasz.

Poniżej przedstawiam przykład własnego prezentera. To co najważniejsze to  pole  klasy SegmentedControlPresenter, inicjalizacja niestandardowych atrybutów w metodzie RegisterAttributeTypes oraz wywołanie AddSegmentedControlViewPresenter na obiekcie prezentera w metodzie ShowRootViewController. Kilka słów wyjaśnienia. 

To ostatnie wywołanie zostało tutaj dodane, ze względu na to, że nasz UISegmentedControl znajduje się w widoku, który jest skonfigurowany jako RootView (atrybut MvxRootPresentation, kod wyżej). Jeżeli Twój UISegmentedControl znajduje się w innym rodzaju widoku – przesłoń inną metodę w prezenterze.

public class CustomViewPresenter : MvxIosViewPresenter
{
    private SegmentedControlPresenter _customPresenter;

    public CustomViewPresenter(IUIApplicationDelegate applicationDelegate, UIWindow window)
        : base(applicationDelegate, window)
    {
        _customPresenter = new SegmentedControlPresenter(this);
    }

    public override void RegisterAttributeTypes()
    {
        base.RegisterAttributeTypes();

        _customPresenter.RegisterAttributeTypes(AttributeTypesToActionsDictionary);
    }

    protected override async Task ShowRootViewController(UIViewController viewController, MvxRootPresentationAttribute attribute, MvxViewModelRequest request)
    {
        _customPresenter.AddSegmentedControlViewController(viewController, request);

        return await base.ShowRootViewController(viewController, attribute, request);
    }
}

4. Dodaj co najmniej dwa widoku pomiędzy, którymi chcesz się przełączać.

Dodaj dwa view controllery. I upiększ je atrybutem SegmentPresentation, wraz z odpowiednimi wartościami parametrów. Zwróć uwagę na SegmentedControlViewModelType. To dzięki niemu możemy odpowiednio powiązać logikę działania. To właśnie RootViewModel jest modelem widoku dla widoku, w którym znajduje się UISegmentedControl!

[SegmentPresentation(Text = "First", SegmentedControlViewModelType = typeof(RootViewModel))]
public partial class FirstTabView : MvxViewController
{
    public FirstTabView() : base("FirstTabView", null)
    {
    }

    public override void ViewDidLoad()
    {
        base.ViewDidLoad();
    }
}

5. Nawigacja

Dodaj nawigację do widoków, które dodałeś. Poniżej umieszczam wcześniej obiecaną akcję z commanda. Dzięki temu prezenter zarejestruje (dzięki dodanym atrybutom) odpowiednio widoki i umieści je w odpowiednich miejscach.

public IMvxAsyncCommand ShowInitialViewModelsCommand { get; private set; }
private async Task ShowInitialViewModels()
{
    var tasks = new List();
    tasks.Add(_navigationService.Navigate());
    tasks.Add(_navigationService.Navigate());
    await Task.WhenAll(tasks);
}

Powinno działać

Zapoznaj się z przykładowym projektem, który znajduje się w repozytorium. Jeżeli napotkasz jakiś problem czy nieścisłość, informuj mnie o tym. A może jakiś pomysł na ciekawą funkcjonalność w MvvmCross? Koniecznie się tym podziel!

GitHubhttps://github.com/krzbbaranowski/KrzBB.MvvmCross.iOS_SegmentedControl_MVVM 
NuGethttps://www.nuget.org/packages/KrzBB.SegmentedControlPresenter_MVVMCross.iOS/

Mam coś dla Ciebie

Zapisz się do mojego newslettera, a ja prześlę Ci zbiór kilkunastu praktycznych wskazówek dla programisty aplikacji mobilnych.

Menu