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.
@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.
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."...
@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.
@RyanHatfieldOld I am getting this error "System.NullReferenceException: Object reference not set to an instance of an object.Xamarin.Forms.in MasterDetailRenderer.cs:99"
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;
}
}
}
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;
}
}
}
`
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 }
};
`
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;
}
}
@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".
Answers
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.
no working for me, any solution. ? On LoadApplication(new App()) (Debug) it says :System.NullReferenceException
Btter 2 use a scroll View inside of the Transparent MasterPage
Yeah, it works for me. Thanks for idea.
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,
@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.
@RyanHatfieldOld I am getting this error "System.NullReferenceException: Object reference not set to an instance of an object.Xamarin.Forms.in MasterDetailRenderer.cs:99"
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.
Nothing in the pipe line for a proper implmentation of this?
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 ) :
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) :
@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
I am also interested by this implementation for iOS. Anyone?
@manishchoudhary or @Kobal Do you find a solution?
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
@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.
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;
}
`
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)
};
`
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.
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;
`
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".
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?