UIPresentationController Hell

For my current project we are doing a direct port of four cross-platform hybrid mobile apps, built with Rho Mobile, to native iOS and Android.

Note: I do not condone the UI of this application. I wish we could have re-designed the apps to follow the respective platform design guidelines

Note #2: We are porting this app to native using the Xamarin platform. I’d rather be writing Swift, but you can’t deny the value of shared code. All code examples in this post will be in C#. Blog post coming soon on my thoughts of Xamarin.

As part of this port I needed to create a reusable modal component. To do this, I decided to learn about and use the (relatively) new UIPresentationControllers.

Making a custom UIPresentationController was pretty easy. I generally followed this tutorial I found online.

At this point I ran into two issues duplicating functionality with the presentation view:

  1. Changing the size of the presented view after the initial animation
  2. Transitioning between views within the presented view

The UIPresentationController APIs aren’t the greatest and my solutions to these issues felt like hacks.

Changing Sizes

First off, the PresentationController property on the controller being presented pointed correctly to the custom presentation controller, but the ContainerView and PresentedView’s were null. To get around this I created a base class with Actions that can be set by the presentation controller and called by subclasses:

public partial class BaseDialogController : UIViewController
    {
        public Action<nfloat> UpdateHeight { get; set; }

        public BaseDialogController(IntPtr handle)
            : base(handle)
        {
            ModalPresentationStyle = UIModalPresentationStyle.Custom;
            TransitioningDelegate = new DialogPresentationManager();
        }
    }
_presentedViewController.UpdateHeight = UpdateToHeight;

Anytime the view controller needs to update its height, it can call this method (preferably inside a UIView.Animate block).

Issue #1, solved.

Height Change

Transitioning Views

For one of my popups, I needed to transition between two views via a card flip to show item details, like this:

Android Card Spin

I searched high and low for an elegant, pragmatic approach to doing this with a PresentationController. I was unsuccessful.

I initially tried using the transitionFromView(_:toView:duration:options:completion:) method in the presentation controller to animate from the PresentedView to the view for the back of the card. Sadly, this animates the entire view, including the background.

Background Flips Also!

I discovered that using the sibling transitionWithView(_:duration:options:animations:completion:) method worked to flip the view as I wanted, but didn't allow me to cleanly transition to a new view.

Card Flip

So then how do I transition to the next view during the animation? What I ended up doing was embedding the view controller into a navigation controller. Then I could leverage the navigation transitions to transition views. Since the app was already using a UINavigationController, I had to embed the new one in a container view.

Dialog Storyboard

There is a point during the card-flip animation where no view is visible (half-way). So I want to push a new view onto the stack in the exact middle of the animation1. My assumption was that I could create a custom navigation transition animation with a delay that would then transition with no duration.

I was able to create a transition animation that did just that. I created a property in my class with this animation.

private CATransition CardFlipTransition
        {
            get
            {
                var transition = new CATransition();
                transition.Duration = 0;
                transition.BeginTime = View.Layer.ConvertTimeFromLayer(CAAnimation.CurrentMediaTime(), null) + (CARD_FLIP_ANIMATION / 2);
                transition.Type = CAAnimation.TransitionFade;
                return transition;
            }
        }

The important parts here are Duration and BeginTime. The duration is 0, so the “animation” will be instant. The begin time simply waits to start the duration until half of the card flip animation time.

It turns out that my assumption was correct. The transition appears seamless!

Final Animation

Helpful Links

1: This approach would only work if the default iOS card flip animation had a linear timing curve (no view was visible is at the half-way point of the duration).