Adjust Width of Master on MasterDetailPage

2»

Answers

  • orvarurKjartanssonorvarurKjartansson USMember
    edited December 2016

    For anyone still having problems with this, the simplest solution I could come up with has been touched on a little bit above.
    It's the following:

    [1] Make the master page transparent.
    [2] Create a grid inside the master page with one column in the size that you want to take away from the overall width.
    [3] Color the rest of the grid however you want but make sure that our special column is still transparent and spacing is set to 0 so the master page will be solid.

    Now you have a master page that appears to be thinner than the default look.

    [4] Finally, to complete the transformation... Make a "Tapped" trigger and assign it to the transparent column.
    The trigger function should do the following:
    (Parent as MasterDetailPage).IsPresented = false;
    This will make sure that if you tap the invisible column, the master page will hide as if you taped the normal background.

  • filiallfiliall BRMember
    edited December 2016

    no working for me, any solution. ? On LoadApplication(new App()) (Debug) it says :System.NullReferenceException

  • ibrahimnadaibrahimnada USMember ✭✭

    Btter 2 use a scroll View inside of the Transparent MasterPage

  • IgnasBagdonasIgnasBagdonas LTMember ✭✭

    @orvarurKjartansson said:
    For anyone still having problems with this, the simplest solution I could come up with has been touched on a little bit above.
    It's the following:

    [1] Make the master page transparent.
    [2] Create a grid inside the master page with one column in the size that you want to take away from the overall width.
    [3] Color the rest of the grid however you want but make sure that our special column is still transparent and spacing is set to 0 so the master page will be solid.

    Now you have a master page that appears to be thinner than the default look.

    [4] Finally, to complete the transformation... Make a "Tapped" trigger and assign it to the transparent column.
    The trigger function should do the following:
    (Parent as MasterDetailPage).IsPresented = false;
    This will make sure that if you tap the invisible column, the master page will hide as if you taped the normal background.

    Yeah, it works for me. Thanks for idea. :)

  • carlossousacarlossousa USMember

    Hi, can you please share the code for solution: "@orvarurKjartansson said:
    For anyone still having problems with this, the simplest solution I could come up with has been touched on a little bit above.
    It's the following:

    [1] Make the master page transparent.
    [2] Create a grid inside the master page with one column in the size that you want to take away from the overall width.
    [3] Color the rest of the grid however you want but make sure that our special column is still transparent and spacing is set to 0 so the master page will be solid."...

    thanks,

  • orvarurKjartanssonorvarurKjartansson USMember
    edited March 2017
    <ContentPage 
            ...
                 BackgroundColor="Transparent">
      <Grid RowSpacing="0" ColumnSpacing="0">
        <Grid.RowDefinitions>
          ...
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
    
          <ColumnDefinition Width="*"/>
          <ColumnDefinition Width="60"/>   <!-- The invisible column -->
    
        </Grid.ColumnDefinitions>
    
            ...
        <!-- some other stuff -->
        <StackLayout Margin="0" Grid.Row="0" HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand" BackgroundColor="#2196F3">
        ...
    
        <!-- Invisible column -->
        <StackLayout Margin="0" x:Name="invisibleZone" Grid.Column="1" Grid.RowSpan="3" HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand" BackgroundColor="Transparent">
    
        </StackLayout>
      </Grid>
    </ContentPage>
    

    @carlossousa Sorry for taking my time with the response, I hope this is what you're looking for. There is a long time since I looked at this so I hope this is everything you need.

  • grv.bajpaigrv.bajpai INMember

    @RyanHatfieldOld I am getting this error "System.NullReferenceException: Object reference not set to an instance of an object.Xamarin.Forms.in MasterDetailRenderer.cs:99"

  • BrightLeeBrightLee KRMember ✭✭✭

    I'm surprised that there is still not a solid solution.

    @orvarurKjartansson
    Hi, what about iOS?
    The detail page is pushed to right on iOS's MasterDetailPage.

  • RicardoJarreeRicardoJarree USMember ✭✭

    Nothing in the pipe line for a proper implmentation of this?

  • MatsuMatsu USMember ✭✭
    edited August 2017

    If you are using AppCompat you can't use "MasterDetailRenderer" and you should use "MasterDetailPageRenderer"
    I find this dirty solution (if anyone have a better solution ) :

    [assembly: Xamarin.Forms.ExportRenderer(typeof(Xamarin.Forms.MasterDetailPage), typeof(MasterDetailPageSizableRenderer))]
    namespace YourNameSpace
    {
    
    public class MasterDetailPageSizableRenderer : MasterDetailPageRenderer
    
    {
    protected override void OnLayout(bool changed, int l, int t, int r, int b)
            {
                int widthPixel= Resources.DisplayMetrics.WidthPixels;
                Resources.DisplayMetrics.WidthPixels = 260; //Find by reflexion, to understand see: MasterDetailContainer class
                base.OnLayout(changed, l, t, r, b);
                Resources.DisplayMetrics.WidthPixels = widthPixel;
            }
    
    }
    
    }
    
  • MatsuMatsu USMember ✭✭
    edited August 2017

    If you are using FormsAppCompatActivity you should use MasterDetailPageRenderer (MasterDetailPage will return null reference exception / If you are not using AppCompat see previous answer ).

    I find this dirty solution (if anyone have a better suggestion) :

    [assembly: Xamarin.Forms.ExportRenderer(typeof(Xamarin.Forms.MasterDetailPage), typeof(MasterDetailPageSizableRenderer))]
    namespace YourNameSpace
    {
    public class MasterDetailPageSizableRenderer : MasterDetailPageRenderer
    {
    protected override void OnLayout(bool changed, int l, int t, int r, int b)
            {
                int widthPixels = Resources.DisplayMetrics.WidthPixels;
                Resources.DisplayMetrics.WidthPixels = 260; //Here master width size, I found it by reflexion, to understand, see: MasterDetailContainer in Xamarin
                base.OnLayout(changed, l, t, r, b);
                Resources.DisplayMetrics.WidthPixels = widthPixels;
            }
    }
    }
    
  • manishchoudharymanishchoudhary USMember ✭✭

    @RayanHatfield Thanks a lot for your code in android. Anyone have implemented for IOS. I have gone through this document but don't know which method need to implement
    https://gist.github.com/radoslavyordanov/e62f673e45983ec0ae56cd6b336f68ff

  • KobalKobal FRMember

    I am also interested by this implementation for iOS. Anyone?

  • PierreSavardPierreSavard CAMember ✭✭

    @manishchoudhary or @Kobal Do you find a solution?

  • Daniel.LochnerDaniel.Lochner ZAMember ✭✭

    Hey, I think I may have found a workaround, but I am stuck on how to execute it. Please check here for more information: https://forums.xamarin.com/discussion/120213/editing-and-using-the-xamarin-forms-source-code/p1?new=1

  • Daniel.LochnerDaniel.Lochner ZAMember ✭✭
    edited February 2018

    @RyanHatfieldOld

    Hello there! :) When I use your solution for Android, it says that the MasterDetailRenderer is obsolete, and so the AddView method does not exist.

  • TheMaeggesTheMaegges DEMember ✭✭
    edited April 2018

    Since the code above didn't work for me (I needed the master to have a width of 400dp), here is what I did in order to get it running on Android:

    I created a class named "SplitViewPage", which basically just inherits from the MasterDetailPage class.

    Then I created a custom Renderer for that class, which is basically a copy of the MasterDetailRenderer Source Code from the official Xamarin repository (https://github.com/xamarin/Xamarin.Forms/blob/master/Xamarin.Forms.Platform.Android/Renderers/MasterDetailRenderer.cs)

    `
    using System;
    using System.ComponentModel;
    using System.Linq;
    using System.Threading.Tasks;
    using Android.App;
    using Android.Content;
    using Android.OS;
    using Android.Support.V4.Widget;
    using Android.Views;
    using CompApp.Droid.Renderer;
    using CompApp.Views;
    using Xamarin.Forms;
    using Xamarin.Forms.Platform.Android;
    using Xamarin.Forms.Platform.Android.AppCompat;
    using AColor = Android.Graphics.Drawables.ColorDrawable;
    using AView = Android.Views.View;

    [assembly: ExportRenderer(typeof(SplitViewPage), typeof(SplitViewPageRenderer))]
    namespace CompApp.Droid.Renderer
    {
    public class SplitViewPageRenderer : DrawerLayout, IVisualElementRenderer, DrawerLayout.IDrawerListener
    {
        public SplitViewPageRenderer(Context context) : base(context)
        {
    
        }
    
        //from Android source code
        const uint DefaultScrimColor = 0x99000000;
        int _currentLockMode = -1;
        DroidMasterDetailContainer _detailLayout;
        bool _isPresentingFromCore;
        DroidMasterDetailContainer _masterLayout;
        MasterDetailPage _page;
        bool _presented;
    
        [Obsolete("This constructor is obsolete as of version 2.5. Please use MasterDetailRenderer(Context) instead.")]
        public SplitViewPageRenderer() : base(Forms.Context)
        {
        }
    
        IMasterDetailPageController MasterDetailPageController => _page as IMasterDetailPageController;
    
        public bool Presented
        {
            get { return _presented; }
            set
            {
                if (value == _presented)
                    return;
                UpdateSplitViewLayout();
                _presented = value;
                if (_page.MasterBehavior == MasterBehavior.Default && MasterDetailPageController.ShouldShowSplitMode)
                    return;
                if (_presented)
                    OpenDrawer(_masterLayout);
                else
                    CloseDrawer(_masterLayout);
            }
        }
    
        IPageController MasterPageController => _page.Master as IPageController;
        IPageController DetailPageController => _page.Detail as IPageController;
        IPageController PageController => Element as IPageController;
    
        public void OnDrawerClosed(AView drawerView)
        {
        }
    
        public void OnDrawerOpened(AView drawerView)
        {
        }
    
        public void OnDrawerSlide(AView drawerView, float slideOffset)
        {
        }
    
        public void OnDrawerStateChanged(int newState)
        {
            _presented = IsDrawerVisible(_masterLayout);
            UpdateIsPresented();
        }
    
        public VisualElement Element
        {
            get { return _page; }
        }
    
        public event EventHandler<VisualElementChangedEventArgs> ElementChanged;
    
        event EventHandler<PropertyChangedEventArgs> ElementPropertyChanged;
        event EventHandler<PropertyChangedEventArgs> IVisualElementRenderer.ElementPropertyChanged
        {
            add { ElementPropertyChanged += value; }
            remove { ElementPropertyChanged -= value; }
        }
    
        public SizeRequest GetDesiredSize(int widthConstraint, int heightConstraint)
        {
            Measure(widthConstraint, heightConstraint);
            return new SizeRequest(new Size(MeasuredWidth, MeasuredHeight));
        }
    
        public void SetElement(VisualElement element)
        {
            MasterDetailPage oldElement = _page;
            _page = element as MasterDetailPage;
    
            _detailLayout = new DroidMasterDetailContainer(_page, false, Context) { LayoutParameters = new LayoutParams(ViewGroup.LayoutParams.WrapContent, ViewGroup.LayoutParams.WrapContent) };
    
            _masterLayout = new DroidMasterDetailContainer(_page, true, Context)
            {
                LayoutParameters = new LayoutParams(ViewGroup.LayoutParams.WrapContent, ViewGroup.LayoutParams.WrapContent) { Gravity = (int)GravityFlags.Start }
            };
    
            AddView(_detailLayout);
    
            AddView(_masterLayout);
    
            var activity = Context as Activity;
            activity.ActionBar.SetDisplayShowHomeEnabled(true);
            activity.ActionBar.SetHomeButtonEnabled(true);
    
            UpdateBackgroundColor(_page);
            UpdateBackgroundImage(_page);
    
            OnElementChanged(oldElement, element);
    
            if (oldElement != null)
                ((IMasterDetailPageController)oldElement).BackButtonPressed -= OnBackButtonPressed;
    
            if (_page != null)
                MasterDetailPageController.BackButtonPressed += OnBackButtonPressed;
    
            if (Tracker == null)
                Tracker = new VisualElementTracker(this);
    
            _page.PropertyChanged += HandlePropertyChanged;
            _page.Appearing += MasterDetailPageAppearing;
            _page.Disappearing += MasterDetailPageDisappearing;
    
            UpdateMaster();
            UpdateDetail();
    
            Device.Info.PropertyChanged += DeviceInfoPropertyChanged;
            SetGestureState();
    
            Presented = _page.IsPresented;
    
            AddDrawerListener(this);
    
            //if (element != null)
            //  element.SendViewInitialized(this);
    
            if (element != null && !string.IsNullOrEmpty(element.AutomationId))
                ContentDescription = element.AutomationId;
        }
    
        public VisualElementTracker Tracker { get; private set; }
    
        public void UpdateLayout()
        {
            if (Tracker != null)
                Tracker.UpdateLayout();
        }
    
        public ViewGroup ViewGroup => this;
        AView IVisualElementRenderer.View => this;
    
        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                if (Tracker != null)
                {
                    Tracker.Dispose();
                    Tracker = null;
                }
    
                if (_detailLayout != null)
                {
                    _detailLayout.Dispose();
                    _detailLayout = null;
                }
    
                if (_masterLayout != null)
                {
                    _masterLayout.Dispose();
                    _masterLayout = null;
                }
    
                Device.Info.PropertyChanged -= DeviceInfoPropertyChanged;
    
                if (_page != null)
                {
                    MasterDetailPageController.BackButtonPressed -= OnBackButtonPressed;
                    _page.PropertyChanged -= HandlePropertyChanged;
                    _page.Appearing -= MasterDetailPageAppearing;
                    _page.Disappearing -= MasterDetailPageDisappearing;
                    //_page.ClearValue(Platform.RendererProperty);
                    _page = null;
                }
            }
    
            base.Dispose(disposing);
        }
    
        protected override void OnAttachedToWindow()
        {
            base.OnAttachedToWindow();
            PageController.SendAppearing();
        }
    
        protected override void OnDetachedFromWindow()
        {
            base.OnDetachedFromWindow();
            PageController.SendDisappearing();
        }
    
        protected virtual void OnElementChanged(VisualElement oldElement, VisualElement newElement)
        {
            EventHandler<VisualElementChangedEventArgs> changed = ElementChanged;
            if (changed != null)
                changed(this, new VisualElementChangedEventArgs(oldElement, newElement));
        }
    
        protected override void OnLayout(bool changed, int l, int t, int r, int b)
        {
            base.OnLayout(changed, l, t, r, b);
            //hack to make the split layout handle touches the full width
            if (MasterDetailPageController.ShouldShowSplitMode && _masterLayout != null)
                _masterLayout.Right = r;
        }
    
        async void DeviceInfoPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (e.PropertyName == "CurrentOrientation")
            {
                if (!MasterDetailPageController.ShouldShowSplitMode && Presented)
                {
                    MasterDetailPageController.CanChangeIsPresented = true;
                    //hack : when the orientation changes and we try to close the Master on Android     
                    //sometimes Android picks the width of the screen previous to the rotation      
                    //this leaves a little of the master visible, the hack is to delay for 50ms closing the drawer
                    await Task.Delay(50);
                    CloseDrawer(_masterLayout);
                }
                UpdateSplitViewLayout();
            }
        }
    
        void HandleMasterPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            //if (e.PropertyName == Page.TitleProperty.PropertyName || e.PropertyName == Page.IconProperty.PropertyName)
                //((Platform)_page.Platform).UpdateMasterDetailToggle(true);
        }
    
        void HandlePropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            ElementPropertyChanged?.Invoke(this, e);
            if (e.PropertyName == "Master")
                UpdateMaster();
            else if (e.PropertyName == "Detail")
            {
                UpdateDetail();
                //((Platform)_page.Platform).UpdateActionBar();
            }
            else if (e.PropertyName == MasterDetailPage.IsPresentedProperty.PropertyName)
            {
                _isPresentingFromCore = true;
                Presented = _page.IsPresented;
                _isPresentingFromCore = false;
            }
            else if (e.PropertyName == "IsGestureEnabled")
                SetGestureState();
            else if (e.PropertyName == Page.BackgroundImageProperty.PropertyName)
                UpdateBackgroundImage(_page);
            if (e.PropertyName == VisualElement.BackgroundColorProperty.PropertyName)
                UpdateBackgroundColor(_page);
        }
    
        void MasterDetailPageAppearing(object sender, EventArgs e)
        {
            MasterPageController?.SendAppearing();
            DetailPageController?.SendAppearing();
        }
    
        void MasterDetailPageDisappearing(object sender, EventArgs e)
        {
            MasterPageController?.SendDisappearing();
            DetailPageController?.SendDisappearing();
        }
    
        void OnBackButtonPressed(object sender, BackButtonPressedEventArgs backButtonPressedEventArgs)
        {
            if (IsDrawerOpen((int)GravityFlags.Start))
            {
                if (_currentLockMode != LockModeLockedOpen)
                {
                    CloseDrawer((int)GravityFlags.Start);
                    backButtonPressedEventArgs.Handled = true;
                }
            }
        }
    
        void SetGestureState()
        {
            SetDrawerLockMode(_page.IsGestureEnabled ? LockModeUnlocked : LockModeLockedClosed);
        }
    
        void IVisualElementRenderer.SetLabelFor(int? id)
        {
        }
    
        void SetLockMode(int lockMode)
        {
            if (_currentLockMode != lockMode)
            {
                SetDrawerLockMode(lockMode);
                _currentLockMode = lockMode;
            }
        }
    
        void UpdateBackgroundColor(Page view)
        {
            if (view.BackgroundColor != Color.Default)
                SetBackgroundColor(view.BackgroundColor.ToAndroid());
        }
    
        void UpdateBackgroundImage(Page view)
        {
            if (!string.IsNullOrEmpty(view.BackgroundImage))
                this.SetBackground(Context.GetDrawable(view.BackgroundImage));
        }
    
        void UpdateDetail()
        {
            Context.HideKeyboard(this);
            _detailLayout.ChildView = _page.Detail;
        }
    
        void UpdateIsPresented()
        {
            if (_isPresentingFromCore)
                return;
            if (Presented != _page.IsPresented)
                ((IElementController)_page).SetValueFromRenderer(MasterDetailPage.IsPresentedProperty, Presented);
        }
    
        void UpdateMaster()
        {
            if (_masterLayout != null && _masterLayout.ChildView != null)
                _masterLayout.ChildView.PropertyChanged -= HandleMasterPropertyChanged;
            _masterLayout.ChildView = _page.Master;
            if (_page.Master != null)
                _page.Master.PropertyChanged += HandleMasterPropertyChanged;
        }
    
        void UpdateSplitViewLayout()
        {
            if (Device.Idiom == TargetIdiom.Tablet)
            {
                bool isShowingSplit = MasterDetailPageController.ShouldShowSplitMode
                    || (MasterDetailPageController.ShouldShowSplitMode && _page.MasterBehavior != MasterBehavior.Default && _page.IsPresented);
                SetLockMode(isShowingSplit ? LockModeLockedOpen : LockModeUnlocked);
                unchecked
                {
                    SetScrimColor(isShowingSplit ? Color.Transparent.ToAndroid() : (int)DefaultScrimColor);
                }
                //((Platform)_page.Platform).UpdateMasterDetailToggle();
            }
        }
    }
    

    }
    `

    As you probably notice, the only significant modification is within these two lines:

    `
    _detailLayout = new DroidMasterDetailContainer(_page, false, Context)
    {
    LayoutParameters = new LayoutParams(ViewGroup.LayoutParams.WrapContent, ViewGroup.LayoutParams.WrapContent)
    };

    _masterLayout = new DroidMasterDetailContainer(_page, true, Context)
    {
        LayoutParameters = new LayoutParams(ViewGroup.LayoutParams.WrapContent, ViewGroup.LayoutParams.WrapContent) { Gravity = (int)GravityFlags.Start }
    };
    

    `

    Instead of using the (internal and therefore inaccessible) MasterDetailContainer, I copied the code from the repository ( https://github.com/xamarin/Xamarin.Forms/blob/master/Xamarin.Forms.Platform.Android/Renderers/MasterDetailContainer.cs ) to a new class called "DroidMasterDetailContainer", which only differs from the original by setting
    const int DefaultMasterSize = 400;

    Et voilá, I have a master width of 400 now.
    I know it is kind of hacky, however the only solution that has worked for me so far.

  • TheMaeggesTheMaegges DEMember ✭✭
    edited April 2018

    And here is the renderer for iOS:

    `
    using MyApp.Customs;
    using MyApp.iOS.Renderer;
    using System;
    using System.Collections.Generic;
    using System.Text;
    using Xamarin.Forms;
    using Xamarin.Forms.Platform.iOS;
    using System.ComponentModel;
    using CoreGraphics;
    using MyApp.Views;
    using UIKit;
    using Xamarin.Forms.Internals;
    using System.Reflection;

    [assembly: ExportRenderer(typeof(SplitViewPage), typeof(SplitViewPageRenderer))]
    namespace MyApp.iOS.Renderer
    {
        public class SplitViewPageRenderer : UISplitViewController, IVisualElementRenderer, IEffectControlProvider
        {
            UIViewController _detailController;
    
            bool _disposed;
            EventTracker _events;
            InnerDelegate _innerDelegate;
            public static nfloat MasterWidth = 400;
            EventedViewController _masterController;
    
            SplitViewPage _masterDetailPage;
    
            bool _masterVisible;
    
            VisualElementTracker _tracker;
    
            Page PageController => Element as Page;
            Element ElementController => Element as Element;
    
            protected SplitViewPage MasterDetailPage => _masterDetailPage ?? (_masterDetailPage = (SplitViewPage)Element);
    
            public VisualElement Element { get; private set; }
    
            public event EventHandler<VisualElementChangedEventArgs> ElementChanged;
    
            UIBarButtonItem PresentButton
            {
                get { return _innerDelegate == null ? null : _innerDelegate.PresentButton; }
            }
    
            public UIView NativeView
            {
                get { return View; }
            }
    
            protected virtual void OnElementChanged(VisualElementChangedEventArgs e)
            {
                if (e.OldElement != null)
                    e.OldElement.PropertyChanged -= HandlePropertyChanged;
    
                if (e.NewElement != null)
                {
                    e.NewElement.PropertyChanged += HandlePropertyChanged;
                }
    
                if (UIDevice.CurrentDevice.Orientation == UIDeviceOrientation.LandscapeLeft || UIDevice.CurrentDevice.Orientation == UIDeviceOrientation.LandscapeRight)
                {
                    PreferredDisplayMode = UISplitViewControllerDisplayMode.AllVisible;
                }
                else if (UIDevice.CurrentDevice.Orientation == UIDeviceOrientation.Portrait || UIDevice.CurrentDevice.Orientation == UIDeviceOrientation.PortraitUpsideDown)
                {
                    PreferredDisplayMode = UISplitViewControllerDisplayMode.PrimaryOverlay;
                }
    
                MasterWidth = 400;
                MasterDetailPage.Master.WidthRequest = 400;
    
                MasterDetailPage.UpdateMasterBehavior();
    
                var changed = ElementChanged;
                if (changed != null)
                    changed(this, e);
    
                UpdateControllers();
            }
    
            public SizeRequest GetDesiredSize(double widthConstraint, double heightConstraint)
            {
                return NativeView.GetSizeRequest(widthConstraint, heightConstraint);
            }
    
            public void SetElement(VisualElement element)
            {
                var oldElement = Element;
                Element = element;
    
                ViewControllers = new[] { _masterController = new EventedViewController(), _detailController = new ChildViewController() };
    
                Delegate = _innerDelegate = new InnerDelegate(MasterDetailPage.MasterBehavior);
                Element.BackgroundColor = Color.Transparent;
                UpdateControllers();
    
                _masterController.WillAppear += MasterControllerWillAppear;
                _masterController.WillDisappear += MasterControllerWillDisappear;
    
                PresentsWithGesture = MasterDetailPage.IsGestureEnabled;
    
                OnElementChanged(new VisualElementChangedEventArgs(oldElement, element));
    
                EffectUtilities.RegisterEffectControlProvider(this, oldElement, element);
    
                if (element != null)
                {
                    MethodInfo sendViewInitialized = typeof(Xamarin.Forms.Forms).GetMethod("SendViewInitialized", BindingFlags.Static | BindingFlags.NonPublic);
                    sendViewInitialized?.Invoke(element, new object[] { element, NativeView });
                }
            }
    
            public void SetElementSize(Size size)
            {
                Element.Layout(new Rectangle(Element.X, Element.Width, size.Width, size.Height));
            }
    
            public UIViewController ViewController
            {
                get { return this; }
            }
    
            public override void ViewDidAppear(bool animated)
            {
                PageController.SendAppearing();
                base.ViewDidAppear(animated);
                ToggleMaster();
            }
    
            public override void ViewDidDisappear(bool animated)
            {
                base.ViewDidDisappear(animated);
                PageController?.SendDisappearing();
            }
    
            public override void ViewDidLayoutSubviews()
            {
                if (View.Subviews.Length < 2)
                    return;
    
                var frameBounds = View.Bounds;
                var masterBounds = _masterController.View.Frame;
                var detailsBounds = _detailController.View.Frame;
    
                nfloat statusBarHeight = UIApplication.SharedApplication.StatusBarFrame.Height;
                masterBounds.Width = 400;
                MasterWidth = (nfloat)Math.Max(MasterWidth, masterBounds.Width);
                if (Xamarin.Forms.Device.Idiom == TargetIdiom.Tablet)
                {
                    bool interfaceInLandscape = UIApplication.SharedApplication.StatusBarOrientation == UIInterfaceOrientation.LandscapeLeft || UIApplication.SharedApplication.StatusBarOrientation == UIInterfaceOrientation.LandscapeRight;
                    if (UIDevice.CurrentDevice.Orientation == UIDeviceOrientation.LandscapeLeft || UIDevice.CurrentDevice.Orientation == UIDeviceOrientation.LandscapeRight || interfaceInLandscape)
                    {
                        detailsBounds.X = 400;
                        detailsBounds.Width = frameBounds.Width - 400;
                    }
                    else
                    {
                        detailsBounds.X = 0;
                        detailsBounds.Width = frameBounds.Width;
                    }
                    _detailController.View.Frame = detailsBounds;
                    _masterController.View.Frame = new CGRect(masterBounds.X, masterBounds.Y, masterBounds.Width, masterBounds.Height);
                    if (!masterBounds.IsEmpty)
                    {
                        MasterDetailPage.MasterBounds = new Rectangle(masterBounds.X, masterBounds.Y, masterBounds.Width, masterBounds.Height);
                    }
    
                    if (!detailsBounds.IsEmpty)
                    {
                        MasterDetailPage.DetailBounds = new Rectangle(detailsBounds.X, detailsBounds.Y, detailsBounds.Width, detailsBounds.Height);
                    }
                    _masterController.View.SetNeedsLayout();
                    _detailController.View.SetNeedsLayout();
    
                }
                else
                {
                    if (!masterBounds.IsEmpty)
                    {
                        MasterDetailPage.MasterBounds = new Rectangle(MasterWidth, 0, MasterWidth, masterBounds.Height);
                    }
    
                    if (!detailsBounds.IsEmpty)
                    {
                        MasterDetailPage.DetailBounds = new Rectangle(0, 0, detailsBounds.Width, detailsBounds.Height);
                    }
                }
    
    
            }
    
            public override void ViewDidLoad()
            {
                base.ViewDidLoad();
                UpdateBackground();
                UpdateFlowDirection();
                _tracker = new VisualElementTracker(this);
                _events = new EventTracker(this);
                _events.LoadEvents(NativeView);
            }
    
            public override void ViewWillDisappear(bool animated)
            {
                if (_masterVisible)
                    PerformButtonSelector();
    
                base.ViewWillDisappear(animated);
            }
    
            public override void ViewWillLayoutSubviews()
            {
                base.ViewWillLayoutSubviews();
                _masterController.View.BackgroundColor = UIColor.White;
                CGRect bounds = _masterController.View.Bounds;
                CGRect frame = _masterController.View.Frame;
                (this.ViewController as UISplitViewController).MinimumPrimaryColumnWidth = 400;
                (this.ViewController as UISplitViewController).MaximumPrimaryColumnWidth = 400;
            }
    
            public override void WillRotate(UIInterfaceOrientation toInterfaceOrientation, double duration)
            {
                // On IOS8 the MasterViewController ViewAppear/Disappear weren't being called correctly after rotation 
                // We now close the Master by using the new SplitView API, basicly we set it to hidden and right back to the Normal/AutomaticMode
                if (!MasterDetailPage.ShouldShowSplitMode && _masterVisible)
                {
                    MasterDetailPage.CanChangeIsPresented = true;
                    PreferredDisplayMode = UISplitViewControllerDisplayMode.PrimaryHidden;
                    PreferredDisplayMode = UISplitViewControllerDisplayMode.Automatic;
                }
    
                var masterBounds = _masterController.View.Frame;
                MessagingCenter.Send<IVisualElementRenderer>(this,  "Xamarin.UpdateToolbarButtons");
    
                MasterDetailPage.UpdateMasterBehavior();
                base.WillRotate(toInterfaceOrientation, duration);
            }
    
            public override void DidRotate(UIInterfaceOrientation fromInterfaceOrientation)
            {
                base.DidRotate(fromInterfaceOrientation);
                var masterBounds = _masterController.View.Frame;
    
                MasterWidth = (nfloat)Math.Max(MasterWidth, masterBounds.Width);
    
                if (!masterBounds.IsEmpty)
                {
                    MasterDetailPage.MasterBounds = new Rectangle(MasterWidth, 0, MasterWidth, masterBounds.Height);
                }
            }
    
            public override UIViewController ChildViewControllerForStatusBarHidden()
            {
                if (((MasterDetailPage)Element).Detail != null)
                    return (UIViewController)Platform.GetRenderer(((MasterDetailPage)Element).Detail);
                else
                    return base.ChildViewControllerForStatusBarHidden();
            }
    
            void ClearControllers()
            {
                foreach (var controller in _masterController.ChildViewControllers)
                {
                    controller.View.RemoveFromSuperview();
                    controller.RemoveFromParentViewController();
                }
    
                foreach (var controller in _detailController.ChildViewControllers)
                {
                    controller.View.RemoveFromSuperview();
                    controller.RemoveFromParentViewController();
                }
            }
    
            void HandleMasterPropertyChanged(object sender, PropertyChangedEventArgs e)
            {
                if (e.PropertyName == Page.IconProperty.PropertyName || e.PropertyName == Page.TitleProperty.PropertyName)
                    MessagingCenter.Send<IVisualElementRenderer>(this, "Xamarin.UpdateToolbarButtons");
            }
    
            void HandlePropertyChanged(object sender, PropertyChangedEventArgs e)
            {
                if (_tracker == null)
                    return;
    
                if (e.PropertyName == "Master" || e.PropertyName == "Detail")
                    UpdateControllers();
                else if (e.PropertyName == Xamarin.Forms.MasterDetailPage.IsPresentedProperty.PropertyName)
                    ToggleMaster();
                else if (e.PropertyName == Xamarin.Forms.MasterDetailPage.IsGestureEnabledProperty.PropertyName)
                    base.PresentsWithGesture = this.MasterDetailPage.IsGestureEnabled;
                else if (e.PropertyName == "FlowDirection")
                    UpdateFlowDirection();
                MessagingCenter.Send<IVisualElementRenderer>(this, "Xamarin.UpdateToolbarButtons");
            }
    
            void MasterControllerWillAppear(object sender, EventArgs e)
            {
                _masterVisible = true;
                if (MasterDetailPage.CanChangeIsPresented)
                    ElementController.SetValueFromRenderer(Xamarin.Forms.MasterDetailPage.IsPresentedProperty, true);
            }
    
            void MasterControllerWillDisappear(object sender, EventArgs e)
            {
                _masterVisible = false;
                if (MasterDetailPage.CanChangeIsPresented)
                    ElementController.SetValueFromRenderer(Xamarin.Forms.MasterDetailPage.IsPresentedProperty, false);
            }
    
            void PerformButtonSelector()
            {
                DisplayModeButtonItem.Target.PerformSelector(DisplayModeButtonItem.Action, DisplayModeButtonItem, 0);
            }
    
            void ToggleMaster()
            {
                if (_masterVisible == MasterDetailPage.IsPresented || MasterDetailPage.ShouldShowSplitMode)
                    return;
    
                PerformButtonSelector();
            }
    
            void UpdateBackground()
            {
                if (!string.IsNullOrEmpty(((Page)Element).BackgroundImage))
                    View.BackgroundColor = UIColor.FromPatternImage(UIImage.FromBundle(((Page)Element).BackgroundImage));
                else if (Element.BackgroundColor == Color.Default)
                    View.BackgroundColor = UIColor.White;
                else
                    View.BackgroundColor = Element.BackgroundColor.ToUIColor();
            }
    
            void UpdateControllers()
            {
                MasterDetailPage.Master.PropertyChanged -= HandleMasterPropertyChanged;
    
                if (Platform.GetRenderer(MasterDetailPage.Master) == null)
                    Platform.SetRenderer(MasterDetailPage.Master, Platform.CreateRenderer(MasterDetailPage.Master));
                if (Platform.GetRenderer(MasterDetailPage.Detail) == null)
                    Platform.SetRenderer(MasterDetailPage.Detail, Platform.CreateRenderer(MasterDetailPage.Detail));
    
                ClearControllers();
    
                MasterDetailPage.Master.PropertyChanged += HandleMasterPropertyChanged;
    
                var master = Platform.GetRenderer(MasterDetailPage.Master).ViewController;
                var detail = Platform.GetRenderer(MasterDetailPage.Detail).ViewController;
    
                _masterController.View.AddSubview(master.View);
                _masterController.AddChildViewController(master);
    
                _detailController.View.AddSubview(detail.View);
                _detailController.AddChildViewController(detail);
            }
    
            void UpdateFlowDirection()
            {
                bool ios9orLater = UIDevice.CurrentDevice.CheckSystemVersion(9, 0);
    
                if (NativeView == null || View == null || !ios9orLater)
                    return;
                View.SemanticContentAttribute = UISemanticContentAttribute.ForceLeftToRight;
            }
    
            class InnerDelegate : UISplitViewControllerDelegate
            {
                readonly MasterBehavior _masterPresentedDefaultState;
    
                public InnerDelegate(MasterBehavior masterPresentedDefaultState)
                {
                    _masterPresentedDefaultState = masterPresentedDefaultState;
                }
    
                public UIBarButtonItem PresentButton { get; set; }
    
                public override bool ShouldHideViewController(UISplitViewController svc, UIViewController viewController, UIInterfaceOrientation inOrientation)
                {
                    bool willHideViewController;
                    switch (_masterPresentedDefaultState)
                    {
                        case MasterBehavior.Split:
                            willHideViewController = false;
                            break;
                        case MasterBehavior.Popover:
                            willHideViewController = true;
                            break;
                        case MasterBehavior.SplitOnPortrait:
                            willHideViewController = !(inOrientation == UIInterfaceOrientation.Portrait || inOrientation == UIInterfaceOrientation.PortraitUpsideDown);
                            break;
                        default:
                            willHideViewController = inOrientation == UIInterfaceOrientation.Portrait || inOrientation == UIInterfaceOrientation.PortraitUpsideDown;
                            break;
                    }
                    return willHideViewController;
                }
    
                public override void WillHideViewController(UISplitViewController svc, UIViewController aViewController, UIBarButtonItem barButtonItem, UIPopoverController pc)
                {
                    PresentButton = barButtonItem;
                }
            }
    
            void IEffectControlProvider.RegisterEffect(Effect effect)
            {
                VisualElementRenderer<VisualElement>.RegisterEffect(effect, View);
            }
    
            protected override void Dispose(bool disposing)
            {
                if (_disposed)
                {
                    return;
                }
    
                _disposed = true;
    
                if (disposing)
                {
                    if (Element != null)
                    {
                        PageController.SendDisappearing();
                        Element.PropertyChanged -= HandlePropertyChanged;
    
                        if (MasterDetailPage?.Master != null)
                        {
                            MasterDetailPage.Master.PropertyChanged -= HandleMasterPropertyChanged;
                        }
    
                        Element = null;
                    }
    
                    if (_tracker != null)
                    {
                        _tracker.Dispose();
                        _tracker = null;
                    }
    
                    if (_events != null)
                    {
                        _events.Dispose();
                        _events = null;
                    }
    
                    if (_masterController != null)
                    {
                        _masterController.WillAppear -= MasterControllerWillAppear;
                        _masterController.WillDisappear -= MasterControllerWillDisappear;
                    }
    
                    ClearControllers();
                }
    
                base.Dispose(disposing);
            }
        }
    
        internal class ChildViewController : UIViewController
        {
            public override void ViewDidLayoutSubviews()
            {
                foreach (var vc in ChildViewControllers)
                {
                    CGRect rect = View.Bounds;
                    vc.View.Frame = rect;
                }
            }
        }
    
        internal class EventedViewController : ChildViewController
        {
            public override void ViewWillAppear(bool animated)
            {
                base.ViewWillAppear(animated);
    
                var eh = WillAppear;
                if (eh != null)
                    eh(this, EventArgs.Empty);
            }
    
            public override void ViewWillDisappear(bool animated)
            {
                base.ViewWillDisappear(animated);
    
                var eh = WillDisappear;
                if (eh != null)
                    eh(this, EventArgs.Empty);
            }
    
            public override void ViewDidLayoutSubviews()
            {
                CGRect rect = View.Bounds;
                View.Bounds = rect;
    
                foreach (var vc in ChildViewControllers)
                {
                    rect = vc.View.Frame;
                    vc.View.Frame = rect;
    
                    rect = vc.View.Bounds;
                    vc.View.Bounds = rect;
    
                }
            }
    
            public event EventHandler WillAppear;
    
            public event EventHandler WillDisappear;
        }
    }
    

    `

  • ClayBrinleeClayBrinlee USMember ✭✭

    I can't get the accepted solution to work. I get a NullReferenceException when trying to create the master detail page object from a Unity IoC.

  • NMackayNMackay GBInsider, University mod
    edited August 2018

    @ClayBrinlee said:
    I can't get the accepted solution to work. I get a NullReferenceException when trying to create the master detail page object from a Unity IoC.

    I doubt that's anything to do with IoC wrapper. Try the solution with a blank template project and see if it fails, these solutions are liable to fail with new versions of Forms, nothing to do the the workarounds but framework changes affect extended code. I don't see a posted as answered so not sure what is the "accepted solution".

  • Mohamed_MagdyMohamed_Magdy Member ✭✭

    Hi @RyanHatfieldOld ,
    your solution was amazing But For UWP render there is no AddView() method to override.
    What would be the way to do this on UWP?

Sign In or Register to comment.