diff options
author | Jason Smith <jason.smith@xamarin.com> | 2016-03-22 13:02:25 -0700 |
---|---|---|
committer | Jason Smith <jason.smith@xamarin.com> | 2016-03-22 16:13:41 -0700 |
commit | 17fdde66d94155fc62a034fa6658995bef6fd6e5 (patch) | |
tree | b5e5073a2a7b15cdbe826faa5c763e270a505729 /Xamarin.Forms.Platform.iOS/Platform.cs | |
download | xamarin-forms-17fdde66d94155fc62a034fa6658995bef6fd6e5.tar.gz xamarin-forms-17fdde66d94155fc62a034fa6658995bef6fd6e5.tar.bz2 xamarin-forms-17fdde66d94155fc62a034fa6658995bef6fd6e5.zip |
Initial import
Diffstat (limited to 'Xamarin.Forms.Platform.iOS/Platform.cs')
-rw-r--r-- | Xamarin.Forms.Platform.iOS/Platform.cs | 505 |
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 |