summaryrefslogtreecommitdiff
path: root/Xamarin.Forms.Platform.iOS/Platform.cs
diff options
context:
space:
mode:
Diffstat (limited to 'Xamarin.Forms.Platform.iOS/Platform.cs')
-rw-r--r--Xamarin.Forms.Platform.iOS/Platform.cs505
1 files changed, 505 insertions, 0 deletions
diff --git a/Xamarin.Forms.Platform.iOS/Platform.cs b/Xamarin.Forms.Platform.iOS/Platform.cs
new file mode 100644
index 00000000..0293e75f
--- /dev/null
+++ b/Xamarin.Forms.Platform.iOS/Platform.cs
@@ -0,0 +1,505 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Drawing;
+using System.Linq;
+using System.Runtime.InteropServices;
+using System.Threading.Tasks;
+#if __UNIFIED__
+using CoreGraphics;
+using Foundation;
+using ObjCRuntime;
+using UIKit;
+#else
+using MonoTouch;
+using MonoTouch.CoreAnimation;
+using MonoTouch.CoreFoundation;
+using MonoTouch.CoreGraphics;
+using MonoTouch.UIKit;
+using System.Drawing;
+using MonoTouch.CoreAnimation;
+using MonoTouch.Foundation;
+using MonoTouch.ObjCRuntime;
+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 Platform : BindableObject, IPlatform, INavigation, IDisposable
+ {
+ internal static readonly BindableProperty RendererProperty = BindableProperty.CreateAttached("Renderer", typeof(IVisualElementRenderer), typeof(Platform), default(IVisualElementRenderer),
+ propertyChanged: (bindable, oldvalue, newvalue) =>
+ {
+ var view = bindable as VisualElement;
+ if (view != null)
+ view.IsPlatformEnabled = newvalue != null;
+ });
+
+ readonly int _alertPadding = 10;
+
+ readonly List<Page> _modals;
+ readonly PlatformRenderer _renderer;
+ bool _animateModals = true;
+ bool _appeared;
+
+ bool _disposed;
+
+ internal Platform()
+ {
+ _renderer = new PlatformRenderer(this);
+ _modals = new List<Page>();
+
+ var busyCount = 0;
+ MessagingCenter.Subscribe(this, Page.BusySetSignalName, (Page sender, bool enabled) =>
+ {
+ if (!PageIsChildOfPlatform(sender))
+ return;
+ busyCount = Math.Max(0, enabled ? busyCount + 1 : busyCount - 1);
+ UIApplication.SharedApplication.NetworkActivityIndicatorVisible = busyCount > 0;
+ });
+
+ MessagingCenter.Subscribe(this, Page.AlertSignalName, (Page sender, AlertArguments arguments) =>
+ {
+ if (!PageIsChildOfPlatform(sender))
+ return;
+
+ if (Forms.IsiOS8OrNewer)
+ {
+ var alert = UIAlertController.Create(arguments.Title, arguments.Message, UIAlertControllerStyle.Alert);
+ var oldFrame = alert.View.Frame;
+ alert.View.Frame = new RectangleF(oldFrame.X, oldFrame.Y, oldFrame.Width, oldFrame.Height - _alertPadding * 2);
+ alert.AddAction(UIAlertAction.Create(arguments.Cancel, UIAlertActionStyle.Cancel, a => arguments.SetResult(false)));
+ if (arguments.Accept != null)
+ alert.AddAction(UIAlertAction.Create(arguments.Accept, UIAlertActionStyle.Default, a => arguments.SetResult(true)));
+ var page = _modals.Any() ? _modals.Last() : Page;
+ var vc = GetRenderer(page).ViewController;
+ vc.PresentViewController(alert, true, null);
+ }
+ else
+ {
+ UIAlertView alertView;
+ if (arguments.Accept != null)
+ alertView = new UIAlertView(arguments.Title, arguments.Message, null, arguments.Cancel, arguments.Accept);
+ else
+ alertView = new UIAlertView(arguments.Title, arguments.Message, null, arguments.Cancel);
+
+ alertView.Dismissed += (o, args) => arguments.SetResult(args.ButtonIndex != 0);
+ alertView.Show();
+ }
+ });
+
+ MessagingCenter.Subscribe(this, Page.ActionSheetSignalName, (Page sender, ActionSheetArguments arguments) =>
+ {
+ if (!PageIsChildOfPlatform(sender))
+ return;
+
+ var pageRoot = sender;
+ while (!Application.IsApplicationOrNull(pageRoot.RealParent))
+ pageRoot = (Page)pageRoot.RealParent;
+ var pageRenderer = GetRenderer(pageRoot);
+
+ if (Forms.IsiOS8OrNewer)
+ {
+ var alert = UIAlertController.Create(arguments.Title, null, UIAlertControllerStyle.ActionSheet);
+
+ if (arguments.Cancel != null)
+ {
+ alert.AddAction(UIAlertAction.Create(arguments.Cancel, UIAlertActionStyle.Cancel, a => arguments.SetResult(arguments.Cancel)));
+ }
+
+ if (arguments.Destruction != null)
+ {
+ alert.AddAction(UIAlertAction.Create(arguments.Destruction, UIAlertActionStyle.Destructive, a => arguments.SetResult(arguments.Destruction)));
+ }
+
+ foreach (var label in arguments.Buttons)
+ {
+ if (label == null)
+ continue;
+
+ var blabel = label;
+ alert.AddAction(UIAlertAction.Create(blabel, UIAlertActionStyle.Default, a => arguments.SetResult(blabel)));
+ }
+
+ if (UIDevice.CurrentDevice.UserInterfaceIdiom == UIUserInterfaceIdiom.Pad)
+ {
+ UIDevice.CurrentDevice.BeginGeneratingDeviceOrientationNotifications();
+ var observer = NSNotificationCenter.DefaultCenter.AddObserver(UIDevice.OrientationDidChangeNotification,
+ n => { alert.PopoverPresentationController.SourceRect = pageRenderer.ViewController.View.Bounds; });
+
+ arguments.Result.Task.ContinueWith(t =>
+ {
+ NSNotificationCenter.DefaultCenter.RemoveObserver(observer);
+ UIDevice.CurrentDevice.EndGeneratingDeviceOrientationNotifications();
+ }, TaskScheduler.FromCurrentSynchronizationContext());
+
+ alert.PopoverPresentationController.SourceView = pageRenderer.ViewController.View;
+ alert.PopoverPresentationController.SourceRect = pageRenderer.ViewController.View.Bounds;
+ alert.PopoverPresentationController.PermittedArrowDirections = 0; // No arrow
+ }
+
+ pageRenderer.ViewController.PresentViewController(alert, true, null);
+ }
+ else
+ {
+ var actionSheet = new UIActionSheet(arguments.Title, null, arguments.Cancel, arguments.Destruction, arguments.Buttons.ToArray());
+
+ actionSheet.ShowInView(pageRenderer.NativeView);
+
+ actionSheet.Clicked += (o, args) =>
+ {
+ string title = null;
+ if (args.ButtonIndex != -1)
+ title = actionSheet.ButtonTitle(args.ButtonIndex);
+
+ // iOS 8 always calls WillDismiss twice, once with the real result, and again with -1.
+ arguments.Result.TrySetResult(title);
+ };
+ }
+ });
+ }
+
+ internal UIViewController ViewController
+ {
+ get { return _renderer; }
+ }
+
+ Page Page { get; set; }
+
+ Application TargetApplication
+ {
+ get
+ {
+ if (Page == null)
+ return null;
+ return Page.RealParent as Application;
+ }
+ }
+
+ void IDisposable.Dispose()
+ {
+ if (_disposed)
+ return;
+ _disposed = true;
+
+ Page.DescendantRemoved -= HandleChildRemoved;
+ MessagingCenter.Unsubscribe<Page, ActionSheetArguments>(this, Page.ActionSheetSignalName);
+ MessagingCenter.Unsubscribe<Page, AlertArguments>(this, Page.AlertSignalName);
+ MessagingCenter.Unsubscribe<Page, bool>(this, Page.BusySetSignalName);
+
+ DisposeModelAndChildrenRenderers(Page);
+ foreach (var modal in _modals)
+ DisposeModelAndChildrenRenderers(modal);
+
+ _renderer.Dispose();
+ }
+
+ void INavigation.InsertPageBefore(Page page, Page before)
+ {
+ throw new InvalidOperationException("InsertPageBefore is not supported globally on iOS, please use a NavigationPage.");
+ }
+
+ IReadOnlyList<Page> INavigation.ModalStack
+ {
+ get { return _modals; }
+ }
+
+ IReadOnlyList<Page> INavigation.NavigationStack
+ {
+ get { return new List<Page>(); }
+ }
+
+ Task<Page> INavigation.PopAsync()
+ {
+ return ((INavigation)this).PopAsync(true);
+ }
+
+ Task<Page> INavigation.PopAsync(bool animated)
+ {
+ throw new InvalidOperationException("PopAsync is not supported globally on iOS, please use a NavigationPage.");
+ }
+
+ Task<Page> INavigation.PopModalAsync()
+ {
+ return ((INavigation)this).PopModalAsync(true);
+ }
+
+ async Task<Page> INavigation.PopModalAsync(bool animated)
+ {
+ var modal = _modals.Last();
+ _modals.Remove(modal);
+ modal.DescendantRemoved -= HandleChildRemoved;
+
+ var controller = GetRenderer(modal) as UIViewController;
+
+ if (_modals.Count >= 1 && controller != null)
+ await controller.DismissViewControllerAsync(animated);
+ else
+ await _renderer.DismissViewControllerAsync(animated);
+
+ DisposeModelAndChildrenRenderers(modal);
+
+ return modal;
+ }
+
+ Task INavigation.PopToRootAsync()
+ {
+ return ((INavigation)this).PopToRootAsync(true);
+ }
+
+ Task INavigation.PopToRootAsync(bool animated)
+ {
+ throw new InvalidOperationException("PopToRootAsync is not supported globally on iOS, please use a NavigationPage.");
+ }
+
+ Task INavigation.PushAsync(Page root)
+ {
+ return ((INavigation)this).PushAsync(root, true);
+ }
+
+ Task INavigation.PushAsync(Page root, bool animated)
+ {
+ throw new InvalidOperationException("PushAsync is not supported globally on iOS, please use a NavigationPage.");
+ }
+
+ Task INavigation.PushModalAsync(Page modal)
+ {
+ return ((INavigation)this).PushModalAsync(modal, true);
+ }
+
+ Task INavigation.PushModalAsync(Page modal, bool animated)
+ {
+ _modals.Add(modal);
+ modal.Platform = this;
+
+ modal.DescendantRemoved += HandleChildRemoved;
+
+ if (_appeared)
+ return PresentModal(modal, _animateModals && animated);
+ return Task.FromResult<object>(null);
+ }
+
+ void INavigation.RemovePage(Page page)
+ {
+ throw new InvalidOperationException("RemovePage is not supported globally on iOS, please use a NavigationPage.");
+ }
+
+ SizeRequest IPlatform.GetNativeSize(VisualElement view, double widthConstraint, double heightConstraint)
+ {
+ var renderView = GetRenderer(view);
+ if (renderView == null || renderView.NativeView == null)
+ return new SizeRequest(Size.Zero);
+
+ return renderView.GetDesiredSize(widthConstraint, heightConstraint);
+ }
+
+ public static IVisualElementRenderer CreateRenderer(VisualElement element)
+ {
+ var t = element.GetType();
+ var renderer = Registrar.Registered.GetHandler<IVisualElementRenderer>(t) ?? new DefaultRenderer();
+ renderer.SetElement(element);
+ return renderer;
+ }
+
+ public static IVisualElementRenderer GetRenderer(VisualElement bindable)
+ {
+ return (IVisualElementRenderer)bindable.GetValue(RendererProperty);
+ }
+
+ public static void SetRenderer(VisualElement bindable, IVisualElementRenderer value)
+ {
+ bindable.SetValue(RendererProperty, value);
+ }
+
+ protected override void OnBindingContextChanged()
+ {
+ SetInheritedBindingContext(Page, BindingContext);
+
+ base.OnBindingContextChanged();
+ }
+
+ internal void DidAppear()
+ {
+ _animateModals = false;
+ TargetApplication.NavigationProxy.Inner = this;
+ _animateModals = true;
+ }
+
+ internal void DisposeModelAndChildrenRenderers(Element view)
+ {
+ IVisualElementRenderer renderer;
+ foreach (VisualElement child in view.Descendants())
+ {
+ renderer = GetRenderer(child);
+ child.ClearValue(RendererProperty);
+
+ if (renderer != null)
+ {
+ renderer.NativeView.RemoveFromSuperview();
+ renderer.Dispose();
+ }
+ }
+
+ renderer = GetRenderer((VisualElement)view);
+ if (renderer != null)
+ {
+ if (renderer.ViewController != null)
+ {
+ var modalWrapper = renderer.ViewController.ParentViewController as ModalWrapper;
+ if (modalWrapper != null)
+ modalWrapper.Dispose();
+ }
+
+ renderer.NativeView.RemoveFromSuperview();
+ renderer.Dispose();
+ }
+ view.ClearValue(RendererProperty);
+ }
+
+ internal void DisposeRendererAndChildren(IVisualElementRenderer rendererToRemove)
+ {
+ if (rendererToRemove == null)
+ return;
+
+ if (rendererToRemove.Element != null && GetRenderer(rendererToRemove.Element) == rendererToRemove)
+ rendererToRemove.Element.ClearValue(RendererProperty);
+
+ var subviews = rendererToRemove.NativeView.Subviews;
+ for (var i = 0; i < subviews.Length; i++)
+ {
+ var childRenderer = subviews[i] as IVisualElementRenderer;
+ if (childRenderer != null)
+ DisposeRendererAndChildren(childRenderer);
+ }
+
+ rendererToRemove.NativeView.RemoveFromSuperview();
+ rendererToRemove.Dispose();
+ }
+
+ internal void LayoutSubviews()
+ {
+ if (Page == null)
+ return;
+
+ var rootRenderer = GetRenderer(Page);
+
+ if (rootRenderer == null)
+ return;
+
+ rootRenderer.SetElementSize(new Size(_renderer.View.Bounds.Width, _renderer.View.Bounds.Height));
+ }
+
+ internal void SetPage(Page newRoot)
+ {
+ if (newRoot == null)
+ return;
+ if (Page != null)
+ throw new NotImplementedException();
+ Page = newRoot;
+
+ if (_appeared == false)
+ return;
+
+ Page.Platform = this;
+ AddChild(Page);
+
+ Page.DescendantRemoved += HandleChildRemoved;
+
+ TargetApplication.NavigationProxy.Inner = this;
+ }
+
+ internal void WillAppear()
+ {
+ if (_appeared)
+ return;
+
+ _renderer.View.BackgroundColor = UIColor.White;
+ _renderer.View.ContentMode = UIViewContentMode.Redraw;
+
+ Page.Platform = this;
+ AddChild(Page);
+
+ Page.DescendantRemoved += HandleChildRemoved;
+
+ _appeared = true;
+ }
+
+ void AddChild(VisualElement view)
+ {
+ if (!Application.IsApplicationOrNull(view.RealParent))
+ Console.Error.WriteLine("Tried to add parented view to canvas directly");
+
+ if (GetRenderer(view) == null)
+ {
+ var viewRenderer = CreateRenderer(view);
+ SetRenderer(view, viewRenderer);
+
+ _renderer.View.AddSubview(viewRenderer.NativeView);
+ if (viewRenderer.ViewController != null)
+ _renderer.AddChildViewController(viewRenderer.ViewController);
+ viewRenderer.NativeView.Frame = new RectangleF(0, 0, _renderer.View.Bounds.Width, _renderer.View.Bounds.Height);
+ viewRenderer.SetElementSize(new Size(_renderer.View.Bounds.Width, _renderer.View.Bounds.Height));
+ }
+ else
+ Console.Error.WriteLine("Potential view double add");
+ }
+
+ void HandleChildRemoved(object sender, ElementEventArgs e)
+ {
+ var view = e.Element;
+ DisposeModelAndChildrenRenderers(view);
+ }
+
+ bool PageIsChildOfPlatform(Page page)
+ {
+ while (!Application.IsApplicationOrNull(page.RealParent))
+ page = (Page)page.RealParent;
+
+ return Page == page || _modals.Contains(page);
+ }
+
+ async Task PresentModal(Page modal, bool animated)
+ {
+ var modalRenderer = GetRenderer(modal);
+ if (modalRenderer == null)
+ {
+ modalRenderer = CreateRenderer(modal);
+ SetRenderer(modal, modalRenderer);
+ }
+
+ var wrapper = new ModalWrapper(modalRenderer);
+
+ if (_modals.Count > 1)
+ {
+ var topPage = _modals[_modals.Count - 2];
+ var controller = GetRenderer(topPage) as UIViewController;
+ if (controller != null)
+ {
+ await controller.PresentViewControllerAsync(wrapper, animated);
+ await Task.Delay(5);
+ return;
+ }
+ }
+
+ // One might wonder why these delays are here... well thats a great question. It turns out iOS will claim the
+ // presentation is complete before it really is. It does not however inform you when it is really done (and thus
+ // would be safe to dismiss the VC). Fortunately this is almost never an issue
+ await _renderer.PresentViewControllerAsync(wrapper, animated);
+ await Task.Delay(5);
+ }
+
+ internal class DefaultRenderer : VisualElementRenderer<VisualElement>
+ {
+ }
+ }
+} \ No newline at end of file