Getting-started

Fundamentals

Advanced

Platforms

Plugins

Samples

Tutorials

Upgrading

Contributing

iOS View Presenter

Edit

View Presenter Overview

The default presenter for iOS named MvxIosViewPresenter offers out of the box support for the following navigation patterns / strategies:

  • Stack navigation
  • Tabs
  • SplitView
  • Modal
  • Modal navigation
  • Nested modals
  • Popover

Also if your app needs another kind of presentation mode, you can easily extend it!

Presentation Attributes

The presenter uses a set of PresentationAttributes to define how a view will be displayed. The existing attributes are:

MvxRootPresentationAttribute

Used to set a view as Root. You should use this attribute over the view class that will be the root of your application (your app can have several root views, one at a time). The view root can be one of the following types:

  • MvxViewController
  • MvxTabBarViewController (actually implementing IMvxTabBarViewController is enough)
  • MvxSplitViewController (actually implementing IMvxSplitViewController is enough)

If you want to initiate a stack navigation, just set the attribute member WrapInNavigationController to true.

MvxChildPresentationAttribute

Used to set a view as a child. You should use this attribute over a view class that will be displayed inside a navigation stack (modal or not). The view class can decide if wants to be displayed animated or not through the attribute member Animated (the default value is true). If your app uses a TabBarController as a child ViewController of a “master” NavigationController, you can decide whether to display a new child ViewController inside a Tab of the TabBarViewController (assuming that Tab is a NavigationController) or to display it as a child of the “master” NavigationController. You can take control of this behavior by overriding MvxTabBarController.ShowChildView.

MvxModalPresentationAttribute

Used to display a view as Modal. You should use this attribute over a view class to present the view as a modal. There are several attribute members that the view class can customize:

Name Type Description
WrapInNavigationController bool If set to true, a modal navigation stack will be initiated (following child presentations will be displayed inside the modal stack). The default value is false.
ModalPresentationStyle UIModalPresentationStyle Corresponds to the ModalPresentationStyle property of UIViewController. The default value is UIModalPresentationStyle.FullScreen.
ModalTransitionStyle UIModalTransitionStyle Corresponds to the ModalTransitionStyle property of UIViewController. The default value is UIModalTransitionStyle.CoverVertical.
PreferredContentSize CGSize Corresponds to the PreferredContentSize property of UIViewController. The property works for iPad only.
Animated bool If set to true, the presentation will be animated. The default value is true.

MvxPopoverPresentationAttribute

Used to display a view as Popover. You should use this attribute over a view class to present the view as a popover. There are several attribute members that the view class can customize:

Name Type Description
WrapInNavigationController bool If set to true, a popover navigation stack will be initiated (following child presentations will be displayed inside the popover stack). The default value is false.
PreferredContentSize CGSize Corresponds to the PreferredContentSize property of UIViewController.
PermittedArrowDirections UIPopoverArrowDirection Corresponds to the PermittedArrowDirections property of UIPopoverPresentationController.
Animated bool If set to true, the presentation will be animated. The default value is true.

iOS requires popovers to have a source view in order to determine arrow direction and view position. Each time before calling Navigate in your view model, make sure you set the sourve view using the IMvxPopoverPresentationSourceProvider.

Here is an example of how this can be done using an IMvxInteraction.

public class FirstViewModel : MvxViewModel
{
    /// <summary>
    /// Request used to setup popover presentation source on iOS.
    /// </summary>
    private readonly MvxInteraction<Action> _iosSetPopoverSourceInteraction = new MvxInteraction<Action>();
    public IMvxInteraction<Action> IosSetPopoverSourceInteraction => _iosSetPopoverSourceInteraction;
    
    private void ShowPopover()
    {
        //In real life, make sure to check running on iOS, otherwise call Navigate as normal!
        _iosSetPopoverSource.Raise(() =>
        {
            _navigationService.Navigate<PopoverViewModel>();
        });
    }
}
public class FirstViewController : MvxViewController<FirstViewModel>
{
    /// <summary>
    /// Interaction needed to setup popover presentation source when displaying
    /// the results popover.
    /// </summary>
    private IMvxInteraction<Action> _setPopoverSourceInteraction;
    public IMvxInteraction<Action> SetPopoverInteractionSourceInteraction
    {
        get => _setPopoverSourceInteraction;
        set
        {
            if (_setPopoverSourceInteraction != null)
                _interaction.Requested -= OnSetPopoverSourceInteractionRequested;
            
            _setPopoverSourceInteraction = value;
            _setPopoverSourceInteraction.Requested += OnSetPopoverSourceInteractionRequested;
        }
    }
    
    public override void ViewDidLoad()
    {
        base.ViewDidLoad();
        
        var set = CreateBindingSet();
        set.Bind(this)
            .For(v => v.SetPopoverSourceInteraction)
            .To(vm => vm.IosSetPopoverSourceInteraction)
            .OneWay();
        set.Apply();
    }
    
    /// <summary>
    /// Prepares a popover to be presented.
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="eventArgs">Value is true if can enter</param>
    private void OnSetPopoverInteractionRequested(object sender, MvxValueEventArgs<Action> eventArgs)
    {
        var provider = Mvx.IoCProvider.Resolve<IMvxPopoverPresentationSourceProvider>();
        provider.SourceView = View;    
    }
}
[MvxPopoverPresentationAttribute(PermittedArrowDirections = 0)] // allow any arrow direction
public class PopoverViewController : MvxViewController<PopoverViewModel>
{
    ...
}

MvxTabPresentationAttribute

This attribute is only useful (and should only be used) when the current Root view is a IMvxTabBarViewController. By using it over a view class, the presenter will show the view as a Tab inside the TabBarController. The presentation can be highly customized through this attribute members:

Name Type Description
TabName string Defines the title of the tab that will be displayed below the tab icon. It has to be a magic string, but it can be for example a key to a localized string that you can grab overriding the method SetTitleAndTabBarItem in your TabBarController.
TabIconName string Defines the name of the resource that will be used as icon for the tab. It also has to be a magic string, but same as before, your app can take control of what happens by overriding the method SetTitleAndTabBarItem in your TabBarController.
TabSelectedIconName string Defines the name of the resource that will be used as icon for the tab when it becomes selected. It also has to be a magic string, your app can take control of what happens by overriding the method SetTitleAndTabBarItem in your TabBarController.
WrapInNavigationController bool If set to true, the view will be wrapped in a MvxNavigationController, which will allow the tab to have its own navigation stack. Important note: When the current Root is a TabBarController and there is no current modal navigation stack, child presentations will be tried to be displayed in the current selected Tab.
TabAccessibilityIdentifier string Corresponds to the UIViewController.View AccessibilityIdentifier property.

MvxSplitViewPresentationAttribute

This attribute is only useful (and should only be used) when the current Root view is a IMvxSplitViewController. The attribute can be used to set the master and detail views of a SplitView.

There is an attribute member that can be used to customize the presentation:

Name Type Description
WrapInNavigationController bool If set to true, the view will be displayed wrapped in a MvxNavigationController, which will allow you to set a title, which is the most common scenario of SplitView. The default value is therefore true on detail and false on master.
Position MasterDetailPosition Can be set to Master to show as the root of the SplitView. The default value is Detail and this will push the viewcontroller onto the detail stack.

Views without attributes: Default values

  • If the initial view class of your app has no attribute over it, the presenter will assume stack navigation and will wrap your initial view in a MvxNavigationController.
  • If a view class has no attribute over it, the presenter will assume animated child presentation and will display the view in the current navigation stack (could be modal or not).

Override a presentation attribute at runtime

To override a presentation attribute at runtime you can implement the IMvxOverridePresentationAttribute in your view controller and determine the presentation attribute in the PresentationAttribute method like this:

[MvxFromStoryboard("Main")]
public partial class LoginView : MvxViewController<LoginViewModel>, IMvxOverridePresentationAttribute
{

    public MvxBasePresentationAttribute PresentationAttribute(MvxViewModelRequest request)
    {
        if (request.PresentationValues != null)
        {
            if (request.PresentationValues.ContainsKey("NavigationMode") &&
                request.PresentationValues["NavigationMode"] == "Modal")
            {
                return new MvxModalPresentationAttribute
                {
                    WrapInNavigationController = true,
                    ModalPresentationStyle = UIModalPresentationStyle.OverFullScreen,
                    ModalTransitionStyle = UIModalTransitionStyle.CrossDissolve
                };
            }
        }

        return null;
    }
}

As you can see in the code snippet, you will be able to make your choice using a MvxViewModelRequest. This object will contain the PresentationValues dictionary alongside other properties. This way your ViewModel can let the presentation (the view) know of a custom case in which it should be opened.

If you return null from the PresentationAttribute method, the ViewPresenter will fallback to the attribute used to decorate the view. If the view is not decorated with any presentation attribute, then it will use the default attribute instead.

Hint: Be aware that this.ViewModel property will be null during PresentationAttribute. If you want to have the ViewModel instance available, you need to use the MvxNavigationService and cast the request parameter to MvxViewModelInstanceRequest.

Extensibility

The presenter is completely extensible! You can override any attribute and customize attribute members.

You can also define new attributes to satisfy your needs. The steps to do so are:

  1. Add a new attribute that extends MvxBasePresentationAttribute
  2. Subclass MvxIosViewPresenter and make it the presenter of your application in Setup.cs (by overriding the method CreatePresenter).
  3. Override the method RegisterAttributeTypes and add a registry to the dictionary like this:
_attributeTypesToShowMethodDictionary.Add(
    typeof(MyCustomModePresentationAttribute),
    (vc, attribute, request) => ShowMyCustomModeViewController(vc, (MyCustomPresentationAttribute)attribute, request));
  1. Implement a method that takes care of the presentation mode (in the example above, ShowMyCustomModeViewController).
  2. Use your attribute over a view class. Ready!

Sample please!

You can browse the code of the Playground (iOS project) to see this presenter in action.