summaryrefslogtreecommitdiff
path: root/Xamarin.Forms.Platform.MacOS
diff options
context:
space:
mode:
Diffstat (limited to 'Xamarin.Forms.Platform.MacOS')
-rw-r--r--Xamarin.Forms.Platform.MacOS/CADisplayLinkTicker.cs78
-rw-r--r--Xamarin.Forms.Platform.MacOS/Cells/CellNSView.cs167
-rw-r--r--Xamarin.Forms.Platform.MacOS/Cells/CellRenderer.cs68
-rw-r--r--Xamarin.Forms.Platform.MacOS/Cells/EntryCellRenderer.cs144
-rw-r--r--Xamarin.Forms.Platform.MacOS/Cells/ImageCellRenderer.cs68
-rw-r--r--Xamarin.Forms.Platform.MacOS/Cells/NSTableViewCellStyle.cs12
-rw-r--r--Xamarin.Forms.Platform.MacOS/Cells/SwitchCellRenderer.cs88
-rw-r--r--Xamarin.Forms.Platform.MacOS/Cells/TextCellRenderer.cs66
-rw-r--r--Xamarin.Forms.Platform.MacOS/Cells/ViewCellNSView.cs105
-rw-r--r--Xamarin.Forms.Platform.MacOS/Cells/ViewCellRenderer.cs46
-rw-r--r--Xamarin.Forms.Platform.MacOS/Controls/FormsImageView.cs16
-rw-r--r--Xamarin.Forms.Platform.MacOS/Controls/FormsPageControllerDelegate.cs29
-rw-r--r--Xamarin.Forms.Platform.MacOS/Controls/MacOSOpenGLView.cs12
-rw-r--r--Xamarin.Forms.Platform.MacOS/Controls/NSToolbarItemGroup.cs102
-rw-r--r--Xamarin.Forms.Platform.MacOS/Controls/NavigationChildPageWrapper.cs41
-rw-r--r--Xamarin.Forms.Platform.MacOS/Controls/ScrollViewScrollChangedEventArgs.cs10
-rw-r--r--Xamarin.Forms.Platform.MacOS/Controls/VerticallyCenteredTextFieldCell.cs36
-rw-r--r--Xamarin.Forms.Platform.MacOS/Extensions/AlignmentExtensions.cs21
-rw-r--r--Xamarin.Forms.Platform.MacOS/Extensions/ButtonExtensions.cs25
-rw-r--r--Xamarin.Forms.Platform.MacOS/Extensions/NSButtonExtensions.cs25
-rw-r--r--Xamarin.Forms.Platform.MacOS/Extensions/NSImageExtensions.cs22
-rw-r--r--Xamarin.Forms.Platform.MacOS/Extensions/NSScrollViewExtensions.cs36
-rw-r--r--Xamarin.Forms.Platform.MacOS/Extensions/NSTableViewExtensions.cs22
-rw-r--r--Xamarin.Forms.Platform.MacOS/Extensions/NSTextFieldExtensions.cs46
-rw-r--r--Xamarin.Forms.Platform.MacOS/Extensions/NSViewControllerExtensions.cs24
-rw-r--r--Xamarin.Forms.Platform.MacOS/Extensions/PageExtensions.cs28
-rw-r--r--Xamarin.Forms.Platform.MacOS/FormsApplicationDelegate.cs85
-rw-r--r--Xamarin.Forms.Platform.MacOS/ImageSourceHandlers.cs64
-rw-r--r--Xamarin.Forms.Platform.MacOS/ModalPageTracker.cs118
-rw-r--r--Xamarin.Forms.Platform.MacOS/NativeToolbarTracker.cs429
-rw-r--r--Xamarin.Forms.Platform.MacOS/Platform.cs262
-rw-r--r--Xamarin.Forms.Platform.MacOS/PlatformNavigation.cs113
-rw-r--r--Xamarin.Forms.Platform.MacOS/PlatformRenderer.cs50
-rw-r--r--Xamarin.Forms.Platform.MacOS/Properties/AssemblyInfo.cs53
-rw-r--r--Xamarin.Forms.Platform.MacOS/Renderers/ActivityIndicatorRenderer.cs70
-rw-r--r--Xamarin.Forms.Platform.MacOS/Renderers/BoxViewRenderer.cs39
-rw-r--r--Xamarin.Forms.Platform.MacOS/Renderers/ButtonRenderer.cs131
-rw-r--r--Xamarin.Forms.Platform.MacOS/Renderers/CarouselPageRenderer.cs229
-rw-r--r--Xamarin.Forms.Platform.MacOS/Renderers/CustomNSTableHeaderView.cs46
-rw-r--r--Xamarin.Forms.Platform.MacOS/Renderers/DatePickerRenderer.cs138
-rw-r--r--Xamarin.Forms.Platform.MacOS/Renderers/DefaultRenderer.cs6
-rw-r--r--Xamarin.Forms.Platform.MacOS/Renderers/EditorRenderer.cs128
-rw-r--r--Xamarin.Forms.Platform.MacOS/Renderers/EntryRenderer.cs205
-rw-r--r--Xamarin.Forms.Platform.MacOS/Renderers/FrameRenderer.cs57
-rw-r--r--Xamarin.Forms.Platform.MacOS/Renderers/ImageRenderer.cs117
-rw-r--r--Xamarin.Forms.Platform.MacOS/Renderers/LayoutRenderer.cs39
-rw-r--r--Xamarin.Forms.Platform.MacOS/Renderers/ListViewDataSource.cs299
-rw-r--r--Xamarin.Forms.Platform.MacOS/Renderers/ListViewRenderer.cs342
-rw-r--r--Xamarin.Forms.Platform.MacOS/Renderers/MasterDetailPageRenderer.cs204
-rw-r--r--Xamarin.Forms.Platform.MacOS/Renderers/NSPageContainer.cs17
-rw-r--r--Xamarin.Forms.Platform.MacOS/Renderers/NavigationPageRenderer.cs355
-rw-r--r--Xamarin.Forms.Platform.MacOS/Renderers/OpenGLViewRenderer.cs104
-rw-r--r--Xamarin.Forms.Platform.MacOS/Renderers/PageControllerDelegate.cs30
-rw-r--r--Xamarin.Forms.Platform.MacOS/Renderers/PageRenderer.cs182
-rw-r--r--Xamarin.Forms.Platform.MacOS/Renderers/PickerRenderer.cs154
-rw-r--r--Xamarin.Forms.Platform.MacOS/Renderers/ProgressBarRenderer.cs66
-rw-r--r--Xamarin.Forms.Platform.MacOS/Renderers/ScrollViewRenderer.cs214
-rw-r--r--Xamarin.Forms.Platform.MacOS/Renderers/SearchBarRenderer.cs179
-rw-r--r--Xamarin.Forms.Platform.MacOS/Renderers/SliderRenderer.cs77
-rw-r--r--Xamarin.Forms.Platform.MacOS/Renderers/StepperRenderer.cs84
-rw-r--r--Xamarin.Forms.Platform.MacOS/Renderers/SwitchRenderer.cs61
-rw-r--r--Xamarin.Forms.Platform.MacOS/Renderers/TabbedPageRenderer.cs403
-rw-r--r--Xamarin.Forms.Platform.MacOS/Renderers/TableViewDataSource.cs131
-rw-r--r--Xamarin.Forms.Platform.MacOS/Renderers/TableViewRenderer.cs98
-rw-r--r--Xamarin.Forms.Platform.MacOS/Renderers/TimePickerRenderer.cs104
-rw-r--r--Xamarin.Forms.Platform.MacOS/Renderers/WebViewRenderer.cs149
-rw-r--r--Xamarin.Forms.Platform.MacOS/Xamarin.Forms.Platform.macOS.csproj239
67 files changed, 7208 insertions, 0 deletions
diff --git a/Xamarin.Forms.Platform.MacOS/CADisplayLinkTicker.cs b/Xamarin.Forms.Platform.MacOS/CADisplayLinkTicker.cs
new file mode 100644
index 0000000..1e965ff
--- /dev/null
+++ b/Xamarin.Forms.Platform.MacOS/CADisplayLinkTicker.cs
@@ -0,0 +1,78 @@
+using System;
+using System.Collections.Concurrent;
+using System.Threading;
+using Foundation;
+using Xamarin.Forms.Internals;
+using CoreVideo;
+using AppKit;
+using CoreAnimation;
+
+namespace Xamarin.Forms.Platform.MacOS
+{
+ // ReSharper disable once InconsistentNaming
+ internal class CADisplayLinkTicker : Ticker
+ {
+ readonly BlockingCollection<Action> _queue = new BlockingCollection<Action>();
+ CVDisplayLink _link;
+
+ public CADisplayLinkTicker()
+ {
+ var thread = new Thread(StartThread);
+ thread.Start();
+ }
+
+ internal new static CADisplayLinkTicker Default => Ticker.Default as CADisplayLinkTicker;
+
+ public void Invoke(Action action)
+ {
+ _queue.Add(action);
+ }
+
+ protected override void DisableTimer()
+ {
+ _link?.Stop();
+ _link?.Dispose();
+ _link = null;
+ }
+
+ protected override void EnableTimer()
+ {
+ _link = new CVDisplayLink();
+ _link.SetOutputCallback(DisplayLinkOutputCallback);
+ _link.Start();
+ }
+
+ public CVReturn DisplayLinkOutputCallback(CVDisplayLink displayLink, ref CVTimeStamp inNow,
+ ref CVTimeStamp inOutputTime, CVOptionFlags flagsIn, ref CVOptionFlags flagsOut)
+ {
+ // There is no autorelease pool when this method is called because it will be called from a background thread
+ // It's important to create one or you will leak objects
+ // ReSharper disable once UnusedVariable
+ using (var pool = new NSAutoreleasePool())
+ {
+ Device.BeginInvokeOnMainThread(() => SendSignals());
+ }
+ return CVReturn.Success;
+ }
+
+ void StartThread()
+ {
+ while (true)
+ {
+ Action action = _queue.Take();
+ bool previous = NSApplication.CheckForIllegalCrossThreadCalls;
+ NSApplication.CheckForIllegalCrossThreadCalls = false;
+
+ CATransaction.Begin();
+ action.Invoke();
+
+ while (_queue.TryTake(out action))
+ action.Invoke();
+ CATransaction.Commit();
+
+ NSApplication.CheckForIllegalCrossThreadCalls = previous;
+ }
+ // ReSharper disable once FunctionNeverReturns
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.MacOS/Cells/CellNSView.cs b/Xamarin.Forms.Platform.MacOS/Cells/CellNSView.cs
new file mode 100644
index 0000000..1ba964e
--- /dev/null
+++ b/Xamarin.Forms.Platform.MacOS/Cells/CellNSView.cs
@@ -0,0 +1,167 @@
+using System;
+using System.ComponentModel;
+using AppKit;
+using CoreGraphics;
+
+namespace Xamarin.Forms.Platform.MacOS
+{
+ internal class CellNSView : NSView, INativeElementView
+ {
+ static readonly NSColor s_defaultChildViewsBackground = NSColor.Clear;
+ static readonly CGColor s_defaultHeaderViewsBackground = NSColor.LightGray.CGColor;
+ Cell _cell;
+ readonly NSTableViewCellStyle _style;
+
+ public Action<object, PropertyChangedEventArgs> PropertyChanged;
+
+ public CellNSView(NSTableViewCellStyle style)
+ {
+ WantsLayer = true;
+ _style = style;
+ CreateUI();
+ }
+
+ public NSTextField TextLabel { get; private set; }
+
+ public NSTextField DetailTextLabel { get; private set; }
+
+ public NSImageView ImageView { get; private set; }
+
+ public NSView AccessoryView { get; private set; }
+
+ public Element Element => Cell;
+
+ public Cell Cell
+ {
+ get { return _cell; }
+ set
+ {
+ if (_cell == value)
+ return;
+
+ ICellController cellController = _cell;
+
+ if (cellController != null)
+ Device.BeginInvokeOnMainThread(cellController.SendDisappearing);
+
+ _cell = value;
+ cellController = value;
+
+ if (cellController != null)
+ Device.BeginInvokeOnMainThread(cellController.SendAppearing);
+ }
+ }
+
+ public void HandlePropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ PropertyChanged?.Invoke(this, e);
+ }
+
+ public override void Layout()
+ {
+ const int padding = 10;
+ nfloat availableHeight = Frame.Height;
+ nfloat availableWidth = Frame.Width - padding * 2;
+ nfloat imageWidth = 0;
+ nfloat accessoryViewWidth = 0;
+
+ if (ImageView != null)
+ {
+ nfloat imageHeight = imageWidth = availableHeight;
+ ImageView.Frame = new CGRect(padding, 0, imageWidth, imageHeight);
+ }
+
+ if (AccessoryView != null)
+ {
+ accessoryViewWidth = _style == NSTableViewCellStyle.Value1 ? 50 : availableWidth - 100;
+ AccessoryView.Frame = new CGRect(availableWidth - accessoryViewWidth + padding, 0, accessoryViewWidth,
+ availableHeight);
+ foreach (var subView in AccessoryView.Subviews)
+ {
+ //try to find the size the control wants, if no width use default width
+ var size = subView.FittingSize;
+ if (size.Width == 0)
+ size.Width = accessoryViewWidth;
+
+ var x = AccessoryView.Bounds.Width - size.Width;
+ var y = (AccessoryView.Bounds.Height - size.Height) / 2;
+ subView.Frame = new CGRect(new CGPoint(x, y), size);
+ }
+ }
+
+ nfloat labelHeights = availableHeight;
+ nfloat labelWidth = availableWidth - imageWidth - accessoryViewWidth;
+
+ if (!string.IsNullOrEmpty(DetailTextLabel?.StringValue))
+ {
+ labelHeights = availableHeight / 2;
+ DetailTextLabel.CenterTextVertically(new CGRect(imageWidth + padding, 0, labelWidth, labelHeights));
+ }
+
+ TextLabel.CenterTextVertically(new CGRect(imageWidth + padding, availableHeight - labelHeights, labelWidth,
+ labelHeights));
+ base.Layout();
+ }
+
+ internal static NSView GetNativeCell(NSTableView tableView, Cell cell, string templateId = "", bool isHeader = false,
+ bool isRecycle = false)
+ {
+ var reusable = tableView.MakeView(templateId, tableView);
+ NSView nativeCell;
+ if (reusable == null || !isRecycle)
+ {
+ var renderer = (CellRenderer)Registrar.Registered.GetHandler(cell.GetType());
+ nativeCell = renderer.GetCell(cell, null, tableView);
+ }
+ else
+ {
+ nativeCell = reusable;
+ }
+
+ if (string.IsNullOrEmpty(nativeCell.Identifier))
+ nativeCell.Identifier = templateId;
+
+ if (!isHeader) return nativeCell;
+ if (nativeCell.Layer != null) nativeCell.Layer.BackgroundColor = s_defaultHeaderViewsBackground;
+ return nativeCell;
+ }
+
+ void CreateUI()
+ {
+ var style = _style;
+
+ AddSubview(TextLabel = new NSTextField
+ {
+ Bordered = false,
+ Selectable = false,
+ Editable = false,
+ Font = NSFont.LabelFontOfSize(NSFont.SystemFontSize)
+ });
+
+ TextLabel.Cell.BackgroundColor = s_defaultChildViewsBackground;
+
+ if (style == NSTableViewCellStyle.Image || style == NSTableViewCellStyle.Subtitle ||
+ style == NSTableViewCellStyle.ImageSubtitle)
+ {
+ AddSubview(DetailTextLabel = new NSTextField
+ {
+ Bordered = false,
+ Selectable = false,
+ Editable = false,
+ Font = NSFont.LabelFontOfSize(NSFont.SmallSystemFontSize)
+ });
+ DetailTextLabel.Cell.BackgroundColor = s_defaultChildViewsBackground;
+ }
+
+ if (style == NSTableViewCellStyle.Image || style == NSTableViewCellStyle.ImageSubtitle)
+ AddSubview(ImageView = new NSImageView());
+
+ if (style == NSTableViewCellStyle.Value1 || style == NSTableViewCellStyle.Value2)
+ {
+ var accessoryView = new NSView { WantsLayer = true };
+ accessoryView.Layer.BackgroundColor = s_defaultChildViewsBackground.CGColor;
+ AddSubview(AccessoryView = accessoryView);
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.MacOS/Cells/CellRenderer.cs b/Xamarin.Forms.Platform.MacOS/Cells/CellRenderer.cs
new file mode 100644
index 0000000..54e540a
--- /dev/null
+++ b/Xamarin.Forms.Platform.MacOS/Cells/CellRenderer.cs
@@ -0,0 +1,68 @@
+using System;
+using AppKit;
+using CoreGraphics;
+
+namespace Xamarin.Forms.Platform.MacOS
+{
+ public class CellRenderer : IRegisterable
+ {
+ static readonly BindableProperty s_realCellProperty = BindableProperty.CreateAttached("RealCell", typeof(NSView),
+ typeof(Cell), null);
+
+ EventHandler _onForceUpdateSizeRequested;
+
+ public virtual NSView GetCell(Cell item, NSView reusableView, NSTableView tv)
+ {
+ var tvc = reusableView as CellNSView ?? new CellNSView(NSTableViewCellStyle.Default);
+
+ tvc.Cell = item;
+
+ WireUpForceUpdateSizeRequested(item, tvc, tv);
+
+ tvc.TextLabel.StringValue = item.ToString();
+
+ UpdateBackground(tvc, item);
+
+ return tvc;
+ }
+
+ protected void UpdateBackground(NSView tableViewCell, Cell cell)
+ {
+ tableViewCell.WantsLayer = true;
+ var bgColor = NSColor.White;
+ var element = cell.RealParent as VisualElement;
+ if (element != null)
+ bgColor = element.BackgroundColor == Color.Default ? bgColor : element.BackgroundColor.ToNSColor();
+
+ UpdateBackgroundChild(cell, bgColor);
+
+ tableViewCell.Layer.BackgroundColor = bgColor.CGColor;
+ }
+
+ protected void WireUpForceUpdateSizeRequested(ICellController cell, NSView nativeCell, NSTableView tableView)
+ {
+ cell.ForceUpdateSizeRequested -= _onForceUpdateSizeRequested;
+
+ _onForceUpdateSizeRequested = (sender, e) =>
+ {
+ //TODO: Implement ForceUpdateSize
+ };
+
+ cell.ForceUpdateSizeRequested += _onForceUpdateSizeRequested;
+ }
+
+ internal virtual void UpdateBackgroundChild(Cell cell, NSColor backgroundColor)
+ {
+ }
+
+ internal static NSView GetRealCell(BindableObject cell)
+ {
+ return (NSView)cell.GetValue(s_realCellProperty);
+ }
+
+ internal static void SetRealCell(BindableObject cell, NSView renderer)
+ {
+ cell.SetValue(s_realCellProperty, renderer);
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.MacOS/Cells/EntryCellRenderer.cs b/Xamarin.Forms.Platform.MacOS/Cells/EntryCellRenderer.cs
new file mode 100644
index 0000000..43789dc
--- /dev/null
+++ b/Xamarin.Forms.Platform.MacOS/Cells/EntryCellRenderer.cs
@@ -0,0 +1,144 @@
+using System;
+using System.ComponentModel;
+using AppKit;
+using CoreGraphics;
+using Foundation;
+
+namespace Xamarin.Forms.Platform.MacOS
+{
+ public class EntryCellRenderer : CellRenderer
+ {
+ static readonly Color s_defaultTextColor = Color.Black;
+
+ public override NSView GetCell(Cell item, NSView reusableView, NSTableView tv)
+ {
+ NSTextField nsEntry = null;
+ var tvc = reusableView as CellNSView;
+ if (tvc == null)
+ tvc = new CellNSView(NSTableViewCellStyle.Value2);
+ else
+ {
+ tvc.Cell.PropertyChanged -= OnCellPropertyChanged;
+
+ nsEntry = tvc.AccessoryView.Subviews[0] as NSTextField;
+ if (nsEntry != null)
+ {
+ nsEntry.RemoveFromSuperview();
+ nsEntry.Changed -= OnTextFieldTextChanged;
+ }
+ }
+
+ SetRealCell(item, tvc);
+
+ if (nsEntry == null)
+ tvc.AccessoryView.AddSubview(nsEntry = new NSTextField());
+
+ var entryCell = (EntryCell)item;
+
+ tvc.Cell = item;
+ tvc.Cell.PropertyChanged += OnCellPropertyChanged;
+ nsEntry.Changed += OnTextFieldTextChanged;
+
+ WireUpForceUpdateSizeRequested(item, tvc, tv);
+
+ UpdateBackground(tvc, entryCell);
+ UpdateLabel(tvc, entryCell);
+ UpdateText(tvc, entryCell);
+ UpdatePlaceholder(tvc, entryCell);
+ UpdateLabelColor(tvc, entryCell);
+ UpdateHorizontalTextAlignment(tvc, entryCell);
+ UpdateIsEnabled(tvc, entryCell);
+
+ return tvc;
+ }
+
+ internal override void UpdateBackgroundChild(Cell cell, NSColor backgroundColor)
+ {
+ var realCell = (CellNSView)GetRealCell(cell);
+
+ var nsTextField = realCell.AccessoryView.Subviews[0] as NSTextField;
+ if (nsTextField != null)
+ nsTextField.BackgroundColor = backgroundColor;
+
+ base.UpdateBackgroundChild(cell, backgroundColor);
+ }
+
+ static void OnCellPropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ var entryCell = (EntryCell)sender;
+ var realCell = (CellNSView)GetRealCell(entryCell);
+
+ if (e.PropertyName == EntryCell.LabelProperty.PropertyName)
+ UpdateLabel(realCell, entryCell);
+ else if (e.PropertyName == EntryCell.TextProperty.PropertyName)
+ UpdateText(realCell, entryCell);
+ else if (e.PropertyName == EntryCell.PlaceholderProperty.PropertyName)
+ UpdatePlaceholder(realCell, entryCell);
+ else if (e.PropertyName == EntryCell.LabelColorProperty.PropertyName)
+ UpdateLabelColor(realCell, entryCell);
+ else if (e.PropertyName == EntryCell.HorizontalTextAlignmentProperty.PropertyName)
+ UpdateHorizontalTextAlignment(realCell, entryCell);
+ else if (e.PropertyName == Cell.IsEnabledProperty.PropertyName)
+ UpdateIsEnabled(realCell, entryCell);
+ }
+
+ static void OnTextFieldTextChanged(object sender, EventArgs eventArgs)
+ {
+ var notification = (NSNotification)sender;
+ var view = (NSView)notification.Object;
+ var field = (NSTextField)view;
+
+ CellNSView realCell = null;
+ while (view.Superview != null && realCell == null)
+ {
+ view = view.Superview;
+ realCell = view as CellNSView;
+ }
+
+ if (realCell != null)
+ ((EntryCell)realCell.Cell).Text = field.StringValue;
+ }
+
+ static void UpdateHorizontalTextAlignment(CellNSView cell, EntryCell entryCell)
+ {
+ var nsTextField = cell.AccessoryView.Subviews[0] as NSTextField;
+ if (nsTextField != null)
+ nsTextField.Alignment = entryCell.HorizontalTextAlignment.ToNativeTextAlignment();
+ }
+
+ static void UpdateIsEnabled(CellNSView cell, EntryCell entryCell)
+ {
+ cell.TextLabel.Enabled = entryCell.IsEnabled;
+ var nsTextField = cell.AccessoryView.Subviews[0] as NSTextField;
+ if (nsTextField != null)
+ nsTextField.Enabled = entryCell.IsEnabled;
+ }
+
+ static void UpdateLabel(CellNSView cell, EntryCell entryCell)
+ {
+ cell.TextLabel.StringValue = entryCell.Label ?? "";
+ }
+
+ static void UpdateLabelColor(CellNSView cell, EntryCell entryCell)
+ {
+ cell.TextLabel.TextColor = entryCell.LabelColor.ToNSColor(s_defaultTextColor);
+ }
+
+ static void UpdatePlaceholder(CellNSView cell, EntryCell entryCell)
+ {
+ var nsTextField = cell.AccessoryView.Subviews[0] as NSTextField;
+ if (nsTextField != null)
+ nsTextField.PlaceholderString = entryCell.Placeholder ?? "";
+ }
+
+ static void UpdateText(CellNSView cell, EntryCell entryCell)
+ {
+ var nsTextField = cell.AccessoryView.Subviews[0] as NSTextField;
+ if (nsTextField != null && nsTextField.StringValue == entryCell.Text)
+ return;
+
+ if (nsTextField != null)
+ nsTextField.StringValue = entryCell.Text ?? "";
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.MacOS/Cells/ImageCellRenderer.cs b/Xamarin.Forms.Platform.MacOS/Cells/ImageCellRenderer.cs
new file mode 100644
index 0000000..8bd7677
--- /dev/null
+++ b/Xamarin.Forms.Platform.MacOS/Cells/ImageCellRenderer.cs
@@ -0,0 +1,68 @@
+using System.ComponentModel;
+using System.Threading.Tasks;
+using AppKit;
+using Foundation;
+
+namespace Xamarin.Forms.Platform.MacOS
+{
+ public class ImageCellRenderer : TextCellRenderer
+ {
+ public override NSView GetCell(Cell item, NSView reusableView, NSTableView tv)
+ {
+ var tvc = reusableView as CellNSView ?? new CellNSView(NSTableViewCellStyle.ImageSubtitle);
+
+ var result = (CellNSView)base.GetCell(item, tvc, tv);
+
+ var imageCell = (ImageCell)item;
+
+ WireUpForceUpdateSizeRequested(item, result, tv);
+
+#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
+ SetImage(imageCell, result);
+#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
+
+ return result;
+ }
+
+ protected override async void HandlePropertyChanged(object sender, PropertyChangedEventArgs args)
+ {
+ var tvc = (CellNSView)sender;
+ var imageCell = (ImageCell)tvc.Cell;
+
+ base.HandlePropertyChanged(sender, args);
+
+ if (args.PropertyName == ImageCell.ImageSourceProperty.PropertyName)
+ await SetImage(imageCell, tvc);
+ }
+
+ static async Task SetImage(ImageCell cell, CellNSView target)
+ {
+ var source = cell.ImageSource;
+
+ target.ImageView.Image = null;
+
+ IImageSourceHandler handler;
+
+ if (source != null && (handler = Registrar.Registered.GetHandler<IImageSourceHandler>(source.GetType())) != null)
+ {
+ NSImage uiimage;
+ try
+ {
+ uiimage = await handler.LoadImageAsync(source).ConfigureAwait(false);
+ }
+ catch (TaskCanceledException)
+ {
+ uiimage = null;
+ }
+
+ NSRunLoop.Main.BeginInvokeOnMainThread(() =>
+ {
+ target.ImageView.Image = uiimage;
+ target.NeedsLayout = true;
+ });
+ }
+ else
+ target.ImageView.Image = null;
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.MacOS/Cells/NSTableViewCellStyle.cs b/Xamarin.Forms.Platform.MacOS/Cells/NSTableViewCellStyle.cs
new file mode 100644
index 0000000..3e0235d
--- /dev/null
+++ b/Xamarin.Forms.Platform.MacOS/Cells/NSTableViewCellStyle.cs
@@ -0,0 +1,12 @@
+namespace Xamarin.Forms.Platform.MacOS
+{
+ internal enum NSTableViewCellStyle
+ {
+ Default,
+ Value1,
+ Value2,
+ Subtitle,
+ Image,
+ ImageSubtitle
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.MacOS/Cells/SwitchCellRenderer.cs b/Xamarin.Forms.Platform.MacOS/Cells/SwitchCellRenderer.cs
new file mode 100644
index 0000000..5f086b4
--- /dev/null
+++ b/Xamarin.Forms.Platform.MacOS/Cells/SwitchCellRenderer.cs
@@ -0,0 +1,88 @@
+using System;
+using System.ComponentModel;
+using AppKit;
+
+namespace Xamarin.Forms.Platform.MacOS
+{
+ public class SwitchCellRenderer : CellRenderer
+ {
+ public override NSView GetCell(Cell item, NSView reusableView, NSTableView tv)
+ {
+ var tvc = reusableView as CellNSView;
+ NSButton nsSwitch = null;
+ if (tvc == null)
+ tvc = new CellNSView(NSTableViewCellStyle.Value1);
+ else
+ {
+ nsSwitch = tvc.AccessoryView.Subviews[0] as NSButton;
+ if (nsSwitch != null)
+ {
+ nsSwitch.RemoveFromSuperview();
+ nsSwitch.Activated -= OnSwitchValueChanged;
+ }
+ tvc.Cell.PropertyChanged -= OnCellPropertyChanged;
+ }
+
+ SetRealCell(item, tvc);
+
+ if (nsSwitch == null)
+ {
+ nsSwitch = new NSButton { AllowsMixedState = false, Title = string.Empty };
+ nsSwitch.SetButtonType(NSButtonType.Switch);
+ }
+
+ var boolCell = (SwitchCell)item;
+
+ tvc.Cell = item;
+ tvc.Cell.PropertyChanged += OnCellPropertyChanged;
+ tvc.AccessoryView.AddSubview(nsSwitch);
+ tvc.TextLabel.StringValue = boolCell.Text ?? "";
+
+ nsSwitch.State = boolCell.On ? NSCellStateValue.On : NSCellStateValue.Off;
+ nsSwitch.Activated += OnSwitchValueChanged;
+ WireUpForceUpdateSizeRequested(item, tvc, tv);
+
+ UpdateBackground(tvc, item);
+ UpdateIsEnabled(tvc, boolCell);
+
+ return tvc;
+ }
+
+ static void UpdateIsEnabled(CellNSView cell, SwitchCell switchCell)
+ {
+ cell.TextLabel.Enabled = switchCell.IsEnabled;
+ var uiSwitch = cell.AccessoryView.Subviews[0] as NSButton;
+ if (uiSwitch != null)
+ uiSwitch.Enabled = switchCell.IsEnabled;
+ }
+
+ void OnCellPropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ var boolCell = (SwitchCell)sender;
+ var realCell = (CellNSView)GetRealCell(boolCell);
+
+ if (e.PropertyName == SwitchCell.OnProperty.PropertyName)
+ ((NSButton)realCell.AccessoryView.Subviews[0]).State = boolCell.On ? NSCellStateValue.On : NSCellStateValue.Off;
+ else if (e.PropertyName == SwitchCell.TextProperty.PropertyName)
+ realCell.TextLabel.StringValue = boolCell.Text ?? "";
+ else if (e.PropertyName == Cell.IsEnabledProperty.PropertyName)
+ UpdateIsEnabled(realCell, boolCell);
+ }
+
+ void OnSwitchValueChanged(object sender, EventArgs eventArgs)
+ {
+ var view = (NSView)sender;
+ var sw = (NSButton)view;
+
+ CellNSView realCell = null;
+ while (view.Superview != null && realCell == null)
+ {
+ view = view.Superview;
+ realCell = view as CellNSView;
+ }
+
+ if (realCell != null)
+ ((SwitchCell)realCell.Cell).On = sw.State == NSCellStateValue.On;
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.MacOS/Cells/TextCellRenderer.cs b/Xamarin.Forms.Platform.MacOS/Cells/TextCellRenderer.cs
new file mode 100644
index 0000000..6e36ce7
--- /dev/null
+++ b/Xamarin.Forms.Platform.MacOS/Cells/TextCellRenderer.cs
@@ -0,0 +1,66 @@
+using System.ComponentModel;
+using AppKit;
+
+namespace Xamarin.Forms.Platform.MacOS
+{
+ public class TextCellRenderer : CellRenderer
+ {
+ static readonly Color s_defaultDetailColor = new Color(.32, .4, .57);
+ static readonly Color s_defaultTextColor = Color.Black;
+
+ public override NSView GetCell(Cell item, NSView reusableView, NSTableView tv)
+ {
+ var textCell = (TextCell)item;
+
+ var tvc = reusableView as CellNSView ?? new CellNSView(NSTableViewCellStyle.Subtitle);
+
+ if (tvc.Cell != null)
+ tvc.Cell.PropertyChanged -= tvc.HandlePropertyChanged;
+
+ tvc.Cell = textCell;
+ textCell.PropertyChanged += tvc.HandlePropertyChanged;
+ tvc.PropertyChanged = HandlePropertyChanged;
+
+ tvc.TextLabel.StringValue = textCell.Text ?? "";
+ tvc.DetailTextLabel.StringValue = textCell.Detail ?? "";
+ tvc.TextLabel.TextColor = textCell.TextColor.ToNSColor(s_defaultTextColor);
+ tvc.DetailTextLabel.TextColor = textCell.DetailColor.ToNSColor(s_defaultDetailColor);
+
+ WireUpForceUpdateSizeRequested(item, tvc, tv);
+
+ UpdateIsEnabled(tvc, textCell);
+
+ UpdateBackground(tvc, item);
+
+ return tvc;
+ }
+
+ protected virtual void HandlePropertyChanged(object sender, PropertyChangedEventArgs args)
+ {
+ var tvc = (CellNSView)sender;
+ var textCell = (TextCell)tvc.Cell;
+ if (args.PropertyName == TextCell.TextProperty.PropertyName)
+ {
+ tvc.TextLabel.StringValue = textCell.Text ?? "";
+ tvc.TextLabel.SizeToFit();
+ }
+ else if (args.PropertyName == TextCell.DetailProperty.PropertyName)
+ {
+ tvc.DetailTextLabel.StringValue = textCell.Detail ?? "";
+ tvc.DetailTextLabel.SizeToFit();
+ }
+ else if (args.PropertyName == TextCell.TextColorProperty.PropertyName)
+ tvc.TextLabel.TextColor = textCell.TextColor.ToNSColor(s_defaultTextColor);
+ else if (args.PropertyName == TextCell.DetailColorProperty.PropertyName)
+ tvc.DetailTextLabel.TextColor = textCell.DetailColor.ToNSColor(s_defaultTextColor);
+ else if (args.PropertyName == Cell.IsEnabledProperty.PropertyName)
+ UpdateIsEnabled(tvc, textCell);
+ }
+
+ static void UpdateIsEnabled(CellNSView cell, TextCell entryCell)
+ {
+ cell.TextLabel.Enabled = entryCell.IsEnabled;
+ cell.DetailTextLabel.Enabled = entryCell.IsEnabled;
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.MacOS/Cells/ViewCellNSView.cs b/Xamarin.Forms.Platform.MacOS/Cells/ViewCellNSView.cs
new file mode 100644
index 0000000..0dd766a
--- /dev/null
+++ b/Xamarin.Forms.Platform.MacOS/Cells/ViewCellNSView.cs
@@ -0,0 +1,105 @@
+using System;
+using AppKit;
+using RectangleF = CoreGraphics.CGRect;
+
+namespace Xamarin.Forms.Platform.MacOS
+{
+ public class ViewCellNSView : NSView, INativeElementView
+ {
+ WeakReference<IVisualElementRenderer> _rendererRef;
+
+ ViewCell _viewCell;
+
+ public Element Element => ViewCell;
+
+ public ViewCell ViewCell
+ {
+ get { return _viewCell; }
+ set
+ {
+ if (_viewCell == value)
+ return;
+ UpdateCell(value);
+ }
+ }
+
+ public override void Layout()
+ {
+ LayoutSubviews();
+ base.Layout();
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ IVisualElementRenderer renderer;
+ if (_rendererRef != null && _rendererRef.TryGetTarget(out renderer) && renderer.Element != null)
+ {
+ Platform.DisposeModelAndChildrenRenderers(renderer.Element);
+
+ _rendererRef = null;
+ }
+ }
+
+ base.Dispose(disposing);
+ }
+
+ void LayoutSubviews()
+ {
+ var contentFrame = Frame;
+ var view = ViewCell.View;
+
+ Xamarin.Forms.Layout.LayoutChildIntoBoundingRegion(view, contentFrame.ToRectangle());
+
+ if (_rendererRef == null)
+ return;
+
+ IVisualElementRenderer renderer;
+ if (_rendererRef.TryGetTarget(out renderer))
+ renderer.NativeView.Frame = view.Bounds.ToRectangleF();
+ }
+
+ IVisualElementRenderer GetNewRenderer()
+ {
+ var newRenderer = Platform.CreateRenderer(_viewCell.View);
+ _rendererRef = new WeakReference<IVisualElementRenderer>(newRenderer);
+ AddSubview(newRenderer.NativeView);
+ return newRenderer;
+ }
+
+ void UpdateCell(ViewCell cell)
+ {
+ ICellController cellController = _viewCell;
+ if (cellController != null)
+ Device.BeginInvokeOnMainThread(cellController.SendDisappearing);
+
+ _viewCell = cell;
+ cellController = cell;
+
+ Device.BeginInvokeOnMainThread(cellController.SendAppearing);
+
+ IVisualElementRenderer renderer;
+ if (_rendererRef == null || !_rendererRef.TryGetTarget(out renderer))
+ renderer = GetNewRenderer();
+ else
+ {
+ if (renderer.Element != null && renderer == Platform.GetRenderer(renderer.Element))
+ renderer.Element.ClearValue(Platform.RendererProperty);
+
+ var type = Registrar.Registered.GetHandlerType(_viewCell.View.GetType());
+ if (renderer.GetType() == type || (renderer is DefaultRenderer && type == null))
+ renderer.SetElement(_viewCell.View);
+ else
+ {
+ //when cells are getting reused the element could be already set to another cell
+ //so we should dispose based on the renderer and not the renderer.Element
+ Platform.DisposeRendererAndChildren(renderer);
+ renderer = GetNewRenderer();
+ }
+ }
+
+ Platform.SetRenderer(_viewCell.View, renderer);
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.MacOS/Cells/ViewCellRenderer.cs b/Xamarin.Forms.Platform.MacOS/Cells/ViewCellRenderer.cs
new file mode 100644
index 0000000..8b34518
--- /dev/null
+++ b/Xamarin.Forms.Platform.MacOS/Cells/ViewCellRenderer.cs
@@ -0,0 +1,46 @@
+using System.ComponentModel;
+using AppKit;
+
+// ReSharper disable UnusedParameter.Local
+
+namespace Xamarin.Forms.Platform.MacOS
+{
+ public class ViewCellRenderer : CellRenderer
+ {
+ public override NSView GetCell(Cell item, NSView reusableView, NSTableView tv)
+ {
+ var viewCell = (ViewCell)item;
+
+ var cell = reusableView as ViewCellNSView;
+ if (cell == null)
+ cell = new ViewCellNSView();
+ else
+ cell.ViewCell.PropertyChanged -= ViewCellPropertyChanged;
+
+ viewCell.PropertyChanged += ViewCellPropertyChanged;
+ cell.ViewCell = viewCell;
+
+ SetRealCell(item, cell);
+
+ WireUpForceUpdateSizeRequested(item, cell, tv);
+
+ UpdateBackground(cell, item);
+ UpdateIsEnabled(cell, viewCell);
+ return cell;
+ }
+
+ static void UpdateIsEnabled(ViewCellNSView cell, ViewCell viewCell)
+ {
+ //TODO: Implement IsEnabled on ViewCell
+ }
+
+ static void ViewCellPropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ var viewCell = (ViewCell)sender;
+ var realCell = (ViewCellNSView)GetRealCell(viewCell);
+
+ if (e.PropertyName == Cell.IsEnabledProperty.PropertyName)
+ UpdateIsEnabled(realCell, viewCell);
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.MacOS/Controls/FormsImageView.cs b/Xamarin.Forms.Platform.MacOS/Controls/FormsImageView.cs
new file mode 100644
index 0000000..33ed73b
--- /dev/null
+++ b/Xamarin.Forms.Platform.MacOS/Controls/FormsImageView.cs
@@ -0,0 +1,16 @@
+using AppKit;
+
+namespace Xamarin.Forms.Platform.MacOS
+{
+ internal class FormsNSImageView : NSImageView
+ {
+ bool _isOpaque;
+
+ public void SetIsOpaque(bool isOpaque)
+ {
+ _isOpaque = isOpaque;
+ }
+
+ public override bool IsOpaque => _isOpaque;
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.MacOS/Controls/FormsPageControllerDelegate.cs b/Xamarin.Forms.Platform.MacOS/Controls/FormsPageControllerDelegate.cs
new file mode 100644
index 0000000..5fb7455
--- /dev/null
+++ b/Xamarin.Forms.Platform.MacOS/Controls/FormsPageControllerDelegate.cs
@@ -0,0 +1,29 @@
+using System;
+using AppKit;
+using Foundation;
+
+namespace Xamarin.Forms.Platform.MacOS
+{
+ internal class FormsPageControllerDelegate : NSPageControllerDelegate
+ {
+ readonly Func<NSObject, string> _getIdentifier;
+ readonly Func<string, NSViewController> _getViewController;
+
+ public FormsPageControllerDelegate(Func<NSObject, string> getIdentifier,
+ Func<string, NSViewController> getViewController)
+ {
+ _getIdentifier = getIdentifier;
+ _getViewController = getViewController;
+ }
+
+ public override NSViewController GetViewController(NSPageController pageController, string identifier)
+ {
+ return _getViewController(identifier);
+ }
+
+ public override string GetIdentifier(NSPageController pv, NSObject obj)
+ {
+ return _getIdentifier(obj);
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.MacOS/Controls/MacOSOpenGLView.cs b/Xamarin.Forms.Platform.MacOS/Controls/MacOSOpenGLView.cs
new file mode 100644
index 0000000..1fc2387
--- /dev/null
+++ b/Xamarin.Forms.Platform.MacOS/Controls/MacOSOpenGLView.cs
@@ -0,0 +1,12 @@
+using AppKit;
+
+namespace Xamarin.Forms.Platform.MacOS
+{
+ //TODO: Still not implemented on MacOS
+ public class MacOSOpenGLView : NSView
+ {
+ public MacOSOpenGLView()
+ {
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.MacOS/Controls/NSToolbarItemGroup.cs b/Xamarin.Forms.Platform.MacOS/Controls/NSToolbarItemGroup.cs
new file mode 100644
index 0000000..ee02293
--- /dev/null
+++ b/Xamarin.Forms.Platform.MacOS/Controls/NSToolbarItemGroup.cs
@@ -0,0 +1,102 @@
+using System;
+using System.Runtime.InteropServices;
+using AppKit;
+using Foundation;
+using ObjCRuntime;
+
+[Register("NSToolbarItemGroup", true)]
+// ReSharper disable once CheckNamespace
+// ReSharper disable once InconsistentNaming
+public class NSToolbarItemGroup : NSToolbarItem
+{
+ const string SelSetSubitems = "setSubitems:";
+ const string SelSubitems = "subitems";
+ const string SelInitWithItemIdentifier = "initWithItemIdentifier:";
+ static readonly IntPtr s_selSetSubitemsHandle = Selector.GetHandle(SelSetSubitems);
+ static readonly IntPtr s_selSubitemsHandle = Selector.GetHandle(SelSubitems);
+ static readonly IntPtr s_selInitWithItemIdentifierHandle = Selector.GetHandle(SelInitWithItemIdentifier);
+ static readonly IntPtr s_classPtr = Class.GetHandle("NSToolbarItemGroup");
+
+ [Export("init")]
+ public NSToolbarItemGroup() : base(NSObjectFlag.Empty)
+ {
+ InitializeHandle(
+ IsDirectBinding
+ ? IntPtr_objc_msgSend(Handle, Selector.GetHandle("init"))
+ : IntPtr_objc_msgSendSuper(SuperHandle, Selector.GetHandle("init")), "init");
+ }
+
+ [Export("initWithItemIdentifier:")]
+ public NSToolbarItemGroup(string itemIdentifier)
+ : base(NSObjectFlag.Empty)
+ {
+ NSApplication.EnsureUIThread();
+ if (itemIdentifier == null)
+ throw new ArgumentNullException(nameof(itemIdentifier));
+ IntPtr nsitemIdentifier = NSString.CreateNative(itemIdentifier);
+
+ InitializeHandle(
+ IsDirectBinding
+ ? IntPtr_objc_msgSend_IntPtr(Handle, s_selInitWithItemIdentifierHandle, nsitemIdentifier)
+ : IntPtr_objc_msgSendSuper_IntPtr(SuperHandle, s_selInitWithItemIdentifierHandle, nsitemIdentifier),
+ "initWithItemIdentifier:");
+ NSString.ReleaseNative(nsitemIdentifier);
+ }
+
+ protected internal NSToolbarItemGroup(IntPtr handle) : base(handle)
+ {
+ }
+
+ protected NSToolbarItemGroup(NSObjectFlag t) : base(t)
+ {
+ }
+
+ public override IntPtr ClassHandle => s_classPtr;
+
+ public virtual NSToolbarItem[] Subitems
+ {
+ [Export(SelSubitems, ArgumentSemantic.Copy)]
+ get
+ {
+ NSApplication.EnsureUIThread();
+ NSToolbarItem[] ret =
+ NSArray.ArrayFromHandle<NSToolbarItem>(IsDirectBinding
+ ? IntPtr_objc_msgSend(Handle, s_selSubitemsHandle)
+ : IntPtr_objc_msgSendSuper(SuperHandle, s_selSubitemsHandle));
+ return ret;
+ }
+
+ [Export(SelSetSubitems, ArgumentSemantic.Copy)]
+ set
+ {
+ NSApplication.EnsureUIThread();
+ if (value == null)
+ throw new ArgumentNullException(nameof(value));
+ // ReSharper disable once CoVariantArrayConversion
+ NSArray nsaValue = NSArray.FromNSObjects(value);
+
+ if (IsDirectBinding)
+ void_objc_msgSend_IntPtr(Handle, s_selSetSubitemsHandle, nsaValue.Handle);
+ else void_objc_msgSendSuper_IntPtr(SuperHandle, s_selSetSubitemsHandle, nsaValue.Handle);
+ nsaValue.Dispose();
+ }
+ }
+
+ [DllImport("/usr/lib/libobjc.dylib", EntryPoint = "objc_msgSend")]
+ public static extern IntPtr IntPtr_objc_msgSend(IntPtr receiver, IntPtr selector);
+
+ [DllImport("/usr/lib/libobjc.dylib", EntryPoint = "objc_msgSend")]
+ public static extern IntPtr IntPtr_objc_msgSend_IntPtr(IntPtr receiver, IntPtr selector, IntPtr arg1);
+
+ [DllImport("/usr/lib/libobjc.dylib", EntryPoint = "objc_msgSendSuper")]
+ public static extern IntPtr IntPtr_objc_msgSendSuper(IntPtr receiver, IntPtr selector);
+
+ [DllImport("/usr/lib/libobjc.dylib", EntryPoint = "objc_msgSendSuper")]
+ public static extern IntPtr IntPtr_objc_msgSendSuper_IntPtr(IntPtr receiver, IntPtr selector, IntPtr arg1);
+
+ [DllImport("/usr/lib/libobjc.dylib", EntryPoint = "objc_msgSend")]
+ public static extern void void_objc_msgSend_IntPtr(IntPtr receiver, IntPtr selector, IntPtr arg1);
+
+ [DllImport("/usr/lib/libobjc.dylib", EntryPoint = "objc_msgSendSuper")]
+ public static extern void void_objc_msgSendSuper_IntPtr(IntPtr receiver, IntPtr selector, IntPtr arg1);
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.MacOS/Controls/NavigationChildPageWrapper.cs b/Xamarin.Forms.Platform.MacOS/Controls/NavigationChildPageWrapper.cs
new file mode 100644
index 0000000..a0e6dd4
--- /dev/null
+++ b/Xamarin.Forms.Platform.MacOS/Controls/NavigationChildPageWrapper.cs
@@ -0,0 +1,41 @@
+using System;
+using Foundation;
+
+namespace Xamarin.Forms.Platform.MacOS
+{
+ internal class NavigationChildPageWrapper : NSObject
+ {
+ bool _disposed;
+
+ public NavigationChildPageWrapper(Page page)
+ {
+ Page = page;
+ Page.PropertyChanged += PagePropertyChanged;
+ Identifier = Guid.NewGuid().ToString();
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing && !_disposed)
+ {
+ _disposed = true;
+ if (Page != null)
+ Page.PropertyChanged -= PagePropertyChanged;
+ Page = null;
+ }
+ base.Dispose(disposing);
+ }
+
+ void PagePropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
+ {
+ if (e.PropertyName == NavigationPage.HasNavigationBarProperty.PropertyName
+ || e.PropertyName == Page.TitleProperty.PropertyName
+ || e.PropertyName == NavigationPage.HasBackButtonProperty.PropertyName)
+ Platform.NativeToolbarTracker.UpdateToolBar();
+ }
+
+ public string Identifier { get; set; }
+
+ public Page Page { get; private set; }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.MacOS/Controls/ScrollViewScrollChangedEventArgs.cs b/Xamarin.Forms.Platform.MacOS/Controls/ScrollViewScrollChangedEventArgs.cs
new file mode 100644
index 0000000..a8a825c
--- /dev/null
+++ b/Xamarin.Forms.Platform.MacOS/Controls/ScrollViewScrollChangedEventArgs.cs
@@ -0,0 +1,10 @@
+using System;
+using PointF = CoreGraphics.CGPoint;
+
+namespace Xamarin.Forms.Platform.MacOS
+{
+ internal class ScrollViewScrollChangedEventArgs : EventArgs
+ {
+ public PointF CurrentScrollPoint { get; set; }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.MacOS/Controls/VerticallyCenteredTextFieldCell.cs b/Xamarin.Forms.Platform.MacOS/Controls/VerticallyCenteredTextFieldCell.cs
new file mode 100644
index 0000000..397f632
--- /dev/null
+++ b/Xamarin.Forms.Platform.MacOS/Controls/VerticallyCenteredTextFieldCell.cs
@@ -0,0 +1,36 @@
+using System;
+using AppKit;
+using CoreGraphics;
+
+namespace Xamarin.Forms.Platform.MacOS
+{
+ sealed class VerticallyCenteredTextFieldCell : NSTextFieldCell
+ {
+ readonly nfloat _yOffset;
+
+ public VerticallyCenteredTextFieldCell(nfloat yOffset, NSFont font = null)
+ {
+ if (font != null)
+ Font = font;
+ _yOffset = yOffset;
+ }
+
+ public override CGRect DrawingRectForBounds(CGRect theRect)
+ {
+ // Get the parent's idea of where we should draw.
+ CGRect newRect = base.DrawingRectForBounds(theRect);
+
+ // Ideal size for the text.
+ CGSize textSize = CellSizeForBounds(theRect);
+
+ // Center in the rect.
+ nfloat heightDelta = newRect.Size.Height - textSize.Height;
+ if (heightDelta > 0)
+ {
+ newRect.Size = new CGSize(newRect.Width, newRect.Height - heightDelta);
+ newRect.Location = new CGPoint(newRect.X, newRect.Y + heightDelta / 2 + _yOffset);
+ }
+ return newRect;
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.MacOS/Extensions/AlignmentExtensions.cs b/Xamarin.Forms.Platform.MacOS/Extensions/AlignmentExtensions.cs
new file mode 100644
index 0000000..35d0f26
--- /dev/null
+++ b/Xamarin.Forms.Platform.MacOS/Extensions/AlignmentExtensions.cs
@@ -0,0 +1,21 @@
+using System;
+using AppKit;
+
+namespace Xamarin.Forms.Platform.MacOS
+{
+ internal static class AlignmentExtensions
+ {
+ internal static NSTextAlignment ToNativeTextAlignment(this TextAlignment alignment)
+ {
+ switch (alignment)
+ {
+ case TextAlignment.Center:
+ return NSTextAlignment.Center;
+ case TextAlignment.End:
+ return NSTextAlignment.Right;
+ default:
+ return NSTextAlignment.Left;
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.MacOS/Extensions/ButtonExtensions.cs b/Xamarin.Forms.Platform.MacOS/Extensions/ButtonExtensions.cs
new file mode 100644
index 0000000..1bdc62a
--- /dev/null
+++ b/Xamarin.Forms.Platform.MacOS/Extensions/ButtonExtensions.cs
@@ -0,0 +1,25 @@
+using System;
+using AppKit;
+
+namespace Xamarin.Forms.Platform.MacOS
+{
+ internal static class ButtonExtensions
+ {
+ public static NSCellImagePosition ToNSCellImagePosition(this Button control)
+ {
+ switch (control.ContentLayout.Position)
+ {
+ case Button.ButtonContentLayout.ImagePosition.Left:
+ return NSCellImagePosition.ImageLeft;
+ case Button.ButtonContentLayout.ImagePosition.Top:
+ return NSCellImagePosition.ImageAbove;
+ case Button.ButtonContentLayout.ImagePosition.Right:
+ return NSCellImagePosition.ImageRight;
+ case Button.ButtonContentLayout.ImagePosition.Bottom:
+ return NSCellImagePosition.ImageBelow;
+ default:
+ return NSCellImagePosition.ImageOnly;
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.MacOS/Extensions/NSButtonExtensions.cs b/Xamarin.Forms.Platform.MacOS/Extensions/NSButtonExtensions.cs
new file mode 100644
index 0000000..d37f1a3
--- /dev/null
+++ b/Xamarin.Forms.Platform.MacOS/Extensions/NSButtonExtensions.cs
@@ -0,0 +1,25 @@
+using System;
+using AppKit;
+
+namespace Xamarin.Forms.Platform.MacOS
+{
+ public static class NSButtonExtensions
+ {
+ public static NSButton CreateButton(string text, Action activate = null)
+ {
+ return CreateButton(text, null, activate);
+ }
+
+ public static NSButton CreateButton(string text, NSImage image = null, Action activate = null)
+ {
+ var btn = new NSButton { Title = text };
+ btn.BezelStyle = NSBezelStyle.TexturedRounded;
+
+ if (image != null)
+ btn.Image = image;
+ if (activate != null)
+ btn.Activated += (sender, e) => activate();
+ return btn;
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.MacOS/Extensions/NSImageExtensions.cs b/Xamarin.Forms.Platform.MacOS/Extensions/NSImageExtensions.cs
new file mode 100644
index 0000000..0952173
--- /dev/null
+++ b/Xamarin.Forms.Platform.MacOS/Extensions/NSImageExtensions.cs
@@ -0,0 +1,22 @@
+using AppKit;
+
+namespace Xamarin.Forms.Platform.MacOS
+{
+ public static class NSImageExtensions
+ {
+ public static NSImage ResizeTo(this NSImage self, CoreGraphics.CGSize newSize)
+ {
+ if (self == null)
+ return null;
+ self.ResizingMode = NSImageResizingMode.Stretch;
+ var resizedImage = new NSImage(newSize);
+ resizedImage.LockFocus();
+ self.Size = newSize;
+ NSGraphicsContext.CurrentContext.ImageInterpolation = NSImageInterpolation.High;
+ self.Draw(CoreGraphics.CGPoint.Empty, new CoreGraphics.CGRect(0, 0, newSize.Width, newSize.Height),
+ NSCompositingOperation.Copy, 1.0f);
+ resizedImage.UnlockFocus();
+ return resizedImage;
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.MacOS/Extensions/NSScrollViewExtensions.cs b/Xamarin.Forms.Platform.MacOS/Extensions/NSScrollViewExtensions.cs
new file mode 100644
index 0000000..f9104d4
--- /dev/null
+++ b/Xamarin.Forms.Platform.MacOS/Extensions/NSScrollViewExtensions.cs
@@ -0,0 +1,36 @@
+using System.Threading.Tasks;
+using AppKit;
+using PointF = CoreGraphics.CGPoint;
+
+namespace Xamarin.Forms.Platform.MacOS
+{
+ internal static class NSScrollViewExtensions
+ {
+ public static Task ScrollToPositionAsync(this NSScrollView scrollView, PointF point, bool animate,
+ double duration = 0.5)
+ {
+ if (!animate)
+ {
+ var nsView = scrollView.DocumentView as NSView;
+ nsView?.ScrollPoint(point);
+ return Task.FromResult(true);
+ }
+
+ TaskCompletionSource<bool> source = new TaskCompletionSource<bool>();
+
+ NSAnimationContext.BeginGrouping();
+
+ NSAnimationContext.CurrentContext.CompletionHandler += () => { source.TrySetResult(true); };
+
+ NSAnimationContext.CurrentContext.Duration = duration;
+
+ var animator = scrollView.ContentView.Animator as NSView;
+
+ animator?.SetBoundsOrigin(point);
+
+ NSAnimationContext.EndGrouping();
+
+ return source.Task;
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.MacOS/Extensions/NSTableViewExtensions.cs b/Xamarin.Forms.Platform.MacOS/Extensions/NSTableViewExtensions.cs
new file mode 100644
index 0000000..c81e31e
--- /dev/null
+++ b/Xamarin.Forms.Platform.MacOS/Extensions/NSTableViewExtensions.cs
@@ -0,0 +1,22 @@
+using AppKit;
+
+namespace Xamarin.Forms.Platform.MacOS
+{
+ internal static class NSTableViewExtensions
+ {
+ public static NSTableView AsListViewLook(this NSTableView self)
+ {
+ self.SelectionHighlightStyle = NSTableViewSelectionHighlightStyle.SourceList;
+
+ self.AllowsColumnReordering = false;
+ self.AllowsColumnResizing = false;
+ self.AllowsColumnSelection = false;
+
+ //this is needed .. can we go around it ?
+ self.AddColumn(new NSTableColumn("1"));
+ //this line hides the header by default
+ self.HeaderView = new CustomNSTableHeaderView();
+ return self;
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.MacOS/Extensions/NSTextFieldExtensions.cs b/Xamarin.Forms.Platform.MacOS/Extensions/NSTextFieldExtensions.cs
new file mode 100644
index 0000000..9905fcd
--- /dev/null
+++ b/Xamarin.Forms.Platform.MacOS/Extensions/NSTextFieldExtensions.cs
@@ -0,0 +1,46 @@
+using AppKit;
+using CoreGraphics;
+
+namespace Xamarin.Forms.Platform.MacOS
+{
+ internal static class NSTextFieldExtensions
+ {
+ public static NSTextField CreateLabel(string text)
+ {
+ var textField = new NSTextField();
+ textField.StringValue = text;
+ textField.DrawsBackground = false;
+ textField.Editable = false;
+ textField.Bezeled = false;
+ textField.Selectable = false;
+ textField.SizeToFit();
+ textField.CenterTextVertically();
+ return textField;
+ }
+
+ public static NSTextFieldCell CreateLabelCentered(string text)
+ {
+ var textField = new VerticallyCenteredTextFieldCell(0);
+ textField.StringValue = text;
+ textField.DrawsBackground = false;
+ textField.Editable = false;
+ textField.Bezeled = false;
+ textField.Selectable = false;
+ return textField;
+ }
+
+ public static void CenterTextVertically(this NSTextField self)
+ {
+ self.CenterTextVertically(self.Frame);
+ }
+
+ public static void CenterTextVertically(this NSTextField self, CGRect frame)
+ {
+ var stringHeight = self.Cell.AttributedStringValue.Size.Height;
+ var titleRect = self.Cell.TitleRectForBounds(frame);
+ var newTitleRect = new CGRect(titleRect.X, frame.Y + (frame.Height - stringHeight) / 2.0, titleRect.Width,
+ stringHeight);
+ self.Frame = newTitleRect;
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.MacOS/Extensions/NSViewControllerExtensions.cs b/Xamarin.Forms.Platform.MacOS/Extensions/NSViewControllerExtensions.cs
new file mode 100644
index 0000000..f556232
--- /dev/null
+++ b/Xamarin.Forms.Platform.MacOS/Extensions/NSViewControllerExtensions.cs
@@ -0,0 +1,24 @@
+using System;
+using System.Threading.Tasks;
+using AppKit;
+
+namespace Xamarin.Forms.Platform.MacOS
+{
+ internal static class NSViewControllerExtensions
+ {
+ public static Task<T> HandleAsyncAnimation<T>(this NSViewController container, NSViewController fromViewController,
+ NSViewController toViewController, NSViewControllerTransitionOptions transitonOption,
+ Action animationFinishedCallback, T result)
+ {
+ var tcs = new TaskCompletionSource<T>();
+
+ container.TransitionFromViewController(fromViewController, toViewController, transitonOption, () =>
+ {
+ tcs.SetResult(result);
+ animationFinishedCallback?.Invoke();
+ });
+
+ return tcs.Task;
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.MacOS/Extensions/PageExtensions.cs b/Xamarin.Forms.Platform.MacOS/Extensions/PageExtensions.cs
new file mode 100644
index 0000000..24c9a52
--- /dev/null
+++ b/Xamarin.Forms.Platform.MacOS/Extensions/PageExtensions.cs
@@ -0,0 +1,28 @@
+using System;
+using AppKit;
+
+namespace Xamarin.Forms.Platform.MacOS
+{
+ public static class PageExtensions
+ {
+ public static NSViewController CreateViewController(this Page view)
+ {
+ if (!Forms.IsInitialized)
+ throw new InvalidOperationException("call Forms.Init() before this");
+
+ if (!(view.RealParent is Application))
+ {
+ Application app = new DefaultApplication();
+ app.MainPage = view;
+ }
+
+ var result = new Platform();
+ result.SetPage(view);
+ return result.ViewController;
+ }
+
+ class DefaultApplication : Application
+ {
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.MacOS/FormsApplicationDelegate.cs b/Xamarin.Forms.Platform.MacOS/FormsApplicationDelegate.cs
new file mode 100644
index 0000000..4899698
--- /dev/null
+++ b/Xamarin.Forms.Platform.MacOS/FormsApplicationDelegate.cs
@@ -0,0 +1,85 @@
+using System;
+using System.ComponentModel;
+using AppKit;
+
+namespace Xamarin.Forms.Platform.MacOS
+{
+ public abstract class FormsApplicationDelegate : NSApplicationDelegate
+ {
+ Application _application;
+ bool _isSuspended;
+
+ public abstract NSWindow MainWindow { get; }
+
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing && _application != null)
+ _application.PropertyChanged -= ApplicationOnPropertyChanged;
+
+ base.Dispose(disposing);
+ }
+
+ protected void LoadApplication(Application application)
+ {
+ if (application == null)
+ throw new ArgumentNullException(nameof(application));
+
+ Application.Current = application;
+ _application = application;
+
+ application.PropertyChanged += ApplicationOnPropertyChanged;
+ }
+
+ public override void DidFinishLaunching(Foundation.NSNotification notification)
+ {
+ if (MainWindow == null)
+ throw new InvalidOperationException("Please provide a main window in your app");
+
+ MainWindow.Display();
+ MainWindow.MakeKeyAndOrderFront(NSApplication.SharedApplication);
+ if (_application == null)
+ throw new InvalidOperationException("You MUST invoke LoadApplication () before calling base.FinishedLaunching ()");
+
+ SetMainPage();
+ _application.SendStart();
+ }
+
+ public override void DidBecomeActive(Foundation.NSNotification notification)
+ {
+ // applicationDidBecomeActive
+ // execute any OpenGL ES drawing calls
+ if (_application == null || !_isSuspended) return;
+ _isSuspended = false;
+ _application.SendResume();
+ }
+
+ public override async void DidResignActive(Foundation.NSNotification notification)
+ {
+ // applicationWillResignActive
+ if (_application == null) return;
+ _isSuspended = true;
+ await _application.SendSleepAsync();
+ }
+
+ void ApplicationOnPropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ if (e.PropertyName == nameof(Application.MainPage))
+ UpdateMainPage();
+ }
+
+ void SetMainPage()
+ {
+ UpdateMainPage();
+ }
+
+ void UpdateMainPage()
+ {
+ if (_application.MainPage == null)
+ return;
+
+ var platformRenderer = (PlatformRenderer)MainWindow.ContentViewController;
+ MainWindow.ContentViewController = _application.MainPage.CreateViewController();
+ (platformRenderer?.Platform as IDisposable)?.Dispose();
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.MacOS/ImageSourceHandlers.cs b/Xamarin.Forms.Platform.MacOS/ImageSourceHandlers.cs
new file mode 100644
index 0000000..7a73ace
--- /dev/null
+++ b/Xamarin.Forms.Platform.MacOS/ImageSourceHandlers.cs
@@ -0,0 +1,64 @@
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+using AppKit;
+
+namespace Xamarin.Forms.Platform.MacOS
+{
+ public interface IImageSourceHandler : IRegisterable
+ {
+ Task<NSImage> LoadImageAsync(ImageSource imagesource, CancellationToken cancelationToken = default(CancellationToken),
+ float scale = 1);
+ }
+
+ public sealed class FileImageSourceHandler : IImageSourceHandler
+ {
+ public Task<NSImage> LoadImageAsync(ImageSource imagesource,
+ CancellationToken cancelationToken = default(CancellationToken), float scale = 1f)
+ {
+ NSImage image = null;
+ var filesource = imagesource as FileImageSource;
+ var file = filesource?.File;
+ if (!string.IsNullOrEmpty(file))
+ image = File.Exists(file) ? new NSImage(file) : null;
+ return Task.FromResult(image);
+ }
+ }
+
+ public sealed class StreamImagesourceHandler : IImageSourceHandler
+ {
+ public async Task<NSImage> LoadImageAsync(ImageSource imagesource,
+ CancellationToken cancelationToken = default(CancellationToken), float scale = 1f)
+ {
+ NSImage image = null;
+ var streamsource = imagesource as StreamImageSource;
+ if (streamsource?.Stream == null) return null;
+ using (
+ var streamImage = await ((IStreamImageSource)streamsource).GetStreamAsync(cancelationToken).ConfigureAwait(false))
+ {
+ if (streamImage != null)
+ image = NSImage.FromStream(streamImage);
+ }
+ return image;
+ }
+ }
+
+ public sealed class ImageLoaderSourceHandler : IImageSourceHandler
+ {
+ public async Task<NSImage> LoadImageAsync(ImageSource imagesource,
+ CancellationToken cancelationToken = default(CancellationToken), float scale = 1f)
+ {
+ NSImage image = null;
+ var imageLoader = imagesource as UriImageSource;
+ if (imageLoader != null && imageLoader.Uri != null)
+ {
+ using (var streamImage = await imageLoader.GetStreamAsync(cancelationToken).ConfigureAwait(false))
+ {
+ if (streamImage != null)
+ image = NSImage.FromStream(streamImage);
+ }
+ }
+ return image;
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.MacOS/ModalPageTracker.cs b/Xamarin.Forms.Platform.MacOS/ModalPageTracker.cs
new file mode 100644
index 0000000..c492f4c
--- /dev/null
+++ b/Xamarin.Forms.Platform.MacOS/ModalPageTracker.cs
@@ -0,0 +1,118 @@
+using System;
+using System.Threading.Tasks;
+using System.Linq;
+using AppKit;
+using System.Collections.Generic;
+
+namespace Xamarin.Forms.Platform.MacOS
+{
+ internal class ModalPageTracker : IDisposable
+ {
+ NSViewController _renderer;
+ List<Page> _modals;
+ bool _disposed;
+
+ public ModalPageTracker(NSViewController mainRenderer)
+ {
+ if (mainRenderer == null)
+ throw new ArgumentNullException(nameof(mainRenderer));
+ _renderer = mainRenderer;
+ _renderer.View.WantsLayer = true;
+ _modals = new List<Page>();
+ }
+
+ public List<Page> ModalStack => _modals;
+
+ public Task PushAsync(Page modal, bool animated)
+ {
+ _modals.Add(modal);
+ modal.DescendantRemoved += HandleChildRemoved;
+ Platform.NativeToolbarTracker.TryHide(modal as NavigationPage);
+ return PresentModalAsync(modal, animated);
+ }
+
+ public Task<Page> PopAsync(bool animated)
+ {
+ var modal = _modals.LastOrDefault();
+ if (modal == null)
+ throw new InvalidOperationException("No Modal pages found in the stack, make sure you pushed a modal page");
+ _modals.Remove(modal);
+ modal.DescendantRemoved -= HandleChildRemoved;
+ return HideModalAsync(modal, animated);
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (!_disposed)
+ {
+ if (disposing)
+ {
+ foreach (var modal in _modals)
+ Platform.DisposeModelAndChildrenRenderers(modal);
+ _renderer = null;
+ }
+ _disposed = true;
+ }
+ }
+
+ void HandleChildRemoved(object sender, ElementEventArgs e)
+ {
+ var view = e.Element;
+ Platform.DisposeModelAndChildrenRenderers(view);
+ }
+
+ Task PresentModalAsync(Page modal, bool animated)
+ {
+ var modalRenderer = Platform.GetRenderer(modal);
+ if (modalRenderer == null)
+ {
+ modalRenderer = Platform.CreateRenderer(modal);
+ Platform.SetRenderer(modal, modalRenderer);
+ modalRenderer.SetElementSize(new Size(_renderer.View.Bounds.Width, _renderer.View.Bounds.Height));
+ }
+
+ var toViewController = modalRenderer as NSViewController;
+
+ var i = Math.Max(0, _renderer.ChildViewControllers.Length - 1);
+ var fromViewController = _renderer.ChildViewControllers[i];
+
+ _renderer.AddChildViewController(toViewController);
+
+ NSViewControllerTransitionOptions option = animated
+ ? NSViewControllerTransitionOptions.SlideUp
+ : NSViewControllerTransitionOptions.None;
+
+ var task = _renderer.HandleAsyncAnimation(fromViewController, toViewController, option,
+ () =>
+ {
+ //Hack: adjust if needed
+ toViewController.View.Frame = _renderer.View.Bounds;
+ fromViewController.View.Layer.Hidden = true;
+ }, true);
+ return task;
+ }
+
+ Task<Page> HideModalAsync(Page modal, bool animated)
+ {
+ var controller = Platform.GetRenderer(modal) as NSViewController;
+
+ var i = Math.Max(0, _renderer.ChildViewControllers.Length - 2);
+ var toViewController = _renderer.ChildViewControllers[i];
+
+ toViewController.View.Layer.Hidden = false;
+
+ NSViewControllerTransitionOptions option = animated
+ ? NSViewControllerTransitionOptions.SlideDown
+ : NSViewControllerTransitionOptions.None;
+
+ var task = _renderer.HandleAsyncAnimation(controller, toViewController, option,
+ () => Platform.DisposeModelAndChildrenRenderers(modal), modal);
+ return task;
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.MacOS/NativeToolbarTracker.cs b/Xamarin.Forms.Platform.MacOS/NativeToolbarTracker.cs
new file mode 100644
index 0000000..826b5b7
--- /dev/null
+++ b/Xamarin.Forms.Platform.MacOS/NativeToolbarTracker.cs
@@ -0,0 +1,429 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using AppKit;
+using CoreGraphics;
+using Xamarin.Forms.Internals;
+using Xamarin.Forms.PlatformConfiguration.macOSSpecific;
+
+namespace Xamarin.Forms.Platform.MacOS
+{
+ class NativeToolbarGroup
+ {
+ public class Item
+ {
+ public NSToolbarItem ToolbarItem;
+ public NSButton Button;
+ }
+
+ public NativeToolbarGroup(NSToolbarItemGroup itemGroup)
+ {
+ Group = itemGroup;
+ Items = new List<Item>();
+ }
+
+ public NSToolbarItemGroup Group { get; }
+
+ public List<Item> Items { get; }
+ }
+
+ internal class NativeToolbarTracker : NSToolbarDelegate
+ {
+ const string ToolBarId = "AwesomeBarToolbar";
+
+ INavigationPageController NavigationController => _navigation;
+
+ readonly string _defaultBackButtonTitle = "Back";
+ readonly ToolbarTracker _toolbarTracker;
+
+ NSToolbar _toolbar;
+ NavigationPage _navigation;
+
+ bool _hasTabs;
+
+ const double BackButtonItemWidth = 36;
+ const double ToolbarItemWidth = 44;
+ const double ToolbarItemHeight = 25;
+ const double ToolbarItemSpacing = 6;
+ const double ToolbarHeight = 30;
+ const double NavigationTitleMinSize = 300;
+
+ const string NavigationGroupIdentifier = "NavigationGroup";
+ const string TabbedGroupIdentifier = "TabbedGroup";
+ const string ToolbarItemsGroupIdentifier = "ToolbarGroup";
+ const string TitleGroupIdentifier = "TitleGroup";
+
+ NativeToolbarGroup _navigationGroup;
+ NativeToolbarGroup _tabbedGroup;
+ NativeToolbarGroup _toolbarGroup;
+ NativeToolbarGroup _titleGroup;
+
+ NSView _nsToolbarItemViewer;
+
+ public NativeToolbarTracker()
+ {
+ _toolbarTracker = new ToolbarTracker();
+ _toolbarTracker.CollectionChanged += ToolbarTrackerOnCollectionChanged;
+ }
+
+ public NavigationPage Navigation
+ {
+ get { return _navigation; }
+ set
+ {
+ if (_navigation == value)
+ return;
+
+ if (_navigation != null)
+ _navigation.PropertyChanged -= NavigationPagePropertyChanged;
+
+ _navigation = value;
+
+ if (_navigation != null)
+ {
+ var parentTabbedPage = _navigation.Parent as TabbedPage;
+ if (parentTabbedPage != null)
+ {
+ _hasTabs = parentTabbedPage.OnThisPlatform().GetTabsStyle() == TabsStyle.OnNavigation;
+ }
+ _toolbarTracker.Target = _navigation.CurrentPage;
+ _navigation.PropertyChanged += NavigationPagePropertyChanged;
+ }
+
+ UpdateToolBar();
+ }
+ }
+
+ public void TryHide(NavigationPage navPage = null)
+ {
+ if (navPage == null || navPage == _navigation)
+ {
+ Navigation = null;
+ }
+ }
+
+ public override string[] AllowedItemIdentifiers(NSToolbar toolbar)
+ {
+ return new string[] { };
+ }
+
+ public override string[] DefaultItemIdentifiers(NSToolbar toolbar)
+ {
+ return new string[] { };
+ }
+
+ public override NSToolbarItem WillInsertItem(NSToolbar toolbar, string itemIdentifier, bool willBeInserted)
+ {
+ var group = new NSToolbarItemGroup(itemIdentifier);
+ var view = new NSView();
+ group.View = view;
+
+ if (itemIdentifier == NavigationGroupIdentifier)
+ _navigationGroup = new NativeToolbarGroup(group);
+ else if (itemIdentifier == TitleGroupIdentifier)
+ _titleGroup = new NativeToolbarGroup(group);
+ else if (itemIdentifier == TabbedGroupIdentifier)
+ _tabbedGroup = new NativeToolbarGroup(group);
+ else if (itemIdentifier == ToolbarItemsGroupIdentifier)
+ _toolbarGroup = new NativeToolbarGroup(group);
+
+ return group;
+ }
+
+ protected virtual bool HasTabs => _hasTabs;
+
+ protected virtual NSToolbar ConfigureToolbar()
+ {
+ var toolbar = new NSToolbar(ToolBarId)
+ {
+ DisplayMode = NSToolbarDisplayMode.Icon,
+ AllowsUserCustomization = false,
+ ShowsBaselineSeparator = true,
+ SizeMode = NSToolbarSizeMode.Regular,
+ Delegate = this
+ };
+
+ return toolbar;
+ }
+
+ internal void UpdateToolBar()
+ {
+ if (NSApplication.SharedApplication.MainWindow == null)
+ return;
+
+ if (NavigationController == null)
+ {
+ if (_toolbar != null)
+ _toolbar.Visible = false;
+ _toolbar = null;
+ return;
+ }
+
+ var currentPage = NavigationController.Peek();
+
+ if (NavigationPage.GetHasNavigationBar(currentPage))
+ {
+ if (_toolbar == null)
+ {
+ _toolbar = ConfigureToolbar();
+ NSApplication.SharedApplication.MainWindow.Toolbar = _toolbar;
+
+ _toolbar.InsertItem(NavigationGroupIdentifier, 0);
+ _toolbar.InsertItem(
+ HasTabs ? NSToolbar.NSToolbarSpaceItemIdentifier : NSToolbar.NSToolbarFlexibleSpaceItemIdentifier, 1);
+ _toolbar.InsertItem(HasTabs ? TabbedGroupIdentifier : TitleGroupIdentifier, 2);
+ _toolbar.InsertItem(NSToolbar.NSToolbarFlexibleSpaceItemIdentifier, 3);
+ _toolbar.InsertItem(ToolbarItemsGroupIdentifier, 4);
+ }
+
+ _toolbar.Visible = true;
+ UpdateToolbarItems();
+ UpdateTitle();
+ UpdateNavigationItems();
+ if (HasTabs)
+ UpdateTabbedItems();
+ UpdateBarBackgroundColor();
+ }
+ else
+ {
+ if (_toolbar != null)
+ {
+ _toolbar.Visible = false;
+ }
+ }
+ }
+
+ void UpdateBarBackgroundColor()
+ {
+ var bgColor = GetBackgroundColor().CGColor;
+
+ if (_nsToolbarItemViewer?.Superview?.Superview == null ||
+ _nsToolbarItemViewer.Superview.Superview.Superview == null) return;
+ // NSTitlebarView
+ _nsToolbarItemViewer.Superview.Superview.Superview.WantsLayer = true;
+ _nsToolbarItemViewer.Superview.Superview.Superview.Layer.BackgroundColor = bgColor;
+ }
+
+ void NavigationPagePropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
+ {
+ if (e.PropertyName.Equals(NavigationPage.BarTextColorProperty.PropertyName) ||
+ e.PropertyName.Equals(NavigationPage.BarBackgroundColorProperty.PropertyName))
+ UpdateToolBar();
+ }
+
+ void ToolbarTrackerOnCollectionChanged(object sender, EventArgs eventArgs)
+ {
+ UpdateToolbarItems();
+ }
+
+ async Task NavigateBackFrombackButton()
+ {
+ var popAsyncInner = NavigationController?.PopAsyncInner(true, true);
+ if (popAsyncInner != null)
+ await popAsyncInner;
+ }
+
+ bool ShowBackButton()
+ {
+ if (_navigation == null)
+ return false;
+
+ return NavigationPage.GetHasBackButton(_navigation.CurrentPage) && !IsRootPage();
+ }
+
+ bool IsRootPage()
+ {
+ if (NavigationController == null)
+ return true;
+ return NavigationController.StackDepth <= 1;
+ }
+
+ NSColor GetBackgroundColor()
+ {
+ var backgroundNSColor = NSColor.Clear;
+ if (Navigation != null && Navigation.BarBackgroundColor != Color.Default)
+ backgroundNSColor = Navigation.BarBackgroundColor.ToNSColor();
+ return backgroundNSColor;
+ }
+
+ NSColor GetTitleColor()
+ {
+ var titleNSColor = NSColor.Black;
+ if (Navigation != null && Navigation?.BarTextColor != Color.Default)
+ titleNSColor = Navigation.BarTextColor.ToNSColor();
+
+ return titleNSColor;
+ }
+
+ string GetCurrentPageTitle()
+ {
+ if (NavigationController == null)
+ return string.Empty;
+ return NavigationController.Peek().Title ?? "";
+ }
+
+ string GetPreviousPageTitle()
+ {
+ if (NavigationController == null || NavigationController.StackDepth <= 1)
+ return string.Empty;
+
+ return NavigationController.Peek(1).Title ?? _defaultBackButtonTitle;
+ }
+
+ List<ToolbarItem> GetToolbarItems()
+ {
+ return _toolbarTracker.ToolbarItems.ToList();
+ }
+
+ void UpdateTitle()
+ {
+ if (_toolbar == null || _navigation == null || _titleGroup == null)
+ return;
+
+ var title = GetCurrentPageTitle();
+ var item = new NSToolbarItem(title);
+ var view = new NSView();
+ var titleField = new NSTextField
+ {
+ AllowsEditingTextAttributes = true,
+ Bordered = false,
+ DrawsBackground = false,
+ Bezeled = false,
+ Editable = false,
+ Selectable = false,
+ Cell = new VerticallyCenteredTextFieldCell(0f, NSFont.TitleBarFontOfSize(18)),
+ StringValue = title
+ };
+ titleField.Cell.TextColor = GetTitleColor();
+ titleField.SizeToFit();
+ _titleGroup.Group.MinSize = new CGSize(NavigationTitleMinSize, ToolbarHeight);
+ _titleGroup.Group.Subitems = new NSToolbarItem[] { item };
+ view.AddSubview(titleField);
+ _titleGroup.Group.View = view;
+ //save a reference so we can paint this for the background
+ _nsToolbarItemViewer = _titleGroup.Group.View.Superview;
+ //position is hard .. we manually set the title to be centered
+ var totalWidth = _titleGroup.Group.View.Superview.Superview.Frame.Width;
+ var fieldWidth = titleField.Frame.Width;
+ var x = ((totalWidth - fieldWidth) / 2) - _nsToolbarItemViewer.Frame.X;
+ titleField.Frame = new CGRect(x, 0, fieldWidth, ToolbarHeight);
+ }
+
+ void UpdateToolbarItems()
+ {
+ if (_toolbar == null || _navigation == null || _toolbarGroup == null)
+ return;
+
+ var currentPage = NavigationController.Peek();
+ UpdateGroup(_toolbarGroup, currentPage.ToolbarItems, ToolbarItemWidth, ToolbarItemSpacing);
+ }
+
+ void UpdateNavigationItems()
+ {
+ if (_toolbar == null || _navigation == null || _navigationGroup == null)
+ return;
+ var items = new List<ToolbarItem>();
+ if (ShowBackButton())
+ {
+ var backButtonItem = new ToolbarItem
+ {
+ Text = GetPreviousPageTitle(),
+ Command = new Command(async () => await NavigateBackFrombackButton())
+ };
+ items.Add(backButtonItem);
+ }
+
+ UpdateGroup(_navigationGroup, items, BackButtonItemWidth, -1);
+
+ var navItemBack = _navigationGroup.Items.FirstOrDefault();
+ if (navItemBack != null)
+ {
+ navItemBack.Button.Image = NSImage.ImageNamed(NSImageName.GoLeftTemplate);
+ navItemBack.Button.SizeToFit();
+ navItemBack.Button.AccessibilityTitle = "NSBackButton";
+ }
+ }
+
+ void UpdateTabbedItems()
+ {
+ if (_toolbar == null || _navigation == null || _tabbedGroup == null)
+ return;
+
+ var items = new List<ToolbarItem>();
+
+ var tabbedPage = _navigation.Parent as TabbedPage;
+ if (tabbedPage != null)
+ {
+ foreach (var item in tabbedPage.Children)
+ {
+ var tbI = new ToolbarItem
+ {
+ Text = item.Title,
+ Icon = item.Icon,
+ Command = new Command(() => tabbedPage.SelectedItem = item)
+ };
+ items.Add(tbI);
+ }
+ }
+
+ UpdateGroup(_tabbedGroup, items, ToolbarItemWidth, ToolbarItemSpacing);
+ }
+
+ static void UpdateGroup(NativeToolbarGroup group, IList<ToolbarItem> toolbarItems, double itemWidth,
+ double itemSpacing)
+ {
+ int count = toolbarItems.Count;
+ group.Items.Clear();
+ if (count > 0)
+ {
+ var subItems = new NSToolbarItem[count];
+ var view = new NSView();
+ nfloat totalWidth = 0;
+ var currentX = 0.0;
+ for (int i = 0; i < toolbarItems.Count; i++)
+ {
+ var element = toolbarItems[i];
+
+ var item = new NSToolbarItem(element.Text);
+ item.Activated += (sender, e) => (element as IMenuItemController).Activate();
+
+ var button = new NSButton();
+ button.Title = element.Text;
+ button.SizeToFit();
+ var buttonWidth = itemWidth;
+ if (button.FittingSize.Width > itemWidth)
+ {
+ buttonWidth = button.FittingSize.Width + 10;
+ }
+ button.Frame = new CGRect(currentX + i * itemSpacing, 0, buttonWidth, ToolbarItemHeight);
+ currentX += buttonWidth;
+ totalWidth += button.Frame.Width;
+ button.Activated += (sender, e) => (element as IMenuItemController).Activate();
+
+ button.BezelStyle = NSBezelStyle.TexturedRounded;
+ if (!string.IsNullOrEmpty(element.Icon))
+ button.Image = new NSImage(element.Icon);
+
+ button.SizeToFit();
+ view.AddSubview(button);
+
+ item.Label = item.PaletteLabel = item.ToolTip = button.ToolTip = element.Text;
+
+ subItems[i] = item;
+
+ group.Items.Add(new NativeToolbarGroup.Item { ToolbarItem = item, Button = button });
+ }
+ view.Frame = new CGRect(0, 0, totalWidth + (itemSpacing * (count - 1)), ToolbarItemHeight);
+
+ group.Group.Subitems = subItems;
+ group.Group.View = view;
+ }
+ else
+ {
+ group.Group.Subitems = new NSToolbarItem[] { };
+ group.Group.View = new NSView();
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.MacOS/Platform.cs b/Xamarin.Forms.Platform.MacOS/Platform.cs
new file mode 100644
index 0000000..8629684
--- /dev/null
+++ b/Xamarin.Forms.Platform.MacOS/Platform.cs
@@ -0,0 +1,262 @@
+using System;
+using AppKit;
+using RectangleF = CoreGraphics.CGRect;
+using System.Linq;
+
+namespace Xamarin.Forms.Platform.MacOS
+{
+ public class Platform : BindableObject, IPlatform, 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 PlatformRenderer PlatformRenderer;
+
+ bool _appeared;
+ bool _disposed;
+
+ internal static NativeToolbarTracker NativeToolbarTracker = new NativeToolbarTracker();
+
+ internal Platform()
+ {
+ PlatformRenderer = new PlatformRenderer(this);
+
+ MessagingCenter.Subscribe(this, Page.AlertSignalName, (Page sender, AlertArguments arguments) =>
+ {
+ var alert = NSAlert.WithMessage(arguments.Title, arguments.Cancel, arguments.Accept, null, arguments.Message);
+ var result = alert.RunModal();
+ arguments.SetResult(result == 1);
+ });
+
+ MessagingCenter.Subscribe(this, Page.ActionSheetSignalName, (Page sender, ActionSheetArguments arguments) =>
+ {
+ var alert = NSAlert.WithMessage(arguments.Title, arguments.Cancel, arguments.Destruction, null, "");
+ if (arguments.Buttons != null)
+ {
+ alert.AccessoryView = GetExtraButton(arguments);
+ alert.Layout();
+ }
+
+ var result = (int)alert.RunSheetModal(NSApplication.SharedApplication.MainWindow);
+ var titleResult = string.Empty;
+ if (result == 1)
+ titleResult = arguments.Cancel;
+ else if (result == 0)
+ titleResult = arguments.Destruction;
+ else if (result > 1 && arguments.Buttons != null && result - 2 <= arguments.Buttons.Count())
+ titleResult = arguments.Buttons.ElementAt(result - 2);
+
+ arguments.SetResult(titleResult);
+ });
+ }
+
+ 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);
+ }
+
+ 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);
+ PlatformRenderer.Dispose();
+ }
+
+ 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 NSViewController ViewController => PlatformRenderer;
+
+ internal static void DisposeModelAndChildrenRenderers(Element view)
+ {
+ IVisualElementRenderer renderer;
+ foreach (VisualElement child in view.Descendants())
+ DisposeModelAndChildrenRenderers(child);
+
+ renderer = GetRenderer((VisualElement)view);
+ if (renderer?.ViewController?.ParentViewController != null)
+ renderer?.ViewController?.RemoveFromParentViewController();
+
+ renderer?.NativeView?.RemoveFromSuperview();
+ renderer?.Dispose();
+
+ view.ClearValue(RendererProperty);
+ }
+
+ internal static void DisposeRendererAndChildren(IVisualElementRenderer rendererToRemove)
+ {
+ if (rendererToRemove == null || rendererToRemove.Element == null)
+ return;
+
+ if (GetRenderer(rendererToRemove.Element) == rendererToRemove)
+ rendererToRemove.Element.ClearValue(RendererProperty);
+
+ if (rendererToRemove.NativeView != null)
+ {
+ 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(PlatformRenderer.View.Bounds.Width, PlatformRenderer.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 = PlatformRenderer.Navigation;
+ }
+
+ internal void DidAppear()
+ {
+ PlatformRenderer.Navigation.AnimateModalPages = false;
+ TargetApplication.NavigationProxy.Inner = PlatformRenderer.Navigation;
+ PlatformRenderer.Navigation.AnimateModalPages = true;
+ }
+
+ internal void WillAppear()
+ {
+ if (_appeared)
+ return;
+
+ Page.Platform = this;
+ AddChild(Page);
+
+ Page.DescendantRemoved += HandleChildRemoved;
+
+ _appeared = true;
+ }
+
+ static NSView GetExtraButton(ActionSheetArguments arguments)
+ {
+ var newView = new NSView();
+ int height = 50;
+ int width = 300;
+ int i = 0;
+ foreach (var button in arguments.Buttons)
+ {
+ var btn = new NSButton { Title = button, Tag = i };
+ btn.SetButtonType(NSButtonType.MomentaryPushIn);
+ btn.Activated +=
+ (s, e) =>
+ {
+ NSApplication.SharedApplication.EndSheet(NSApplication.SharedApplication.MainWindow.AttachedSheet,
+ ((NSButton)s).Tag + 2);
+ };
+ btn.Frame = new RectangleF(0, height * i, width, height);
+ newView.AddSubview(btn);
+ i++;
+ }
+ newView.Frame = new RectangleF(0, 0, width, height * i);
+ return newView;
+ }
+
+ 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);
+
+ PlatformRenderer.View.AddSubview(viewRenderer.NativeView);
+ if (viewRenderer.ViewController != null)
+ PlatformRenderer.AddChildViewController(viewRenderer.ViewController);
+ viewRenderer.SetElementSize(new Size(PlatformRenderer.View.Bounds.Width, PlatformRenderer.View.Bounds.Height));
+ }
+ else
+ Console.Error.WriteLine("A Renderer was already found, potential view double add");
+ }
+
+ void HandleChildRemoved(object sender, ElementEventArgs e)
+ {
+ var view = e.Element;
+ DisposeModelAndChildrenRenderers(view);
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.MacOS/PlatformNavigation.cs b/Xamarin.Forms.Platform.MacOS/PlatformNavigation.cs
new file mode 100644
index 0000000..7af8f07
--- /dev/null
+++ b/Xamarin.Forms.Platform.MacOS/PlatformNavigation.cs
@@ -0,0 +1,113 @@
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+
+namespace Xamarin.Forms.Platform.MacOS
+{
+ internal class PlatformNavigation : INavigation, IDisposable
+ {
+ ModalPageTracker _modalTracker;
+ PlatformRenderer _platformRenderer;
+ bool _animateModals;
+ bool _disposed;
+
+ public PlatformNavigation(PlatformRenderer mainRenderer)
+ {
+ _platformRenderer = mainRenderer;
+ _modalTracker = new ModalPageTracker(_platformRenderer);
+ _animateModals = true;
+ }
+
+ public IReadOnlyList<Page> ModalStack => _modalTracker.ModalStack;
+
+ public IReadOnlyList<Page> NavigationStack => new List<Page>();
+
+ public bool AnimateModalPages
+ {
+ get { return _animateModals; }
+ set { _animateModals = value; }
+ }
+
+ Task<Page> INavigation.PopAsync()
+ {
+ return ((INavigation)this).PopAsync(true);
+ }
+
+ Task<Page> INavigation.PopAsync(bool animated)
+ {
+ throw new InvalidOperationException("PopAsync is not supported globally on MacOS, please use a NavigationPage.");
+ }
+
+ Task INavigation.PopToRootAsync()
+ {
+ return ((INavigation)this).PopToRootAsync(true);
+ }
+
+ Task INavigation.PopToRootAsync(bool animated)
+ {
+ throw new InvalidOperationException("PopToRootAsync is not supported globally on MacOS, 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 MacOS, please use a NavigationPage.");
+ }
+
+ Task INavigation.PushModalAsync(Page modal)
+ {
+ return ((INavigation)this).PushModalAsync(modal, true);
+ }
+
+ Task<Page> INavigation.PopModalAsync()
+ {
+ return ((INavigation)this).PopModalAsync(true);
+ }
+
+ Task INavigation.PushModalAsync(Page modal, bool animated)
+ {
+ modal.Platform = _platformRenderer.Platform;
+ return _modalTracker.PushAsync(modal, _animateModals && animated);
+ }
+
+ Task<Page> INavigation.PopModalAsync(bool animated)
+ {
+ return _modalTracker.PopAsync(animated);
+ }
+
+ void INavigation.RemovePage(Page page)
+ {
+ throw new InvalidOperationException("RemovePage is not supported globally on macOS, please use a NavigationPage.");
+ }
+
+ void INavigation.InsertPageBefore(Page page, Page before)
+ {
+ throw new InvalidOperationException(
+ "InsertPageBefore is not supported globally on macOS, please use a NavigationPage.");
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (!_disposed)
+ {
+ if (disposing)
+ {
+ _modalTracker.Dispose();
+ _modalTracker = null;
+ _platformRenderer = null;
+ }
+
+ _disposed = true;
+ }
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.MacOS/PlatformRenderer.cs b/Xamarin.Forms.Platform.MacOS/PlatformRenderer.cs
new file mode 100644
index 0000000..c0fcae3
--- /dev/null
+++ b/Xamarin.Forms.Platform.MacOS/PlatformRenderer.cs
@@ -0,0 +1,50 @@
+using AppKit;
+
+namespace Xamarin.Forms.Platform.MacOS
+{
+ internal class PlatformRenderer : NSViewController
+ {
+ PlatformNavigation _platformNavigation;
+ bool _disposed;
+
+ internal PlatformRenderer(Platform platform)
+ {
+ Platform = platform;
+ View = new NSView(NSApplication.SharedApplication.Windows[0].Frame);
+ _platformNavigation = new PlatformNavigation(this);
+ }
+
+ public Platform Platform { get; set; }
+
+ public PlatformNavigation Navigation => _platformNavigation;
+
+ public override void ViewDidAppear()
+ {
+ Platform.DidAppear();
+ base.ViewDidAppear();
+ }
+
+ public override void ViewDidLayout()
+ {
+ base.ViewDidLayout();
+ Platform.LayoutSubviews();
+ }
+
+ public override void ViewWillAppear()
+ {
+ Platform.WillAppear();
+ base.ViewWillAppear();
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ if (!_disposed)
+ {
+ _platformNavigation.Dispose();
+ _platformNavigation = null;
+ }
+ _disposed = true;
+ base.Dispose(disposing);
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.MacOS/Properties/AssemblyInfo.cs b/Xamarin.Forms.Platform.MacOS/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..a9383ba
--- /dev/null
+++ b/Xamarin.Forms.Platform.MacOS/Properties/AssemblyInfo.cs
@@ -0,0 +1,53 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using Xamarin.Forms.Platform.MacOS;
+using Xamarin.Forms;
+using Xamarin.Forms.Internals;
+
+[assembly: AssemblyTitle("Xamarin.Forms.Platform.macOS")]
+[assembly: AssemblyDescription("macOS Backend for Xamarin.Forms")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCulture("")]
+
+//[assembly: AssemblyDelaySign(false)]
+//[assembly: AssemblyKeyFile("")]
+
+[assembly: Xamarin.Forms.Dependency(typeof(Deserializer))]
+[assembly: Xamarin.Forms.Dependency(typeof(ResourcesProvider))]
+[assembly: ExportImageSourceHandler(typeof(FileImageSource), typeof(FileImageSourceHandler))]
+[assembly: ExportImageSourceHandler(typeof(StreamImageSource), typeof(StreamImagesourceHandler))]
+[assembly: ExportImageSourceHandler(typeof(UriImageSource), typeof(ImageLoaderSourceHandler))]
+[assembly: ExportRenderer(typeof(Page), typeof(PageRenderer))]
+[assembly: ExportRenderer(typeof(CarouselPage), typeof(CarouselPageRenderer))]
+[assembly: ExportRenderer(typeof(MasterDetailPage), typeof(MasterDetailPageRenderer))]
+[assembly: ExportRenderer(typeof(TabbedPage), typeof(TabbedPageRenderer))]
+[assembly: ExportRenderer(typeof(NavigationPage), typeof(NavigationPageRenderer))]
+[assembly: ExportRenderer(typeof(Label), typeof(LabelRenderer))]
+[assembly: ExportRenderer(typeof(Button), typeof(ButtonRenderer))]
+[assembly: ExportRenderer(typeof(BoxView), typeof(BoxViewRenderer))]
+[assembly: ExportRenderer(typeof(ScrollView), typeof(ScrollViewRenderer))]
+[assembly: ExportRenderer(typeof(ActivityIndicator), typeof(ActivityIndicatorRenderer))]
+[assembly: ExportRenderer(typeof(DatePicker), typeof(DatePickerRenderer))]
+[assembly: ExportRenderer(typeof(Editor), typeof(EditorRenderer))]
+[assembly: ExportRenderer(typeof(Entry), typeof(EntryRenderer))]
+[assembly: ExportRenderer(typeof(Frame), typeof(FrameRenderer))]
+[assembly: ExportRenderer(typeof(Image), typeof(ImageRenderer))]
+[assembly: ExportRenderer(typeof(OpenGLView), typeof(OpenGLViewRenderer))]
+[assembly: ExportRenderer(typeof(Picker), typeof(PickerRenderer))]
+[assembly: ExportRenderer(typeof(ProgressBar), typeof(ProgressBarRenderer))]
+[assembly: ExportRenderer(typeof(SearchBar), typeof(SearchBarRenderer))]
+[assembly: ExportRenderer(typeof(Slider), typeof(SliderRenderer))]
+[assembly: ExportRenderer(typeof(Stepper), typeof(StepperRenderer))]
+[assembly: ExportRenderer(typeof(Switch), typeof(SwitchRenderer))]
+[assembly: ExportRenderer(typeof(TimePicker), typeof(TimePickerRenderer))]
+[assembly: ExportRenderer(typeof(WebView), typeof(WebViewRenderer))]
+[assembly: ExportRenderer(typeof(ListView), typeof(ListViewRenderer))]
+[assembly: ExportRenderer(typeof(TableView), typeof(TableViewRenderer))]
+[assembly: ExportRenderer(typeof(NativeViewWrapper), typeof(NativeViewWrapperRenderer))]
+[assembly: ExportRenderer(typeof(Layout), typeof(LayoutRenderer))]
+[assembly: ExportCell(typeof(Cell), typeof(CellRenderer))]
+[assembly: ExportCell(typeof(TextCell), typeof(TextCellRenderer))]
+[assembly: ExportCell(typeof(ImageCell), typeof(ImageCellRenderer))]
+[assembly: ExportCell(typeof(EntryCell), typeof(EntryCellRenderer))]
+[assembly: ExportCell(typeof(ViewCell), typeof(ViewCellRenderer))]
+[assembly: ExportCell(typeof(SwitchCell), typeof(SwitchCellRenderer))] \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.MacOS/Renderers/ActivityIndicatorRenderer.cs b/Xamarin.Forms.Platform.MacOS/Renderers/ActivityIndicatorRenderer.cs
new file mode 100644
index 0000000..2e17916
--- /dev/null
+++ b/Xamarin.Forms.Platform.MacOS/Renderers/ActivityIndicatorRenderer.cs
@@ -0,0 +1,70 @@
+using System.ComponentModel;
+using System.Drawing;
+using AppKit;
+using CoreImage;
+
+namespace Xamarin.Forms.Platform.MacOS
+{
+ public class ActivityIndicatorRenderer : ViewRenderer<ActivityIndicator, NSProgressIndicator>
+ {
+ static CIColorPolynomial s_currentColorFilter;
+ static NSColor s_currentColor;
+
+ protected override void OnElementChanged(ElementChangedEventArgs<ActivityIndicator> e)
+ {
+ if (e.NewElement != null)
+ {
+ if (Control == null)
+ SetNativeControl(new NSProgressIndicator(RectangleF.Empty) { Style = NSProgressIndicatorStyle.Spinning });
+
+ UpdateColor();
+ UpdateIsRunning();
+ }
+
+ base.OnElementChanged(e);
+ }
+
+ protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ base.OnElementPropertyChanged(sender, e);
+
+ if (e.PropertyName == ActivityIndicator.ColorProperty.PropertyName)
+ UpdateColor();
+ else if (e.PropertyName == ActivityIndicator.IsRunningProperty.PropertyName)
+ UpdateIsRunning();
+ }
+
+ void UpdateColor()
+ {
+ var color = Element.Color;
+ if (s_currentColorFilter == null && color.IsDefault)
+ return;
+
+ if (color.IsDefault)
+ Control.ContentFilters = new CIFilter[0];
+
+ var newColor = Element.Color.ToNSColor();
+ if (Equals(s_currentColor, newColor))
+ return;
+
+ s_currentColor = newColor;
+
+ s_currentColorFilter = new CIColorPolynomial
+ {
+ RedCoefficients = new CIVector(s_currentColor.RedComponent),
+ BlueCoefficients = new CIVector(s_currentColor.BlueComponent),
+ GreenCoefficients = new CIVector(s_currentColor.GreenComponent)
+ };
+
+ Control.ContentFilters = new CIFilter[] { s_currentColorFilter };
+ }
+
+ void UpdateIsRunning()
+ {
+ if (Element.IsRunning)
+ Control.StartAnimation(this);
+ else
+ Control.StopAnimation(this);
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.MacOS/Renderers/BoxViewRenderer.cs b/Xamarin.Forms.Platform.MacOS/Renderers/BoxViewRenderer.cs
new file mode 100644
index 0000000..d4e9ad0
--- /dev/null
+++ b/Xamarin.Forms.Platform.MacOS/Renderers/BoxViewRenderer.cs
@@ -0,0 +1,39 @@
+using System.ComponentModel;
+using AppKit;
+
+namespace Xamarin.Forms.Platform.MacOS
+{
+ public class BoxViewRenderer : ViewRenderer<BoxView, NSView>
+ {
+ protected override void OnElementChanged(ElementChangedEventArgs<BoxView> e)
+ {
+ if (e.NewElement != null)
+ {
+ if (Control == null)
+ {
+ SetNativeControl(new NSView());
+ }
+ SetBackgroundColor(Element.Color);
+ }
+
+ base.OnElementChanged(e);
+ }
+
+ protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ base.OnElementPropertyChanged(sender, e);
+ if (e.PropertyName == BoxView.ColorProperty.PropertyName)
+ SetBackgroundColor(Element.BackgroundColor);
+ else if (e.PropertyName == VisualElement.IsVisibleProperty.PropertyName && Element.IsVisible)
+ SetNeedsDisplayInRect(Bounds);
+ }
+
+ protected override void SetBackgroundColor(Color color)
+ {
+ if (Element == null || Control == null)
+ return;
+ Control.WantsLayer = true;
+ Control.Layer.BackgroundColor = color.ToCGColor();
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.MacOS/Renderers/ButtonRenderer.cs b/Xamarin.Forms.Platform.MacOS/Renderers/ButtonRenderer.cs
new file mode 100644
index 0000000..05b87fa
--- /dev/null
+++ b/Xamarin.Forms.Platform.MacOS/Renderers/ButtonRenderer.cs
@@ -0,0 +1,131 @@
+using System;
+using System.ComponentModel;
+using AppKit;
+using Foundation;
+using SizeF = CoreGraphics.CGSize;
+
+namespace Xamarin.Forms.Platform.MacOS
+{
+ public class ButtonRenderer : ViewRenderer<Button, NSButton>
+ {
+ protected override void Dispose(bool disposing)
+ {
+ if (Control != null)
+ Control.Activated -= OnButtonActivated;
+
+ base.Dispose(disposing);
+ }
+
+ protected override void OnElementChanged(ElementChangedEventArgs<Button> e)
+ {
+ base.OnElementChanged(e);
+
+ if (e.NewElement != null)
+ {
+ if (Control == null)
+ {
+ var btn = new NSButton();
+ btn.SetButtonType(NSButtonType.MomentaryPushIn);
+ SetNativeControl(btn);
+
+ Control.Activated += OnButtonActivated;
+ }
+
+ UpdateText();
+ UpdateFont();
+ UpdateBorder();
+ UpdateImage();
+ }
+ }
+
+ protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ base.OnElementPropertyChanged(sender, e);
+
+ if (e.PropertyName == Button.TextProperty.PropertyName || e.PropertyName == Button.TextColorProperty.PropertyName)
+ UpdateText();
+ else if (e.PropertyName == Button.FontProperty.PropertyName)
+ UpdateFont();
+ else if (e.PropertyName == Button.BorderWidthProperty.PropertyName ||
+ e.PropertyName == Button.BorderRadiusProperty.PropertyName ||
+ e.PropertyName == Button.BorderColorProperty.PropertyName)
+ UpdateBorder();
+ else if (e.PropertyName == VisualElement.BackgroundColorProperty.PropertyName)
+ UpdateBackgroundVisibility();
+ else if (e.PropertyName == Button.ImageProperty.PropertyName)
+ UpdateImage();
+ }
+
+ void OnButtonActivated(object sender, EventArgs eventArgs)
+ {
+ ((IButtonController)Element)?.SendClicked();
+ }
+
+ void UpdateBackgroundVisibility()
+ {
+ var model = Element;
+ var shouldDrawImage = model.BackgroundColor == Color.Default;
+ if (!shouldDrawImage)
+ Control.Cell.BackgroundColor = model.BackgroundColor.ToNSColor();
+ }
+
+ void UpdateBorder()
+ {
+ var uiButton = Control;
+ var button = Element;
+
+ if (button.BorderColor != Color.Default)
+ uiButton.Layer.BorderColor = button.BorderColor.ToCGColor();
+
+ uiButton.Layer.BorderWidth = (float)button.BorderWidth;
+ uiButton.Layer.CornerRadius = button.BorderRadius;
+
+ UpdateBackgroundVisibility();
+ }
+
+ void UpdateFont()
+ {
+ Control.Font = Element.Font.ToNSFont();
+ }
+
+ async void UpdateImage()
+ {
+ IImageSourceHandler handler;
+ FileImageSource source = Element.Image;
+ if (source != null && (handler = Registrar.Registered.GetHandler<IImageSourceHandler>(source.GetType())) != null)
+ {
+ NSImage uiimage;
+ try
+ {
+ uiimage = await handler.LoadImageAsync(source);
+ }
+ catch (OperationCanceledException)
+ {
+ uiimage = null;
+ }
+ NSButton button = Control;
+ if (button != null && uiimage != null)
+ {
+ button.Image = uiimage;
+ if (!string.IsNullOrEmpty(button.Title))
+ button.ImagePosition = Element.ToNSCellImagePosition();
+ }
+ }
+ ((IVisualElementController)Element).NativeSizeChanged();
+ }
+
+ void UpdateText()
+ {
+ var color = Element.TextColor;
+ if (color == Color.Default)
+ {
+ Control.Title = Element.Text ?? "";
+ }
+ else
+ {
+ var textWithColor = new NSAttributedString(Element.Text ?? "", foregroundColor: color.ToNSColor());
+ Control.AttributedTitle = textWithColor;
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.MacOS/Renderers/CarouselPageRenderer.cs b/Xamarin.Forms.Platform.MacOS/Renderers/CarouselPageRenderer.cs
new file mode 100644
index 0000000..014d503
--- /dev/null
+++ b/Xamarin.Forms.Platform.MacOS/Renderers/CarouselPageRenderer.cs
@@ -0,0 +1,229 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.Specialized;
+using System.ComponentModel;
+using AppKit;
+using Foundation;
+
+namespace Xamarin.Forms.Platform.MacOS
+{
+ [Register("CarouselPageRenderer")]
+ public class CarouselPageRenderer : NSPageController, IVisualElementRenderer
+ {
+ bool _appeared;
+ bool _disposed;
+ EventTracker _events;
+ VisualElementTracker _tracker;
+
+ public CarouselPageRenderer()
+ {
+ View = new NSView
+ {
+ WantsLayer = true,
+ Layer = { BackgroundColor = NSColor.White.CGColor }
+ };
+ }
+
+ public CarouselPageRenderer(IntPtr handle) : base(handle)
+ {
+ }
+
+ IElementController ElementController => Element;
+
+ IPageController PageController => (IPageController)Element;
+
+ public override nint SelectedIndex
+ {
+ get { return base.SelectedIndex; }
+ set
+ {
+ if (base.SelectedIndex == value)
+ return;
+ base.SelectedIndex = value;
+ if (Carousel != null)
+ Carousel.CurrentPage = (ContentPage)ElementController.LogicalChildren[(int)SelectedIndex];
+ }
+ }
+
+ public VisualElement Element { get; private set; }
+
+ public event EventHandler<VisualElementChangedEventArgs> ElementChanged;
+
+ public SizeRequest GetDesiredSize(double widthConstraint, double heightConstraint)
+ {
+ return NativeView.GetSizeRequest(widthConstraint, heightConstraint);
+ }
+
+ public NSView NativeView => View;
+
+ public void SetElement(VisualElement element)
+ {
+ VisualElement oldElement = Element;
+ Element = element;
+
+ Init();
+
+ OnElementChanged(new VisualElementChangedEventArgs(oldElement, element));
+ }
+
+ public void SetElementSize(Size size)
+ {
+ Element.Layout(new Rectangle(Element.X, Element.Y, size.Width, size.Height));
+ }
+
+ public NSViewController ViewController => this;
+
+ public override void ViewDidAppear()
+ {
+ base.ViewDidAppear();
+ if (_appeared || _disposed)
+ return;
+
+ _appeared = true;
+ PageController.SendAppearing();
+ }
+
+ public override void ViewDidDisappear()
+ {
+ base.ViewDidDisappear();
+
+ if (!_appeared || _disposed)
+ return;
+
+ _appeared = false;
+ PageController.SendDisappearing();
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing && !_disposed)
+ {
+ if (Carousel != null)
+ {
+ Carousel.PropertyChanged -= OnPropertyChanged;
+ Carousel.PagesChanged -= OnPagesChanged;
+ }
+
+ Platform.SetRenderer(Element, null);
+
+ if (_appeared)
+ {
+ _appeared = false;
+ PageController?.SendDisappearing();
+ }
+
+ if (_events != null)
+ {
+ _events.Dispose();
+ _events = null;
+ }
+
+ if (_tracker != null)
+ {
+ _tracker.Dispose();
+ _tracker = null;
+ }
+
+ Element = null;
+ _disposed = true;
+ }
+
+ base.Dispose(disposing);
+ }
+
+ void OnElementChanged(VisualElementChangedEventArgs e)
+ {
+ ElementChanged?.Invoke(this, e);
+ }
+
+ void ConfigureNSPageController()
+ {
+ TransitionStyle = NSPageControllerTransitionStyle.HorizontalStrip;
+ }
+
+ CarouselPage Carousel => Element as CarouselPage;
+
+ void Init()
+ {
+ Delegate = new PageControllerDelegate();
+
+ _tracker = new VisualElementTracker(this);
+ _events = new EventTracker(this);
+ _events.LoadEvents(View);
+
+ ConfigureNSPageController();
+
+ UpdateBackground();
+ UpdateSource();
+
+ Carousel.PropertyChanged += OnPropertyChanged;
+ Carousel.PagesChanged += OnPagesChanged;
+ }
+
+ void UpdateSource()
+ {
+ var pages = new List<NSPageContainer>();
+ for (var i = 0; i < ElementController.LogicalChildren.Count; i++)
+ {
+ Element element = ElementController.LogicalChildren[i];
+ var child = element as ContentPage;
+ if (child != null)
+ pages.Add(new NSPageContainer(child, i));
+ }
+
+ ArrangedObjects = pages.ToArray();
+ UpdateCurrentPage(false);
+ }
+
+ void OnPagesChanged(object sender, NotifyCollectionChangedEventArgs e)
+ {
+ UpdateSource();
+ }
+
+ void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ if (e.PropertyName == nameof(TabbedPage.CurrentPage))
+ UpdateCurrentPage();
+ else if (e.PropertyName == VisualElement.BackgroundColorProperty.PropertyName)
+ UpdateBackground();
+ else if (e.PropertyName == Page.BackgroundImageProperty.PropertyName)
+ UpdateBackground();
+ }
+
+ void UpdateBackground()
+ {
+ if (View.Layer == null)
+ return;
+
+ string bgImage = ((Page)Element).BackgroundImage;
+
+ if (!string.IsNullOrEmpty(bgImage))
+ {
+ View.Layer.BackgroundColor = NSColor.FromPatternImage(NSImage.ImageNamed(bgImage)).CGColor;
+ return;
+ }
+
+ Color bgColor = Element.BackgroundColor;
+ View.Layer.BackgroundColor = bgColor.IsDefault ? NSColor.White.CGColor : bgColor.ToCGColor();
+ }
+
+ void UpdateCurrentPage(bool animated = true)
+ {
+ ContentPage current = Carousel.CurrentPage;
+ if (current != null)
+ {
+ int index = Carousel.CurrentPage != null ? CarouselPage.GetIndex(Carousel.CurrentPage) : 0;
+ if (index < 0)
+ index = 0;
+
+ if (SelectedIndex == index)
+ return;
+
+ if (animated)
+ NSAnimationContext.RunAnimation(context => { ((NSPageController)Animator).SelectedIndex = index; },
+ CompleteTransition);
+ else SelectedIndex = index;
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.MacOS/Renderers/CustomNSTableHeaderView.cs b/Xamarin.Forms.Platform.MacOS/Renderers/CustomNSTableHeaderView.cs
new file mode 100644
index 0000000..79628b6
--- /dev/null
+++ b/Xamarin.Forms.Platform.MacOS/Renderers/CustomNSTableHeaderView.cs
@@ -0,0 +1,46 @@
+using AppKit;
+using CoreGraphics;
+using Foundation;
+
+namespace Xamarin.Forms.Platform.MacOS
+{
+ sealed class CustomNSTableHeaderView : NSTableHeaderView
+ {
+ public CustomNSTableHeaderView() : this(0, null) { }
+ public CustomNSTableHeaderView(double width, IVisualElementRenderer headerRenderer)
+ {
+ var view = new NSView { WantsLayer = true, Layer = { BackgroundColor = NSColor.Clear.CGColor } };
+ AddSubview(view);
+ Update(width, headerRenderer);
+ }
+
+ public void Update(double width, IVisualElementRenderer headerRenderer)
+ {
+ double height = 1;
+ if (headerRenderer != null)
+ {
+ var headerView = headerRenderer.Element;
+ var request = headerView.Measure(double.PositiveInfinity, double.PositiveInfinity, MeasureFlags.IncludeMargins);
+ height = request.Request.Height;
+ var bounds = new Rectangle(0, 0, width, height);
+ Xamarin.Forms.Layout.LayoutChildIntoBoundingRegion(headerView, bounds);
+ headerRenderer.NativeView.Frame = bounds.ToRectangleF();
+ AddSubview(headerRenderer.NativeView);
+
+ }
+ Frame = new CGRect(0, 0, width, height);
+ }
+
+ //hides default text field
+ public override NSAttributedString PageHeader => new NSAttributedString("");
+
+ public override void DrawRect(CGRect dirtyRect) { }
+
+ public override void Layout()
+ {
+ foreach (var view in Subviews)
+ view.Frame = Frame;
+ base.Layout();
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.MacOS/Renderers/DatePickerRenderer.cs b/Xamarin.Forms.Platform.MacOS/Renderers/DatePickerRenderer.cs
new file mode 100644
index 0000000..f31fa36
--- /dev/null
+++ b/Xamarin.Forms.Platform.MacOS/Renderers/DatePickerRenderer.cs
@@ -0,0 +1,138 @@
+using System;
+using System.ComponentModel;
+using AppKit;
+using Foundation;
+
+namespace Xamarin.Forms.Platform.MacOS
+{
+ public class DatePickerRenderer : ViewRenderer<DatePicker, NSDatePicker>
+ {
+ NSDatePicker _picker;
+ NSColor _defaultTextColor;
+ NSColor _defaultBackgroundColor;
+ bool _disposed;
+
+ IElementController ElementController => Element;
+
+ protected override void OnElementChanged(ElementChangedEventArgs<DatePicker> e)
+ {
+ base.OnElementChanged(e);
+
+ if (e.OldElement == null)
+ {
+ if (Control == null)
+ {
+ _picker = new NSDatePicker
+ {
+ DatePickerMode = NSDatePickerMode.Single,
+ TimeZone = new NSTimeZone("UTC"),
+ DatePickerStyle = NSDatePickerStyle.TextFieldAndStepper,
+ DatePickerElements = NSDatePickerElementFlags.YearMonthDateDay
+ };
+ _picker.ValidateProposedDateValue += HandleValueChanged;
+ _defaultTextColor = _picker.TextColor;
+ _defaultBackgroundColor = _picker.BackgroundColor;
+
+ SetNativeControl(_picker);
+ }
+ }
+
+ UpdateDateFromModel();
+ UpdateMaximumDate();
+ UpdateMinimumDate();
+ UpdateTextColor();
+ }
+
+ protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ base.OnElementPropertyChanged(sender, e);
+
+ if (e.PropertyName == DatePicker.DateProperty.PropertyName ||
+ e.PropertyName == DatePicker.FormatProperty.PropertyName)
+ UpdateDateFromModel();
+ else if (e.PropertyName == DatePicker.MinimumDateProperty.PropertyName)
+ UpdateMinimumDate();
+ else if (e.PropertyName == DatePicker.MaximumDateProperty.PropertyName)
+ UpdateMaximumDate();
+ else if (e.PropertyName == DatePicker.TextColorProperty.PropertyName ||
+ e.PropertyName == VisualElement.IsEnabledProperty.PropertyName)
+ UpdateTextColor();
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing && !_disposed)
+ {
+ if (_picker != null)
+ _picker.ValidateProposedDateValue -= HandleValueChanged;
+
+ _disposed = true;
+ }
+ base.Dispose(disposing);
+ }
+
+ protected override void SetBackgroundColor(Color color)
+ {
+ base.SetBackgroundColor(color);
+
+ if (Control == null)
+ return;
+
+ if (color == Color.Default)
+ Control.BackgroundColor = _defaultBackgroundColor;
+ else
+ Control.BackgroundColor = color.ToNSColor();
+ }
+
+ void HandleValueChanged(object sender, NSDatePickerValidatorEventArgs e)
+ {
+ if (Control == null || Element == null)
+ return;
+ ElementController?.SetValueFromRenderer(DatePicker.DateProperty, _picker.DateValue.ToDateTime().Date);
+ }
+
+ void OnEnded(object sender, EventArgs eventArgs)
+ {
+ ElementController?.SetValueFromRenderer(VisualElement.IsFocusedPropertyKey, false);
+ }
+
+ void OnStarted(object sender, EventArgs eventArgs)
+ {
+ ElementController?.SetValueFromRenderer(VisualElement.IsFocusedPropertyKey, true);
+ }
+
+ void UpdateDateFromModel()
+ {
+ if (Control == null || Element == null)
+ return;
+ if (_picker.DateValue.ToDateTime().Date != Element.Date.Date)
+ _picker.DateValue = Element.Date.ToNSDate();
+ }
+
+ void UpdateMaximumDate()
+ {
+ if (Control == null || Element == null)
+ return;
+ _picker.MaxDate = Element.MaximumDate.ToNSDate();
+ }
+
+ void UpdateMinimumDate()
+ {
+ if (Control == null || Element == null)
+ return;
+ _picker.MinDate = Element.MinimumDate.ToNSDate();
+ }
+
+ void UpdateTextColor()
+ {
+ if (Control == null || Element == null)
+ return;
+ var textColor = Element.TextColor;
+
+ if (textColor.IsDefault || !Element.IsEnabled)
+ Control.TextColor = _defaultTextColor;
+ else
+ Control.TextColor = textColor.ToNSColor();
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.MacOS/Renderers/DefaultRenderer.cs b/Xamarin.Forms.Platform.MacOS/Renderers/DefaultRenderer.cs
new file mode 100644
index 0000000..3d1d7cd
--- /dev/null
+++ b/Xamarin.Forms.Platform.MacOS/Renderers/DefaultRenderer.cs
@@ -0,0 +1,6 @@
+namespace Xamarin.Forms.Platform.MacOS
+{
+ internal class DefaultRenderer : VisualElementRenderer<VisualElement>
+ {
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.MacOS/Renderers/EditorRenderer.cs b/Xamarin.Forms.Platform.MacOS/Renderers/EditorRenderer.cs
new file mode 100644
index 0000000..75a6020
--- /dev/null
+++ b/Xamarin.Forms.Platform.MacOS/Renderers/EditorRenderer.cs
@@ -0,0 +1,128 @@
+using System;
+using System.ComponentModel;
+using AppKit;
+using Foundation;
+
+namespace Xamarin.Forms.Platform.MacOS
+{
+ public class EditorRenderer : ViewRenderer<Editor, NSTextField>
+ {
+ const string NewLineSelector = "insertNewline";
+ bool _disposed;
+
+ IElementController ElementController => Element;
+
+ protected override void OnElementChanged(ElementChangedEventArgs<Editor> e)
+ {
+ base.OnElementChanged(e);
+
+ if (Control == null)
+ {
+ SetNativeControl(new NSTextField { UsesSingleLineMode = false });
+ Control.Cell.Scrollable = true;
+ Control.Cell.Wraps = true;
+ Control.Changed += HandleChanged;
+ Control.EditingBegan += OnEditingBegan;
+ Control.EditingEnded += OnEditingEnded;
+ Control.DoCommandBySelector = (control, textView, commandSelector) =>
+ {
+ var result = false;
+ if (commandSelector.Name.StartsWith(NewLineSelector, StringComparison.InvariantCultureIgnoreCase))
+ {
+ textView.InsertText(new NSString(Environment.NewLine));
+ result = true;
+ }
+ return result;
+ };
+ }
+
+ if (e.NewElement == null) return;
+ UpdateText();
+ UpdateFont();
+ UpdateTextColor();
+ UpdateEditable();
+ }
+
+ protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ base.OnElementPropertyChanged(sender, e);
+
+ if (e.PropertyName == Editor.TextProperty.PropertyName)
+ UpdateText();
+ else if (e.PropertyName == VisualElement.IsEnabledProperty.PropertyName)
+ UpdateEditable();
+ else if (e.PropertyName == Editor.TextColorProperty.PropertyName)
+ UpdateTextColor();
+ else if (e.PropertyName == Editor.FontAttributesProperty.PropertyName)
+ UpdateFont();
+ else if (e.PropertyName == Editor.FontFamilyProperty.PropertyName)
+ UpdateFont();
+ else if (e.PropertyName == Editor.FontSizeProperty.PropertyName)
+ UpdateFont();
+ }
+
+ protected override void SetBackgroundColor(Color color)
+ {
+ if (Control == null)
+ return;
+
+ Control.BackgroundColor = color == Color.Default ? NSColor.Clear : color.ToNSColor();
+
+ base.SetBackgroundColor(color);
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing && !_disposed)
+ {
+ _disposed = true;
+ if (Control != null)
+ {
+ Control.Changed -= HandleChanged;
+ Control.EditingBegan -= OnEditingBegan;
+ Control.EditingEnded -= OnEditingEnded;
+ }
+ }
+ base.Dispose(disposing);
+ }
+
+ void HandleChanged(object sender, EventArgs e)
+ {
+ ElementController.SetValueFromRenderer(Editor.TextProperty, Control.StringValue);
+ }
+
+ void OnEditingEnded(object sender, EventArgs eventArgs)
+ {
+ Element.SetValue(VisualElement.IsFocusedPropertyKey, false);
+ Element.SendCompleted();
+ }
+
+ void OnEditingBegan(object sender, EventArgs eventArgs)
+ {
+ ElementController.SetValueFromRenderer(VisualElement.IsFocusedPropertyKey, true);
+ }
+
+ void UpdateEditable()
+ {
+ Control.Editable = Element.IsEnabled;
+ }
+
+ void UpdateFont()
+ {
+ Control.Font = Element.ToNSFont();
+ }
+
+ void UpdateText()
+ {
+ if (Control.StringValue != Element.Text)
+ Control.StringValue = Element.Text ?? string.Empty;
+ }
+
+ void UpdateTextColor()
+ {
+ var textColor = Element.TextColor;
+
+ Control.TextColor = textColor.IsDefault ? NSColor.Black : textColor.ToNSColor();
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.MacOS/Renderers/EntryRenderer.cs b/Xamarin.Forms.Platform.MacOS/Renderers/EntryRenderer.cs
new file mode 100644
index 0000000..5fb6554
--- /dev/null
+++ b/Xamarin.Forms.Platform.MacOS/Renderers/EntryRenderer.cs
@@ -0,0 +1,205 @@
+using System;
+using System.ComponentModel;
+using AppKit;
+
+namespace Xamarin.Forms.Platform.MacOS
+{
+ public class EntryRenderer : ViewRenderer<Entry, NSTextField>
+ {
+ class BoolEventArgs : EventArgs
+ {
+ public BoolEventArgs(bool value)
+ {
+ Value = value;
+ }
+ public bool Value
+ {
+ get;
+ private set;
+ }
+ }
+ class FormsNSTextField : NSTextField
+ {
+ public EventHandler<BoolEventArgs> FocusChanged;
+ public override bool ResignFirstResponder()
+ {
+ FocusChanged?.Invoke(this, new BoolEventArgs(false));
+ return base.ResignFirstResponder();
+ }
+ public override bool BecomeFirstResponder()
+ {
+ FocusChanged?.Invoke(this, new BoolEventArgs(true));
+ return base.BecomeFirstResponder();
+ }
+ }
+
+ bool _disposed;
+ NSColor _defaultTextColor;
+
+ IElementController ElementController => Element;
+
+ IEntryController EntryController => Element;
+
+ protected override void OnElementChanged(ElementChangedEventArgs<Entry> e)
+ {
+ base.OnElementChanged(e);
+
+ if (Control == null)
+ {
+ NSTextField textField;
+ if (e.NewElement.IsPassword)
+ textField = new NSSecureTextField();
+ else
+ {
+ textField = new FormsNSTextField();
+ (textField as FormsNSTextField).FocusChanged += TextFieldFocusChanged;
+ }
+
+ SetNativeControl(textField);
+
+ _defaultTextColor = textField.TextColor;
+
+ textField.Changed += OnChanged;
+ textField.EditingBegan += OnEditingBegan;
+ textField.EditingEnded += OnEditingEnded;
+ }
+
+ if (e.NewElement != null)
+ {
+ UpdatePlaceholder();
+ UpdateText();
+ UpdateColor();
+ UpdateFont();
+ UpdateAlignment();
+ }
+ }
+
+ protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ if (e.PropertyName == Entry.PlaceholderProperty.PropertyName ||
+ e.PropertyName == Entry.PlaceholderColorProperty.PropertyName)
+ UpdatePlaceholder();
+ else if (e.PropertyName == Entry.IsPasswordProperty.PropertyName)
+ UpdatePassword();
+ else if (e.PropertyName == Entry.TextProperty.PropertyName)
+ UpdateText();
+ else if (e.PropertyName == Entry.TextColorProperty.PropertyName)
+ UpdateColor();
+ else if (e.PropertyName == Entry.HorizontalTextAlignmentProperty.PropertyName)
+ UpdateAlignment();
+ else if (e.PropertyName == Entry.FontAttributesProperty.PropertyName)
+ UpdateFont();
+ else if (e.PropertyName == Entry.FontFamilyProperty.PropertyName)
+ UpdateFont();
+ else if (e.PropertyName == Entry.FontSizeProperty.PropertyName)
+ UpdateFont();
+ else if (e.PropertyName == VisualElement.IsEnabledProperty.PropertyName)
+ {
+ UpdateColor();
+ UpdatePlaceholder();
+ }
+
+ base.OnElementPropertyChanged(sender, e);
+ }
+
+ protected override void SetBackgroundColor(Color color)
+ {
+ if (Control == null)
+ return;
+ Control.BackgroundColor = color == Color.Default ? NSColor.Clear : color.ToNSColor();
+
+ base.SetBackgroundColor(color);
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing && !_disposed)
+ {
+ _disposed = true;
+ if (Control != null)
+ {
+ Control.EditingBegan -= OnEditingBegan;
+ Control.Changed -= OnChanged;
+ Control.EditingEnded -= OnEditingEnded;
+ var formsNSTextField = (Control as FormsNSTextField);
+ if (formsNSTextField != null)
+ formsNSTextField.FocusChanged -= TextFieldFocusChanged;
+ }
+ }
+
+ base.Dispose(disposing);
+ }
+ void TextFieldFocusChanged(object sender, BoolEventArgs e)
+ {
+ ElementController.SetValueFromRenderer(VisualElement.IsFocusedPropertyKey, e.Value);
+ }
+
+ void OnEditingBegan(object sender, EventArgs e)
+ {
+ ElementController.SetValueFromRenderer(VisualElement.IsFocusedPropertyKey, true);
+ }
+
+ void OnChanged(object sender, EventArgs eventArgs)
+ {
+ ElementController.SetValueFromRenderer(Entry.TextProperty, Control.StringValue);
+ }
+
+ void OnEditingEnded(object sender, EventArgs e)
+ {
+ ElementController.SetValueFromRenderer(VisualElement.IsFocusedPropertyKey, false);
+ EntryController?.SendCompleted();
+ }
+
+ void UpdateAlignment()
+ {
+ Control.Alignment = Element.HorizontalTextAlignment.ToNativeTextAlignment();
+ }
+
+ void UpdateColor()
+ {
+ var textColor = Element.TextColor;
+
+ if (textColor.IsDefault || !Element.IsEnabled)
+ Control.TextColor = _defaultTextColor;
+ else
+ Control.TextColor = textColor.ToNSColor();
+ }
+
+ void UpdatePassword()
+ {
+ if (Element.IsPassword && (Control is NSSecureTextField))
+ return;
+ if (!Element.IsPassword && !(Control is NSSecureTextField))
+ return;
+ }
+
+ void UpdateFont()
+ {
+ Control.Font = Element.ToNSFont();
+ }
+
+ void UpdatePlaceholder()
+ {
+ var formatted = (FormattedString)Element.Placeholder;
+
+ if (formatted == null)
+ return;
+
+ var targetColor = Element.PlaceholderColor;
+
+ // Placeholder default color is 70% gray
+ // https://developer.apple.com/library/prerelease/ios/documentation/UIKit/Reference/UITextField_Class/index.html#//apple_ref/occ/instp/UITextField/placeholder
+
+ var color = Element.IsEnabled && !targetColor.IsDefault ? targetColor : ColorExtensions.SeventyPercentGrey.ToColor();
+
+ Control.PlaceholderAttributedString = formatted.ToAttributed(Element, color);
+ }
+
+ void UpdateText()
+ {
+ // ReSharper disable once RedundantCheckBeforeAssignment
+ if (Control.StringValue != Element.Text)
+ Control.StringValue = Element.Text ?? string.Empty;
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.MacOS/Renderers/FrameRenderer.cs b/Xamarin.Forms.Platform.MacOS/Renderers/FrameRenderer.cs
new file mode 100644
index 0000000..7cbabc1
--- /dev/null
+++ b/Xamarin.Forms.Platform.MacOS/Renderers/FrameRenderer.cs
@@ -0,0 +1,57 @@
+using System.ComponentModel;
+using System.Drawing;
+using AppKit;
+
+namespace Xamarin.Forms.Platform.MacOS
+{
+ public class FrameRenderer : VisualElementRenderer<Frame>
+ {
+ protected override void OnElementChanged(ElementChangedEventArgs<Frame> e)
+ {
+ base.OnElementChanged(e);
+
+ if (e.NewElement != null)
+ SetupLayer();
+ }
+
+ protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ base.OnElementPropertyChanged(sender, e);
+
+ if (e.PropertyName == VisualElement.BackgroundColorProperty.PropertyName ||
+ e.PropertyName == Xamarin.Forms.Frame.OutlineColorProperty.PropertyName ||
+ e.PropertyName == Xamarin.Forms.Frame.HasShadowProperty.PropertyName)
+ SetupLayer();
+ }
+
+ void SetupLayer()
+ {
+ Layer.CornerRadius = 5;
+ if (Element.BackgroundColor == Color.Default)
+ Layer.BackgroundColor = NSColor.White.CGColor;
+ else
+ Layer.BackgroundColor = Element.BackgroundColor.ToCGColor();
+
+ if (Element.HasShadow)
+ {
+ Layer.ShadowRadius = 5;
+ Layer.ShadowColor = NSColor.Black.CGColor;
+ Layer.ShadowOpacity = 0.8f;
+ Layer.ShadowOffset = new SizeF();
+ }
+ else
+ Layer.ShadowOpacity = 0;
+
+ if (Element.OutlineColor == Color.Default)
+ Layer.BorderColor = NSColor.Clear.CGColor;
+ else
+ {
+ Layer.BorderColor = Element.OutlineColor.ToCGColor();
+ Layer.BorderWidth = 1;
+ }
+
+ Layer.RasterizationScale = NSScreen.MainScreen.BackingScaleFactor;
+ Layer.ShouldRasterize = true;
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.MacOS/Renderers/ImageRenderer.cs b/Xamarin.Forms.Platform.MacOS/Renderers/ImageRenderer.cs
new file mode 100644
index 0000000..090df9a
--- /dev/null
+++ b/Xamarin.Forms.Platform.MacOS/Renderers/ImageRenderer.cs
@@ -0,0 +1,117 @@
+using System;
+using System.ComponentModel;
+using AppKit;
+
+namespace Xamarin.Forms.Platform.MacOS
+{
+ public class ImageRenderer : ViewRenderer<Image, NSImageView>
+ {
+ bool _isDisposed;
+
+ protected override void Dispose(bool disposing)
+ {
+ if (_isDisposed)
+ return;
+
+ if (disposing)
+ {
+ NSImage oldUIImage;
+ if (Control != null && (oldUIImage = Control.Image) != null)
+ {
+ oldUIImage.Dispose();
+ }
+ }
+
+ _isDisposed = true;
+
+ base.Dispose(disposing);
+ }
+
+ protected override void OnElementChanged(ElementChangedEventArgs<Image> e)
+ {
+ if (Control == null)
+ {
+ var imageView = new FormsNSImageView();
+ SetNativeControl(imageView);
+ }
+
+ if (e.NewElement != null)
+ {
+ SetAspect();
+ SetImage(e.OldElement);
+ SetOpacity();
+ }
+
+ base.OnElementChanged(e);
+ }
+
+ protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ base.OnElementPropertyChanged(sender, e);
+ if (e.PropertyName == Image.SourceProperty.PropertyName)
+ SetImage();
+ else if (e.PropertyName == Image.IsOpaqueProperty.PropertyName)
+ SetOpacity();
+ else if (e.PropertyName == Image.AspectProperty.PropertyName)
+ SetAspect();
+ }
+
+ void SetAspect()
+ {
+ //TODO: Implement set Image Aspect
+ //Control.ContentMode = Element.Aspect.ToUIViewContentMode();
+ }
+
+ async void SetImage(Image oldElement = null)
+ {
+ var source = Element.Source;
+
+ if (oldElement != null)
+ {
+ var oldSource = oldElement.Source;
+ if (Equals(oldSource, source))
+ return;
+
+ var imageSource = oldSource as FileImageSource;
+ if (imageSource != null && source is FileImageSource && imageSource.File == ((FileImageSource)source).File)
+ return;
+
+ Control.Image = null;
+ }
+
+ IImageSourceHandler handler;
+
+ ((IImageController)Element).SetIsLoading(true);
+
+ if (source != null && (handler = Registrar.Registered.GetHandler<IImageSourceHandler>(source.GetType())) != null)
+ {
+ NSImage uiimage;
+ try
+ {
+ uiimage = await handler.LoadImageAsync(source, scale: (float)NSScreen.MainScreen.BackingScaleFactor);
+ }
+ catch (OperationCanceledException)
+ {
+ uiimage = null;
+ }
+
+ var imageView = Control;
+ if (imageView != null)
+ imageView.Image = uiimage;
+
+ if (!_isDisposed)
+ ((IVisualElementController)Element).NativeSizeChanged();
+ }
+ else
+ Control.Image = null;
+
+ if (!_isDisposed)
+ ((IImageController)Element).SetIsLoading(false);
+ }
+
+ void SetOpacity()
+ {
+ (Control as FormsNSImageView)?.SetIsOpaque(Element.IsOpaque);
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.MacOS/Renderers/LayoutRenderer.cs b/Xamarin.Forms.Platform.MacOS/Renderers/LayoutRenderer.cs
new file mode 100644
index 0000000..1be5a1a
--- /dev/null
+++ b/Xamarin.Forms.Platform.MacOS/Renderers/LayoutRenderer.cs
@@ -0,0 +1,39 @@
+using System;
+using CoreGraphics;
+
+namespace Xamarin.Forms.Platform.MacOS
+{
+ internal class LayoutRenderer : DefaultRenderer
+ {
+ CGRect _bounds;
+
+ public override void Layout()
+ {
+ base.Layout();
+
+ if (_bounds == Bounds)
+ return;
+
+ _bounds = Bounds;
+
+ //when the layout changes we might need to update the children position based in our new size,
+ //this is only needed in MacOS because of the inversion of the Y coordinate.
+ //Forms layout system doesn't know we need to relayout the other items ,(first ones for example)
+ //so we do it here
+ for (int i = 0; i < Subviews.Length; i++)
+ {
+ var item = Subviews[i] as IVisualElementRenderer;
+ if (item == null)
+ continue;
+ var oldFrame = item.NativeView.Frame;
+
+ var newY = Math.Max(0, (float)(Element.Height - item.Element.Height - item.Element.Y));
+ if (oldFrame.Y == newY)
+ continue;
+ var newPosition = new CGPoint(oldFrame.X, newY);
+ item.NativeView.Frame = new CGRect(newPosition, oldFrame.Size);
+ Console.WriteLine($"New Frame - {item.NativeView.Frame}");
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.MacOS/Renderers/ListViewDataSource.cs b/Xamarin.Forms.Platform.MacOS/Renderers/ListViewDataSource.cs
new file mode 100644
index 0000000..6742053
--- /dev/null
+++ b/Xamarin.Forms.Platform.MacOS/Renderers/ListViewDataSource.cs
@@ -0,0 +1,299 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using AppKit;
+using Foundation;
+
+namespace Xamarin.Forms.Platform.MacOS
+{
+ internal class ListViewDataSource : NSTableViewSource
+ {
+ IVisualElementRenderer _prototype;
+ const int DefaultItemTemplateId = 1;
+ static int s_dataTemplateIncrementer = 2; // lets start at not 0 because
+ static int s_sectionCount;
+ readonly nfloat _defaultSectionHeight;
+ readonly Dictionary<DataTemplate, int> _templateToId = new Dictionary<DataTemplate, int>();
+ readonly NSTableView _nsTableView;
+ protected readonly ListView List;
+
+ IListViewController Controller => List;
+
+ ITemplatedItemsView<Cell> TemplatedItemsView => List;
+
+ bool _selectionFromNative;
+
+ public virtual bool IsGroupingEnabled => List.IsGroupingEnabled;
+
+ public Dictionary<int, int> Counts { get; set; }
+
+ public ListViewDataSource(ListViewDataSource source)
+ {
+ List = source.List;
+ _nsTableView = source._nsTableView;
+ _defaultSectionHeight = source._defaultSectionHeight;
+ _selectionFromNative = source._selectionFromNative;
+ Counts = new Dictionary<int, int>();
+ }
+
+ public ListViewDataSource(ListView list, NSTableView tableView)
+ {
+ List = list;
+ List.ItemSelected += OnItemSelected;
+ _nsTableView = tableView;
+ Counts = new Dictionary<int, int>();
+ }
+
+ public void Update()
+ {
+ _nsTableView.ReloadData();
+ }
+
+ public void OnRowClicked()
+ {
+ var selectedRow = _nsTableView.SelectedRow;
+ if (selectedRow == -1)
+ return;
+
+ Cell cell = null;
+ NSIndexPath indexPath = GetPathFromRow(selectedRow, ref cell);
+
+ if (cell == null)
+ return;
+
+ _selectionFromNative = true;
+ Controller.NotifyRowTapped((int)indexPath.Section, (int)indexPath.Item, cell);
+ }
+
+
+ public void OnItemSelected(object sender, SelectedItemChangedEventArgs eventArg)
+ {
+ if (_selectionFromNative)
+ {
+ _selectionFromNative = false;
+ return;
+ }
+
+ var location = TemplatedItemsView.TemplatedItems.GetGroupAndIndexOfItem(eventArg.SelectedItem);
+ if (location.Item1 == -1 || location.Item2 == -1)
+ {
+ var row = _nsTableView.SelectedRow;
+ int groupIndex = 1;
+ var selectedIndexPath = NSIndexPath.FromItemSection(row, groupIndex);
+ if (selectedIndexPath != null)
+ _nsTableView.DeselectRow(selectedIndexPath.Item);
+ return;
+ }
+
+ var rowId = location.Item2;
+
+ _nsTableView.SelectRow(rowId, false);
+ }
+
+ public override bool IsGroupRow(NSTableView tableView, nint row)
+ {
+ if (!IsGroupingEnabled)
+ return false;
+
+ int sectionIndex;
+ bool isGroupHeader;
+ int itemIndexInSection;
+
+ GetComputedIndexes(row, out sectionIndex, out itemIndexInSection, out isGroupHeader);
+ return isGroupHeader;
+ }
+
+ public override bool ShouldSelectRow(NSTableView tableView, nint row)
+ {
+ return !IsGroupRow(tableView, row);
+ }
+
+ public override nfloat GetRowHeight(NSTableView tableView, nint row)
+ {
+ if (!List.HasUnevenRows)
+ return List.RowHeight == -1 ? ListViewRenderer.DefaultRowHeight : List.RowHeight;
+
+ Cell cell = null;
+ GetPathFromRow(row, ref cell);
+
+ return CalculateHeightForCell(tableView, cell);
+ }
+
+ public override nint GetRowCount(NSTableView tableView)
+ {
+ var templatedItems = TemplatedItemsView.TemplatedItems;
+ nint count = 0;
+
+ if (!IsGroupingEnabled)
+ {
+ count = templatedItems.Count;
+ }
+ else
+ {
+ var sections = templatedItems.Count;
+ for (int i = 0; i < sections; i++)
+ {
+ var group = (IList)((IList)templatedItems)[i];
+ count += group.Count + 1;
+ }
+ s_sectionCount = sections;
+ }
+ return count;
+ }
+
+ public override NSView GetViewForItem(NSTableView tableView, NSTableColumn tableColumn, nint row)
+ {
+ var sectionIndex = 0;
+ var itemIndexInSection = (int)row;
+ Cell cell;
+
+ var isHeader = false;
+
+ if (IsGroupingEnabled)
+ GetComputedIndexes(row, out sectionIndex, out itemIndexInSection, out isHeader);
+
+ var indexPath = NSIndexPath.FromItemSection(itemIndexInSection, sectionIndex);
+ var templateId = isHeader ? "headerCell" : TemplateIdForPath(indexPath).ToString();
+
+ NSView nativeCell;
+
+ var cachingStrategy = Controller.CachingStrategy;
+ if (cachingStrategy == ListViewCachingStrategy.RetainElement)
+ {
+ cell = GetCellForPath(indexPath, isHeader);
+ nativeCell = CellNSView.GetNativeCell(tableView, cell, templateId, isHeader);
+ }
+ else if (cachingStrategy == ListViewCachingStrategy.RecycleElement)
+ {
+ nativeCell = tableView.MakeView(templateId, tableView);
+ if (nativeCell == null)
+ {
+ cell = GetCellForPath(indexPath, isHeader);
+ nativeCell = CellNSView.GetNativeCell(tableView, cell, templateId, isHeader, true);
+ }
+ else
+ {
+ var templatedList = TemplatedItemsView.TemplatedItems.GetGroup(sectionIndex);
+ cell = (Cell)((INativeElementView)nativeCell).Element;
+ ICellController controller = cell;
+ controller.SendDisappearing();
+ templatedList.UpdateContent(cell, itemIndexInSection);
+ controller.SendAppearing();
+ }
+ }
+ else
+ throw new NotSupportedException();
+ return nativeCell;
+ }
+
+ protected virtual Cell GetCellForPath(NSIndexPath indexPath, bool isGroupHeader)
+ {
+ var templatedItems = TemplatedItemsView.TemplatedItems;
+ if (IsGroupingEnabled)
+ templatedItems = (TemplatedItemsList<ItemsView<Cell>, Cell>)((IList)templatedItems)[(int)indexPath.Section];
+
+ var cell = isGroupHeader ? templatedItems.HeaderContent : templatedItems[(int)indexPath.Item];
+ return cell;
+ }
+
+ int TemplateIdForPath(NSIndexPath indexPath)
+ {
+ var itemTemplate = List.ItemTemplate;
+ var selector = itemTemplate as DataTemplateSelector;
+ if (selector == null)
+ return DefaultItemTemplateId;
+
+ var templatedList = TemplatedItemsView.TemplatedItems;
+ if (List.IsGroupingEnabled)
+ templatedList = (TemplatedItemsList<ItemsView<Cell>, Cell>)((IList)templatedList)[(int)indexPath.Section];
+
+ var item = templatedList.ListProxy[(int)indexPath.Item];
+
+ itemTemplate = selector.SelectTemplate(item, List);
+ int key;
+ if (!_templateToId.TryGetValue(itemTemplate, out key))
+ {
+ s_dataTemplateIncrementer++;
+ key = s_dataTemplateIncrementer;
+ _templateToId[itemTemplate] = key;
+ }
+ return key;
+ }
+
+ NSIndexPath GetPathFromRow(nint row, ref Cell cell)
+ {
+ var sectionIndex = 0;
+ bool isGroupHeader = false;
+ int itemIndexInSection;
+ if (IsGroupingEnabled)
+ GetComputedIndexes(row, out sectionIndex, out itemIndexInSection, out isGroupHeader);
+ else
+ itemIndexInSection = (int)row;
+ NSIndexPath indexPath = NSIndexPath.FromItemSection(itemIndexInSection, sectionIndex);
+ cell = GetCellForPath(indexPath, isGroupHeader);
+ return indexPath;
+ }
+
+ nfloat CalculateHeightForCell(NSTableView tableView, Cell cell)
+ {
+ var viewCell = cell as ViewCell;
+ double renderHeight;
+ if (List.RowHeight == -1 && viewCell?.View != null)
+ {
+ var target = viewCell.View;
+ if (_prototype == null)
+ {
+ _prototype = Platform.CreateRenderer(target);
+ Platform.SetRenderer(target, _prototype);
+ }
+ else
+ {
+ _prototype.SetElement(target);
+ Platform.SetRenderer(target, _prototype);
+ }
+
+ var req = target.Measure(tableView.Frame.Width, double.PositiveInfinity, MeasureFlags.IncludeMargins);
+
+ target.ClearValue(Platform.RendererProperty);
+ foreach (var descendant in target.Descendants())
+ descendant.ClearValue(Platform.RendererProperty);
+
+ renderHeight = req.Request.Height;
+ }
+ else
+ {
+ renderHeight = cell.RenderHeight;
+ }
+
+ return renderHeight > 0 ? (nfloat)renderHeight : ListViewRenderer.DefaultRowHeight;
+ }
+
+ void GetComputedIndexes(nint row, out int sectionIndex, out int itemIndexInSection, out bool isHeader)
+ {
+ var templatedItems = TemplatedItemsView.TemplatedItems;
+ var totalItems = 0;
+ isHeader = false;
+ sectionIndex = 0;
+ itemIndexInSection = 0;
+
+ for (int i = 0; i < s_sectionCount; i++)
+ {
+ var group = (IList)((IList)templatedItems)[i];
+ var itemsInSection = group.Count + 1;
+
+ if (row < totalItems + itemsInSection)
+ {
+ sectionIndex = i;
+ itemIndexInSection = (int)row - totalItems;
+ isHeader = itemIndexInSection == 0;
+ if (isHeader)
+ itemIndexInSection = -1;
+ else
+ itemIndexInSection = itemIndexInSection - 1;
+ break;
+ }
+ totalItems += itemsInSection;
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.MacOS/Renderers/ListViewRenderer.cs b/Xamarin.Forms.Platform.MacOS/Renderers/ListViewRenderer.cs
new file mode 100644
index 0000000..08265b3
--- /dev/null
+++ b/Xamarin.Forms.Platform.MacOS/Renderers/ListViewRenderer.cs
@@ -0,0 +1,342 @@
+using System.Collections.Generic;
+using System.Collections.Specialized;
+using System.ComponentModel;
+using System.Linq;
+using AppKit;
+using Foundation;
+using Xamarin.Forms.Internals;
+
+namespace Xamarin.Forms.Platform.MacOS
+{
+ public class ListViewRenderer : ViewRenderer<ListView, NSView>
+ {
+ bool _disposed;
+ NSTableView _table;
+ ListViewDataSource _dataSource;
+ IVisualElementRenderer _headerRenderer;
+ IVisualElementRenderer _footerRenderer;
+
+ IListViewController Controller => Element;
+
+ ITemplatedItemsView<Cell> TemplatedItemsView => Element;
+
+ public const int DefaultRowHeight = 16;
+
+ public NSTableView NativeTableView => _table;
+
+ public override void ViewWillDraw()
+ {
+ UpdateHeader();
+ base.ViewWillDraw();
+ }
+
+ protected virtual NSTableView CreateNSTableView(ListView list)
+ {
+ NSTableView table = new FormsNSTableView().AsListViewLook();
+ table.Source = _dataSource = new ListViewDataSource(list, table);
+ return table;
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing && !_disposed)
+ {
+ _disposed = true;
+
+ var viewsToLookAt = new Stack<NSView>(Subviews);
+ while (viewsToLookAt.Count > 0)
+ {
+ var view = viewsToLookAt.Pop();
+ var viewCellRenderer = view as ViewCellNSView;
+ if (viewCellRenderer != null)
+ viewCellRenderer.Dispose();
+ else
+ {
+ foreach (var child in view.Subviews)
+ viewsToLookAt.Push(child);
+ }
+ }
+
+ if (Element != null)
+ {
+ var templatedItems = TemplatedItemsView.TemplatedItems;
+ templatedItems.CollectionChanged -= OnCollectionChanged;
+ templatedItems.GroupedCollectionChanged -= OnGroupedCollectionChanged;
+ }
+ }
+
+ if (disposing)
+ {
+ ClearHeader();
+ if (_footerRenderer != null)
+ {
+ Platform.DisposeModelAndChildrenRenderers(_footerRenderer.Element);
+ _footerRenderer = null;
+ }
+ }
+
+ base.Dispose(disposing);
+ }
+
+ protected override void SetBackgroundColor(Color color)
+ {
+ base.SetBackgroundColor(color);
+ if (_table == null)
+ return;
+
+ _table.BackgroundColor = color.ToNSColor(NSColor.White);
+ }
+
+ protected override void OnElementChanged(ElementChangedEventArgs<ListView> e)
+ {
+ if (e.OldElement != null)
+ {
+ var controller = (IListViewController)e.OldElement;
+ controller.ScrollToRequested -= OnScrollToRequested;
+
+ var templatedItems = ((ITemplatedItemsView<Cell>)e.OldElement).TemplatedItems;
+ templatedItems.CollectionChanged -= OnCollectionChanged;
+ templatedItems.GroupedCollectionChanged -= OnGroupedCollectionChanged;
+ }
+
+ if (e.NewElement != null)
+ {
+ if (Control == null)
+ {
+ var scroller = new NSScrollView
+ {
+ AutoresizingMask = NSViewResizingMask.HeightSizable | NSViewResizingMask.WidthSizable,
+ DocumentView = _table = CreateNSTableView(e.NewElement)
+ };
+ SetNativeControl(scroller);
+ }
+
+ var controller = (IListViewController)e.NewElement;
+ controller.ScrollToRequested += OnScrollToRequested;
+
+ var templatedItems = ((ITemplatedItemsView<Cell>)e.NewElement).TemplatedItems;
+ templatedItems.CollectionChanged += OnCollectionChanged;
+ templatedItems.GroupedCollectionChanged += OnGroupedCollectionChanged;
+
+ UpdateRowHeight();
+ UpdateHeader();
+ UpdateFooter();
+ UpdatePullToRefreshEnabled();
+ UpdateIsRefreshing();
+ UpdateSeparatorColor();
+ UpdateSeparatorVisibility();
+
+ var selected = e.NewElement.SelectedItem;
+ if (selected != null)
+ _dataSource.OnItemSelected(null, new SelectedItemChangedEventArgs(selected));
+ }
+
+ base.OnElementChanged(e);
+ }
+
+ protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ base.OnElementPropertyChanged(sender, e);
+ if (e.PropertyName == ListView.RowHeightProperty.PropertyName)
+ UpdateRowHeight();
+ else if (e.PropertyName == ListView.IsGroupingEnabledProperty.PropertyName ||
+ (e.PropertyName == ListView.HasUnevenRowsProperty.PropertyName))
+ _dataSource.Update();
+ else if (e.PropertyName == ListView.IsPullToRefreshEnabledProperty.PropertyName)
+ UpdatePullToRefreshEnabled();
+ else if (e.PropertyName == ListView.IsRefreshingProperty.PropertyName)
+ UpdateIsRefreshing();
+ else if (e.PropertyName == ListView.SeparatorColorProperty.PropertyName)
+ UpdateSeparatorColor();
+ else if (e.PropertyName == ListView.SeparatorVisibilityProperty.PropertyName)
+ UpdateSeparatorVisibility();
+ else if (e.PropertyName == "HeaderElement")
+ UpdateHeader();
+ else if (e.PropertyName == "FooterElement")
+ UpdateFooter();
+ else if (e.PropertyName == "RefreshAllowed")
+ UpdatePullToRefreshEnabled();
+ }
+
+ void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
+ {
+ UpdateItems(e, 0, true);
+ }
+
+ void OnGroupedCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
+ {
+ var til = (TemplatedItemsList<ItemsView<Cell>, Cell>)sender;
+
+ var templatedItems = TemplatedItemsView.TemplatedItems;
+ var groupIndex = templatedItems.IndexOf(til.HeaderContent);
+ UpdateItems(e, groupIndex, false);
+ }
+
+ void UpdateHeader()
+ {
+ var header = Controller.HeaderElement;
+ var headerView = (View)header;
+
+ if (headerView != null)
+ {
+ //Header reuse is not working for now , problem with size of something that is not inside a layout
+ //if (_headerRenderer != null)
+ //{
+ // if (header != null && _headerRenderer.GetType() == Registrar.Registered.GetHandlerType(header.GetType()))
+ // {
+ // _headerRenderer.SetElement(headerView);
+ // _table.HeaderView = new CustomNSTableHeaderView(Bounds.Width, _headerRenderer);
+ // // Layout();
+ // //var customNSTableHeaderView = _table.HeaderView as CustomNSTableHeaderView;
+ // //customNSTableHeaderView?.Update(Bounds.Width, _headerRenderer);
+ // //NativeView.Layout();
+ // //NativeView.SetNeedsDisplayInRect(NativeView.Bounds);
+ // //NativeView.LayoutSubtreeIfNeeded();
+ // //_table.LayoutSubtreeIfNeeded();
+ // //_table.NeedsDisplay = true;
+ // //NativeView.NeedsDisplay = true;
+ // return;
+ // }
+ ClearHeader();
+ //}
+
+ _headerRenderer = Platform.CreateRenderer(headerView);
+ Platform.SetRenderer(headerView, _headerRenderer);
+ _table.HeaderView = new CustomNSTableHeaderView(Bounds.Width, _headerRenderer);
+
+ //We need this since the NSSCrollView doesn't know of the new size of our header
+ //TODO: Find a better solution
+ (Control as NSScrollView)?.ContentView.ScrollToPoint(new CoreGraphics.CGPoint(0, -_table.HeaderView.Frame.Height));
+ }
+ else if (_headerRenderer != null)
+ {
+ ClearHeader();
+ }
+ }
+
+ void ClearHeader()
+ {
+ _table.HeaderView = null;
+ if (_headerRenderer == null)
+ return;
+ Platform.DisposeModelAndChildrenRenderers(_headerRenderer.Element);
+ _headerRenderer = null;
+ }
+
+ void UpdateItems(NotifyCollectionChangedEventArgs e, int section, bool resetWhenGrouped)
+ {
+ var exArgs = e as NotifyCollectionChangedEventArgsEx;
+ if (exArgs != null)
+ _dataSource.Counts[section] = exArgs.Count;
+
+ var groupReset = resetWhenGrouped && Element.IsGroupingEnabled;
+
+ switch (e.Action)
+ {
+ case NotifyCollectionChangedAction.Add:
+ if (e.NewStartingIndex == -1 || groupReset)
+ goto case NotifyCollectionChangedAction.Reset;
+
+ _table.BeginUpdates();
+ _table.InsertRows(NSIndexSet.FromArray(Enumerable.Range(e.NewStartingIndex, e.NewItems.Count).ToArray()),
+ NSTableViewAnimation.SlideUp);
+ _table.EndUpdates();
+
+ break;
+
+ case NotifyCollectionChangedAction.Remove:
+ if (e.OldStartingIndex == -1 || groupReset)
+ goto case NotifyCollectionChangedAction.Reset;
+
+ _table.BeginUpdates();
+ _table.RemoveRows(NSIndexSet.FromArray(Enumerable.Range(e.OldStartingIndex, e.OldItems.Count).ToArray()),
+ NSTableViewAnimation.SlideDown);
+ _table.EndUpdates();
+
+ break;
+
+ case NotifyCollectionChangedAction.Move:
+ if (e.OldStartingIndex == -1 || e.NewStartingIndex == -1 || groupReset)
+ goto case NotifyCollectionChangedAction.Reset;
+ _table.BeginUpdates();
+ for (var i = 0; i < e.OldItems.Count; i++)
+ {
+ var oldi = e.OldStartingIndex;
+ var newi = e.NewStartingIndex;
+
+ if (e.NewStartingIndex < e.OldStartingIndex)
+ {
+ oldi += i;
+ newi += i;
+ }
+
+ _table.MoveRow(oldi, newi);
+ }
+ _table.EndUpdates();
+
+ break;
+
+ case NotifyCollectionChangedAction.Replace:
+ case NotifyCollectionChangedAction.Reset:
+ _table.ReloadData();
+ return;
+ }
+ }
+
+ void UpdateRowHeight()
+ {
+ var rowHeight = Element.RowHeight;
+ if (Element.HasUnevenRows && rowHeight == -1)
+ {
+ // _table.RowHeight = NoIntrinsicMetric;
+ }
+ else
+ _table.RowHeight = rowHeight <= 0 ? DefaultRowHeight : rowHeight;
+ }
+
+ //TODO: Implement UpdateIsRefreshing
+ void UpdateIsRefreshing()
+ {
+ }
+
+ //TODO: Implement PullToRefresh
+ void UpdatePullToRefreshEnabled()
+ {
+ }
+
+ //TODO: Implement SeparatorColor
+ void UpdateSeparatorColor()
+ {
+ }
+
+ //TODO: Implement UpdateSeparatorVisibility
+ void UpdateSeparatorVisibility()
+ {
+ }
+
+ //TODO: Implement ScrollTo
+ void OnScrollToRequested(object sender, ScrollToRequestedEventArgs e)
+ {
+ }
+
+ //TODO: Implement Footer
+ void UpdateFooter()
+ {
+ }
+
+ class FormsNSTableView : NSTableView
+ {
+ //NSTableView doesn't support selection notfications after the items is already selected
+ //so we do it ourselves by hooking mouse down event
+ public override void MouseDown(NSEvent theEvent)
+ {
+ var clickLocation = ConvertPointFromView(theEvent.LocationInWindow, null);
+ var clickedRow = GetRow(clickLocation);
+
+ base.MouseDown(theEvent);
+ if (clickedRow != -1)
+ (Source as ListViewDataSource)?.OnRowClicked();
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.MacOS/Renderers/MasterDetailPageRenderer.cs b/Xamarin.Forms.Platform.MacOS/Renderers/MasterDetailPageRenderer.cs
new file mode 100644
index 0000000..8d2fd55
--- /dev/null
+++ b/Xamarin.Forms.Platform.MacOS/Renderers/MasterDetailPageRenderer.cs
@@ -0,0 +1,204 @@
+using System;
+using System.ComponentModel;
+using System.Linq;
+using AppKit;
+using CoreGraphics;
+
+namespace Xamarin.Forms.Platform.MacOS
+{
+ public class MasterDetailPageRenderer : NSSplitViewController, IVisualElementRenderer, IEffectControlProvider
+ {
+ bool _disposed;
+ EventTracker _events;
+ VisualElementTracker _tracker;
+ MasterDetailPage _masterDetailPage;
+
+ IPageController PageController => Element as IPageController;
+
+ void IEffectControlProvider.RegisterEffect(Effect effect)
+ {
+ var platformEffect = effect as PlatformEffect;
+ if (platformEffect != null)
+ platformEffect.Container = View;
+ }
+
+ protected MasterDetailPage MasterDetailPage => _masterDetailPage ?? (_masterDetailPage = (MasterDetailPage)Element);
+
+ protected override void Dispose(bool disposing)
+ {
+ if (!_disposed && disposing)
+ {
+ PageController?.SendDisappearing();
+
+ if (Element != null)
+ {
+ Element.PropertyChanged -= HandlePropertyChanged;
+ Element = null;
+ }
+
+ ClearControllers();
+
+ _tracker?.Dispose();
+ _tracker = null;
+ _events?.Dispose();
+ _events = null;
+
+ _disposed = true;
+ }
+ base.Dispose(disposing);
+ }
+
+ public NSViewController ViewController => this;
+
+ public NSView NativeView => View;
+
+ public VisualElement Element { get; private set; }
+
+ public event EventHandler<VisualElementChangedEventArgs> ElementChanged;
+
+ public SizeRequest GetDesiredSize(double widthConstraint, double heightConstraint)
+ {
+ return NativeView.GetSizeRequest(widthConstraint, heightConstraint);
+ }
+
+ public void SetElement(VisualElement element)
+ {
+ var oldElement = Element;
+ Element = element;
+
+ UpdateControllers();
+
+ OnElementChanged(new VisualElementChangedEventArgs(oldElement, element));
+
+ EffectUtilities.RegisterEffectControlProvider(this, oldElement, element);
+ }
+
+ public void SetElementSize(Size size)
+ {
+ Element.Layout(new Rectangle(Element.X, Element.Y, size.Width, size.Height));
+ UpdateChildrenLayout();
+ }
+
+ protected virtual void OnElementChanged(VisualElementChangedEventArgs e)
+ {
+ if (e.OldElement != null)
+ e.OldElement.PropertyChanged -= HandlePropertyChanged;
+
+ if (e.NewElement != null)
+ e.NewElement.PropertyChanged += HandlePropertyChanged;
+
+ ElementChanged?.Invoke(this, e);
+ }
+
+ protected virtual double MasterWidthPercentage => 0.3;
+
+ public override void ViewWillAppear()
+ {
+ UpdateBackground();
+ _tracker = new VisualElementTracker(this);
+ _events = new EventTracker(this);
+ _events.LoadEvents(NativeView);
+ UpdateChildrenLayout();
+
+ base.ViewWillAppear();
+ }
+
+ public override CGRect GetEffectiveRect(NSSplitView splitView, CGRect proposedEffectiveRect, CGRect drawnRect,
+ nint dividerIndex)
+ {
+ return CGRect.Empty;
+ }
+
+ void UpdateChildrenLayout()
+ {
+ if (View.Frame.Width == -1)
+ return;
+ var width = View.Frame.Width;
+ var masterWidth = MasterWidthPercentage * width;
+ if (SplitViewItems.Length > 0)
+ SplitViewItems[0].MaximumThickness = SplitViewItems[0].MinimumThickness = (nfloat)masterWidth;
+ }
+
+ void HandlePropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ if (_tracker == null)
+ return;
+
+ if (e.PropertyName == "Master" || e.PropertyName == "Detail")
+ UpdateControllers();
+ }
+
+ void UpdateControllers()
+ {
+ ClearControllers();
+
+ 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));
+
+ AddSplitViewItem(new NSSplitViewItem
+ {
+ ViewController = new ViewControllerWrapper(Platform.GetRenderer(MasterDetailPage.Master))
+ });
+ AddSplitViewItem(new NSSplitViewItem
+ {
+ ViewController = new ViewControllerWrapper(Platform.GetRenderer(MasterDetailPage.Detail))
+ });
+
+ UpdateChildrenLayout();
+ }
+
+ void ClearControllers()
+ {
+ while (SplitViewItems.Length > 0)
+ {
+ var splitItem = SplitViewItems.Last();
+ var childVisualRenderer = splitItem.ViewController as ViewControllerWrapper;
+ RemoveSplitViewItem(splitItem);
+ IVisualElementRenderer render = null;
+ if (childVisualRenderer.RendererWeakRef.TryGetTarget(out render))
+ {
+ render.Dispose();
+ }
+ childVisualRenderer.Dispose();
+ childVisualRenderer = null;
+ }
+ }
+
+ //TODO: Implement Background color on MDP
+ void UpdateBackground()
+ {
+ }
+
+ sealed class ViewControllerWrapper : NSViewController
+ {
+ internal WeakReference<IVisualElementRenderer> RendererWeakRef;
+
+ public ViewControllerWrapper(IVisualElementRenderer renderer)
+ {
+ RendererWeakRef = new WeakReference<IVisualElementRenderer>(renderer);
+ View = new NSView { WantsLayer = true };
+ AddChildViewController(renderer.ViewController);
+ View.AddSubview(renderer.NativeView);
+ }
+
+ public override void ViewWillLayout()
+ {
+ IVisualElementRenderer renderer;
+ if (RendererWeakRef.TryGetTarget(out renderer))
+ renderer?.Element?.Layout(new Rectangle(0, 0, View.Bounds.Width, View.Bounds.Height));
+ base.ViewWillLayout();
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing && RendererWeakRef != null)
+ {
+ RendererWeakRef = null;
+ }
+ base.Dispose(disposing);
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.MacOS/Renderers/NSPageContainer.cs b/Xamarin.Forms.Platform.MacOS/Renderers/NSPageContainer.cs
new file mode 100644
index 0000000..80f409c
--- /dev/null
+++ b/Xamarin.Forms.Platform.MacOS/Renderers/NSPageContainer.cs
@@ -0,0 +1,17 @@
+using Foundation;
+
+namespace Xamarin.Forms.Platform.MacOS
+{
+ internal class NSPageContainer : NSObject
+ {
+ public NSPageContainer(Page element, int index)
+ {
+ Page = element;
+ Index = index;
+ }
+
+ public Page Page { get; }
+
+ public int Index { get; set; }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.MacOS/Renderers/NavigationPageRenderer.cs b/Xamarin.Forms.Platform.MacOS/Renderers/NavigationPageRenderer.cs
new file mode 100644
index 0000000..23d7f85
--- /dev/null
+++ b/Xamarin.Forms.Platform.MacOS/Renderers/NavigationPageRenderer.cs
@@ -0,0 +1,355 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using System.Threading.Tasks;
+using AppKit;
+using CoreAnimation;
+using Foundation;
+using Xamarin.Forms.Internals;
+
+namespace Xamarin.Forms.Platform.MacOS
+{
+ public class NavigationPageRenderer : NSViewController, IVisualElementRenderer, IEffectControlProvider
+ {
+ bool _disposed;
+ bool _appeared;
+ EventTracker _events;
+ VisualElementTracker _tracker;
+ Stack<NavigationChildPageWrapper> _currentStack = new Stack<NavigationChildPageWrapper>();
+
+ IPageController PageController => Element as IPageController;
+
+ IElementController ElementController => Element as IElementController;
+
+ INavigationPageController NavigationController => Element as INavigationPageController;
+
+ void IEffectControlProvider.RegisterEffect(Effect effect)
+ {
+ var platformEffect = effect as PlatformEffect;
+ if (platformEffect != null)
+ platformEffect.Container = View;
+ }
+
+ public NavigationPageRenderer() : this(IntPtr.Zero)
+ {
+ }
+
+ public NavigationPageRenderer(IntPtr handle)
+ {
+ View = new NSView { WantsLayer = true };
+ }
+
+ public VisualElement Element { get; private set; }
+
+ public event EventHandler<VisualElementChangedEventArgs> ElementChanged;
+
+ public SizeRequest GetDesiredSize(double widthConstraint, double heightConstraint)
+ {
+ return NativeView.GetSizeRequest(widthConstraint, heightConstraint);
+ }
+
+ public NSViewController ViewController => this;
+
+ public NSView NativeView => View;
+
+ public void SetElement(VisualElement element)
+ {
+ var oldElement = Element;
+ Element = element;
+
+ Init();
+
+ OnElementChanged(new VisualElementChangedEventArgs(oldElement, element));
+
+ EffectUtilities.RegisterEffectControlProvider(this, oldElement, element);
+ }
+
+ public void SetElementSize(Size size)
+ {
+ Element.Layout(new Rectangle(Element.X, Element.Y, size.Width, size.Height));
+ }
+
+ public Task<bool> PopToRootAsync(Page page, bool animated = true)
+ {
+ return OnPopToRoot(page, animated);
+ }
+
+ public Task<bool> PopViewAsync(Page page, bool animated = true)
+ {
+ return OnPop(page, animated);
+ }
+
+ public Task<bool> PushPageAsync(Page page, bool animated = true)
+ {
+ return OnPush(page, animated);
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ if (!_disposed && disposing)
+ {
+ if (Element != null)
+ {
+ PageController?.SendDisappearing();
+ ((Element as IPageContainer<Page>)?.CurrentPage as IPageController)?.SendDisappearing();
+ Element.PropertyChanged -= HandlePropertyChanged;
+ Element = null;
+ }
+
+ _tracker?.Dispose();
+ _tracker = null;
+
+ _events?.Dispose();
+ _events = null;
+
+ _disposed = true;
+ }
+ base.Dispose(disposing);
+ }
+
+ public override void ViewDidDisappear()
+ {
+ base.ViewDidDisappear();
+ if (!_appeared)
+ return;
+ Platform.NativeToolbarTracker.TryHide(Element as NavigationPage);
+ _appeared = false;
+ PageController?.SendDisappearing();
+ }
+
+ public override void ViewDidAppear()
+ {
+ base.ViewDidAppear();
+ Platform.NativeToolbarTracker.Navigation = (NavigationPage)Element;
+ if (_appeared)
+ return;
+
+ _appeared = true;
+ PageController?.SendAppearing();
+ }
+
+ protected virtual void OnElementChanged(VisualElementChangedEventArgs e)
+ {
+ if (e.OldElement != null)
+ e.OldElement.PropertyChanged -= HandlePropertyChanged;
+
+ if (e.NewElement != null)
+ e.NewElement.PropertyChanged += HandlePropertyChanged;
+
+ ElementChanged?.Invoke(this, e);
+ }
+
+ protected virtual void ConfigurePageRenderer()
+ {
+ View.WantsLayer = true;
+ }
+
+ protected virtual Task<bool> OnPopToRoot(Page page, bool animated)
+ {
+ var renderer = Platform.GetRenderer(page);
+ if (renderer == null || renderer.ViewController == null)
+ return Task.FromResult(false);
+
+ Platform.NativeToolbarTracker.UpdateToolBar();
+ return Task.FromResult(true);
+ }
+
+ protected virtual async Task<bool> OnPop(Page page, bool animated)
+ {
+ var removed = await PopPageAsync(page, animated);
+ Platform.NativeToolbarTracker.UpdateToolBar();
+ return removed;
+ }
+
+ protected virtual async Task<bool> OnPush(Page page, bool animated)
+ {
+ var shown = await AddPage(page, animated);
+ Platform.NativeToolbarTracker.UpdateToolBar();
+ return shown;
+ }
+
+ void Init()
+ {
+ ConfigurePageRenderer();
+
+ var navPage = (NavigationPage)Element;
+
+ if (navPage.CurrentPage == null)
+ throw new InvalidOperationException(
+ "NavigationPage must have a root Page before being used. Either call PushAsync with a valid Page, or pass a Page to the constructor before usage.");
+
+ Platform.NativeToolbarTracker.Navigation = navPage;
+
+ NavigationController.PushRequested += OnPushRequested;
+ NavigationController.PopRequested += OnPopRequested;
+ NavigationController.PopToRootRequested += OnPopToRootRequested;
+ NavigationController.RemovePageRequested += OnRemovedPageRequested;
+ NavigationController.InsertPageBeforeRequested += OnInsertPageBeforeRequested;
+
+ navPage.Popped += (sender, e) => Platform.NativeToolbarTracker.UpdateToolBar();
+ navPage.PoppedToRoot += (sender, e) => Platform.NativeToolbarTracker.UpdateToolBar();
+
+ UpdateBarBackgroundColor();
+ UpdateBarTextColor();
+
+ _events = new EventTracker(this);
+ _events.LoadEvents(NativeView);
+ _tracker = new VisualElementTracker(this);
+
+ ((INavigationPageController)navPage).Pages.ForEach(async p => await PushPageAsync(p, false));
+
+ UpdateBackgroundColor();
+ }
+
+ IVisualElementRenderer CreateViewControllerForPage(Page page)
+ {
+ if (Platform.GetRenderer(page) == null)
+ Platform.SetRenderer(page, Platform.CreateRenderer(page));
+
+ var pageRenderer = Platform.GetRenderer(page);
+ return pageRenderer;
+ }
+
+ //TODO: Implement InserPageBefore
+ void InsertPageBefore(Page page, Page before)
+ {
+ if (before == null)
+ throw new ArgumentNullException(nameof(before));
+ if (page == null)
+ throw new ArgumentNullException(nameof(page));
+ }
+
+ void OnInsertPageBeforeRequested(object sender, NavigationRequestedEventArgs e)
+ {
+ InsertPageBefore(e.Page, e.BeforePage);
+ }
+
+ void OnPopRequested(object sender, NavigationRequestedEventArgs e)
+ {
+ e.Task = PopViewAsync(e.Page, e.Animated);
+ }
+
+ void OnPopToRootRequested(object sender, NavigationRequestedEventArgs e)
+ {
+ e.Task = PopToRootAsync(e.Page, e.Animated);
+ }
+
+ void OnPushRequested(object sender, NavigationRequestedEventArgs e)
+ {
+ e.Task = PushPageAsync(e.Page, e.Animated);
+ }
+
+ void OnRemovedPageRequested(object sender, NavigationRequestedEventArgs e)
+ {
+ RemovePage(e.Page, true);
+ Platform.NativeToolbarTracker.UpdateToolBar();
+ }
+
+ void RemovePage(Page page, bool removeFromStack)
+ {
+ (page as IPageController)?.SendDisappearing();
+ var target = Platform.GetRenderer(page);
+ target?.NativeView?.RemoveFromSuperview();
+ target?.ViewController?.RemoveFromParentViewController();
+ target?.Dispose();
+ if (removeFromStack)
+ {
+ var newStack = new Stack<NavigationChildPageWrapper>();
+ foreach (var stack in _currentStack)
+ {
+ if (stack.Page != page)
+ {
+ newStack.Push(stack);
+ }
+ }
+ _currentStack = newStack;
+ }
+ }
+
+ async Task<bool> PopPageAsync(Page page, bool animated)
+ {
+ if (page == null)
+ throw new ArgumentNullException(nameof(page));
+
+ var wrapper = _currentStack.Peek();
+ if (page != wrapper.Page)
+ throw new NotSupportedException("Popped page does not appear on top of current navigation stack, please file a bug.");
+
+ _currentStack.Pop();
+ (page as IPageController)?.SendDisappearing();
+
+ var target = Platform.GetRenderer(page);
+ var previousPage = _currentStack.Peek().Page;
+
+ if (animated)
+ {
+ var previousPageRenderer = Platform.GetRenderer(previousPage);
+ return await this.HandleAsyncAnimation(target.ViewController, previousPageRenderer.ViewController,
+ NSViewControllerTransitionOptions.SlideBackward, () => Platform.DisposeRendererAndChildren(target), true);
+ }
+
+ RemovePage(page, false);
+ return true;
+ }
+
+ async Task<bool> AddPage(Page page, bool animated)
+ {
+ if (page == null)
+ throw new ArgumentNullException(nameof(page));
+
+ Page oldPage = null;
+ if (_currentStack.Count >= 1)
+ oldPage = _currentStack.Peek().Page;
+
+ _currentStack.Push(new NavigationChildPageWrapper(page));
+
+ var vc = CreateViewControllerForPage(page);
+ vc.SetElementSize(new Size(View.Bounds.Width, View.Bounds.Height));
+ page.Layout(new Rectangle(0, 0, View.Bounds.Width, View.Frame.Height));
+
+ if (_currentStack.Count == 1 || !animated)
+ {
+ vc.NativeView.WantsLayer = true;
+ AddChildViewController(vc.ViewController);
+ View.AddSubview(vc.NativeView);
+ return true;
+ }
+ var vco = Platform.GetRenderer(oldPage);
+ AddChildViewController(vc.ViewController);
+ return await this.HandleAsyncAnimation(vco.ViewController, vc.ViewController,
+ NSViewControllerTransitionOptions.SlideForward, () => (page as IPageController)?.SendAppearing(), true);
+ }
+
+ void UpdateBackgroundColor()
+ {
+ if (View.Layer == null)
+ return;
+ var color = Element.BackgroundColor == Color.Default ? Color.White : Element.BackgroundColor;
+ View.Layer.BackgroundColor = color.ToCGColor();
+ }
+
+ void UpdateBarBackgroundColor()
+ {
+ Platform.NativeToolbarTracker.UpdateToolBar();
+ }
+
+ void UpdateBarTextColor()
+ {
+ Platform.NativeToolbarTracker.UpdateToolBar();
+ }
+
+ void HandlePropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ if (_tracker == null)
+ return;
+
+ if (e.PropertyName == NavigationPage.BarBackgroundColorProperty.PropertyName)
+ UpdateBarBackgroundColor();
+ else if (e.PropertyName == NavigationPage.BarTextColorProperty.PropertyName)
+ UpdateBarTextColor();
+ else if (e.PropertyName == VisualElement.BackgroundColorProperty.PropertyName)
+ UpdateBackgroundColor();
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.MacOS/Renderers/OpenGLViewRenderer.cs b/Xamarin.Forms.Platform.MacOS/Renderers/OpenGLViewRenderer.cs
new file mode 100644
index 0000000..dda6ac5
--- /dev/null
+++ b/Xamarin.Forms.Platform.MacOS/Renderers/OpenGLViewRenderer.cs
@@ -0,0 +1,104 @@
+using System;
+using System.ComponentModel;
+using CoreVideo;
+
+namespace Xamarin.Forms.Platform.MacOS
+{
+ // ReSharper disable once InconsistentNaming
+ internal class OpenGLViewRenderer : ViewRenderer<OpenGLView, MacOSOpenGLView>
+ {
+ CVDisplayLink _displayLink;
+
+ public void Display(object sender, EventArgs eventArgs)
+ {
+ if (Element.HasRenderLoop)
+ return;
+ SetupRenderLoop(true);
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ if (_displayLink != null)
+ {
+ _displayLink.Dispose();
+ _displayLink = null;
+
+ if (Element != null)
+ ((IOpenGlViewController)Element).DisplayRequested -= Display;
+ }
+
+ base.Dispose(disposing);
+ }
+
+ protected override void OnElementChanged(ElementChangedEventArgs<OpenGLView> e)
+ {
+ if (e.OldElement != null)
+ ((IOpenGlViewController)e.OldElement).DisplayRequested -= Display;
+
+ if (e.NewElement != null)
+ {
+ //var context = new EAGLContext(EAGLRenderingAPI.OpenGLES2);
+ //var glkView = new GLKView(RectangleF.Empty) { Context = context, DrawableDepthFormat = GLKViewDrawableDepthFormat.Format24, Delegate = new Delegate(e.NewElement) };
+ var glkView = new MacOSOpenGLView();
+ SetNativeControl(glkView);
+
+ ((IOpenGlViewController)e.NewElement).DisplayRequested += Display;
+
+ SetupRenderLoop(false);
+ }
+
+ base.OnElementChanged(e);
+ }
+
+ protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ base.OnElementPropertyChanged(sender, e);
+
+ if (e.PropertyName == OpenGLView.HasRenderLoopProperty.PropertyName)
+ SetupRenderLoop(false);
+ }
+
+ void SetupRenderLoop(bool oneShot)
+ {
+ if (_displayLink != null)
+ return;
+ if (!oneShot && !Element.HasRenderLoop)
+ return;
+
+ _displayLink = new CVDisplayLink();
+
+ //.Create(() =>
+ //{
+ // var control = Control;
+ // var model = Element;
+ // if (control != null)
+ // control.Display();
+ // if (control == null || model == null || !model.HasRenderLoop)
+ // {
+ // _displayLink.Invalidate();
+ // _displayLink.Dispose();
+ // _displayLink = null;
+ // }
+ //});
+ //_displayLink.(NSRunLoop.Current, NSRunLoop.NSDefaultRunLoopMode);
+ }
+
+ //class Delegate : GLKViewDelegate
+ //{
+ // readonly OpenGLView _model;
+
+ // public Delegate(OpenGLView model)
+ // {
+ // _model = model;
+ // }
+
+ // public override void DrawInRect(GLKView view, RectangleF rect)
+ // {
+ // var onDisplay = _model.OnDisplay;
+ // if (onDisplay == null)
+ // return;
+ // onDisplay(rect.ToRectangle());
+ // }
+ //}
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.MacOS/Renderers/PageControllerDelegate.cs b/Xamarin.Forms.Platform.MacOS/Renderers/PageControllerDelegate.cs
new file mode 100644
index 0000000..d1f40d3
--- /dev/null
+++ b/Xamarin.Forms.Platform.MacOS/Renderers/PageControllerDelegate.cs
@@ -0,0 +1,30 @@
+using AppKit;
+using Foundation;
+
+namespace Xamarin.Forms.Platform.MacOS
+{
+ internal class PageControllerDelegate : NSPageControllerDelegate
+ {
+ public override string GetIdentifier(NSPageController pageController, NSObject targetObject)
+ {
+ return nameof(PageRenderer);
+ }
+
+ public override NSViewController GetViewController(NSPageController pageController, string identifier)
+ {
+ return new PageRenderer();
+ }
+
+ public override void PrepareViewController(NSPageController pageController, NSViewController viewController,
+ NSObject targetObject)
+ {
+ var pageContainer = targetObject as NSPageContainer;
+ var pageRenderer = (viewController as PageRenderer);
+ if (pageContainer == null || pageRenderer == null)
+ return;
+ Page page = pageContainer.Page;
+ pageRenderer.SetElement(page);
+ Platform.SetRenderer(page, pageRenderer);
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.MacOS/Renderers/PageRenderer.cs b/Xamarin.Forms.Platform.MacOS/Renderers/PageRenderer.cs
new file mode 100644
index 0000000..e0845d5
--- /dev/null
+++ b/Xamarin.Forms.Platform.MacOS/Renderers/PageRenderer.cs
@@ -0,0 +1,182 @@
+using System;
+using System.ComponentModel;
+using AppKit;
+
+namespace Xamarin.Forms.Platform.MacOS
+{
+ public class PageRenderer : NSViewController, IVisualElementRenderer, IEffectControlProvider
+ {
+ bool _init;
+ bool _appeared;
+ bool _disposed;
+ EventTracker _events;
+ VisualElementPackager _packager;
+ VisualElementTracker _tracker;
+
+ IPageController PageController => Element as IPageController;
+
+ public PageRenderer()
+ {
+ View = new NSView { WantsLayer = true };
+ }
+
+ void IEffectControlProvider.RegisterEffect(Effect effect)
+ {
+ var platformEffect = effect as PlatformEffect;
+ if (platformEffect != null)
+ platformEffect.Container = View;
+ }
+
+ public VisualElement Element { get; private set; }
+
+ public event EventHandler<VisualElementChangedEventArgs> ElementChanged;
+
+ public SizeRequest GetDesiredSize(double widthConstraint, double heightConstraint)
+ {
+ return NativeView.GetSizeRequest(widthConstraint, heightConstraint);
+ }
+
+ public NSView NativeView => _disposed ? null : View;
+
+ public void SetElement(VisualElement element)
+ {
+ VisualElement oldElement = Element;
+ Element = element;
+ UpdateTitle();
+
+ OnElementChanged(new VisualElementChangedEventArgs(oldElement, element));
+
+ if (Element != null && !string.IsNullOrEmpty(Element.AutomationId))
+ SetAutomationId(Element.AutomationId);
+
+ EffectUtilities.RegisterEffectControlProvider(this, oldElement, element);
+ }
+
+ public void SetElementSize(Size size)
+ {
+ Element.Layout(new Rectangle(Element.X, Element.Y, size.Width, size.Height));
+ }
+
+ public NSViewController ViewController => _disposed ? null : this;
+
+ public override void ViewDidAppear()
+ {
+ base.ViewDidAppear();
+
+ if (_appeared || _disposed)
+ return;
+
+ _appeared = true;
+ PageController.SendAppearing();
+ }
+
+ public override void ViewDidDisappear()
+ {
+ base.ViewDidDisappear();
+
+ if (!_appeared || _disposed)
+ return;
+
+ _appeared = false;
+ PageController.SendDisappearing();
+ }
+
+ public override void ViewWillAppear()
+ {
+ Init();
+ base.ViewWillAppear();
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing && !_disposed)
+ {
+ Element.PropertyChanged -= OnHandlePropertyChanged;
+ Platform.SetRenderer(Element, null);
+ if (_appeared)
+ PageController.SendDisappearing();
+
+ _appeared = false;
+
+ if (_events != null)
+ {
+ _events.Dispose();
+ _events = null;
+ }
+
+ if (_packager != null)
+ {
+ _packager.Dispose();
+ _packager = null;
+ }
+
+ if (_tracker != null)
+ {
+ _tracker.Dispose();
+ _tracker = null;
+ }
+
+ Element = null;
+ _disposed = true;
+ }
+
+ base.Dispose(disposing);
+ }
+
+ void OnElementChanged(VisualElementChangedEventArgs e)
+ {
+ ElementChanged?.Invoke(this, e);
+ }
+
+ void SetAutomationId(string id)
+ {
+ if (NativeView != null)
+ NativeView.AccessibilityIdentifier = id;
+ }
+
+ void Init()
+ {
+ if (_init)
+ return;
+ UpdateBackground();
+
+ _packager = new VisualElementPackager(this);
+ _packager.Load();
+
+ Element.PropertyChanged += OnHandlePropertyChanged;
+ _tracker = new VisualElementTracker(this);
+
+ _events = new EventTracker(this);
+ _events.LoadEvents(View);
+ _init = true;
+ }
+
+ void OnHandlePropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ if (e.PropertyName == VisualElement.BackgroundColorProperty.PropertyName)
+ UpdateBackground();
+ else if (e.PropertyName == Page.BackgroundImageProperty.PropertyName)
+ UpdateBackground();
+ else if (e.PropertyName == Page.TitleProperty.PropertyName)
+ UpdateTitle();
+ }
+
+ void UpdateBackground()
+ {
+ string bgImage = ((Page)Element).BackgroundImage;
+ if (!string.IsNullOrEmpty(bgImage))
+ {
+ View.Layer.BackgroundColor = NSColor.FromPatternImage(NSImage.ImageNamed(bgImage)).CGColor;
+ return;
+ }
+ Color bgColor = Element.BackgroundColor;
+ View.Layer.BackgroundColor = bgColor.IsDefault ? NSColor.White.CGColor : bgColor.ToCGColor();
+ }
+
+ void UpdateTitle()
+ {
+ if (!string.IsNullOrWhiteSpace(((Page)Element).Title))
+ Title = ((Page)Element).Title;
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.MacOS/Renderers/PickerRenderer.cs b/Xamarin.Forms.Platform.MacOS/Renderers/PickerRenderer.cs
new file mode 100644
index 0000000..ac07bfd
--- /dev/null
+++ b/Xamarin.Forms.Platform.MacOS/Renderers/PickerRenderer.cs
@@ -0,0 +1,154 @@
+using System;
+using AppKit;
+using System.ComponentModel;
+using Foundation;
+
+namespace Xamarin.Forms.Platform.MacOS
+{
+ public class PickerRenderer : ViewRenderer<Picker, NSComboBox>
+ {
+ bool _disposed;
+ NSColor _defaultTextColor;
+ NSColor _defaultBackgroundColor;
+
+ IElementController ElementController => Element;
+
+ protected override void OnElementChanged(ElementChangedEventArgs<Picker> e)
+ {
+ if (e.NewElement != null)
+ {
+ if (Control == null)
+ SetNativeControl(new NSComboBox { Editable = false });
+
+ _defaultTextColor = Control.TextColor;
+ _defaultBackgroundColor = Control.BackgroundColor;
+
+ Control.UsesDataSource = true;
+ Control.DataSource = new ComboDataSource(this);
+
+ Control.SelectionChanged += ComboBoxSelectionChanged;
+
+ UpdatePicker();
+ UpdateTextColor();
+ }
+
+ base.OnElementChanged(e);
+ }
+
+ protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ base.OnElementPropertyChanged(sender, e);
+ if (e.PropertyName == Picker.TitleProperty.PropertyName)
+ UpdatePicker();
+ if (e.PropertyName == Picker.SelectedIndexProperty.PropertyName)
+ UpdatePicker();
+ if (e.PropertyName == Picker.TextColorProperty.PropertyName ||
+ e.PropertyName == VisualElement.IsEnabledProperty.PropertyName)
+ UpdateTextColor();
+ }
+
+ protected override void SetBackgroundColor(Color color)
+ {
+ base.SetBackgroundColor(color);
+
+ if (Control == null)
+ return;
+
+ Control.BackgroundColor = color == Color.Default ? _defaultBackgroundColor : color.ToNSColor();
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ if (!_disposed)
+ {
+ _disposed = true;
+ if (Element != null)
+ {
+ //TODO: Implement ObservableList picker source change
+ //((ObservableList<string>)Element.Items).CollectionChanged -= RowsCollectionChanged;
+ }
+
+ if (Control != null)
+ Control.SelectionChanged -= ComboBoxSelectionChanged;
+ }
+ }
+ base.Dispose(disposing);
+ }
+
+ void ComboBoxSelectionChanged(object sender, EventArgs e)
+ {
+ ElementController?.SetValueFromRenderer(Picker.SelectedIndexProperty, (int)Control.SelectedIndex);
+ }
+
+ void OnEnded(object sender, EventArgs eventArgs)
+ {
+ ElementController?.SetValueFromRenderer(VisualElement.IsFocusedPropertyKey, false);
+ }
+
+ void OnStarted(object sender, EventArgs eventArgs)
+ {
+ ElementController?.SetValueFromRenderer(VisualElement.IsFocusedPropertyKey, true);
+ }
+
+ void RowsCollectionChanged(object sender, EventArgs e)
+ {
+ UpdatePicker();
+ }
+
+ void UpdatePicker()
+ {
+ if (Control == null || Element == null)
+ return;
+
+ var selectedIndex = Element.SelectedIndex;
+ var items = Element.Items;
+ Control.PlaceholderString = Element.Title ?? string.Empty;
+ Control.ReloadData();
+ if (items == null || items.Count == 0 || selectedIndex < 0)
+ return;
+
+ Control.SelectItem(selectedIndex);
+ }
+
+ void UpdateTextColor()
+ {
+ if (Control == null || Element == null)
+ return;
+
+ var textColor = Element.TextColor;
+
+ if (textColor.IsDefault || !Element.IsEnabled)
+ Control.TextColor = _defaultTextColor;
+ else
+ Control.TextColor = textColor.ToNSColor();
+ }
+
+ class ComboDataSource : NSComboBoxDataSource
+ {
+ readonly PickerRenderer _renderer;
+
+ public ComboDataSource(PickerRenderer model)
+ {
+ _renderer = model;
+ }
+
+ public override nint ItemCount(NSComboBox comboBox)
+ {
+ return _renderer.Element.Items?.Count ?? 0;
+ }
+
+ public override NSObject ObjectValueForItem(NSComboBox comboBox, nint index)
+ {
+ return new NSString(_renderer.Element.Items[(int)index]);
+ }
+
+ public override nint IndexOfItem(NSComboBox comboBox, string value)
+ {
+ var index = _renderer.Element.Items?.IndexOf(value) ?? -1;
+ return index;
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.MacOS/Renderers/ProgressBarRenderer.cs b/Xamarin.Forms.Platform.MacOS/Renderers/ProgressBarRenderer.cs
new file mode 100644
index 0000000..7b3c84a
--- /dev/null
+++ b/Xamarin.Forms.Platform.MacOS/Renderers/ProgressBarRenderer.cs
@@ -0,0 +1,66 @@
+using System.ComponentModel;
+using AppKit;
+using CoreImage;
+
+namespace Xamarin.Forms.Platform.MacOS
+{
+ public class ProgressBarRenderer : ViewRenderer<ProgressBar, NSProgressIndicator>
+ {
+ static CIColorPolynomial s_currentColorFilter;
+ static NSColor s_currentColor;
+
+ protected override void OnElementChanged(ElementChangedEventArgs<ProgressBar> e)
+ {
+ if (e.NewElement == null) return;
+ if (Control == null)
+ SetNativeControl(new NSProgressIndicator
+ {
+ IsDisplayedWhenStopped = true,
+ Style = NSProgressIndicatorStyle.Bar,
+ MinValue = 0,
+ MaxValue = 1
+ });
+ UpdateProgress();
+ }
+
+ protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ base.OnElementPropertyChanged(sender, e);
+
+ if (e.PropertyName == ProgressBar.ProgressProperty.PropertyName)
+ UpdateProgress();
+ }
+
+ protected override void SetBackgroundColor(Color color)
+ {
+ if (Control == null)
+ return;
+
+ if (s_currentColorFilter == null && color.IsDefault)
+ return;
+
+ if (color.IsDefault)
+ Control.ContentFilters = new CIFilter[0];
+
+ var newColor = Element.BackgroundColor.ToNSColor();
+ if (Equals(s_currentColor, newColor))
+ return;
+
+ s_currentColor = newColor;
+
+ s_currentColorFilter = new CIColorPolynomial
+ {
+ RedCoefficients = new CIVector(s_currentColor.RedComponent),
+ BlueCoefficients = new CIVector(s_currentColor.BlueComponent),
+ GreenCoefficients = new CIVector(s_currentColor.GreenComponent)
+ };
+
+ Control.ContentFilters = new CIFilter[] { s_currentColorFilter };
+ }
+
+ void UpdateProgress()
+ {
+ Control.DoubleValue = Element.Progress;
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.MacOS/Renderers/ScrollViewRenderer.cs b/Xamarin.Forms.Platform.MacOS/Renderers/ScrollViewRenderer.cs
new file mode 100644
index 0000000..c41d1d2
--- /dev/null
+++ b/Xamarin.Forms.Platform.MacOS/Renderers/ScrollViewRenderer.cs
@@ -0,0 +1,214 @@
+using System;
+using System.ComponentModel;
+using AppKit;
+using RectangleF = CoreGraphics.CGRect;
+using ObjCRuntime;
+using Foundation;
+
+// ReSharper disable UnusedMember.Local
+// ReSharper disable UnusedParameter.Local
+
+namespace Xamarin.Forms.Platform.MacOS
+{
+ public class ScrollViewRenderer : NSScrollView, IVisualElementRenderer
+ {
+ EventTracker _events;
+ VisualElementTracker _tracker;
+ ScrollToRequestedEventArgs _requestedScroll;
+ IVisualElementRenderer _contentRenderer;
+
+ public ScrollViewRenderer() : base(RectangleF.Empty)
+ {
+ DrawsBackground = false;
+ ContentView.PostsBoundsChangedNotifications = true;
+ NSNotificationCenter.DefaultCenter.AddObserver(this, new Selector(nameof(UpdateScrollPosition)),
+ BoundsChangedNotification, ContentView);
+ }
+
+ IScrollViewController Controller => Element as IScrollViewController;
+
+ public VisualElement Element { get; private set; }
+
+ public event EventHandler<VisualElementChangedEventArgs> ElementChanged;
+
+ public SizeRequest GetDesiredSize(double widthConstraint, double heightConstraint)
+ {
+ return NativeView.GetSizeRequest(widthConstraint, heightConstraint, 44, 44);
+ }
+
+ public NSView NativeView => this;
+
+ public void SetElement(VisualElement element)
+ {
+ _requestedScroll = null;
+ var oldElement = Element;
+ Element = element;
+
+ if (oldElement != null)
+ {
+ oldElement.PropertyChanged -= HandlePropertyChanged;
+ ((IScrollViewController)oldElement).ScrollToRequested -= OnScrollToRequested;
+ }
+
+ if (element != null)
+ {
+ element.PropertyChanged += HandlePropertyChanged;
+ ((IScrollViewController)element).ScrollToRequested += OnScrollToRequested;
+ if (_tracker == null)
+ {
+ PackContent();
+
+ _events = new EventTracker(this);
+ _events.LoadEvents(this);
+
+ _tracker = new VisualElementTracker(this);
+ _tracker.NativeControlUpdated += OnNativeControlUpdated;
+ }
+
+ UpdateContentSize();
+ UpdateBackgroundColor();
+
+ OnElementChanged(new VisualElementChangedEventArgs(oldElement, element));
+ }
+ }
+
+ public void SetElementSize(Size size)
+ {
+ Xamarin.Forms.Layout.LayoutChildIntoBoundingRegion(Element,
+ new Rectangle(Element.X, Element.Y, size.Width, size.Height));
+ }
+
+ public NSViewController ViewController => null;
+
+ public override void Layout()
+ {
+ base.Layout();
+ LayoutSubviews();
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ if (_tracker == null)
+ return;
+
+ SetElement(null);
+
+ _tracker.NativeControlUpdated -= OnNativeControlUpdated;
+ _tracker.Dispose();
+ _tracker = null;
+
+ _events.Dispose();
+ _events = null;
+
+ ClearContentRenderer();
+
+ //NSNotificationCenter.DefaultCenter.RemoveObserver(this, BoundsChangedNotification);
+ }
+
+ base.Dispose(disposing);
+ }
+
+ void OnElementChanged(VisualElementChangedEventArgs e)
+ {
+ ElementChanged?.Invoke(this, e);
+ }
+
+ void PackContent()
+ {
+ ClearContentRenderer();
+
+ if (Controller.Children.Count == 0 || !(Controller.Children[0] is VisualElement))
+ return;
+
+ var content = (VisualElement)Controller.Children[0];
+ if (Platform.GetRenderer(content) == null)
+ Platform.SetRenderer(content, Platform.CreateRenderer(content));
+
+ _contentRenderer = Platform.GetRenderer(content);
+
+ DocumentView = _contentRenderer.NativeView;
+ }
+
+ void LayoutSubviews()
+ {
+ if (_requestedScroll != null && Superview != null)
+ {
+ var request = _requestedScroll;
+ _requestedScroll = null;
+ OnScrollToRequested(this, request);
+ }
+ }
+
+ void HandlePropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ if (e.PropertyName == ScrollView.ContentSizeProperty.PropertyName)
+ UpdateContentSize();
+ else if (e.PropertyName == VisualElement.BackgroundColorProperty.PropertyName)
+ UpdateBackgroundColor();
+ }
+
+ void HandleScrollAnimationEnded(object sender, EventArgs e)
+ {
+ Controller.SendScrollFinished();
+ }
+
+ void HandleScrolled(object sender, EventArgs e)
+ {
+ UpdateScrollPosition();
+ }
+
+ void OnNativeControlUpdated(object sender, EventArgs eventArgs)
+ {
+ UpdateContentSize();
+ }
+
+ void OnScrollToRequested(object sender, ScrollToRequestedEventArgs e)
+ {
+ if (Superview == null)
+ {
+ _requestedScroll = e;
+ return;
+ }
+
+ Point scrollPoint = (e.Mode == ScrollToMode.Position)
+ ? new Point(e.ScrollX, Element.Height - e.ScrollY)
+ : Controller.GetScrollPositionForElement(e.Element as VisualElement, e.Position);
+
+ (DocumentView as NSView)?.ScrollPoint(scrollPoint.ToPointF());
+
+ Controller.SendScrollFinished();
+ }
+
+ void UpdateBackgroundColor()
+ {
+ BackgroundColor = Element.BackgroundColor.ToNSColor(Color.Transparent);
+ }
+
+ void UpdateContentSize()
+ {
+ if (_contentRenderer == null)
+ return;
+ var contentSize = ((ScrollView)Element).ContentSize.ToSizeF();
+ if (!contentSize.IsEmpty)
+ _contentRenderer.NativeView.Frame = new RectangleF(0, Element.Height - contentSize.Height, contentSize.Width,
+ contentSize.Height);
+ }
+
+ [Export(nameof(UpdateScrollPosition))]
+ void UpdateScrollPosition()
+ {
+ var convertedPoint = (DocumentView as NSView)?.ConvertPointFromView(ContentView.Bounds.Location, ContentView);
+ if (convertedPoint.HasValue)
+ Controller.SetScrolledPosition(Math.Max(0, convertedPoint.Value.X), Math.Max(0, convertedPoint.Value.Y));
+ }
+
+ void ClearContentRenderer()
+ {
+ _contentRenderer?.NativeView?.RemoveFromSuperview();
+ _contentRenderer?.Dispose();
+ _contentRenderer = null;
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.MacOS/Renderers/SearchBarRenderer.cs b/Xamarin.Forms.Platform.MacOS/Renderers/SearchBarRenderer.cs
new file mode 100644
index 0000000..50c7d5f
--- /dev/null
+++ b/Xamarin.Forms.Platform.MacOS/Renderers/SearchBarRenderer.cs
@@ -0,0 +1,179 @@
+using System;
+using System.ComponentModel;
+using AppKit;
+using Foundation;
+
+namespace Xamarin.Forms.Platform.MacOS
+{
+ public class SearchBarRenderer : ViewRenderer<SearchBar, NSSearchField>
+ {
+ NSColor _defaultTextColor;
+
+ IElementController ElementController => Element;
+
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ if (Control != null)
+ {
+ Control.Changed -= OnTextChanged;
+ Control.Cell.CancelButtonCell.Activated -= OnCancelClicked;
+ Control.Cell.SearchButtonCell.Activated -= OnSearchButtonClicked;
+ Control.EditingEnded -= OnEditingEnded;
+ Control.EditingBegan -= OnEditingStarted;
+ }
+ }
+
+ base.Dispose(disposing);
+ }
+
+ protected override void OnElementChanged(ElementChangedEventArgs<SearchBar> e)
+ {
+ if (e.NewElement != null)
+ {
+ if (Control == null)
+ {
+ SetNativeControl(new NSSearchField { BackgroundColor = NSColor.Clear, DrawsBackground = false });
+
+ Control.Cell.CancelButtonCell.Activated += OnCancelClicked;
+ Control.Cell.SearchButtonCell.Activated += OnSearchButtonClicked;
+
+ Control.Changed += OnTextChanged;
+ Control.EditingBegan += OnEditingStarted;
+ Control.EditingEnded += OnEditingEnded;
+ }
+
+ UpdatePlaceholder();
+ UpdateText();
+ UpdateFont();
+ UpdateIsEnabled();
+ UpdateCancelButton();
+ UpdateAlignment();
+ UpdateTextColor();
+ }
+
+ base.OnElementChanged(e);
+ }
+
+ protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ base.OnElementPropertyChanged(sender, e);
+
+ if (e.PropertyName == SearchBar.PlaceholderProperty.PropertyName ||
+ e.PropertyName == SearchBar.PlaceholderColorProperty.PropertyName)
+ UpdatePlaceholder();
+ else if (e.PropertyName == VisualElement.IsEnabledProperty.PropertyName)
+ {
+ UpdateIsEnabled();
+ UpdateTextColor();
+ UpdatePlaceholder();
+ }
+ else if (e.PropertyName == SearchBar.TextColorProperty.PropertyName)
+ UpdateTextColor();
+ else if (e.PropertyName == SearchBar.TextProperty.PropertyName)
+ UpdateText();
+ else if (e.PropertyName == SearchBar.CancelButtonColorProperty.PropertyName)
+ UpdateCancelButton();
+ else if (e.PropertyName == SearchBar.FontAttributesProperty.PropertyName)
+ UpdateFont();
+ else if (e.PropertyName == SearchBar.FontFamilyProperty.PropertyName)
+ UpdateFont();
+ else if (e.PropertyName == SearchBar.FontSizeProperty.PropertyName)
+ UpdateFont();
+ else if (e.PropertyName == SearchBar.HorizontalTextAlignmentProperty.PropertyName)
+ UpdateAlignment();
+ }
+
+ protected override void SetBackgroundColor(Color color)
+ {
+ base.SetBackgroundColor(color);
+
+ if (Control == null)
+ return;
+ Control.BackgroundColor = color == Color.Default ? NSColor.Clear : color.ToNSColor();
+
+ UpdateCancelButton();
+ }
+
+ void OnCancelClicked(object sender, EventArgs args)
+ {
+ ElementController.SetValueFromRenderer(SearchBar.TextProperty, null);
+ Control.ResignFirstResponder();
+ }
+
+ void OnEditingEnded(object sender, EventArgs e)
+ {
+ ElementController?.SetValueFromRenderer(VisualElement.IsFocusedPropertyKey, false);
+ }
+
+ void OnEditingStarted(object sender, EventArgs e)
+ {
+ ElementController?.SetValueFromRenderer(VisualElement.IsFocusedPropertyKey, true);
+ }
+
+ void OnSearchButtonClicked(object sender, EventArgs e)
+ {
+ ((ISearchBarController)Element).OnSearchButtonPressed();
+ Control.ResignFirstResponder();
+ }
+
+ void OnTextChanged(object sender, EventArgs a)
+ {
+ ElementController.SetValueFromRenderer(SearchBar.TextProperty, Control.StringValue);
+ }
+
+ void UpdateAlignment()
+ {
+ Control.Alignment = Element.HorizontalTextAlignment.ToNativeTextAlignment();
+ }
+
+ void UpdateCancelButton()
+ {
+ var cancelButtonColor = Element.CancelButtonColor;
+
+ if (cancelButtonColor.IsDefault)
+ {
+ Control.Cell.CancelButtonCell.Title = "";
+ }
+ else
+ {
+ var textWithColor = new NSAttributedString(Control.Cell.CancelButtonCell.Title ?? "",
+ foregroundColor: cancelButtonColor.ToNSColor());
+ Control.Cell.CancelButtonCell.AttributedTitle = textWithColor;
+ }
+ }
+
+ void UpdateFont()
+ {
+ Control.Font = Element.ToNSFont();
+ }
+
+ void UpdateIsEnabled()
+ {
+ Control.Enabled = Element.IsEnabled;
+ }
+
+ void UpdatePlaceholder()
+ {
+ var formatted = (FormattedString)Element.Placeholder ?? string.Empty;
+ var targetColor = Element.PlaceholderColor;
+ var color = Element.IsEnabled && !targetColor.IsDefault ? targetColor : ColorExtensions.SeventyPercentGrey.ToColor();
+ Control.PlaceholderAttributedString = formatted.ToAttributed(Element, color);
+ }
+
+ void UpdateText()
+ {
+ Control.StringValue = Element.Text ?? "";
+ UpdateCancelButton();
+ }
+
+ void UpdateTextColor()
+ {
+ _defaultTextColor = _defaultTextColor ?? Control.TextColor;
+ var targetColor = Element.TextColor;
+
+ Control.TextColor = targetColor.ToNSColor(_defaultTextColor);
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.MacOS/Renderers/SliderRenderer.cs b/Xamarin.Forms.Platform.MacOS/Renderers/SliderRenderer.cs
new file mode 100644
index 0000000..9b495de
--- /dev/null
+++ b/Xamarin.Forms.Platform.MacOS/Renderers/SliderRenderer.cs
@@ -0,0 +1,77 @@
+using System;
+using SizeF = CoreGraphics.CGSize;
+using AppKit;
+using System.ComponentModel;
+
+namespace Xamarin.Forms.Platform.MacOS
+{
+ public class SliderRenderer : ViewRenderer<Slider, NSSlider>
+ {
+ bool _disposed;
+
+ IElementController ElementController => Element;
+
+ protected override void OnElementChanged(ElementChangedEventArgs<Slider> e)
+ {
+ if (e.NewElement != null)
+ {
+ if (Control == null)
+ {
+ SetNativeControl(new NSSlider { Continuous = true });
+ Control.Activated += OnControlActivated;
+ }
+
+ UpdateMaximum();
+ UpdateMinimum();
+ UpdateValue();
+ }
+
+ base.OnElementChanged(e);
+ }
+
+ protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ base.OnElementPropertyChanged(sender, e);
+
+ if (e.PropertyName == Slider.MaximumProperty.PropertyName)
+ UpdateMaximum();
+ else if (e.PropertyName == Slider.MinimumProperty.PropertyName)
+ UpdateMinimum();
+ else if (e.PropertyName == Slider.ValueProperty.PropertyName)
+ UpdateValue();
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing && !_disposed)
+ {
+ _disposed = true;
+ if (Control != null)
+ Control.Activated -= OnControlActivated;
+ }
+
+ base.Dispose(disposing);
+ }
+
+ void OnControlActivated(object sender, EventArgs eventArgs)
+ {
+ ElementController?.SetValueFromRenderer(Slider.ValueProperty, Control.DoubleValue);
+ }
+
+ void UpdateMaximum()
+ {
+ Control.MaxValue = (float)Element.Maximum;
+ }
+
+ void UpdateMinimum()
+ {
+ Control.MinValue = (float)Element.Minimum;
+ }
+
+ void UpdateValue()
+ {
+ if (Math.Abs(Element.Value - Control.DoubleValue) > 0)
+ Control.DoubleValue = (float)Element.Value;
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.MacOS/Renderers/StepperRenderer.cs b/Xamarin.Forms.Platform.MacOS/Renderers/StepperRenderer.cs
new file mode 100644
index 0000000..f28bf1f
--- /dev/null
+++ b/Xamarin.Forms.Platform.MacOS/Renderers/StepperRenderer.cs
@@ -0,0 +1,84 @@
+using System;
+using System.ComponentModel;
+using AppKit;
+
+namespace Xamarin.Forms.Platform.MacOS
+{
+ public class StepperRenderer : ViewRenderer<Stepper, NSStepper>
+ {
+ bool _disposed;
+
+ IElementController ElementController => Element;
+
+ protected override void OnElementChanged(ElementChangedEventArgs<Stepper> e)
+ {
+ if (e.NewElement != null)
+ {
+ if (Control == null)
+ {
+ SetNativeControl(new NSStepper());
+ Control.Activated += OnControlActivated;
+ }
+
+ UpdateMinimum();
+ UpdateMaximum();
+ UpdateValue();
+ UpdateIncrement();
+ }
+
+ base.OnElementChanged(e);
+ }
+
+ protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ base.OnElementPropertyChanged(sender, e);
+
+ if (e.PropertyName == Stepper.MinimumProperty.PropertyName)
+ UpdateMinimum();
+ else if (e.PropertyName == Stepper.MaximumProperty.PropertyName)
+ UpdateMaximum();
+ else if (e.PropertyName == Stepper.ValueProperty.PropertyName)
+ UpdateValue();
+ else if (e.PropertyName == Stepper.IncrementProperty.PropertyName)
+ UpdateIncrement();
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing && !_disposed)
+ {
+ _disposed = true;
+ if (Control != null)
+ Control.Activated -= OnControlActivated;
+ }
+
+ base.Dispose(disposing);
+ }
+
+ void OnControlActivated(object sender, EventArgs e)
+ {
+ ElementController?.SetValueFromRenderer(Stepper.ValueProperty, Control.DoubleValue);
+ }
+
+ void UpdateIncrement()
+ {
+ Control.Increment = Element.Increment;
+ }
+
+ void UpdateMaximum()
+ {
+ Control.MaxValue = Element.Maximum;
+ }
+
+ void UpdateMinimum()
+ {
+ Control.MinValue = Element.Minimum;
+ }
+
+ void UpdateValue()
+ {
+ if (Math.Abs(Control.DoubleValue - Element.Value) > 0)
+ Control.DoubleValue = Element.Value;
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.MacOS/Renderers/SwitchRenderer.cs b/Xamarin.Forms.Platform.MacOS/Renderers/SwitchRenderer.cs
new file mode 100644
index 0000000..c93007e
--- /dev/null
+++ b/Xamarin.Forms.Platform.MacOS/Renderers/SwitchRenderer.cs
@@ -0,0 +1,61 @@
+using System;
+using AppKit;
+
+namespace Xamarin.Forms.Platform.MacOS
+{
+ public class SwitchRenderer : ViewRenderer<Switch, NSButton>
+ {
+ bool _disposed;
+
+ IElementController ElementController => Element;
+
+ protected override void OnElementChanged(ElementChangedEventArgs<Switch> e)
+ {
+ if (e.OldElement != null)
+ e.OldElement.Toggled -= OnElementToggled;
+
+ if (e.NewElement != null)
+ {
+ if (Control == null)
+ {
+ SetNativeControl(new NSButton { AllowsMixedState = false, Title = string.Empty });
+
+ Control.SetButtonType(NSButtonType.Switch);
+ Control.Activated += OnControlActivated;
+ }
+
+ UpdateState();
+ e.NewElement.Toggled += OnElementToggled;
+ }
+
+ base.OnElementChanged(e);
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing && !_disposed)
+ {
+ _disposed = true;
+ if (Control != null)
+ Control.Activated -= OnControlActivated;
+ }
+
+ base.Dispose(disposing);
+ }
+
+ void OnControlActivated(object sender, EventArgs e)
+ {
+ ElementController?.SetValueFromRenderer(Switch.IsToggledProperty, Control.State == NSCellStateValue.On);
+ }
+
+ void OnElementToggled(object sender, EventArgs e)
+ {
+ UpdateState();
+ }
+
+ void UpdateState()
+ {
+ Control.State = Element.IsToggled ? NSCellStateValue.On : NSCellStateValue.Off;
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.MacOS/Renderers/TabbedPageRenderer.cs b/Xamarin.Forms.Platform.MacOS/Renderers/TabbedPageRenderer.cs
new file mode 100644
index 0000000..3aee095
--- /dev/null
+++ b/Xamarin.Forms.Platform.MacOS/Renderers/TabbedPageRenderer.cs
@@ -0,0 +1,403 @@
+using System;
+using System.Collections.Specialized;
+using System.ComponentModel;
+using AppKit;
+using CoreGraphics;
+using Xamarin.Forms.Internals;
+using Xamarin.Forms.PlatformConfiguration.macOSSpecific;
+
+namespace Xamarin.Forms.Platform.MacOS
+{
+ public class TabbedPageRenderer : NSTabViewController, IVisualElementRenderer, IEffectControlProvider
+ {
+ const float DefaultImageSizeSegmentedButton = 19;
+ const int TabHolderHeight = 30;
+
+ bool _disposed;
+ bool _updatingControllers;
+ bool _barBackgroundColorWasSet;
+ bool _barTextColorWasSet;
+ bool _defaultBarTextColorSet;
+ bool _defaultBarColorSet;
+ VisualElementTracker _tracker;
+ bool _loaded;
+ Size _queuedSize;
+
+
+ public VisualElement Element { get; private set; }
+
+ public event EventHandler<VisualElementChangedEventArgs> ElementChanged;
+
+ public SizeRequest GetDesiredSize(double widthConstraint, double heightConstraint)
+ {
+ return NativeView.GetSizeRequest(widthConstraint, heightConstraint);
+ }
+
+ public NSView NativeView => View;
+
+ public void SetElement(VisualElement element)
+ {
+ var oldElement = Element;
+ Element = element;
+
+ if (oldElement != null)
+ {
+ oldElement.PropertyChanged -= OnPropertyChanged;
+ var tabbedPage = oldElement as TabbedPage;
+ if (tabbedPage != null) tabbedPage.PagesChanged -= OnPagesChanged;
+ }
+
+ if (element != null)
+ {
+ if (_tracker == null)
+ {
+ _tracker = new VisualElementTracker(this);
+ _tracker.NativeControlUpdated += (sender, e) => UpdateNativeWidget();
+ }
+ }
+
+ OnElementChanged(new VisualElementChangedEventArgs(oldElement, element));
+
+ ConfigureTabView();
+
+ OnPagesChanged(null, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
+
+ Tabbed.PropertyChanged += OnPropertyChanged;
+ Tabbed.PagesChanged += OnPagesChanged;
+
+ UpdateBarBackgroundColor();
+
+ UpdateBarTextColor();
+
+ EffectUtilities.RegisterEffectControlProvider(this, oldElement, element);
+ }
+
+ IPageController PageController => Element as IPageController;
+
+ IElementController ElementController => Element;
+
+ void IEffectControlProvider.RegisterEffect(Effect effect)
+ {
+ var platformEffect = effect as PlatformEffect;
+ if (platformEffect != null)
+ platformEffect.Container = View;
+ }
+
+ public void SetElementSize(Size size)
+ {
+ if (_loaded)
+ Element.Layout(new Rectangle(Element.X, Element.Y, size.Width, size.Height));
+ else
+ _queuedSize = size;
+ }
+
+ public NSViewController ViewController => this;
+
+ public override void ViewWillLayout()
+ {
+ base.ViewWillLayout();
+
+ if (Element == null)
+ return;
+
+ if (!Element.Bounds.IsEmpty)
+ View.Frame = new System.Drawing.RectangleF((float)Element.X, (float)Element.Y, (float)Element.Width, (float)Element.Height);
+
+ var frame = View.Frame;
+ PageController.ContainerArea = new Rectangle(0, 0, frame.Width, frame.Height - TabHolderHeight);
+
+ if (!_queuedSize.IsZero)
+ {
+ Element.Layout(new Rectangle(Element.X, Element.Y, _queuedSize.Width, _queuedSize.Height));
+ _queuedSize = Size.Zero;
+ }
+
+ _loaded = true;
+ }
+
+
+ public override nint SelectedTabViewItemIndex
+ {
+ get { return base.SelectedTabViewItemIndex; }
+ set
+ {
+ base.SelectedTabViewItemIndex = value;
+ if (!_updatingControllers)
+ UpdateCurrentPage();
+ }
+ }
+
+ public override void ViewDidAppear()
+ {
+ PageController.SendAppearing();
+ base.ViewDidAppear();
+ }
+
+ public override void ViewDidDisappear()
+ {
+ base.ViewDidDisappear();
+ PageController.SendDisappearing();
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing && !_disposed)
+ {
+ _disposed = true;
+ PageController.SendDisappearing();
+ Tabbed.PropertyChanged -= OnPropertyChanged;
+ Tabbed.PagesChanged -= OnPagesChanged;
+
+ if (_tracker != null)
+ {
+ _tracker.Dispose();
+ _tracker = null;
+ }
+ }
+
+ base.Dispose(disposing);
+ }
+
+ protected virtual void ConfigureTabView()
+ {
+ View.WantsLayer = true;
+ TabView.WantsLayer = true;
+ TabView.DrawsBackground = false;
+ var tabStyle = Tabbed.OnThisPlatform().GetTabsStyle();
+ switch (tabStyle)
+ {
+ case TabsStyle.OnNavigation:
+ case TabsStyle.Hidden:
+ TabStyle = NSTabViewControllerTabStyle.Unspecified;
+ break;
+ case TabsStyle.Icons:
+ TabStyle = NSTabViewControllerTabStyle.Toolbar;
+ break;
+ case TabsStyle.OnBottom:
+ TabStyle = NSTabViewControllerTabStyle.SegmentedControlOnBottom;
+ break;
+ default:
+ TabStyle = NSTabViewControllerTabStyle.SegmentedControlOnTop;
+ break;
+ }
+
+ TabView.TabViewType = NSTabViewType.NSNoTabsNoBorder;
+ }
+
+ protected virtual void OnElementChanged(VisualElementChangedEventArgs e)
+ {
+ ElementChanged?.Invoke(this, e);
+ }
+
+ protected virtual NSTabViewItem GetTabViewItem(Page page, IVisualElementRenderer pageRenderer)
+ {
+ var tvi = new NSTabViewItem { ViewController = pageRenderer.ViewController, Label = page.Title ?? "" };
+ if (!string.IsNullOrEmpty (page.Icon)) {
+ var image = GetTabViewItemIcon (page.Icon);
+ if (image != null)
+ tvi.Image = image;
+ }
+ return tvi;
+ }
+
+ protected virtual NSImage GetTabViewItemIcon(string imageName)
+ {
+ var image = NSImage.ImageNamed (imageName);
+ if(image == null)
+ image = new NSImage (imageName);
+
+ if (image == null)
+ return null;
+
+ bool shouldResize = TabStyle == NSTabViewControllerTabStyle.SegmentedControlOnTop ||
+ TabStyle == NSTabViewControllerTabStyle.SegmentedControlOnBottom;
+ if (shouldResize)
+ image = image.ResizeTo(new CGSize(DefaultImageSizeSegmentedButton, DefaultImageSizeSegmentedButton));
+ return image;
+ }
+
+ protected virtual void UpdateNativeWidget()
+ {
+ TabView.Layout();
+ }
+
+ protected TabbedPage Tabbed => (TabbedPage)Element;
+
+ void OnPagePropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ if (e.PropertyName == Page.TitleProperty.PropertyName)
+ {
+ var page = (Page)sender;
+ var index = TabbedPage.GetIndex(page);
+ TabViewItems[index].Label = page.Title;
+ }
+ else if (e.PropertyName == Page.IconProperty.PropertyName)
+ {
+ var page = (Page)sender;
+
+ var index = TabbedPage.GetIndex(page);
+ TabViewItems[index].Label = page.Title;
+
+ if (!string.IsNullOrEmpty(page.Icon))
+ {
+ TabViewItems[index].Image = new NSImage(page.Icon);
+ }
+ else if (TabViewItems[index].Image != null)
+ {
+ TabViewItems[index].Image = new NSImage();
+ }
+ }
+ }
+
+ void OnPagesChanged(object sender, NotifyCollectionChangedEventArgs e)
+ {
+ e.Apply((o, i, c) => SetupPage((Page)o, i), (o, i) => TeardownPage((Page)o), Reset);
+
+ SetControllers();
+
+ UpdateChildrenOrderIndex();
+
+ SetSelectedTabViewItem();
+ }
+
+ void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ if (e.PropertyName == nameof(TabbedPage.CurrentPage))
+ {
+ var current = Tabbed.CurrentPage;
+ if (current == null)
+ return;
+
+ SetSelectedTabViewItem();
+ }
+ else if (e.PropertyName == TabbedPage.BarBackgroundColorProperty.PropertyName)
+ UpdateBarBackgroundColor();
+ else if (e.PropertyName == TabbedPage.BarTextColorProperty.PropertyName)
+ UpdateBarTextColor();
+ }
+
+ void Reset()
+ {
+ var i = 0;
+ foreach (var page in Tabbed.Children)
+ SetupPage(page, i++);
+ }
+
+ void SetControllers()
+ {
+ _updatingControllers = true;
+ for (var i = 0; i < ElementController.LogicalChildren.Count; i++)
+ {
+ var child = ElementController.LogicalChildren[i];
+ var page = child as Page;
+ if (page == null)
+ continue;
+
+ var pageRenderer = Platform.GetRenderer(page);
+ if (pageRenderer != null)
+ {
+ pageRenderer.ViewController.Identifier = i.ToString();
+
+ NSTabViewItem newTvi = GetTabViewItem(page, pageRenderer);
+
+ AddTabViewItem(newTvi);
+ }
+ }
+ _updatingControllers = false;
+ }
+
+ void SetupPage(Page page, int index)
+ {
+ var renderer = Platform.GetRenderer(page);
+ if (renderer == null)
+ {
+ renderer = Platform.CreateRenderer(page);
+ Platform.SetRenderer(page, renderer);
+ }
+
+ renderer.ViewController.Identifier = index.ToString();
+
+ page.PropertyChanged += OnPagePropertyChanged;
+ }
+
+ void TeardownPage(Page page)
+ {
+ page.PropertyChanged -= OnPagePropertyChanged;
+
+ Platform.SetRenderer(page, null);
+ }
+
+ void SetSelectedTabViewItem()
+ {
+ if (Tabbed.CurrentPage == null)
+ return;
+ var selectedIndex = TabbedPage.GetIndex(Tabbed.CurrentPage);
+ SelectedTabViewItemIndex = selectedIndex;
+ }
+
+ void UpdateChildrenOrderIndex()
+ {
+ for (var i = 0; i < TabViewItems.Length; i++)
+ {
+ int originalIndex;
+ if (int.TryParse(TabViewItems[i].ViewController.Identifier, out originalIndex))
+ {
+ var page = PageController.InternalChildren[originalIndex];
+ TabbedPage.SetIndex(page as Page, i);
+ }
+ }
+ }
+
+ void UpdateCurrentPage()
+ {
+ var count = PageController.InternalChildren.Count;
+ Tabbed.CurrentPage = SelectedTabViewItemIndex >= 0 && SelectedTabViewItemIndex < count
+ ? Tabbed.GetPageByIndex((int)SelectedTabViewItemIndex)
+ : null;
+ }
+
+ //TODO: Implement UpdateBarBackgroundColor
+ void UpdateBarBackgroundColor()
+ {
+ if (Tabbed == null || TabView == null)
+ return;
+
+ var barBackgroundColor = Tabbed.BarBackgroundColor;
+ var isDefaultColor = barBackgroundColor.IsDefault;
+
+ if (isDefaultColor && !_barBackgroundColorWasSet)
+ return;
+
+ if (!_defaultBarColorSet)
+ {
+ //_defaultBarColor = TabView.color;
+ _defaultBarColorSet = true;
+ }
+
+ if (!isDefaultColor)
+ _barBackgroundColorWasSet = true;
+ }
+
+ //TODO: Implement UpdateBarTextColor
+ void UpdateBarTextColor()
+ {
+ if (Tabbed == null || TabView == null)
+ return;
+
+ var barTextColor = Tabbed.BarTextColor;
+ var isDefaultColor = barTextColor.IsDefault;
+
+ if (isDefaultColor && !_barTextColorWasSet)
+ return;
+
+ if (!_defaultBarTextColorSet)
+ {
+ // _defaultBarTextColor = TabBar.TintColor;
+ _defaultBarTextColorSet = true;
+ }
+
+ if (!isDefaultColor)
+ _barTextColorWasSet = true;
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.MacOS/Renderers/TableViewDataSource.cs b/Xamarin.Forms.Platform.MacOS/Renderers/TableViewDataSource.cs
new file mode 100644
index 0000000..90335ee
--- /dev/null
+++ b/Xamarin.Forms.Platform.MacOS/Renderers/TableViewDataSource.cs
@@ -0,0 +1,131 @@
+using System;
+using AppKit;
+using Foundation;
+
+namespace Xamarin.Forms.Platform.MacOS
+{
+ internal class TableViewDataSource : NSTableViewSource
+ {
+ static int s_sectionCount;
+
+ const string HeaderIdentifier = nameof(TextCell);
+ const string ItemIdentifier = nameof(ViewCell);
+
+ protected ITableViewController Controller => _tableView;
+
+ readonly NSTableView _nsTableView;
+ readonly TableView _tableView;
+
+ public TableViewDataSource(TableViewRenderer tableViewRenderer)
+ {
+ _tableView = tableViewRenderer.Element;
+ _nsTableView = tableViewRenderer.TableView;
+ Controller.ModelChanged += (s, e) => { _nsTableView?.ReloadData(); };
+ AutomaticallyDeselect = true;
+ }
+
+ public bool AutomaticallyDeselect { get; set; }
+
+ public override void SelectionDidChange(NSNotification notification)
+ {
+ var row = _nsTableView.SelectedRow;
+ if (row == -1)
+ return;
+
+ int sectionIndex;
+ bool isHeader;
+ int itemIndexInSection;
+
+ GetComputedIndexes(row, out sectionIndex, out itemIndexInSection, out isHeader);
+
+ var cell = Controller.Model.GetCell(sectionIndex, itemIndexInSection);
+ Controller.Model.RowSelected(cell);
+ if (AutomaticallyDeselect)
+ _nsTableView.DeselectRow(row);
+ }
+
+ public override nint GetRowCount(NSTableView tableView)
+ {
+ nint count = 0;
+ s_sectionCount = Controller.Model.GetSectionCount();
+ for (int i = 0; i < s_sectionCount; i++)
+ {
+ count += Controller.Model.GetRowCount(i) + 1;
+ }
+
+ return count;
+ }
+
+ public override bool ShouldSelectRow(NSTableView tableView, nint row)
+ {
+ int sectionIndex;
+ bool isHeader;
+ int itemIndexInSection;
+
+ GetComputedIndexes(row, out sectionIndex, out itemIndexInSection, out isHeader);
+
+ return !isHeader;
+ }
+
+ public override NSView GetViewForItem(NSTableView tableView, NSTableColumn tableColumn, nint row)
+ {
+ int sectionIndex;
+ bool isHeader;
+ int itemIndexInSection;
+
+ GetComputedIndexes(row, out sectionIndex, out itemIndexInSection, out isHeader);
+
+ string id;
+ Cell cell;
+ if (isHeader)
+ {
+ id = HeaderIdentifier;
+ cell = Controller.Model.GetHeaderCell(sectionIndex) ??
+ new TextCell { Text = Controller.Model.GetSectionTitle(sectionIndex) };
+ }
+ else
+ {
+ id = ItemIdentifier;
+ cell = Controller.Model.GetCell(sectionIndex, itemIndexInSection);
+ }
+
+ var nativeCell = CellNSView.GetNativeCell(tableView, cell, id, isHeader);
+ return nativeCell;
+ }
+
+ void GetComputedIndexes(nint row, out int sectionIndex, out int itemIndexInSection, out bool isHeader)
+ {
+ var totalItems = 0;
+ isHeader = false;
+ sectionIndex = 0;
+ itemIndexInSection = 0;
+
+ for (int i = 0; i < s_sectionCount; i++)
+ {
+ var groupCount = Controller.Model.GetRowCount(i);
+ var itemsInSection = groupCount + 1;
+
+ if (row < totalItems + itemsInSection)
+ {
+ sectionIndex = i;
+ itemIndexInSection = (int)row - totalItems;
+ isHeader = itemIndexInSection == 0;
+ if (isHeader)
+ itemIndexInSection = -1;
+ else
+ itemIndexInSection = itemIndexInSection - 1;
+ break;
+ }
+ totalItems += itemsInSection;
+ }
+ }
+ }
+
+ //TODO: Implement Uneven rows
+ internal class UnEvenTableViewModelRenderer : TableViewDataSource
+ {
+ public UnEvenTableViewModelRenderer(TableViewRenderer model) : base(model)
+ {
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.MacOS/Renderers/TableViewRenderer.cs b/Xamarin.Forms.Platform.MacOS/Renderers/TableViewRenderer.cs
new file mode 100644
index 0000000..de447dc
--- /dev/null
+++ b/Xamarin.Forms.Platform.MacOS/Renderers/TableViewRenderer.cs
@@ -0,0 +1,98 @@
+using System.Collections.Generic;
+using System.ComponentModel;
+using AppKit;
+
+namespace Xamarin.Forms.Platform.MacOS
+{
+ public class TableViewRenderer : ViewRenderer<TableView, NSView>
+ {
+ const int DefaultRowHeight = 44;
+
+ internal NSTableView TableView { get; set; }
+
+ public override SizeRequest GetDesiredSize(double widthConstraint, double heightConstraint)
+ {
+ return Control.GetSizeRequest(widthConstraint, heightConstraint, DefaultRowHeight, DefaultRowHeight);
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ var viewsToLookAt = new Stack<NSView>(Subviews);
+ while (viewsToLookAt.Count > 0)
+ {
+ var view = viewsToLookAt.Pop();
+ var viewCellRenderer = view as IVisualElementRenderer;
+ if (viewCellRenderer != null)
+ {
+ viewCellRenderer.Dispose();
+ }
+ else
+ {
+ foreach (var child in view.Subviews)
+ viewsToLookAt.Push(child);
+ }
+ }
+ }
+
+ base.Dispose(disposing);
+ }
+
+ protected virtual NSTableView CreateNSTableView(TableView list)
+ {
+ return new NSTableView().AsListViewLook();
+ }
+
+ protected override void OnElementChanged(ElementChangedEventArgs<TableView> e)
+ {
+ if (e.NewElement != null)
+ {
+ if (Control == null)
+ {
+ var scroller = new NSScrollView
+ {
+ AutoresizingMask = NSViewResizingMask.HeightSizable | NSViewResizingMask.WidthSizable,
+ DocumentView = TableView = CreateNSTableView(e.NewElement)
+ };
+
+ SetNativeControl(scroller);
+ }
+
+ SetSource();
+ UpdateRowHeight();
+ UpdateBackgroundView();
+ }
+
+ base.OnElementChanged(e);
+ }
+
+ protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ base.OnElementPropertyChanged(sender, e);
+
+ if (e.PropertyName == Xamarin.Forms.TableView.RowHeightProperty.PropertyName)
+ UpdateRowHeight();
+ else if (e.PropertyName == Xamarin.Forms.TableView.HasUnevenRowsProperty.PropertyName)
+ SetSource();
+ else if (e.PropertyName == VisualElement.BackgroundColorProperty.PropertyName)
+ UpdateBackgroundView();
+ }
+
+ void SetSource()
+ {
+ var modeledView = Element;
+ TableView.Source = modeledView.HasUnevenRows ? new UnEvenTableViewModelRenderer(this) : new TableViewDataSource(this);
+ }
+
+ void UpdateBackgroundView()
+ {
+ }
+
+ void UpdateRowHeight()
+ {
+ var rowHeight = Element.RowHeight;
+ TableView.RowHeight = rowHeight <= 0 ? DefaultRowHeight : rowHeight;
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.MacOS/Renderers/TimePickerRenderer.cs b/Xamarin.Forms.Platform.MacOS/Renderers/TimePickerRenderer.cs
new file mode 100644
index 0000000..fc645c4
--- /dev/null
+++ b/Xamarin.Forms.Platform.MacOS/Renderers/TimePickerRenderer.cs
@@ -0,0 +1,104 @@
+using System;
+using System.ComponentModel;
+using AppKit;
+using Foundation;
+
+namespace Xamarin.Forms.Platform.MacOS
+{
+ public class TimePickerRenderer : ViewRenderer<TimePicker, NSDatePicker>
+ {
+ NSColor _defaultTextColor;
+ NSColor _defaultBackgroundColor;
+ bool _disposed;
+
+ IElementController ElementController => Element;
+
+ protected override void OnElementChanged(ElementChangedEventArgs<TimePicker> e)
+ {
+ base.OnElementChanged(e);
+
+ if (e.NewElement != null)
+ {
+ if (Control == null)
+ {
+ SetNativeControl(new NSDatePicker
+ {
+ DatePickerMode = NSDatePickerMode.Single,
+ TimeZone = new NSTimeZone("UTC"),
+ DatePickerStyle = NSDatePickerStyle.TextFieldAndStepper,
+ DatePickerElements = NSDatePickerElementFlags.HourMinuteSecond
+ });
+
+ Control.ValidateProposedDateValue += HandleValueChanged;
+ _defaultTextColor = Control.TextColor;
+ _defaultBackgroundColor = Control.BackgroundColor;
+ }
+
+ UpdateTime();
+ UpdateTextColor();
+ }
+ }
+
+ protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ base.OnElementPropertyChanged(sender, e);
+
+ if (e.PropertyName == TimePicker.TimeProperty.PropertyName ||
+ e.PropertyName == TimePicker.FormatProperty.PropertyName)
+ UpdateTime();
+
+ if (e.PropertyName == TimePicker.TextColorProperty.PropertyName ||
+ e.PropertyName == VisualElement.IsEnabledProperty.PropertyName)
+ UpdateTextColor();
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing && !_disposed)
+ {
+ if (Control != null)
+ Control.ValidateProposedDateValue -= HandleValueChanged;
+
+ _disposed = true;
+ }
+ base.Dispose(disposing);
+ }
+
+ protected override void SetBackgroundColor(Color color)
+ {
+ base.SetBackgroundColor(color);
+
+ if (Control == null)
+ return;
+ Control.BackgroundColor = color == Color.Default ? _defaultBackgroundColor : color.ToNSColor();
+ }
+
+ void HandleValueChanged(object sender, NSDatePickerValidatorEventArgs e)
+ {
+ ElementController?.SetValueFromRenderer(TimePicker.TimeProperty,
+ Control.DateValue.ToDateTime() - new DateTime(2001, 1, 1));
+ }
+
+ void UpdateTime()
+ {
+ if (Control == null || Element == null)
+ return;
+ var time = new DateTime(2001, 1, 1).Add(Element.Time);
+ var newDate = time.ToNSDate();
+ if (!Equals(Control.DateValue, newDate))
+ Control.DateValue = newDate;
+ }
+
+ void UpdateTextColor()
+ {
+ if (Control == null || Element == null)
+ return;
+ var textColor = Element.TextColor;
+
+ if (textColor.IsDefault || !Element.IsEnabled)
+ Control.TextColor = _defaultTextColor;
+ else
+ Control.TextColor = textColor.ToNSColor();
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.MacOS/Renderers/WebViewRenderer.cs b/Xamarin.Forms.Platform.MacOS/Renderers/WebViewRenderer.cs
new file mode 100644
index 0000000..8d93c2c
--- /dev/null
+++ b/Xamarin.Forms.Platform.MacOS/Renderers/WebViewRenderer.cs
@@ -0,0 +1,149 @@
+using System;
+using System.ComponentModel;
+using AppKit;
+using Foundation;
+using Xamarin.Forms.Internals;
+
+namespace Xamarin.Forms.Platform.MacOS
+{
+ public class WebViewRenderer : ViewRenderer<WebView, WebKit.WebView>, IWebViewDelegate
+ {
+ bool _disposed;
+ bool _ignoreSourceChanges;
+ WebNavigationEvent _lastBackForwardEvent;
+ WebNavigationEvent _lastEvent;
+
+ IElementController ElementController => Element;
+
+ void IWebViewDelegate.LoadHtml(string html, string baseUrl)
+ {
+ if (html != null)
+ Control.MainFrame.LoadHtmlString(html,
+ baseUrl == null ? new NSUrl(NSBundle.MainBundle.BundlePath, true) : new NSUrl(baseUrl, true));
+ }
+
+ void IWebViewDelegate.LoadUrl(string url)
+ {
+ Control.MainFrame.LoadRequest(new NSUrlRequest(new NSUrl(url)));
+ }
+
+ protected override void OnElementChanged(ElementChangedEventArgs<WebView> e)
+ {
+ base.OnElementChanged(e);
+
+ if (e.NewElement != null)
+ {
+ if (Control == null)
+ {
+ SetNativeControl(new WebKit.WebView
+ {
+ AutoresizingMask = NSViewResizingMask.WidthSizable,
+ AutoresizesSubviews = true
+ });
+ Control.OnFinishedLoading += OnNSWebViewFinishedLoad;
+ Control.OnFailedLoading += OnNSWebViewFailedLoadWithError;
+
+ Element.EvalRequested += OnEvalRequested;
+ Element.GoBackRequested += OnGoBackRequested;
+ Element.GoForwardRequested += OnGoForwardRequested;
+
+ Layer.BackgroundColor = NSColor.Clear.CGColor;
+ }
+ }
+
+ Load();
+ }
+
+ protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ base.OnElementPropertyChanged(sender, e);
+
+ if (e.PropertyName == WebView.SourceProperty.PropertyName)
+ Load();
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing && !_disposed)
+ {
+ _disposed = true;
+ Control.OnFinishedLoading -= OnNSWebViewFinishedLoad;
+ Control.OnFailedLoading -= OnNSWebViewFailedLoadWithError;
+ Element.EvalRequested -= OnEvalRequested;
+ Element.GoBackRequested -= OnGoBackRequested;
+ Element.GoForwardRequested -= OnGoForwardRequested;
+ }
+ base.Dispose(disposing);
+ }
+
+ void Load()
+ {
+ if (_ignoreSourceChanges)
+ return;
+
+ Element?.Source?.Load(this);
+
+ UpdateCanGoBackForward();
+ }
+
+ void UpdateCanGoBackForward()
+ {
+ if (Element == null)
+ return;
+ Element.CanGoBack = Control.CanGoBack();
+ Element.CanGoForward = Control.CanGoForward();
+ }
+
+ void OnEvalRequested(object sender, EvalRequested eventArg)
+ {
+ Control?.StringByEvaluatingJavaScriptFromString(eventArg?.Script);
+ }
+
+ void OnGoBackRequested(object sender, EventArgs eventArgs)
+ {
+ if (Control.CanGoBack())
+ {
+ _lastBackForwardEvent = WebNavigationEvent.Back;
+ Control.GoBack();
+ }
+
+ UpdateCanGoBackForward();
+ }
+
+ void OnGoForwardRequested(object sender, EventArgs eventArgs)
+ {
+ if (Control.CanGoForward())
+ {
+ _lastBackForwardEvent = WebNavigationEvent.Forward;
+ Control.GoForward();
+ }
+
+ UpdateCanGoBackForward();
+ }
+
+ void OnNSWebViewFailedLoadWithError(object sender, WebKit.WebResourceErrorEventArgs e)
+ {
+ _lastEvent = _lastBackForwardEvent;
+ Element?.SendNavigated(new WebNavigatedEventArgs(_lastEvent, new UrlWebViewSource { Url = Control.MainFrameUrl },
+ Control.MainFrameUrl, WebNavigationResult.Failure));
+
+ UpdateCanGoBackForward();
+ }
+
+ void OnNSWebViewFinishedLoad(object sender, WebKit.WebResourceCompletedEventArgs e)
+ {
+ if (Control.IsLoading)
+ return;
+
+ _ignoreSourceChanges = true;
+ ElementController?.SetValueFromRenderer(WebView.SourceProperty, new UrlWebViewSource { Url = Control.MainFrameUrl });
+ _ignoreSourceChanges = false;
+
+ _lastEvent = _lastBackForwardEvent;
+ Element?.SendNavigated(new WebNavigatedEventArgs(_lastEvent, Element?.Source, Control.MainFrameUrl,
+ WebNavigationResult.Success));
+
+ UpdateCanGoBackForward();
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.MacOS/Xamarin.Forms.Platform.macOS.csproj b/Xamarin.Forms.Platform.MacOS/Xamarin.Forms.Platform.macOS.csproj
new file mode 100644
index 0000000..fe0eff2
--- /dev/null
+++ b/Xamarin.Forms.Platform.MacOS/Xamarin.Forms.Platform.macOS.csproj
@@ -0,0 +1,239 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <PropertyGroup>
+ <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+ <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+ <ProjectGuid>{C0059C45-EA1E-42F3-8A0E-794BB547EC3C}</ProjectGuid>
+ <ProjectTypeGuids>{A3F8F2AB-B479-4A4A-A458-A89E7DC349F1};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
+ <OutputType>Library</OutputType>
+ <RootNamespace>Xamarin.Forms.Platform.macOS</RootNamespace>
+ <AssemblyName>Xamarin.Forms.Platform.macOS</AssemblyName>
+ <TargetFrameworkVersion>v2.0</TargetFrameworkVersion>
+ <TargetFrameworkIdentifier>Xamarin.Mac</TargetFrameworkIdentifier>
+ <MonoMacResourcePrefix>Resources</MonoMacResourcePrefix>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+ <DebugSymbols>true</DebugSymbols>
+ <DebugType>full</DebugType>
+ <Optimize>false</Optimize>
+ <OutputPath>bin\Debug</OutputPath>
+ <DefineConstants>DEBUG;</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
+ <ConsolePause>false</ConsolePause>
+ <EnableCodeSigning>false</EnableCodeSigning>
+ <CreatePackage>false</CreatePackage>
+ <EnablePackageSigning>false</EnablePackageSigning>
+ <IncludeMonoRuntime>false</IncludeMonoRuntime>
+ <UseSGen>false</UseSGen>
+ <HttpClientHandler>
+ </HttpClientHandler>
+ <TlsProvider>
+ </TlsProvider>
+ <LinkMode>
+ </LinkMode>
+ <XamMacArch>
+ </XamMacArch>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+ <Optimize>true</Optimize>
+ <OutputPath>bin\Release</OutputPath>
+ <DefineConstants>
+ </DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ <ConsolePause>false</ConsolePause>
+ <EnableCodeSigning>false</EnableCodeSigning>
+ <CreatePackage>false</CreatePackage>
+ <EnablePackageSigning>false</EnablePackageSigning>
+ <IncludeMonoRuntime>false</IncludeMonoRuntime>
+ <UseSGen>false</UseSGen>
+ <HttpClientHandler>
+ </HttpClientHandler>
+ <TlsProvider>
+ </TlsProvider>
+ <LinkMode>
+ </LinkMode>
+ <XamMacArch>
+ </XamMacArch>
+ </PropertyGroup>
+ <ItemGroup>
+ <Reference Include="System" />
+ <Reference Include="System.Core" />
+ <Reference Include="Xamarin.Mac" />
+ <Reference Include="System.Xml" />
+ <Reference Include="System.Runtime.Serialization" />
+ <Reference Include="System.Net.Http" />
+ </ItemGroup>
+ <ItemGroup>
+ <Compile Include="FormsApplicationDelegate.cs" />
+ <Compile Include="Properties\AssemblyInfo.cs" />
+ <Compile Include="PlatformRenderer.cs" />
+ <Compile Include="Platform.cs" />
+ <Compile Include="CADisplayLinkTicker.cs" />
+ <Compile Include="Extensions\PageExtensions.cs" />
+ <Compile Include="..\Xamarin.Forms.Platform.iOS\ExportCellAttribute.cs">
+ <Link>ExportCellAttribute.cs</Link>
+ </Compile>
+ <Compile Include="..\Xamarin.Forms.Platform.iOS\ExportImageSourceHandlerAttribute.cs">
+ <Link>ExportImageSourceHandlerAttribute.cs</Link>
+ </Compile>
+ <Compile Include="..\Xamarin.Forms.Platform.iOS\Deserializer.cs">
+ <Link>Deserializer.cs</Link>
+ </Compile>
+ <Compile Include="..\Xamarin.Forms.Platform.iOS\RendererPool.cs">
+ <Link>RendererPool.cs</Link>
+ </Compile>
+ <Compile Include="..\Xamarin.Forms.Platform.iOS\EffectUtilities.cs">
+ <Link>EffectUtilities.cs</Link>
+ </Compile>
+ <Compile Include="Renderers\PageRenderer.cs" />
+ <Compile Include="Renderers\DefaultRenderer.cs" />
+ <Compile Include="Extensions\AlignmentExtensions.cs" />
+ <Compile Include="Renderers\ButtonRenderer.cs" />
+ <Compile Include="ImageSourceHandlers.cs" />
+ <Compile Include="Extensions\ButtonExtensions.cs" />
+ <Compile Include="Renderers\BoxViewRenderer.cs" />
+ <Compile Include="Renderers\ScrollViewRenderer.cs" />
+ <Compile Include="Renderers\ActivityIndicatorRenderer.cs" />
+ <Compile Include="Renderers\DatePickerRenderer.cs" />
+ <Compile Include="Renderers\EntryRenderer.cs" />
+ <Compile Include="Renderers\EditorRenderer.cs" />
+ <Compile Include="Renderers\FrameRenderer.cs" />
+ <Compile Include="Controls\FormsImageView.cs" />
+ <Compile Include="Renderers\ImageRenderer.cs" />
+ <Compile Include="Renderers\OpenGLViewRenderer.cs" />
+ <Compile Include="Controls\MacOSOpenGLView.cs" />
+ <Compile Include="Renderers\PickerRenderer.cs" />
+ <Compile Include="Renderers\ProgressBarRenderer.cs" />
+ <Compile Include="Renderers\SearchBarRenderer.cs" />
+ <Compile Include="Renderers\SliderRenderer.cs" />
+ <Compile Include="Renderers\StepperRenderer.cs" />
+ <Compile Include="Renderers\SwitchRenderer.cs" />
+ <Compile Include="Renderers\TimePickerRenderer.cs" />
+ <Compile Include="Renderers\WebViewRenderer.cs" />
+ <Compile Include="Renderers\ListViewRenderer.cs" />
+ <Compile Include="Cells\CellNSView.cs" />
+ <Compile Include="Cells\CellRenderer.cs" />
+ <Compile Include="Cells\TextCellRenderer.cs" />
+ <Compile Include="Cells\NSTableViewCellStyle.cs" />
+ <Compile Include="Extensions\NSTextFieldExtensions.cs" />
+ <Compile Include="Cells\ImageCellRenderer.cs" />
+ <Compile Include="Cells\SwitchCellRenderer.cs" />
+ <Compile Include="Cells\EntryCellRenderer.cs" />
+ <Compile Include="Cells\ViewCellRenderer.cs" />
+ <Compile Include="Cells\ViewCellNSView.cs" />
+ <Compile Include="Renderers\ListViewDataSource.cs" />
+ <Compile Include="Renderers\CustomNSTableHeaderView.cs" />
+ <Compile Include="Renderers\CarouselPageRenderer.cs" />
+ <Compile Include="Extensions\NSScrollViewExtensions.cs" />
+ <Compile Include="Controls\ScrollViewScrollChangedEventArgs.cs" />
+ <Compile Include="Renderers\MasterDetailPageRenderer.cs" />
+ <Compile Include="Renderers\TabbedPageRenderer.cs" />
+ <Compile Include="Renderers\NavigationPageRenderer.cs" />
+ <Compile Include="Controls\FormsPageControllerDelegate.cs" />
+ <Compile Include="Controls\NavigationChildPageWrapper.cs" />
+ <Compile Include="..\Xamarin.Forms.Platform.iOS\NativeViewPropertyListener.cs">
+ <Link>NativeViewPropertyListener.cs</Link>
+ </Compile>
+ <Compile Include="..\Xamarin.Forms.Core\Properties\GlobalAssemblyInfo.cs">
+ <Link>Properties\GlobalAssemblyInfo.cs</Link>
+ </Compile>
+ <Compile Include="Renderers\LayoutRenderer.cs" />
+ <Compile Include="Extensions\NSImageExtensions.cs" />
+ <Compile Include="Renderers\NSPageContainer.cs" />
+ <Compile Include="Renderers\PageControllerDelegate.cs" />
+ <Compile Include="Extensions\NSViewControllerExtensions.cs" />
+ <Compile Include="ModalPageTracker.cs" />
+ <Compile Include="PlatformNavigation.cs" />
+ <Compile Include="Renderers\TableViewRenderer.cs" />
+ <Compile Include="Extensions\NSTableViewExtensions.cs" />
+ <Compile Include="Renderers\TableViewDataSource.cs" />
+ <Compile Include="NativeToolbarTracker.cs" />
+ <Compile Include="Extensions\NSButtonExtensions.cs" />
+ <Compile Include="Controls\VerticallyCenteredTextFieldCell.cs" />
+ <Compile Include="..\Xamarin.Forms.Platform.iOS\Forms.cs">
+ <Link>Forms.cs</Link>
+ </Compile>
+ <Compile Include="..\Xamarin.Forms.Platform.iOS\Extensions\DateExtensions.cs">
+ <Link>Extensions\DateExtensions.cs</Link>
+ </Compile>
+ <Compile Include="..\Xamarin.Forms.Platform.iOS\Extensions\LayoutExtensions.cs">
+ <Link>Extensions\LayoutExtensions.cs</Link>
+ </Compile>
+ <Compile Include="..\Xamarin.Forms.Platform.iOS\Extensions\ColorExtensions.cs">
+ <Link>Extensions\ColorExtensions.cs</Link>
+ </Compile>
+ <Compile Include="..\Xamarin.Forms.Platform.iOS\Extensions\UIViewExtensions.cs">
+ <Link>Extensions\UIViewExtensions.cs</Link>
+ </Compile>
+ <Compile Include="..\Xamarin.Forms.Platform.iOS\Renderers\FontExtensions.cs">
+ <Link>Extensions\FontExtensions.cs</Link>
+ </Compile>
+ <Compile Include="..\Xamarin.Forms.Platform.iOS\Renderers\FormattedStringExtensions.cs">
+ <Link>Extensions\FormattedStringExtensions.cs</Link>
+ </Compile>
+ <Compile Include="..\Xamarin.Forms.Platform.iOS\IVisualElementRenderer.cs">
+ <Link>IVisualElementRenderer.cs</Link>
+ </Compile>
+ <Compile Include="..\Xamarin.Forms.Platform.iOS\NativeValueConverterService.cs">
+ <Link>NativeValueConverterService.cs</Link>
+ </Compile>
+ <Compile Include="..\Xamarin.Forms.Platform.iOS\NativeViewWrapper.cs">
+ <Link>NativeViewWrapper.cs</Link>
+ </Compile>
+ <Compile Include="..\Xamarin.Forms.Platform.iOS\NativeViewWrapperRenderer.cs">
+ <Link>NativeViewWrapperRenderer.cs</Link>
+ </Compile>
+ <Compile Include="..\Xamarin.Forms.Platform.iOS\ElementChangedEventArgs.cs">
+ <Link>ElementChangedEventArgs.cs</Link>
+ </Compile>
+ <Compile Include="..\Xamarin.Forms.Platform.iOS\ExportRendererAttribute.cs">
+ <Link>ExportRendererAttribute.cs</Link>
+ </Compile>
+ <Compile Include="..\Xamarin.Forms.Platform.iOS\PlatformEffect.cs">
+ <Link>PlatformEffect.cs</Link>
+ </Compile>
+ <Compile Include="Controls\NSToolbarItemGroup.cs" />
+ <Compile Include="..\Xamarin.Forms.Platform.iOS\Extensions\PlatformConfigurationExtensions.cs">
+ <Link>Extensions\PlatformConfigurationExtensions.cs</Link>
+ </Compile>
+ <Compile Include="..\Xamarin.Forms.Platform.iOS\ViewInitializedEventArgs.cs">
+ <Link>ViewInitializedEventArgs.cs</Link>
+ </Compile>
+ <Compile Include="..\Xamarin.Forms.Platform.iOS\Renderers\LabelRenderer.cs">
+ <Link>Renderers\LabelRenderer.cs</Link>
+ </Compile>
+ <Compile Include="..\Xamarin.Forms.Platform.iOS\ResourcesProvider.cs">
+ <Link>ResourcesProvider.cs</Link>
+ </Compile>
+ <Compile Include="..\Xamarin.Forms.Platform.iOS\VisualElementPackager.cs">
+ <Link>VisualElementPackager.cs</Link>
+ </Compile>
+ <Compile Include="..\Xamarin.Forms.Platform.iOS\ViewRenderer.cs">
+ <Link>ViewRenderer.cs</Link>
+ </Compile>
+ <Compile Include="..\Xamarin.Forms.Platform.iOS\VisualElementTracker.cs">
+ <Link>VisualElementTracker.cs</Link>
+ </Compile>
+ <Compile Include="..\Xamarin.Forms.Platform.iOS\EventTracker.cs">
+ <Link>EventTracker.cs</Link>
+ </Compile>
+ <Compile Include="..\Xamarin.Forms.Platform.iOS\VisualElementRenderer.cs">
+ <Link>VisualElementRenderer.cs</Link>
+ </Compile>
+ </ItemGroup>
+ <ItemGroup>
+ <ProjectReference Include="..\Xamarin.Forms.Platform\Xamarin.Forms.Platform.csproj">
+ <Project>{67F9D3A8-F71E-4428-913F-C37AE82CDB24}</Project>
+ <Name>Xamarin.Forms.Platform</Name>
+ </ProjectReference>
+ <ProjectReference Include="..\Xamarin.Forms.Core\Xamarin.Forms.Core.csproj">
+ <Project>{57B8B73D-C3B5-4C42-869E-7B2F17D354AC}</Project>
+ <Name>Xamarin.Forms.Core</Name>
+ </ProjectReference>
+ </ItemGroup>
+ <ItemGroup />
+ <Import Project="$(MSBuildExtensionsPath)\Xamarin\Mac\Xamarin.Mac.CSharp.targets" />
+</Project> \ No newline at end of file