Width of the Detail page on iPad

MichaelRumplerMichaelRumpler ATMember ✭✭✭✭✭

Is there a way to have the Detail page use only ~60% of the device width when the Master is shown and 100% if it is not shown?

Until now I used MasterBehavior = MasterBehavior.Split. This means that both Master and Detail are always visible. The Master is about 40% wide (in portrait) and the Detail only gets 60%. The Master can neither be swiped away by the user nor can it be hidden programmatically.

When I use MasterBehavior.Default or Popover, then the Master page can be swiped in and out. The problem is, that the Detail page is always 100% wide and it is overlapped by the Master page. I want the width of the Detail page to vary depending if the Master is shown or not.

If this cannot be done in Xamarin.Forms, I would also be happy if somebody can suggest how to do it in a custom renderer.

Best Answers

Answers

  • AndrewMobileAndrewMobile USMember ✭✭✭✭

    I edited my answer.

  • MichaelRumplerMichaelRumpler ATMember ✭✭✭✭✭
    edited May 2015

    Thanks @AndreiNitescu
    Could you reference something where this is explained? Which properties are you talking about?
    But keep in mind, I don't want to change the width of the Master, but only the Detail.
    As it happens, I already do subclass the TabletMasterDetailRenderer, so I could just add some more there.

  • AndrewMobileAndrewMobile USMember ✭✭✭✭

    @MichaelRumpler I'm thinking that you could dynamically switch from popover to side by side when master is visible. but you need to make master disappear on swipe like in popover mode

  • AndrewMobileAndrewMobile USMember ✭✭✭✭

    @MichaelRumpler I wonder how difficult would it be to use MGSplitViewController instead. It allows you to change widths easily

  • MichaelRumplerMichaelRumpler ATMember ✭✭✭✭✭

    I'll see how far I'll come with jrc's answer on SO and the maximumPrimaryColumnWidth. If that won't work, I'll have a look at the MGSplitViewController too. I'll let you know.
    Thank you!

  • MichaelRumplerMichaelRumpler ATMember ✭✭✭✭✭

    I got it working, but with a limitation due to a bug in Xamarin.Forms. Here is my complete TabletMasterDetailRenderer:

    using System;
    using System.Reflection;
    using CoreGraphics;
    using UIKit;
    using Xamarin.Forms;
    
    [assembly: ExportRenderer(
        typeof(MasterDetailPage),
        typeof(MyNamespace.iOS.Renderers.TabletMasterDetailRenderer),
        UIUserInterfaceIdiom.Pad)]
    
    namespace MyNamespace.iOS.Renderers
    {
        public class TabletMasterDetailRenderer : Xamarin.Forms.Platform.iOS.TabletMasterDetailRenderer
        {
            private readonly nfloat SplitterWidth = (nfloat)0.5;
    
            protected override void OnElementChanged(Xamarin.Forms.Platform.iOS.VisualElementChangedEventArgs e)
            {
                base.OnElementChanged(e);
    
                if (e.OldElement != null)
                    ((MasterDetailPage)e.OldElement).IsPresentedChanged -= onIsPresentedChanged;
                if (e.NewElement != null)
                    ((MasterDetailPage)e.NewElement).IsPresentedChanged += onIsPresentedChanged;
            }
    
            protected override void Dispose(bool disposing)
            {
                base.Dispose(disposing);
    
                if (Element != null)
                    ((MasterDetailPage)Element).IsPresentedChanged -= onIsPresentedChanged;
            }
    
            // Workaround to preserve delimiter color. It is set to white (hardcoded) by Xamarins ViewDidLoad method.
            public override void ViewDidLoad()
            {
                var color = this.View.BackgroundColor;
                base.ViewDidLoad();
                this.View.BackgroundColor = color;
            }
    
            private void onIsPresentedChanged(object sender, EventArgs e)
            {
                adjustDetailWidth();
            }
    
            public override void ViewDidLayoutSubviews()
            {
                base.ViewDidLayoutSubviews();
    
                adjustDetailWidth();
            }
    
            private void adjustDetailWidth()
            {
                var mainWidth = View.Frame.Width;
                var masterWidth = ViewControllers[0].View.Frame.Width;
                var detailFrame = ViewControllers[1].View.Frame;
                var masterVisible = ((MasterDetailPage)Element).IsPresented;
    
                nfloat desiredX = 0;
                nfloat desiredWidth = mainWidth;
                if (masterVisible)
                {
                    desiredX = masterWidth + SplitterWidth;
                    desiredWidth = mainWidth - desiredX;
                }
    
                if (desiredX != detailFrame.X || desiredWidth != detailFrame.Width)
                {
                    SetDetailBounds(new Rectangle(desiredX, detailFrame.Y, desiredWidth, detailFrame.Height));
                }
            }
    
            PropertyInfo detailBoundsProperty;
    
            private void SetDetailBounds(Rectangle rectangle)
            {
                // unfortunately, this doesnt animate the width, but I can live with that
                // (width is set at once, X is animated)
                UIView.Animate(
                    0.2,
                    () => ViewControllers[1].View.Frame = new CGRect(rectangle.X, rectangle.Y, rectangle.Width, rectangle.Height)
                );
    
                // must set internal DetailBounds because base.ViewDidLayoutSubviews always sets X to 0
                if (detailBoundsProperty == null)
                    detailBoundsProperty = this.Element.GetType().GetProperty("DetailBounds", BindingFlags.NonPublic | BindingFlags.Instance);
                if (detailBoundsProperty != null && detailBoundsProperty.CanWrite)
                    detailBoundsProperty.SetValue(this.Element, rectangle);
            }
        }
    }
    

    The problem is, that the IsPresentedChanged event is not always raised. When the Master is visible and you rotate your device, then it is not raised anymore. I created bug 30353 for that.

    I tried using the Master.Appearing/Disappearing events instead, but they are also not raised anymore after the bug explained above got triggered. Moreover they are only raised after the Master slided in/out, but I need to start the animation for the Detail position when it starts.

    @ChrisKing from Xamarin already confirmed the bug, but he didn't change the status yet.

  • MichaelRumplerMichaelRumpler ATMember ✭✭✭✭✭

    @AndreiNitescu I just recognized some new problems which came up after I changed the MasterBehavior from Split to Popover. Whenever I tap something on the Detail page, the Master will be scrolled off. There is also always a "Navigation" link at the top left of the Detail page, even though the Master is visible. Both are not desired of course.

    Will I have to make the whole TabletMasterDetailRenderer new without inheriting from the existing?

Sign In or Register to comment.