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:
- Changing the size of the presented view after the initial animation
- 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 Action
s 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.
Transitioning Views
For one of my popups, I needed to transition between two views via a card flip to show item details, like this:
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.
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.
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.
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!
Helpful Links
- Presentation Controllers and Adaptive Presentations
- Swifty Presenters
- iOS8 Day-by-Day :: Day 24 :: Presentation Controllers
- Time Warp in Animation
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). ↩