With the official release of iOS 13, I’ve found that as I move some of the apps I work on up to targeting this that various UI components don’t work as they used to. So far, this isn’t nearly as much of an issue as the transition from iOS 6 to iOS 7 (for those who remember) however it has come with its own hiccups here and there.
I’ve been using modal screens in apps for a long time now, very often for one simple purpose: it forces a user to make a decision. This is especially relevant when dealing with things like records being edited and either saving or abandoning changes.
To save the headache of tying into all the possible ways of navigating back from within a navigation stack, displaying a screen as modal has been a great way to solve this and not worry about the differences between Android physical buttons vs virtual (again, for those who remember) and new gestures like when Apple introduced the swipe from left to right to navigate back.
Now it seems, with Apple changing gestures that this has broken the paradigm again by displaying modals as sheets that the user can swipe away without any warning about losing potentially unsaved data.
This fundamentally broke a few apps that I ran up on iOS 13. Apple fortunately however has provided a way to explicitly display modals in full screen, the way they previously were, and the Xamarin.Forms team did a great job addressing this.
You can set the modal presentation style in XAML or C# to set this to what you want the modal behaviour to be like. This this is a very nice and clean approach:
XAML
1 2 3 4 5 |
<ContentPage ... xmlns:ios="clr-namespace:Xamarin.Forms.PlatformConfiguration.iOSSpecific;assembly=Xamarin.Forms.Core" ios:Page.ModalPresentationStyle="FormSheet"> ... </ContentPage> |
C#
1 2 3 4 5 6 7 8 9 10 11 12 |
using Xamarin.Forms.PlatformConfiguration; using Xamarin.Forms.PlatformConfiguration.iOSSpecific; ... public class iOSModalFormSheetPageCS : ContentPage { public iOSModalFormSheetPageCS() { On<iOS>().SetModalPresentationStyle(UIModalPresentationStyle.FormSheet); ... } } |
Microsoft provide good documentation here if you want to read more on this for projects where you can use this.
For several of my projects however, this wasn’t an option because they were on older versions of Xamarin.Forms that not all clients would would update for various reasons.
Another issue I faced on projects that I did update Xamarin.Forms for, was that (at the time of writing this) the awesome MVVM framework I use for some projects, Michael Ridland’s FreshMvvm for Xamarin.Forms, references Xamarin.Forms 3.4.0.1009999 that takes the iOS 13 default behaviour and displays modals using the over fullscreen sheet approach, which breaks screens that are intended to force the user to make a choice because they can simply swipe it away.
Thankfully, without too much effort, there is a solution that enables you to choose your modal presentation style with older Xamarin.Forms versions.
To get around this, in the end going back to the good old tried and true custom renderer approach and setting the presentation style in WillMoveToParentViewController for the NavigationRenderer does the trick:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
using System; using ControllingIos13ModalPresentationInXamarinForms.iOS.Renderers; using UIKit; using Xamarin.Forms; using Xamarin.Forms.Platform.iOS; [assembly: ExportRenderer(typeof(NavigationPage), typeof(NavigationPageRenderer))] namespace ControllingIos13ModalPresentationInXamarinForms.iOS.Renderers { public class NavigationPageRenderer : NavigationRenderer { public override void WillMoveToParentViewController(UIViewController parent) { try { if (parent != null) { if (UIDevice.CurrentDevice.CheckSystemVersion(13, 0) ) { // Obviously in a real application this would be more sophisticated or concrete here, but for // the purposes of this demo we're just doing a quick n dirty check to illustrate things parent.ModalPresentationStyle = App.UseIos13FullScreenModal ? UIModalPresentationStyle.FullScreen : UIModalPresentationStyle.Automatic; } } base.WillMoveToParentViewController(parent); } catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex); } } } } |
It’s not as elegant as the official Xamarin.Forms way, but this should hopefully work if like me you find issues or you’re on older versions of Xamarin.Forms through third-party libraries or have clients who don’t want to upgrade because of test footprint or whatever reason.
This was driving me mad for a couple of hours with some projects so hopefully this helps save some headaches if you find yourself in a similar situation.
Also if you want to play around with the different modal presentation styles using either approach, the different types are defined in this enum for Xamarin.Forms:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
[Unavailable (PlatformName.WatchOS, PlatformArchitecture.All, null)] [Native] public enum UIModalPresentationStyle : long { None = -1L, [Introduced (PlatformName.iOS, 13, 0, PlatformArchitecture.All, null)] Automatic = -2L, FullScreen = 0L, [Unavailable (PlatformName.TvOS, PlatformArchitecture.All, null)] PageSheet = 1L, [Unavailable (PlatformName.TvOS, PlatformArchitecture.All, null)] FormSheet = 2L, CurrentContext = 3L, Custom = 4L, OverFullScreen = 5L, OverCurrentContext = 6L, [Unavailable (PlatformName.TvOS, PlatformArchitecture.All, null)] Popover = 7L, BlurOverFullScreen = 8L } |
As usual, if you want the entire working example, please have a look here on GitHub.
Edit 20 Oct 2019: As pointed out by Gerald Versluis below, if you do want to use the sheet display in iOS 13+ and not the above full screen mode that was the default, there is, at time of writing this, an issue in Xamarin.Forms where the swipe down to dismiss is not detected on iOS 13+ and thus the modal stack becomes out of sync and further calls to PushModalAsync() won’t work. To get around this, you can use the following code in the modal pages to ensure the stack integrity is maintained.
1 2 3 4 5 6 7 8 9 |
protected override async void OnDisappearing() { base.OnDisappearing(); if (Navigation.ModalStack.Count > 0) { await Navigation.PopModalAsync(); } } |
Also worth noting that from the comments in the GitHub issue that Gerald has flagged below that he has a draft fix ready so hopefully this will be in a version of Xamarin.Forms soon so that people who are in a position to update and not held back due to the reasons mentioned above, will be able to use both approaches easily with the latest Xamarin.Forms soon :).
Unless I missed something in your post, please do note that the model close is not detected at this time by Forms when you swipe it down to close. There is an issue (and pr) for this here; https://github.com/xamarin/Xamarin.Forms/issues/7878. You might want to take the code from that and take it in your custom renderer to make that work too 🙂
I hadn’t actually noticed that, Gerald, good find. My intention was to get away from that sheet display entirely and show full screen so that the user is forced to make a choice (e.g. Save or Cancel) and not have to worry about platform specifics handling the different ways to close a screen. A common thing I find is the whole cancel and the client wants “Do you want to save your changes?” sort of thing.
You’ve raised a valid point though for people who do want to utilise this on iOS 13+ and are still held back on older versions of Xamarin.Forms (or current it sounds like at time of writing) so I’ve updated the post to include the code you’ve flagged.
Cheers for the feedback 🙂.
Thanks for your tutorial!
But how you can solve it ( disabling FormSheet presentation style ) if you are pushing one new view controller without navigation bar?
Its not possible to?
Thanks!
I haven’t actually tried that sorry. Generally when presenting modal views I’ve always had a navigation heading with buttons for things like Save/Close. Could you try controlling the sort of view you want with the NavigationPage.TitleView?
When I try and set ios:Page.ModalPresentationStyle=”FullScreen” in an effort to return iOS13 to the previous default it fails. I notice that ModalPresentationStyle does not appear to be available in my XAML. I get an error in the editor saying “The attachable property ‘ModalPresentationStyle’ was not found in type ‘Page’. This is despite the fact I can see that property in Object Browser. Do you get a similar warning?
Have you imported the namespace into your page?
<ContentPage xmlns=”http://xamarin.com/schemas/2014/forms”
xmlns:x=”http://schemas.microsoft.com/winfx/2009/xaml”
xmlns:ios=”clr-namespace:Xamarin.Forms.PlatformConfiguration.iOSSpecific;assembly=Xamarin.Forms.Core”
….
I have somehow the opposite problem. I would like my modals the be in the Form Sheet style. But even if I set it explicitly the are fullscreen on my iPhone11 Pro. Any idea how that is?