Bug 34824 - On iOS, the MasterDetailPage.IsPresented property can fail to be updated correctly
Summary: On iOS, the MasterDetailPage.IsPresented property can fail to be updated corr...
Status: CONFIRMED
Alias: None
Product: Forms
Classification: Xamarin
Component: iOS ()
Version: 2.3.3
Hardware: Macintosh Mac OS
: Normal normal
Target Milestone: ---
Assignee: Bugzilla
URL:
Depends on:
Blocks:
 
Reported: 2015-10-13 11:53 UTC by David Schwarz
Modified: 2017-08-30 16:54 UTC (History)
5 users (show)

Tags: ac, ios, ipad, masterdetailpage
Is this bug a regression?: ---
Last known good build:


Attachments
Repro Solution (83.54 KB, application/zip)
2015-10-16 15:41 UTC, Jimmy [MSFT]
Details
unified sample (74.12 KB, application/zip)
2017-08-30 16:53 UTC, David Ortinau [MSFT]
Details


Notice (2018-05-24): bugzilla.xamarin.com is now in read-only mode.

Please join us on Visual Studio Developer Community and in the Xamarin and Mono organizations on GitHub to continue tracking issues. Bugzilla will remain available for reference in read-only mode. We will continue to work on open Bugzilla bugs, copy them to the new locations as needed for follow-up, and add the new items under Related Links.

Our sincere thanks to everyone who has contributed on this bug tracker over the years. Thanks also for your understanding as we make these adjustments and improvements for the future.


Please create a new report for Bug 34824 on Developer Community or GitHub if you have new information to add and do not yet see a matching new report.

If the latest results still closely match this report, you can use the original description:

  • Export the original title and description: Developer Community HTML or GitHub Markdown
  • Copy the title and description into the new report. Adjust them to be up-to-date if needed.
  • Add your new information.

In special cases on GitHub you might also want the comments: GitHub Markdown with public comments

Related Links:
Status:
CONFIRMED

Description David Schwarz 2015-10-13 11:53:21 UTC
Builds: Using Xamarin iOS 9.0.1.29 (2015-09-25 18:08:44-0400), Forms 1.5.0.0

------

To reproduce this issue, which occurs only on iOS tablets (first-gen iPad Mini in my case):

1) Set a MasterDetailPage as the main page of an App.
2) Set its MasterDetailBehavior to Popover.
3) On the Detail page, provide a button whose Command sets MasterDetailPage.IsPresented to true.

4) The normal behavior is that when the user taps the button, the Master page slides out, and when they tap outside the Master, it slides back in, and MasterDetailPage.IsPresented is correctly updated to false.

If a user taps rapidly, the second tap can occur while the Master is still sliding out.  When this happens, the MasterDetailPage ends up in an inconsistent state.  The Master is hidden, but MasterDetailPage.IsPresented remains true.  Also, the Master still responds to an edge swipe-right gesture to reappear, but swiping it between visible and hidden does not cause MasterDetailPage.IsPresented to update.  The button's attempts to display the Master by setting IsPresented to true do nothing because it is already true.

I can provide a small sample app that illustrates the problem.

------

I did some debugging, and here's what's going on:

Normally, when the Master UIViewController is dismissed by a tap outside of it, its WillDisappear event fires, and the TabletMasterDetailRenderer updates both the native and Forms objects appropriately in that event's handler.

When the Master is dismissed before it has finished appearing, its UIViewController.WillDisappear event never fires, so the states of the Forms and native controls become inconsistent.

------

Playing with disassembly, I also figured out one possible fix in the TabletMasterViewRenderer:

Under the covers, displaying and hiding the Master is executed by triggering the UISplitViewController DisplayModeButtonItem behavior.  This causes the UISplitViewController to change its DisplayMode between Hidden and Overlay.  The DisplayMode change is the only event I could find that always fires when tap-dismissing the Master, even if it hasn't finished sliding out yet.

Through disassembly and reflection kludgery, I determined that I could fix the problem by adding a WillChangeDisplayMode() override to TabletMasterDetailRenderer.InnerDelegate.  In this handler, I execute the following Action, which I added as a parameter the the InnerDelegate constructor:

Action action = () => {
   masterVisible = false;
   ((IElementController)this.Element).SetValueFromRenderer(MasterDetailPage.IsPresentedProperty, false);
};

I'm not suggesting this as a good solution, just offering it as a possible starting point for investigation for the Xamarin Team.

------

Partial workarounds that don't require disassembly and reflection hackery:

1) Instead of making the button set MasterDetailPage.IsPresented = true, have it set it to !MasterDetailPage.IsPresented.  This causes one unresponsive button tap that gets the Forms and native controls back in sync, but the next tap will correctly display the Master.

2) Derive a class from MasterDetailPage, and set this derived class's renderer to PhoneMasterDetailRenderer, regardless of the device's UIUserInterfaceIdiom.  The phone renderer isn't as slick and visually appealing as the tablet renderer, but it is immune to this issue.  Unlike option 1), this solution depends on the current (Forms 1.5) implementation of the renderer.
Comment 1 Jimmy [MSFT] 2015-10-16 15:41:38 UTC
Created attachment 13386 [details]
Repro Solution

I've attached a sample solution that I can reproduce this issue with. This issue is still present in Forms 1.5.1-pre2

### Steps to Reproduce
1. Download and run the attached solution. Reproduction is easier to do on a iPad.

2. Open the Master view and select “Try yourself”

3. Repeatedly press the “Click here” button quickly


### Environment Info
=== Xamarin Studio ===

Version 5.9.7 (build 22)
Installation UUID: 94ce5106-6a72-4691-b34e-cd5857b1db66
Runtime:
	Mono 4.0.4 ((detached/d481017)
	GTK+ 2.24.23 (Raleigh theme)

	Package version: 400040004

=== Apple Developer Tools ===

Xcode 7.0 (8227)
Build 7A220

=== Xamarin.Android ===

Version: 5.1.7.12 (Business Edition)
Android SDK: /Users/jimmygarrido/Library/Developer/Xamarin/android-sdk-macosx
	Supported Android versions:
		2.3   (API level 10)
		4.0.3 (API level 15)
		4.1   (API level 16)
		4.4   (API level 19)
		5.0   (API level 21)
		5.1   (API level 22)
		6.0   (API level 23)
Java SDK: /usr
java version "1.7.0_79"
Java(TM) SE Runtime Environment (build 1.7.0_79-b15)
Java HotSpot(TM) 64-Bit Server VM (build 24.79-b02, mixed mode)

=== Xamarin Android Player ===

Version: Unknown version
Location: /Applications/Xamarin Android Player.app

=== Xamarin.iOS ===

Version: 9.2.0.84 (Business Edition)
Hash: b5396c2
Branch: master
Build date: 2015-09-30 15:22:15-0400

=== Xamarin.Mac ===

Version: 2.0.2.111 (Business Edition)

=== Build Information ===

Release ID: 509070022
Git revision: 6bd1f169df44ca96addf8a035316c535a4fa46fa
Build date: 2015-09-30 12:30:15-04
Xamarin addins: 1c3e5c0859bdfec0ecd481a57ad6c03bc22f5536

=== Operating System ===

Mac OS X 10.11.0
Darwin Jimmys-MacBook-Pro.local 15.0.0 Darwin Kernel Version 15.0.0
    Wed Aug 26 16:57:32 PDT 2015
    root:xnu-3247.1.106~1/RELEASE_X86_64 x86_64
Comment 2 Jimmy [MSFT] 2015-10-16 15:44:56 UTC
Updating version and marking as CONFIRMED per info in comment #1
Comment 3 Radoslav Yordanov 2016-10-12 06:06:40 UTC
Hey there,
I have experienced the same problem using
Xamarin.iOS Version: 10.0.1.10 and Forms 2.3.2.

@David Schwarz is it possible that you provide me with the code you used to fix the bug, because I haven't used reflection before?
Comment 4 David Ortinau [MSFT] 2017-08-30 16:53:59 UTC
Created attachment 24485 [details]
unified sample

It appears there's a race condition that can be triggered by rapidly toggling the IsPresented and putting it into a negative state. 

Confirmed bad on 2.4.0.269-pre2, iPad, iOS 10.3.1.

Attached an updated project.