using System; using System.Linq; using System.Drawing; using System.ComponentModel; #if __UNIFIED__ using UIKit; #else using MonoTouch.UIKit; #endif #if __UNIFIED__ using RectangleF = CoreGraphics.CGRect; using SizeF = CoreGraphics.CGSize; using PointF = CoreGraphics.CGPoint; #else using nfloat=System.Single; using nint=System.Int32; using nuint=System.UInt32; #endif namespace Xamarin.Forms.Platform.iOS { public class PhoneMasterDetailRenderer : UIViewController, IVisualElementRenderer, IEffectControlProvider { UIView _clickOffView; UIViewController _detailController; bool _disposed; EventTracker _events; UIViewController _masterController; UIPanGestureRecognizer _panGesture; bool _presented; UIGestureRecognizer _tapGesture; VisualElementTracker _tracker; public PhoneMasterDetailRenderer() { if (!Forms.IsiOS7OrNewer) WantsFullScreenLayout = true; } bool Presented { get { return _presented; } set { if (_presented == value) return; _presented = value; LayoutChildren(true); if (value) AddClickOffView(); else RemoveClickOffView(); ((IElementController)Element).SetValueFromRenderer(MasterDetailPage.IsPresentedProperty, value); } } public VisualElement Element { get; private set; } public event EventHandler ElementChanged; public SizeRequest GetDesiredSize(double widthConstraint, double heightConstraint) { return NativeView.GetSizeRequest(widthConstraint, heightConstraint); } public UIView NativeView { get { return View; } } public void SetElement(VisualElement element) { var oldElement = Element; Element = element; Element.SizeChanged += PageOnSizeChanged; _masterController = new ChildViewController(); _detailController = new ChildViewController(); _clickOffView = new UIView(); _clickOffView.BackgroundColor = new Color(0, 0, 0, 0).ToUIColor(); Presented = ((MasterDetailPage)Element).IsPresented; OnElementChanged(new VisualElementChangedEventArgs(oldElement, element)); EffectUtilities.RegisterEffectControlProvider(this, oldElement, element); if (element != null) element.SendViewInitialized(NativeView); } public void SetElementSize(Size size) { Element.Layout(new Rectangle(Element.X, Element.Y, size.Width, size.Height)); } public UIViewController ViewController { get { return this; } } public override void ViewDidAppear(bool animated) { base.ViewDidAppear(animated); ((Page)Element).SendAppearing(); } public override void ViewDidDisappear(bool animated) { base.ViewDidDisappear(animated); ((Page)Element).SendDisappearing(); } public override void ViewDidLayoutSubviews() { base.ViewDidLayoutSubviews(); LayoutChildren(false); } public override void ViewDidLoad() { base.ViewDidLoad(); _tracker = new VisualElementTracker(this); _events = new EventTracker(this); _events.LoadEvents(View); ((MasterDetailPage)Element).PropertyChanged += HandlePropertyChanged; _tapGesture = new UITapGestureRecognizer(() => { if (Presented) Presented = false; }); _clickOffView.AddGestureRecognizer(_tapGesture); PackContainers(); UpdateMasterDetailContainers(); UpdateBackground(); UpdatePanGesture(); } public override void WillRotate(UIInterfaceOrientation toInterfaceOrientation, double duration) { if (!((MasterDetailPage)Element).ShouldShowSplitMode && _presented) Presented = false; base.WillRotate(toInterfaceOrientation, duration); } protected override void Dispose(bool disposing) { if (disposing && !_disposed) { Element.SizeChanged -= PageOnSizeChanged; Element.PropertyChanged -= HandlePropertyChanged; if (_tracker != null) { _tracker.Dispose(); _tracker = null; } if (_events != null) { _events.Dispose(); _events = null; } if (_tapGesture != null) { if (_clickOffView != null && _clickOffView.GestureRecognizers.Contains(_panGesture)) { _clickOffView.GestureRecognizers.Remove(_tapGesture); _clickOffView.Dispose(); } _tapGesture.Dispose(); } if (_panGesture != null) { if (View != null && View.GestureRecognizers.Contains(_panGesture)) View.GestureRecognizers.Remove(_panGesture); _panGesture.Dispose(); } EmptyContainers(); ((Page)Element).SendDisappearing(); _disposed = true; } base.Dispose(disposing); } protected virtual void OnElementChanged(VisualElementChangedEventArgs e) { var changed = ElementChanged; if (changed != null) changed(this, e); } void AddClickOffView() { View.Add(_clickOffView); _clickOffView.Frame = _detailController.View.Frame; } void EmptyContainers() { foreach (var child in _detailController.View.Subviews.Concat(_masterController.View.Subviews)) child.RemoveFromSuperview(); foreach (var vc in _detailController.ChildViewControllers.Concat(_masterController.ChildViewControllers)) vc.RemoveFromParentViewController(); } void HandleMasterPropertyChanged(object sender, PropertyChangedEventArgs e) { if (e.PropertyName == Page.IconProperty.PropertyName || e.PropertyName == Page.TitleProperty.PropertyName) MessagingCenter.Send(this, NavigationRenderer.UpdateToolbarButtons); } void HandlePropertyChanged(object sender, PropertyChangedEventArgs e) { if (e.PropertyName == "Master" || e.PropertyName == "Detail") UpdateMasterDetailContainers(); else if (e.PropertyName == MasterDetailPage.IsPresentedProperty.PropertyName) Presented = ((MasterDetailPage)Element).IsPresented; else if (e.PropertyName == MasterDetailPage.IsGestureEnabledProperty.PropertyName) UpdatePanGesture(); else if (e.PropertyName == VisualElement.BackgroundColorProperty.PropertyName) UpdateBackground(); else if (e.PropertyName == Page.BackgroundImageProperty.PropertyName) UpdateBackground(); } void LayoutChildren(bool animated) { var frame = Element.Bounds.ToRectangleF(); var masterFrame = frame; masterFrame.Width = (int)(Math.Min(masterFrame.Width, masterFrame.Height) * 0.8); _masterController.View.Frame = masterFrame; var target = frame; if (Presented) target.X += masterFrame.Width; if (animated) { UIView.BeginAnimations("Flyout"); var view = _detailController.View; view.Frame = target; UIView.SetAnimationCurve(UIViewAnimationCurve.EaseOut); UIView.SetAnimationDuration(250); UIView.CommitAnimations(); } else _detailController.View.Frame = target; ((MasterDetailPage)Element).MasterBounds = new Rectangle(0, 0, masterFrame.Width, masterFrame.Height); ((MasterDetailPage)Element).DetailBounds = new Rectangle(0, 0, frame.Width, frame.Height); if (Presented) _clickOffView.Frame = _detailController.View.Frame; } void PackContainers() { _detailController.View.BackgroundColor = new UIColor(1, 1, 1, 1); View.AddSubview(_masterController.View); View.AddSubview(_detailController.View); AddChildViewController(_masterController); AddChildViewController(_detailController); } void PageOnSizeChanged(object sender, EventArgs eventArgs) { LayoutChildren(false); } void RemoveClickOffView() { _clickOffView.RemoveFromSuperview(); } 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 UpdateMasterDetailContainers() { ((MasterDetailPage)Element).Master.PropertyChanged -= HandleMasterPropertyChanged; EmptyContainers(); if (Platform.GetRenderer(((MasterDetailPage)Element).Master) == null) Platform.SetRenderer(((MasterDetailPage)Element).Master, Platform.CreateRenderer(((MasterDetailPage)Element).Master)); if (Platform.GetRenderer(((MasterDetailPage)Element).Detail) == null) Platform.SetRenderer(((MasterDetailPage)Element).Detail, Platform.CreateRenderer(((MasterDetailPage)Element).Detail)); var masterRenderer = Platform.GetRenderer(((MasterDetailPage)Element).Master); var detailRenderer = Platform.GetRenderer(((MasterDetailPage)Element).Detail); ((MasterDetailPage)Element).Master.PropertyChanged += HandleMasterPropertyChanged; _masterController.View.AddSubview(masterRenderer.NativeView); _masterController.AddChildViewController(masterRenderer.ViewController); _detailController.View.AddSubview(detailRenderer.NativeView); _detailController.AddChildViewController(detailRenderer.ViewController); } void UpdatePanGesture() { var model = (MasterDetailPage)Element; if (!model.IsGestureEnabled) { if (_panGesture != null) View.RemoveGestureRecognizer(_panGesture); return; } if (_panGesture != null) { View.AddGestureRecognizer(_panGesture); return; } UITouchEventArgs shouldRecieve = (g, t) => !(t.View is UISlider); var center = new PointF(); _panGesture = new UIPanGestureRecognizer(g => { switch (g.State) { case UIGestureRecognizerState.Began: center = g.LocationInView(g.View); break; case UIGestureRecognizerState.Changed: var currentPosition = g.LocationInView(g.View); var motion = currentPosition.X - center.X; var detailView = _detailController.View; var targetFrame = detailView.Frame; if (Presented) targetFrame.X = (nfloat)Math.Max(0, _masterController.View.Frame.Width + Math.Min(0, motion)); else targetFrame.X = (nfloat)Math.Min(_masterController.View.Frame.Width, Math.Max(0, motion)); detailView.Frame = targetFrame; break; case UIGestureRecognizerState.Ended: var detailFrame = _detailController.View.Frame; var masterFrame = _masterController.View.Frame; if (Presented) { if (detailFrame.X < masterFrame.Width * .75) Presented = false; else LayoutChildren(true); } else { if (detailFrame.X > masterFrame.Width * .25) Presented = true; else LayoutChildren(true); } break; } }); _panGesture.ShouldReceiveTouch = shouldRecieve; _panGesture.MaximumNumberOfTouches = 2; View.AddGestureRecognizer(_panGesture); } class ChildViewController : UIViewController { public override void ViewDidLayoutSubviews() { foreach (var vc in ChildViewControllers) vc.View.Frame = View.Bounds; } } void IEffectControlProvider.RegisterEffect(Effect effect) { var platformEffect = effect as PlatformEffect; if (platformEffect != null) platformEffect.Container = View; } } }