Bug 3803 - iOS 5.1 UISplitViewController.ShouldHideViewController binding "ignored"?
Summary: iOS 5.1 UISplitViewController.ShouldHideViewController binding "ignored"?
Status: RESOLVED NOT_ON_ROADMAP
Alias: None
Product: iOS
Classification: Xamarin
Component: Xamarin.iOS.dll ()
Version: 5.2
Hardware: Macintosh Mac OS
: --- normal
Target Milestone: Untriaged
Assignee: Sebastien Pouliot
URL:
Depends on:
Blocks:
 
Reported: 2012-03-07 20:08 UTC by CraigD
Modified: 2012-03-28 11:58 UTC (History)
2 users (show)

Tags:
Is this bug a regression?: ---
Last known good build:


Attachments
complete project. change bool to see it work/break in iOS5.1 (5.87 KB, application/zip)
2012-03-07 20:08 UTC, CraigD
Details
Error occurs with lambdas (7.20 KB, application/zip)
2012-03-11 19:12 UTC, CraigD
Details
Error does NOT occur with Delegate class (7.48 KB, application/zip)
2012-03-11 19:13 UTC, CraigD
Details
screenshot of lambdas code that errors (171.82 KB, image/png)
2012-03-11 19:14 UTC, CraigD
Details
screenshot of delegate subclass that works (148.01 KB, image/png)
2012-03-11 19:15 UTC, CraigD
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 on Developer Community or GitHub with your current version information, steps to reproduce, and relevant error messages or log files if you are hitting an issue that looks similar to this resolved bug and you do not yet see a matching new report.

Related Links:
Status:
RESOLVED NOT_ON_ROADMAP

Description CraigD 2012-03-07 20:08:41 UTC
Created attachment 1477 [details]
complete project. change bool to see it work/break in iOS5.1

I have a UISplitViewController sample, which worked as expected on iOS5.
The same sample on iOS5.1 doesn't seem to call  UISplitViewController.ShouldHideViewController BUT if you assign a delegate that implements bool ShouldHideViewController then that works.

so:

ShouldHideViewController = (svc, viewController, inOrientation) => {}
iOS5   -> popover in portrait; visible in landscape
iOS5.1 -> popover in both orientations (ERROR)

UISplitViewControllerDelegate bool ShouldHideViewController (svc, viewController, inOrientation) {}
iOS5   -> popover in portrait; visible in landscape
iOS5.1 -> popover in portrait; visible in landscape


not sure *why* the behaviour would differ... FYI, iOS5.1 is installed on iPad1. I'm *building* against iOS5.0 SDK (like any existing app would have).

============== the code ==================

using System;
using MonoTouch.UIKit;
using MonoTouch.CoreFoundation;
using MonoTouch.Foundation;
using MonoTouch.ObjCRuntime;

namespace SplitView
{
	public class SplitViewContoller : UISplitViewController
	{
		MasterViewController masterView;
		DetailViewController detailView;
		
		public SplitViewContoller () : base()
		{
			// create our master and detail views
			masterView = new MasterViewController ();
			detailView = new DetailViewController ();

			// create an array of controllers from them and then assign it to the 
			// controllers property
			ViewControllers = new UIViewController[] 
				{ masterView, detailView }; // order is important: master first, detail second
			

			// in this example, i expose an event on the master view called RowClicked, and i listen 
			// for it in here, and then call a method on the detail view to update. this class thereby 
			// becomes the defacto controller for the screen (both views).
			masterView.RowClicked += (object sender, MasterViewController.RowClickedEventArgs e) => {
				detailView.Update (e.Item);
			};
			

			bool useDelegateWhichWorksIniOS51 = true;
			if (useDelegateWhichWorksIniOS51)
				Delegate = new SplitViewDelegate();
			else {
				// when the master view controller is hidden (portrait mode), we add a button to 
				// the detail view that will show the master view in a popover
				WillHideViewController += (object sender, UISplitViewHideEventArgs e) => {
					detailView.Popover = e.Pc;
					detailView.AddContentsButton(e.BarButtonItem);
				};
				
				// when the master view controller is shown (landscape mode), remove the button
				WillShowViewController += (object sender, UISplitViewShowEventArgs e) => {
					detailView.Popover = null;
					detailView.RemoveContentsButton ();
				};
	
				// #### Doesn't work in iOS 5.1 ####
				// hide the master view controller 
				ShouldHideViewController = (svc, viewController, inOrientation) => {
					return inOrientation == UIInterfaceOrientation.Portrait ||
						inOrientation == UIInterfaceOrientation.PortraitUpsideDown;
				};
			}
		}
		
		public override bool ShouldAutorotateToInterfaceOrientation (UIInterfaceOrientation toInterfaceOrientation)
        {
            return true;
        }


		public class SplitViewDelegate : UISplitViewControllerDelegate
	    {
			public override bool ShouldHideViewController (UISplitViewController svc, UIViewController viewController, UIInterfaceOrientation inOrientation)
			{
				return inOrientation == UIInterfaceOrientation.Portrait
					|| inOrientation == UIInterfaceOrientation.PortraitUpsideDown;
			}
			
			public override void WillHideViewController (UISplitViewController svc, UIViewController aViewController, UIBarButtonItem barButtonItem, UIPopoverController pc)
			{
				DetailViewController dvc = svc.ViewControllers[1] as DetailViewController;
				
				dvc.AddContentsButton (barButtonItem);
				dvc.Popover = pc;
			}
			
			public override void WillShowViewController (UISplitViewController svc, UIViewController aViewController, UIBarButtonItem button)
			{
				DetailViewController dvc = svc.ViewControllers[1] as DetailViewController;
				
				dvc.RemoveContentsButton ();
				dvc.Popover = null;
			}
		}
	}
}
Comment 1 Sebastien Pouliot 2012-03-08 16:10:35 UTC
Looks like a duplicate of (or related to) https://bugzilla.xamarin.com/show_bug.cgi?id=3461

What exact version of MonoTouch are you using ? 
bug report says "5.0" but I guess it's 5.2.something ?
Comment 2 CraigD 2012-03-08 16:41:52 UTC
Ooops sorry, my bad. Monotouch: 5.2.5

It does sound the same as https://bugzilla.xamarin.com/show_bug.cgi?id=3461 -- the 'fix' there was listed as implementing "new in iOS5" ShouldHideViewController with a lambda, but for me the lambda is never hit (while the override on the Delegate subclass is)...
Comment 3 Sebastien Pouliot 2012-03-09 09:17:25 UTC
MonoTouch's binding events do not work when you set your own *Delegate type (since it overwrites the internal one used by MonoTouch to implement the events) so the solution cannot be used as-is (i.e. it's normal that your override gets called).
Comment 4 CraigD 2012-03-11 19:12:33 UTC
Created attachment 1495 [details]
Error occurs with lambdas

I've split my previous example into two separate projects to avoid confusion
Comment 5 CraigD 2012-03-11 19:13:00 UTC
Created attachment 1496 [details]
Error does NOT occur with Delegate class

I've split my previous example into two separate projects to avoid confusion
Comment 6 CraigD 2012-03-11 19:14:19 UTC
Created attachment 1497 [details]
screenshot of lambdas code that errors

the error occurs when there is no Delegate subclass, and 'ShouldHide...' is implemented like this
Comment 7 CraigD 2012-03-11 19:15:06 UTC
Created attachment 1498 [details]
screenshot of delegate subclass that works

using the Delegate subclass works fine in iOS 5.1
Comment 8 CraigD 2012-03-11 19:19:24 UTC
Sorry - my previously attached sample was confusing because I included the code that works and the code that breaks in the same solution. It looked like I was using lambdas _and_ a Delegate class, but it wasn't really.

I've broken it into two separate samples - if you put both of these on an iOS 5.1 iPad then only one of them will exhibit the correct behaviour (the Delegate subclass) while the other one (lambdas) shows the master list as a popover in both orientations, and on rotation it causes the popover to be blank.

Hope that makes more sense :)
Comment 9 Sebastien Pouliot 2012-03-16 20:29:36 UTC
my bad, I saw the code for both but missed the condition :)
Comment 10 Sebastien Pouliot 2012-03-16 21:05:52 UTC
Yucky-yuck!

The (internal) order of calls changed in iOS 5.1 - so setting the ViewControllers property triggers the creation of the default (MT internal) delegate before you set the events (and some get called too).

Moving the following code at the end of your method "fix" the issue:

			// create an array of controllers from them and then assign it to the 
			// controllers property
			ViewControllers = new UIViewController[] 
				{ masterView, detailView }; // order is important: master first, detail second


Not (yet) 100% sure why it works when setting the Delegate property (since you're doing it later too) so I would have assumed the same result. I think the default implementation (without methods) to call back plays a role too. That needs a bit more investigation...
Comment 11 Sebastien Pouliot 2012-03-16 21:49:19 UTC
To be more precise anything that sets the delegate cause it (in iOS 5.1) to be used *before* the call to set the delegate is completed. Setting the [Weak]Delegate property is more "atomic" so the order to setting the events is not a problem.

note: That includes MD debugger showing properties that can trigger EnsureUISplitViewControllerDelegate... which sets WeakDelegate (causing weirdness in testing).

E.g. the stack trace below shows that ShouldHideViewController gets called (from native) from set_WeakDelegate when we try to call add_WillHideViewController.

   at MonoTouch.UIKit.UISplitViewController+_UISplitViewControllerDelegate.ShouldHideViewController(MonoTouch.UIKit.UISplitViewController svc, MonoTouch.UIKit.UIViewController viewController, UIInterfaceOrientation inOrientation) in /Developer/MonoTouch/Source/monotouch/src/UIKit/UISplitViewController.g.cs:line 252
   at MonoTouch.ObjCRuntime.Messaging.void_objc_msgSendSuper_IntPtr(IntPtr , IntPtr , IntPtr )
   at MonoTouch.UIKit.UISplitViewController.set_WeakDelegate(MonoTouch.Foundation.NSObject value) in /Developer/MonoTouch/Source/monotouch/src/UIKit/UISplitViewController.g.cs:line 168
   at MonoTouch.UIKit.UISplitViewController.EnsureUISplitViewControllerDelegate() in /Developer/MonoTouch/Source/monotouch/src/UIKit/UISplitViewController.g.cs:line 207
   at MonoTouch.UIKit.UISplitViewController.add_WillHideViewController(System.EventHandler`1 value) in /Developer/MonoTouch/Source/monotouch/src/UIKit/UISplitViewController.g.cs:line 263
   at SplitView.SplitViewContoller..ctor() in /Users/poupou/Downloads/SplitViewiOS51_err/SplitViewContoller.cs:line 28
   at SplitView.AppDelegate.FinishedLaunching(MonoTouch.UIKit.UIApplication app, MonoTouch.Foundation.NSDictionary options) in /Users/poupou/Downloads/SplitViewiOS51_err/AppDelegate.cs:line 33

Quick test shows that, under iOS 5.1, ShouldHideViewController *must* be set before setting ViewControllers property, otherwise bad things occurs.

Not sure how to best deal with this right now...
Code wise: it can't be handled by the generated code.
Documentation-wise it's: set [Weak]Delegate or events before other properties
Comment 12 Sebastien Pouliot 2012-03-26 09:33:40 UTC
No magic workaround was found, so documentation it will be.
http://spouliot.wordpress.com/2012/03/26/events-vs-objective-c-delegates/

I'll update the specific API (monodoc) and look if we can add an "automatic" note to every type that has an internal *Delegate type.
Comment 13 Sebastien Pouliot 2012-03-28 11:58:07 UTC
The general Delegate/events behaviour is already documented
Specific (for this case) documentation updated in: c28a9c3820572e1e9eda8dc38a5be7f2377f2509