summaryrefslogtreecommitdiff
path: root/Xamarin.Forms.Platform.iOS
diff options
context:
space:
mode:
Diffstat (limited to 'Xamarin.Forms.Platform.iOS')
-rw-r--r--Xamarin.Forms.Platform.iOS/CADisplayLinkTicker.cs73
-rw-r--r--Xamarin.Forms.Platform.iOS/Cells/CellRenderer.cs77
-rw-r--r--Xamarin.Forms.Platform.iOS/Cells/CellTableViewCell.cs96
-rw-r--r--Xamarin.Forms.Platform.iOS/Cells/EntryCellRenderer.cs186
-rw-r--r--Xamarin.Forms.Platform.iOS/Cells/ImageCellRenderer.cs70
-rw-r--r--Xamarin.Forms.Platform.iOS/Cells/SwitchCellRenderer.cs94
-rw-r--r--Xamarin.Forms.Platform.iOS/Cells/TextCellRenderer.cs73
-rw-r--r--Xamarin.Forms.Platform.iOS/Cells/ViewCellRenderer.cs176
-rw-r--r--Xamarin.Forms.Platform.iOS/ContextActionCell.cs715
-rw-r--r--Xamarin.Forms.Platform.iOS/ContextScrollViewDelegate.cs298
-rw-r--r--Xamarin.Forms.Platform.iOS/Deserializer.cs89
-rw-r--r--Xamarin.Forms.Platform.iOS/ElementChangedEventArgs.cs24
-rw-r--r--Xamarin.Forms.Platform.iOS/EventTracker.cs303
-rw-r--r--Xamarin.Forms.Platform.iOS/ExportCellAttribute.cs12
-rw-r--r--Xamarin.Forms.Platform.iOS/ExportImageSourceHandlerAttribute.cs12
-rw-r--r--Xamarin.Forms.Platform.iOS/ExportRendererAttribute.cs29
-rw-r--r--Xamarin.Forms.Platform.iOS/Extensions/ArrayExtensions.cs38
-rw-r--r--Xamarin.Forms.Platform.iOS/Extensions/CellExtensions.cs41
-rw-r--r--Xamarin.Forms.Platform.iOS/Extensions/ColorExtensions.cs99
-rw-r--r--Xamarin.Forms.Platform.iOS/Extensions/DateExtensions.cs33
-rw-r--r--Xamarin.Forms.Platform.iOS/Extensions/Extensions.cs76
-rw-r--r--Xamarin.Forms.Platform.iOS/Extensions/ToolbarItemExtensions.cs228
-rw-r--r--Xamarin.Forms.Platform.iOS/Extensions/UIViewExtensions.cs66
-rw-r--r--Xamarin.Forms.Platform.iOS/Extensions/ViewExtensions.cs20
-rw-r--r--Xamarin.Forms.Platform.iOS/Forms.cs373
-rw-r--r--Xamarin.Forms.Platform.iOS/FormsApplicationDelegate.cs151
-rw-r--r--Xamarin.Forms.Platform.iOS/GlobalCloseContextGestureRecognizer.cs72
-rw-r--r--Xamarin.Forms.Platform.iOS/IVisualElementRenderer.cs27
-rw-r--r--Xamarin.Forms.Platform.iOS/LayoutExtensions.cs36
-rw-r--r--Xamarin.Forms.Platform.iOS/NativeViewWrapper.cs35
-rw-r--r--Xamarin.Forms.Platform.iOS/NativeViewWrapperRenderer.cs72
-rw-r--r--Xamarin.Forms.Platform.iOS/PageExtensions.cs33
-rw-r--r--Xamarin.Forms.Platform.iOS/Platform.cs505
-rw-r--r--Xamarin.Forms.Platform.iOS/PlatformEffect.cs14
-rw-r--r--Xamarin.Forms.Platform.iOS/PlatformRenderer.cs85
-rw-r--r--Xamarin.Forms.Platform.iOS/Properties/AssemblyInfo.cs69
-rw-r--r--Xamarin.Forms.Platform.iOS/RendererFactory.cs13
-rw-r--r--Xamarin.Forms.Platform.iOS/RendererPool.cs154
-rw-r--r--Xamarin.Forms.Platform.iOS/Renderers/ActivityIndicatorRenderer.cs53
-rw-r--r--Xamarin.Forms.Platform.iOS/Renderers/AlignmentExtensions.cs25
-rw-r--r--Xamarin.Forms.Platform.iOS/Renderers/BoxRenderer.cs78
-rw-r--r--Xamarin.Forms.Platform.iOS/Renderers/ButtonRenderer.cs190
-rw-r--r--Xamarin.Forms.Platform.iOS/Renderers/CarouselPageRenderer.cs395
-rw-r--r--Xamarin.Forms.Platform.iOS/Renderers/CarouselViewRenderer.cs454
-rw-r--r--Xamarin.Forms.Platform.iOS/Renderers/DatePickerRenderer.cs122
-rw-r--r--Xamarin.Forms.Platform.iOS/Renderers/EditorRenderer.cs159
-rw-r--r--Xamarin.Forms.Platform.iOS/Renderers/EntryRenderer.cs184
-rw-r--r--Xamarin.Forms.Platform.iOS/Renderers/ExportCellAttribute.cs11
-rw-r--r--Xamarin.Forms.Platform.iOS/Renderers/ExportImageSourceHandlerAttribute.cs11
-rw-r--r--Xamarin.Forms.Platform.iOS/Renderers/ExportRendererAttribute.cs30
-rw-r--r--Xamarin.Forms.Platform.iOS/Renderers/FontExtensions.cs205
-rw-r--r--Xamarin.Forms.Platform.iOS/Renderers/FormattedStringExtensions.cs82
-rw-r--r--Xamarin.Forms.Platform.iOS/Renderers/FrameRenderer.cs61
-rw-r--r--Xamarin.Forms.Platform.iOS/Renderers/ImageRenderer.cs209
-rw-r--r--Xamarin.Forms.Platform.iOS/Renderers/KeyboardInsetTracker.cs109
-rw-r--r--Xamarin.Forms.Platform.iOS/Renderers/KeyboardObserver.cs37
-rw-r--r--Xamarin.Forms.Platform.iOS/Renderers/LabelRenderer.cs183
-rw-r--r--Xamarin.Forms.Platform.iOS/Renderers/ListViewRenderer.cs1119
-rw-r--r--Xamarin.Forms.Platform.iOS/Renderers/NavigationMenuRenderer.cs152
-rw-r--r--Xamarin.Forms.Platform.iOS/Renderers/NavigationRenderer.cs918
-rw-r--r--Xamarin.Forms.Platform.iOS/Renderers/OpenGLViewRenderer.cs122
-rw-r--r--Xamarin.Forms.Platform.iOS/Renderers/PageRenderer.cs218
-rw-r--r--Xamarin.Forms.Platform.iOS/Renderers/PhoneMasterDetailRenderer.cs401
-rw-r--r--Xamarin.Forms.Platform.iOS/Renderers/PickerRenderer.cs180
-rw-r--r--Xamarin.Forms.Platform.iOS/Renderers/ProgressBarRenderer.cs67
-rw-r--r--Xamarin.Forms.Platform.iOS/Renderers/ScrollViewRenderer.cs246
-rw-r--r--Xamarin.Forms.Platform.iOS/Renderers/SearchBarRenderer.cs249
-rw-r--r--Xamarin.Forms.Platform.iOS/Renderers/SliderRenderer.cs99
-rw-r--r--Xamarin.Forms.Platform.iOS/Renderers/StepperRenderer.cs82
-rw-r--r--Xamarin.Forms.Platform.iOS/Renderers/SwitchRenderer.cs52
-rw-r--r--Xamarin.Forms.Platform.iOS/Renderers/TabbedRenderer.cs311
-rw-r--r--Xamarin.Forms.Platform.iOS/Renderers/TableViewModelRenderer.cs157
-rw-r--r--Xamarin.Forms.Platform.iOS/Renderers/TableViewRenderer.cs150
-rw-r--r--Xamarin.Forms.Platform.iOS/Renderers/TabletMasterDetailRenderer.cs371
-rw-r--r--Xamarin.Forms.Platform.iOS/Renderers/TimePickerRenderer.cs104
-rw-r--r--Xamarin.Forms.Platform.iOS/Renderers/ToolbarRenderer.cs66
-rw-r--r--Xamarin.Forms.Platform.iOS/Renderers/WebViewRenderer.cs270
-rw-r--r--Xamarin.Forms.Platform.iOS/Resources/StringResources.Designer.cs81
-rw-r--r--Xamarin.Forms.Platform.iOS/Resources/StringResources.ar.resx128
-rw-r--r--Xamarin.Forms.Platform.iOS/Resources/StringResources.ca.resx128
-rw-r--r--Xamarin.Forms.Platform.iOS/Resources/StringResources.cs.resx128
-rw-r--r--Xamarin.Forms.Platform.iOS/Resources/StringResources.da.resx128
-rw-r--r--Xamarin.Forms.Platform.iOS/Resources/StringResources.de.resx128
-rw-r--r--Xamarin.Forms.Platform.iOS/Resources/StringResources.el.resx128
-rw-r--r--Xamarin.Forms.Platform.iOS/Resources/StringResources.es.resx128
-rw-r--r--Xamarin.Forms.Platform.iOS/Resources/StringResources.fi.resx128
-rw-r--r--Xamarin.Forms.Platform.iOS/Resources/StringResources.fr.resx128
-rw-r--r--Xamarin.Forms.Platform.iOS/Resources/StringResources.he.resx128
-rw-r--r--Xamarin.Forms.Platform.iOS/Resources/StringResources.hi.resx128
-rw-r--r--Xamarin.Forms.Platform.iOS/Resources/StringResources.hr.resx128
-rw-r--r--Xamarin.Forms.Platform.iOS/Resources/StringResources.hu.resx128
-rw-r--r--Xamarin.Forms.Platform.iOS/Resources/StringResources.id.resx128
-rw-r--r--Xamarin.Forms.Platform.iOS/Resources/StringResources.it.resx128
-rw-r--r--Xamarin.Forms.Platform.iOS/Resources/StringResources.ja.resx128
-rw-r--r--Xamarin.Forms.Platform.iOS/Resources/StringResources.ko.resx128
-rw-r--r--Xamarin.Forms.Platform.iOS/Resources/StringResources.ms.resx128
-rw-r--r--Xamarin.Forms.Platform.iOS/Resources/StringResources.nb.resx128
-rw-r--r--Xamarin.Forms.Platform.iOS/Resources/StringResources.nl.resx128
-rw-r--r--Xamarin.Forms.Platform.iOS/Resources/StringResources.pl.resx128
-rw-r--r--Xamarin.Forms.Platform.iOS/Resources/StringResources.pt-BR.resx128
-rw-r--r--Xamarin.Forms.Platform.iOS/Resources/StringResources.pt.resx128
-rw-r--r--Xamarin.Forms.Platform.iOS/Resources/StringResources.resx128
-rw-r--r--Xamarin.Forms.Platform.iOS/Resources/StringResources.ro.resx128
-rw-r--r--Xamarin.Forms.Platform.iOS/Resources/StringResources.ru.resx128
-rw-r--r--Xamarin.Forms.Platform.iOS/Resources/StringResources.sk.resx128
-rw-r--r--Xamarin.Forms.Platform.iOS/Resources/StringResources.sv.resx128
-rw-r--r--Xamarin.Forms.Platform.iOS/Resources/StringResources.th.resx128
-rw-r--r--Xamarin.Forms.Platform.iOS/Resources/StringResources.tr.resx128
-rw-r--r--Xamarin.Forms.Platform.iOS/Resources/StringResources.uk.resx128
-rw-r--r--Xamarin.Forms.Platform.iOS/Resources/StringResources.vi.resx128
-rw-r--r--Xamarin.Forms.Platform.iOS/Resources/StringResources.zh-HK.resx128
-rw-r--r--Xamarin.Forms.Platform.iOS/Resources/StringResources.zh-Hans.resx128
-rw-r--r--Xamarin.Forms.Platform.iOS/Resources/StringResources.zh-Hant.resx128
-rw-r--r--Xamarin.Forms.Platform.iOS/ResourcesProvider.cs72
-rw-r--r--Xamarin.Forms.Platform.iOS/ViewInitializedEventArgs.cs17
-rw-r--r--Xamarin.Forms.Platform.iOS/ViewRenderer.cs151
-rw-r--r--Xamarin.Forms.Platform.iOS/VisualElementPackager.cs179
-rw-r--r--Xamarin.Forms.Platform.iOS/VisualElementRenderer.cs273
-rw-r--r--Xamarin.Forms.Platform.iOS/VisualElementTracker.cs259
-rw-r--r--Xamarin.Forms.Platform.iOS/Xamarin.Forms.Platform.iOS.Classic.csproj257
-rw-r--r--Xamarin.Forms.Platform.iOS/Xamarin.Forms.Platform.iOS.csproj191
-rw-r--r--Xamarin.Forms.Platform.iOS/Xamarin.Forms.Platform.iOS.nuspec14
122 files changed, 18473 insertions, 0 deletions
diff --git a/Xamarin.Forms.Platform.iOS/CADisplayLinkTicker.cs b/Xamarin.Forms.Platform.iOS/CADisplayLinkTicker.cs
new file mode 100644
index 00000000..29dd732a
--- /dev/null
+++ b/Xamarin.Forms.Platform.iOS/CADisplayLinkTicker.cs
@@ -0,0 +1,73 @@
+using System;
+using System.Collections.Concurrent;
+using System.Threading;
+#if __UNIFIED__
+using UIKit;
+using CoreAnimation;
+using Foundation;
+
+#else
+using MonoTouch.UIKit;
+using MonoTouch.CoreAnimation;
+using MonoTouch.Foundation;
+#endif
+
+namespace Xamarin.Forms.Platform.iOS
+{
+ internal class CADisplayLinkTicker : Ticker
+ {
+ readonly BlockingCollection<Action> _queue = new BlockingCollection<Action>();
+ CADisplayLink _link;
+
+ public CADisplayLinkTicker()
+ {
+ var thread = new Thread(StartThread);
+ thread.Start();
+ }
+
+ internal new static CADisplayLinkTicker Default
+ {
+ get { return Ticker.Default as CADisplayLinkTicker; }
+ }
+
+ public void Invoke(Action action)
+ {
+ _queue.Add(action);
+ }
+
+ protected override void DisableTimer()
+ {
+ if (_link != null)
+ {
+ _link.RemoveFromRunLoop(NSRunLoop.Current, NSRunLoop.NSRunLoopCommonModes);
+ _link.Dispose();
+ }
+ _link = null;
+ }
+
+ protected override void EnableTimer()
+ {
+ _link = CADisplayLink.Create(() => SendSignals());
+ _link.AddToRunLoop(NSRunLoop.Current, NSRunLoop.NSRunLoopCommonModes);
+ }
+
+ void StartThread()
+ {
+ while (true)
+ {
+ var action = _queue.Take();
+ var previous = UIApplication.CheckForIllegalCrossThreadCalls;
+ UIApplication.CheckForIllegalCrossThreadCalls = false;
+
+ CATransaction.Begin();
+ action.Invoke();
+
+ while (_queue.TryTake(out action))
+ action.Invoke();
+ CATransaction.Commit();
+
+ UIApplication.CheckForIllegalCrossThreadCalls = previous;
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.iOS/Cells/CellRenderer.cs b/Xamarin.Forms.Platform.iOS/Cells/CellRenderer.cs
new file mode 100644
index 00000000..c01a7774
--- /dev/null
+++ b/Xamarin.Forms.Platform.iOS/Cells/CellRenderer.cs
@@ -0,0 +1,77 @@
+using System;
+#if __UNIFIED__
+using UIKit;
+using Foundation;
+
+#else
+using MonoTouch.UIKit;
+using MonoTouch.Foundation;
+#endif
+
+namespace Xamarin.Forms.Platform.iOS
+{
+ public class CellRenderer : IRegisterable
+ {
+ static readonly BindableProperty RealCellProperty = BindableProperty.CreateAttached("RealCell", typeof(UITableViewCell), typeof(Cell), null);
+
+ EventHandler _onForceUpdateSizeRequested;
+
+ public virtual UITableViewCell GetCell(Cell item, UITableViewCell reusableCell, UITableView tv)
+ {
+ var tvc = reusableCell as CellTableViewCell ?? new CellTableViewCell(UITableViewCellStyle.Default, item.GetType().FullName);
+
+ tvc.Cell = item;
+
+ WireUpForceUpdateSizeRequested(item, tvc, tv);
+
+ tvc.TextLabel.Text = item.ToString();
+
+ UpdateBackground(tvc, item);
+ return tvc;
+ }
+
+ protected void UpdateBackground(UITableViewCell tableViewCell, Cell cell)
+ {
+ if (TemplatedItemsList<ItemsView<Cell>, Cell>.GetIsGroupHeader(cell))
+ {
+ if (UIDevice.CurrentDevice.CheckSystemVersion(7, 0))
+ tableViewCell.BackgroundColor = new UIColor(247f / 255f, 247f / 255f, 247f / 255f, 1);
+ }
+ else
+ {
+ // Must be set to a solid color or blending issues will occur
+ var bgColor = UIColor.White;
+
+ var element = cell.RealParent as VisualElement;
+ if (element != null)
+ bgColor = element.BackgroundColor == Color.Default ? bgColor : element.BackgroundColor.ToUIColor();
+
+ tableViewCell.BackgroundColor = bgColor;
+ }
+ }
+
+ protected void WireUpForceUpdateSizeRequested(Cell cell, UITableViewCell nativeCell, UITableView tableView)
+ {
+ cell.ForceUpdateSizeRequested -= _onForceUpdateSizeRequested;
+
+ _onForceUpdateSizeRequested = delegate
+ {
+ var index = tableView.IndexPathForCell(nativeCell);
+ if (index != null)
+ tableView.ReloadRows(new[] { index }, UITableViewRowAnimation.None);
+ };
+
+ cell.ForceUpdateSizeRequested += _onForceUpdateSizeRequested;
+ }
+
+ internal static UITableViewCell GetRealCell(BindableObject cell)
+ {
+ return (UITableViewCell)cell.GetValue(RealCellProperty);
+ }
+
+ internal static void SetRealCell(BindableObject cell, UITableViewCell renderer)
+ {
+ cell.SetValue(RealCellProperty, renderer);
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.iOS/Cells/CellTableViewCell.cs b/Xamarin.Forms.Platform.iOS/Cells/CellTableViewCell.cs
new file mode 100644
index 00000000..a9265868
--- /dev/null
+++ b/Xamarin.Forms.Platform.iOS/Cells/CellTableViewCell.cs
@@ -0,0 +1,96 @@
+using System;
+using System.ComponentModel;
+#if __UNIFIED__
+using UIKit;
+
+#else
+using MonoTouch.UIKit;
+#endif
+
+namespace Xamarin.Forms.Platform.iOS
+{
+ public class CellTableViewCell : UITableViewCell, INativeElementView
+ {
+ Cell _cell;
+
+ public Action<object, PropertyChangedEventArgs> PropertyChanged;
+
+ public CellTableViewCell(UITableViewCellStyle style, string key) : base(style, key)
+ {
+ }
+
+ public Cell Cell
+ {
+ get { return _cell; }
+ set
+ {
+ if (_cell == value)
+ return;
+
+ if (_cell != null)
+ Device.BeginInvokeOnMainThread(_cell.SendDisappearing);
+ _cell = value;
+ if (_cell != null)
+ Device.BeginInvokeOnMainThread(_cell.SendAppearing);
+ }
+ }
+
+ public Element Element => Cell;
+
+ public void HandlePropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ if (PropertyChanged != null)
+ PropertyChanged(this, e);
+ }
+
+ internal static UITableViewCell GetNativeCell(UITableView tableView, Cell cell, bool recycleCells = false, string templateId = "")
+ {
+ var id = cell.GetType().FullName;
+
+ var renderer = (CellRenderer)Registrar.Registered.GetHandler(cell.GetType());
+
+ ContextActionsCell contextCell = null;
+ UITableViewCell reusableCell = null;
+ if (cell.HasContextActions || recycleCells)
+ {
+ contextCell = (ContextActionsCell)tableView.DequeueReusableCell(ContextActionsCell.Key + templateId);
+ if (contextCell == null)
+ {
+ contextCell = new ContextActionsCell(templateId);
+ reusableCell = tableView.DequeueReusableCell(id);
+ }
+ else
+ {
+ contextCell.Close();
+ reusableCell = contextCell.ContentCell;
+
+ if (reusableCell.ReuseIdentifier.ToString() != id)
+ reusableCell = null;
+ }
+ }
+ else
+ reusableCell = tableView.DequeueReusableCell(id);
+
+ var nativeCell = renderer.GetCell(cell, reusableCell, tableView);
+
+ var cellWithContent = nativeCell;
+
+ // Sometimes iOS for returns a dequeued cell whose Layer is hidden.
+ // This prevents it from showing up, so lets turn it back on!
+ if (cellWithContent.Layer.Hidden)
+ cellWithContent.Layer.Hidden = false;
+
+ if (contextCell != null)
+ {
+ contextCell.Update(tableView, cell, nativeCell);
+ nativeCell = contextCell;
+ }
+
+ // Because the layer was hidden we need to layout the cell by hand
+ if (cellWithContent != null)
+ cellWithContent.LayoutSubviews();
+
+ return nativeCell;
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.iOS/Cells/EntryCellRenderer.cs b/Xamarin.Forms.Platform.iOS/Cells/EntryCellRenderer.cs
new file mode 100644
index 00000000..1af49b4a
--- /dev/null
+++ b/Xamarin.Forms.Platform.iOS/Cells/EntryCellRenderer.cs
@@ -0,0 +1,186 @@
+using System;
+using System.ComponentModel;
+using System.Drawing;
+#if __UNIFIED__
+using UIKit;
+#else
+using MonoTouch.UIKit;
+#endif
+#if __UNIFIED__
+using RectangleF = CoreGraphics.CGRect;
+using SizeF = CoreGraphics.CGSize;
+using PointF = CoreGraphics.CGPoint;
+
+#else
+using nfloat=System.Single;
+using nint=System.Int32;
+using nuint=System.UInt32;
+#endif
+
+namespace Xamarin.Forms.Platform.iOS
+{
+ public class EntryCellRenderer : CellRenderer
+ {
+ static readonly Color DefaultTextColor = Color.Black;
+
+ public override UITableViewCell GetCell(Cell item, UITableViewCell reusableCell, UITableView tv)
+ {
+ var entryCell = (EntryCell)item;
+
+ var tvc = reusableCell as EntryCellTableViewCell;
+ if (tvc == null)
+ tvc = new EntryCellTableViewCell(item.GetType().FullName);
+ else
+ {
+ tvc.Cell.PropertyChanged -= OnCellPropertyChanged;
+ tvc.TextFieldTextChanged -= OnTextFieldTextChanged;
+ tvc.KeyboardDoneButtonPressed -= OnKeyBoardDoneButtonPressed;
+ }
+
+ SetRealCell(item, tvc);
+
+ tvc.Cell = item;
+ tvc.Cell.PropertyChanged += OnCellPropertyChanged;
+ tvc.TextFieldTextChanged += OnTextFieldTextChanged;
+ tvc.KeyboardDoneButtonPressed += OnKeyBoardDoneButtonPressed;
+
+ WireUpForceUpdateSizeRequested(item, tvc, tv);
+
+ UpdateBackground(tvc, entryCell);
+ UpdateLabel(tvc, entryCell);
+ UpdateText(tvc, entryCell);
+ UpdateKeyboard(tvc, entryCell);
+ UpdatePlaceholder(tvc, entryCell);
+ UpdateLabelColor(tvc, entryCell);
+ UpdateHorizontalTextAlignment(tvc, entryCell);
+ UpdateIsEnabled(tvc, entryCell);
+
+ return tvc;
+ }
+
+ static void OnCellPropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ var entryCell = (EntryCell)sender;
+ var realCell = (EntryCellTableViewCell)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.KeyboardProperty.PropertyName)
+ UpdateKeyboard(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 OnKeyBoardDoneButtonPressed(object sender, EventArgs e)
+ {
+ var cell = (EntryCellTableViewCell)sender;
+ var model = (EntryCell)cell.Cell;
+
+ model.SendCompleted();
+ }
+
+ static void OnTextFieldTextChanged(object sender, EventArgs eventArgs)
+ {
+ var cell = (EntryCellTableViewCell)sender;
+ var model = (EntryCell)cell.Cell;
+
+ model.Text = cell.TextField.Text;
+ }
+
+ static void UpdateHorizontalTextAlignment(EntryCellTableViewCell cell, EntryCell entryCell)
+ {
+ cell.TextField.TextAlignment = entryCell.HorizontalTextAlignment.ToNativeTextAlignment();
+ }
+
+ static void UpdateIsEnabled(EntryCellTableViewCell cell, EntryCell entryCell)
+ {
+ cell.UserInteractionEnabled = entryCell.IsEnabled;
+ cell.TextLabel.Enabled = entryCell.IsEnabled;
+ cell.DetailTextLabel.Enabled = entryCell.IsEnabled;
+ cell.TextField.Enabled = entryCell.IsEnabled;
+ }
+
+ static void UpdateKeyboard(EntryCellTableViewCell cell, EntryCell entryCell)
+ {
+ cell.TextField.ApplyKeyboard(entryCell.Keyboard);
+ }
+
+ static void UpdateLabel(EntryCellTableViewCell cell, EntryCell entryCell)
+ {
+ cell.TextLabel.Text = entryCell.Label;
+ }
+
+ static void UpdateLabelColor(EntryCellTableViewCell cell, EntryCell entryCell)
+ {
+ cell.TextLabel.TextColor = entryCell.LabelColor.ToUIColor(DefaultTextColor);
+ }
+
+ static void UpdatePlaceholder(EntryCellTableViewCell cell, EntryCell entryCell)
+ {
+ cell.TextField.Placeholder = entryCell.Placeholder;
+ }
+
+ static void UpdateText(EntryCellTableViewCell cell, EntryCell entryCell)
+ {
+ if (cell.TextField.Text == entryCell.Text)
+ return;
+ // double sets side effect on iOS, YAY
+ cell.TextField.Text = entryCell.Text;
+ }
+
+ class EntryCellTableViewCell : CellTableViewCell
+ {
+ public EntryCellTableViewCell(string cellName) : base(UITableViewCellStyle.Value1, cellName)
+ {
+ TextField = new UITextField(new RectangleF(0, 0, 100, 30)) { BorderStyle = UITextBorderStyle.None };
+
+ TextField.EditingChanged += TextFieldOnEditingChanged;
+ TextField.ShouldReturn = OnShouldReturn;
+
+ ContentView.AddSubview(TextField);
+ }
+
+ public UITextField TextField { get; }
+
+ public event EventHandler KeyboardDoneButtonPressed;
+
+ public override void LayoutSubviews()
+ {
+ base.LayoutSubviews();
+
+ // simple algorithm to generally line up entries
+ var start = (nfloat)Math.Round(Math.Max(Frame.Width * 0.3, TextLabel.Frame.Right + 10));
+ TextField.Frame = new RectangleF(start, (Frame.Height - 30) / 2, Frame.Width - TextLabel.Frame.Left - start, 30);
+ // Centers TextField Content (iOS6)
+ TextField.VerticalAlignment = UIControlContentVerticalAlignment.Center;
+ }
+
+ public event EventHandler TextFieldTextChanged;
+
+ bool OnShouldReturn(UITextField view)
+ {
+ var handler = KeyboardDoneButtonPressed;
+ if (handler != null)
+ handler(this, EventArgs.Empty);
+
+ TextField.ResignFirstResponder();
+ return true;
+ }
+
+ void TextFieldOnEditingChanged(object sender, EventArgs eventArgs)
+ {
+ var handler = TextFieldTextChanged;
+ if (handler != null)
+ handler(this, EventArgs.Empty);
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.iOS/Cells/ImageCellRenderer.cs b/Xamarin.Forms.Platform.iOS/Cells/ImageCellRenderer.cs
new file mode 100644
index 00000000..ade59e82
--- /dev/null
+++ b/Xamarin.Forms.Platform.iOS/Cells/ImageCellRenderer.cs
@@ -0,0 +1,70 @@
+using System.ComponentModel;
+using System.Threading.Tasks;
+#if __UNIFIED__
+using UIKit;
+using Foundation;
+
+#else
+using MonoTouch.UIKit;
+using MonoTouch.Foundation;
+#endif
+
+namespace Xamarin.Forms.Platform.iOS
+{
+ public class ImageCellRenderer : TextCellRenderer
+ {
+ public override UITableViewCell GetCell(Cell item, UITableViewCell reusableCell, UITableView tv)
+ {
+ var result = (CellTableViewCell)base.GetCell(item, reusableCell, tv);
+
+ var imageCell = (ImageCell)item;
+
+ WireUpForceUpdateSizeRequested(item, result, tv);
+
+ SetImage(imageCell, result);
+
+ return result;
+ }
+
+ protected override void HandlePropertyChanged(object sender, PropertyChangedEventArgs args)
+ {
+ var tvc = (CellTableViewCell)sender;
+ var imageCell = (ImageCell)tvc.Cell;
+
+ base.HandlePropertyChanged(sender, args);
+
+ if (args.PropertyName == ImageCell.ImageSourceProperty.PropertyName)
+ SetImage(imageCell, tvc);
+ }
+
+ async void SetImage(ImageCell cell, CellTableViewCell target)
+ {
+ var source = cell.ImageSource;
+
+ target.ImageView.Image = null;
+
+ IImageSourceHandler handler;
+
+ if (source != null && (handler = Registrar.Registered.GetHandler<IImageSourceHandler>(source.GetType())) != null)
+ {
+ UIImage uiimage;
+ try
+ {
+ uiimage = await handler.LoadImageAsync(source).ConfigureAwait(false);
+ }
+ catch (TaskCanceledException)
+ {
+ uiimage = null;
+ }
+
+ NSRunLoop.Main.BeginInvokeOnMainThread(() =>
+ {
+ target.ImageView.Image = uiimage;
+ target.SetNeedsLayout();
+ });
+ }
+ else
+ target.ImageView.Image = null;
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.iOS/Cells/SwitchCellRenderer.cs b/Xamarin.Forms.Platform.iOS/Cells/SwitchCellRenderer.cs
new file mode 100644
index 00000000..ed7ddc10
--- /dev/null
+++ b/Xamarin.Forms.Platform.iOS/Cells/SwitchCellRenderer.cs
@@ -0,0 +1,94 @@
+using System;
+using System.ComponentModel;
+using System.Drawing;
+#if __UNIFIED__
+using UIKit;
+
+#else
+using MonoTouch.UIKit;
+#endif
+
+namespace Xamarin.Forms.Platform.iOS
+{
+ public class SwitchCellRenderer : CellRenderer
+ {
+ const string CellName = "Xamarin.SwitchCell";
+
+ public override UITableViewCell GetCell(Cell item, UITableViewCell reusableCell, UITableView tv)
+ {
+ var tvc = reusableCell as CellTableViewCell;
+ UISwitch uiSwitch = null;
+ if (tvc == null)
+ tvc = new CellTableViewCell(UITableViewCellStyle.Value1, CellName);
+ else
+ {
+ uiSwitch = tvc.AccessoryView as UISwitch;
+ tvc.Cell.PropertyChanged -= OnCellPropertyChanged;
+ }
+
+ SetRealCell(item, tvc);
+
+ if (uiSwitch == null)
+ {
+ uiSwitch = new UISwitch(new RectangleF());
+ uiSwitch.ValueChanged += OnSwitchValueChanged;
+ tvc.AccessoryView = uiSwitch;
+ }
+
+ var boolCell = (SwitchCell)item;
+
+ tvc.Cell = item;
+ tvc.Cell.PropertyChanged += OnCellPropertyChanged;
+ tvc.AccessoryView = uiSwitch;
+ tvc.TextLabel.Text = boolCell.Text;
+
+ uiSwitch.On = boolCell.On;
+
+ WireUpForceUpdateSizeRequested(item, tvc, tv);
+
+ UpdateBackground(tvc, item);
+ UpdateIsEnabled(tvc, boolCell);
+
+ return tvc;
+ }
+
+ void OnCellPropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ var boolCell = (SwitchCell)sender;
+ var realCell = (CellTableViewCell)GetRealCell(boolCell);
+
+ if (e.PropertyName == SwitchCell.OnProperty.PropertyName)
+ ((UISwitch)realCell.AccessoryView).SetState(boolCell.On, true);
+ else if (e.PropertyName == SwitchCell.TextProperty.PropertyName)
+ realCell.TextLabel.Text = boolCell.Text;
+ else if (e.PropertyName == Cell.IsEnabledProperty.PropertyName)
+ UpdateIsEnabled(realCell, boolCell);
+ }
+
+ void OnSwitchValueChanged(object sender, EventArgs eventArgs)
+ {
+ var view = (UIView)sender;
+ var sw = (UISwitch)view;
+
+ CellTableViewCell realCell = null;
+ while (view.Superview != null && realCell == null)
+ {
+ view = view.Superview;
+ realCell = view as CellTableViewCell;
+ }
+
+ if (realCell != null)
+ ((SwitchCell)realCell.Cell).On = sw.On;
+ }
+
+ void UpdateIsEnabled(CellTableViewCell cell, SwitchCell switchCell)
+ {
+ cell.UserInteractionEnabled = switchCell.IsEnabled;
+ cell.TextLabel.Enabled = switchCell.IsEnabled;
+ cell.DetailTextLabel.Enabled = switchCell.IsEnabled;
+ var uiSwitch = cell.AccessoryView as UISwitch;
+ if (uiSwitch != null)
+ uiSwitch.Enabled = switchCell.IsEnabled;
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.iOS/Cells/TextCellRenderer.cs b/Xamarin.Forms.Platform.iOS/Cells/TextCellRenderer.cs
new file mode 100644
index 00000000..41c43341
--- /dev/null
+++ b/Xamarin.Forms.Platform.iOS/Cells/TextCellRenderer.cs
@@ -0,0 +1,73 @@
+using System.ComponentModel;
+#if __UNIFIED__
+using UIKit;
+
+#else
+using MonoTouch.UIKit;
+#endif
+
+namespace Xamarin.Forms.Platform.iOS
+{
+ public class TextCellRenderer : CellRenderer
+ {
+ static readonly Color DefaultDetailColor = new Color(.32, .4, .57);
+ static readonly Color DefaultTextColor = Color.Black;
+
+ public override UITableViewCell GetCell(Cell item, UITableViewCell reusableCell, UITableView tv)
+ {
+ var textCell = (TextCell)item;
+
+ var tvc = reusableCell as CellTableViewCell;
+ if (tvc == null)
+ tvc = new CellTableViewCell(UITableViewCellStyle.Subtitle, item.GetType().FullName);
+ else
+ tvc.Cell.PropertyChanged -= tvc.HandlePropertyChanged;
+
+ tvc.Cell = textCell;
+ textCell.PropertyChanged += tvc.HandlePropertyChanged;
+ tvc.PropertyChanged = HandlePropertyChanged;
+
+ tvc.TextLabel.Text = textCell.Text;
+ tvc.DetailTextLabel.Text = textCell.Detail;
+ tvc.TextLabel.TextColor = textCell.TextColor.ToUIColor(DefaultTextColor);
+ tvc.DetailTextLabel.TextColor = textCell.DetailColor.ToUIColor(DefaultDetailColor);
+
+ WireUpForceUpdateSizeRequested(item, tvc, tv);
+
+ UpdateIsEnabled(tvc, textCell);
+
+ UpdateBackground(tvc, item);
+
+ return tvc;
+ }
+
+ protected virtual void HandlePropertyChanged(object sender, PropertyChangedEventArgs args)
+ {
+ var tvc = (CellTableViewCell)sender;
+ var textCell = (TextCell)tvc.Cell;
+ if (args.PropertyName == TextCell.TextProperty.PropertyName)
+ {
+ tvc.TextLabel.Text = ((TextCell)tvc.Cell).Text;
+ tvc.TextLabel.SizeToFit();
+ }
+ else if (args.PropertyName == TextCell.DetailProperty.PropertyName)
+ {
+ tvc.DetailTextLabel.Text = ((TextCell)tvc.Cell).Detail;
+ tvc.DetailTextLabel.SizeToFit();
+ }
+ else if (args.PropertyName == TextCell.TextColorProperty.PropertyName)
+ tvc.TextLabel.TextColor = textCell.TextColor.ToUIColor(DefaultTextColor);
+ else if (args.PropertyName == TextCell.DetailColorProperty.PropertyName)
+ tvc.DetailTextLabel.TextColor = textCell.DetailColor.ToUIColor(DefaultTextColor);
+ else if (args.PropertyName == Cell.IsEnabledProperty.PropertyName)
+ UpdateIsEnabled(tvc, textCell);
+ }
+
+ static void UpdateIsEnabled(CellTableViewCell cell, TextCell entryCell)
+ {
+ cell.UserInteractionEnabled = entryCell.IsEnabled;
+ cell.TextLabel.Enabled = entryCell.IsEnabled;
+ cell.DetailTextLabel.Enabled = entryCell.IsEnabled;
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.iOS/Cells/ViewCellRenderer.cs b/Xamarin.Forms.Platform.iOS/Cells/ViewCellRenderer.cs
new file mode 100644
index 00000000..b1416295
--- /dev/null
+++ b/Xamarin.Forms.Platform.iOS/Cells/ViewCellRenderer.cs
@@ -0,0 +1,176 @@
+using System;
+using System.ComponentModel;
+using System.Diagnostics;
+#if __UNIFIED__
+using UIKit;
+#else
+using MonoTouch.UIKit;
+using System.Drawing;
+#endif
+#if __UNIFIED__
+using RectangleF = CoreGraphics.CGRect;
+using SizeF = CoreGraphics.CGSize;
+using PointF = CoreGraphics.CGPoint;
+
+#else
+using nfloat=System.Single;
+using nint=System.Int32;
+using nuint=System.UInt32;
+#endif
+
+namespace Xamarin.Forms.Platform.iOS
+{
+ public class ViewCellRenderer : CellRenderer
+ {
+ public override UITableViewCell GetCell(Cell item, UITableViewCell reusableCell, UITableView tv)
+ {
+ var viewCell = (ViewCell)item;
+
+ var cell = reusableCell as ViewTableCell;
+ if (cell == null)
+ cell = new ViewTableCell(item.GetType().FullName);
+ else
+ cell.ViewCell.PropertyChanged -= ViewCellPropertyChanged;
+
+ viewCell.PropertyChanged += ViewCellPropertyChanged;
+ cell.ViewCell = viewCell;
+
+ WireUpForceUpdateSizeRequested(item, cell, tv);
+
+ UpdateBackground(cell, item);
+ UpdateIsEnabled(cell, viewCell);
+ return cell;
+ }
+
+ static void UpdateIsEnabled(ViewTableCell cell, ViewCell viewCell)
+ {
+ cell.UserInteractionEnabled = viewCell.IsEnabled;
+ cell.TextLabel.Enabled = viewCell.IsEnabled;
+ }
+
+ void ViewCellPropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ var viewCell = (ViewCell)sender;
+ var realCell = (ViewTableCell)GetRealCell(viewCell);
+
+ if (e.PropertyName == Cell.IsEnabledProperty.PropertyName)
+ UpdateIsEnabled(realCell, viewCell);
+ }
+
+ internal class ViewTableCell : UITableViewCell, INativeElementView
+ {
+ WeakReference<IVisualElementRenderer> _rendererRef;
+
+ ViewCell _viewCell;
+
+ public ViewTableCell(string key) : base(UITableViewCellStyle.Default, key)
+ {
+ }
+
+ public ViewCell ViewCell
+ {
+ get { return _viewCell; }
+ set
+ {
+ if (_viewCell == value)
+ return;
+ UpdateCell(value);
+ }
+ }
+
+ Element INativeElementView.Element
+ {
+ get { return ViewCell; }
+ }
+
+ public override void LayoutSubviews()
+ {
+ //This sets the content views frame.
+ base.LayoutSubviews();
+
+ var contentFrame = ContentView.Frame;
+
+ Layout.LayoutChildIntoBoundingRegion(ViewCell.View, contentFrame.ToRectangle());
+
+ if (_rendererRef == null)
+ return;
+
+ IVisualElementRenderer renderer;
+ if (_rendererRef.TryGetTarget(out renderer))
+ renderer.NativeView.Frame = contentFrame;
+ }
+
+ public override SizeF SizeThatFits(SizeF size)
+ {
+ IVisualElementRenderer renderer;
+ if (!_rendererRef.TryGetTarget(out renderer))
+ return base.SizeThatFits(size);
+
+ double width = size.Width;
+ var height = size.Height > 0 ? size.Height : double.PositiveInfinity;
+ var result = renderer.Element.Measure(width, height);
+
+ // make sure to add in the separator
+ return new SizeF(size.Width, (float)result.Request.Height + 1f / UIScreen.MainScreen.Scale);
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ IVisualElementRenderer renderer;
+ if (_rendererRef != null && _rendererRef.TryGetTarget(out renderer) && renderer.Element != null)
+ {
+ var platform = renderer.Element.Platform as Platform;
+ if (platform != null)
+ platform.DisposeModelAndChildrenRenderers(renderer.Element);
+
+ _rendererRef = null;
+ }
+ }
+
+ base.Dispose(disposing);
+ }
+
+ IVisualElementRenderer GetNewRenderer()
+ {
+ var newRenderer = Platform.CreateRenderer(_viewCell.View);
+ _rendererRef = new WeakReference<IVisualElementRenderer>(newRenderer);
+ ContentView.AddSubview(newRenderer.NativeView);
+ return newRenderer;
+ }
+
+ void UpdateCell(ViewCell cell)
+ {
+ if (_viewCell != null)
+ Device.BeginInvokeOnMainThread(_viewCell.SendDisappearing);
+
+ _viewCell = cell;
+ Device.BeginInvokeOnMainThread(_viewCell.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 Platform.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
+ var platform = renderer.Element.Platform as Platform;
+ platform.DisposeRendererAndChildren(renderer);
+ renderer = GetNewRenderer();
+ }
+ }
+
+ Platform.SetRenderer(_viewCell.View, renderer);
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.iOS/ContextActionCell.cs b/Xamarin.Forms.Platform.iOS/ContextActionCell.cs
new file mode 100644
index 00000000..a2409c62
--- /dev/null
+++ b/Xamarin.Forms.Platform.iOS/ContextActionCell.cs
@@ -0,0 +1,715 @@
+using System;
+using System.Collections.Specialized;
+using System.ComponentModel;
+using System.Collections.Generic;
+using System.Drawing;
+using Xamarin.Forms.Platform.iOS.Resources;
+#if __UNIFIED__
+using UIKit;
+using Foundation;
+#else
+using MonoTouch.UIKit;
+using MonoTouch.Foundation;
+#endif
+#if __UNIFIED__
+using RectangleF = CoreGraphics.CGRect;
+using SizeF = CoreGraphics.CGSize;
+using PointF = CoreGraphics.CGPoint;
+
+#else
+using nfloat=System.Single;
+using nint=System.Int32;
+#endif
+
+namespace Xamarin.Forms.Platform.iOS
+{
+ internal class ContextActionsCell : UITableViewCell, INativeElementView
+ {
+ public const string Key = "ContextActionsCell";
+
+ static readonly UIImage DestructiveBackground;
+ static readonly UIImage NormalBackground;
+ readonly List<UIButton> _buttons = new List<UIButton>();
+ readonly List<MenuItem> _menuItems = new List<MenuItem>();
+
+ Cell _cell;
+ UIButton _moreButton;
+ UIScrollView _scroller;
+ UITableView _tableView;
+
+ static ContextActionsCell()
+ {
+ var rect = new RectangleF(0, 0, 1, 1);
+ var size = rect.Size;
+
+ UIGraphics.BeginImageContext(size);
+ var context = UIGraphics.GetCurrentContext();
+ context.SetFillColor(1, 0, 0, 1);
+ context.FillRect(rect);
+ DestructiveBackground = UIGraphics.GetImageFromCurrentImageContext();
+
+ context.SetFillColor(UIColor.LightGray.ToColor().ToCGColor());
+ context.FillRect(rect);
+
+ NormalBackground = UIGraphics.GetImageFromCurrentImageContext();
+
+ context.Dispose();
+ }
+
+ public ContextActionsCell() : base(UITableViewCellStyle.Default, Key)
+ {
+ }
+
+ public ContextActionsCell(string templateId) : base(UITableViewCellStyle.Default, Key + templateId)
+ {
+ }
+
+ public UITableViewCell ContentCell { get; private set; }
+
+ public bool IsOpen
+ {
+ get { return ScrollDelegate.IsOpen; }
+ }
+
+ ContextScrollViewDelegate ScrollDelegate
+ {
+ get { return (ContextScrollViewDelegate)_scroller.Delegate; }
+ }
+
+ Element INativeElementView.Element
+ {
+ get
+ {
+ var boxedCell = ContentCell as INativeElementView;
+ if (boxedCell == null)
+ {
+ throw new InvalidOperationException($"Implement {nameof(INativeElementView)} on cell renderer: {ContentCell.GetType().AssemblyQualifiedName}");
+ }
+
+ return boxedCell.Element;
+ }
+ }
+
+ public void Close()
+ {
+ _scroller.ContentOffset = new PointF(0, 0);
+ }
+
+ public override void LayoutSubviews()
+ {
+ base.LayoutSubviews();
+
+ if (_scroller == null || (_scroller != null && _scroller.Frame == Bounds))
+ return;
+
+ Update(_tableView, _cell, ContentCell);
+
+ _scroller.Frame = Bounds;
+ ContentCell.Frame = Bounds;
+
+ if (ContentCell is ViewCellRenderer.ViewTableCell && ContentCell.Subviews.Length > 0 && Math.Abs(ContentCell.Subviews[0].Frame.Height - Bounds.Height) > 1)
+ {
+ // Something goes weird inside iOS where LayoutSubviews wont get called when updating the bounds if the user
+ // forces us to flip flop between a ContextActionCell and a normal cell in the middle of actually displaying the cell
+ // so here we are going to hack it a forced update. Leave room for 1px of play because the border is 1 or .5px and must
+ // be accounted for.
+ //
+ // Fixes https://bugzilla.xamarin.com/show_bug.cgi?id=39450
+ ContentCell.LayoutSubviews();
+ }
+ }
+
+ public void PrepareForDeselect()
+ {
+ ScrollDelegate.PrepareForDeselect(_scroller);
+ }
+
+ public override SizeF SizeThatFits(SizeF size)
+ {
+ return ContentCell.SizeThatFits(size);
+ }
+
+ public void Update(UITableView tableView, Cell cell, UITableViewCell nativeCell)
+ {
+ var parentListView = cell.RealParent as ListView;
+ var recycling = parentListView != null && parentListView.CachingStrategy == ListViewCachingStrategy.RecycleElement;
+ if (_cell != cell && recycling)
+ {
+ if (_cell != null)
+ ((INotifyCollectionChanged)_cell.ContextActions).CollectionChanged -= OnContextItemsChanged;
+
+ ((INotifyCollectionChanged)cell.ContextActions).CollectionChanged += OnContextItemsChanged;
+ }
+
+ var height = Frame.Height;
+ var width = tableView.Frame.Width;
+
+ nativeCell.Frame = new RectangleF(0, 0, width, height);
+ nativeCell.SetNeedsLayout();
+
+ var handler = new PropertyChangedEventHandler(OnMenuItemPropertyChanged);
+
+ _tableView = tableView;
+ SetupSelection(tableView);
+
+ if (_cell != null)
+ {
+ if (!recycling)
+ _cell.PropertyChanged -= OnCellPropertyChanged;
+ if (_menuItems.Count > 0)
+ {
+ if (!recycling)
+ ((INotifyCollectionChanged)_cell.ContextActions).CollectionChanged -= OnContextItemsChanged;
+
+ foreach (var item in _menuItems)
+ item.PropertyChanged -= handler;
+ }
+
+ _menuItems.Clear();
+ }
+
+ _menuItems.AddRange(cell.ContextActions);
+
+ _cell = cell;
+ if (!recycling)
+ {
+ cell.PropertyChanged += OnCellPropertyChanged;
+ ((INotifyCollectionChanged)_cell.ContextActions).CollectionChanged += OnContextItemsChanged;
+ }
+
+ var isOpen = false;
+
+ if (_scroller == null)
+ {
+ _scroller = new UIScrollView(new RectangleF(0, 0, width, height));
+ _scroller.ScrollsToTop = false;
+ _scroller.ShowsHorizontalScrollIndicator = false;
+
+ if (Forms.IsiOS8OrNewer)
+ _scroller.PreservesSuperviewLayoutMargins = true;
+
+ ContentView.AddSubview(_scroller);
+ }
+ else
+ {
+ _scroller.Frame = new RectangleF(0, 0, width, height);
+ isOpen = ScrollDelegate.IsOpen;
+
+ for (var i = 0; i < _buttons.Count; i++)
+ {
+ var b = _buttons[i];
+ b.RemoveFromSuperview();
+ b.Dispose();
+ }
+
+ _buttons.Clear();
+
+ ScrollDelegate.Unhook(_scroller);
+ ScrollDelegate.Dispose();
+ }
+
+ if (ContentCell != nativeCell)
+ {
+ if (ContentCell != null)
+ {
+ ContentCell.RemoveFromSuperview();
+ ContentCell = null;
+ }
+
+ ContentCell = nativeCell;
+
+ //Hack: if we have a ImageCell the insets are slightly different,
+ //the inset numbers user below were taken using the Reveal app from the default cells
+ if ((ContentCell as CellTableViewCell)?.Cell is ImageCell)
+ {
+ nfloat imageCellInsetLeft = 57;
+ nfloat imageCellInsetRight = 0;
+ if (UIDevice.CurrentDevice.UserInterfaceIdiom == UIUserInterfaceIdiom.Pad)
+ {
+ imageCellInsetLeft = 89;
+ imageCellInsetRight = imageCellInsetLeft / 2;
+ }
+ SeparatorInset = new UIEdgeInsets(0, imageCellInsetLeft, 0, imageCellInsetRight);
+ }
+
+ _scroller.AddSubview(nativeCell);
+ }
+
+ SetupButtons(width, height);
+
+ UIView container = null;
+
+ var totalWidth = width;
+ for (var i = _buttons.Count - 1; i >= 0; i--)
+ {
+ var b = _buttons[i];
+ totalWidth += b.Frame.Width;
+
+ if (UIDevice.CurrentDevice.CheckSystemVersion(8, 0))
+ _scroller.AddSubview(b);
+ else
+ {
+ if (container == null)
+ {
+ container = new iOS7ButtonContainer(b.Frame.Width);
+ _scroller.InsertSubview(container, 0);
+ }
+
+ container.AddSubview(b);
+ }
+ }
+
+ _scroller.Delegate = new ContextScrollViewDelegate(container, _buttons, isOpen);
+ _scroller.ContentSize = new SizeF(totalWidth, height);
+
+ if (isOpen)
+ _scroller.SetContentOffset(new PointF(ScrollDelegate.ButtonsWidth, 0), false);
+ else
+ _scroller.SetContentOffset(new PointF(0, 0), false);
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ if (_scroller != null)
+ {
+ _scroller.Dispose();
+ _scroller = null;
+ }
+
+ _tableView = null;
+
+ if (_moreButton != null)
+ {
+ _moreButton.Dispose();
+ _moreButton = null;
+ }
+
+ for (var i = 0; i < _buttons.Count; i++)
+ _buttons[i].Dispose();
+
+ _buttons.Clear();
+ _menuItems.Clear();
+
+ if (_cell != null)
+ {
+ if (_cell.HasContextActions)
+ ((INotifyCollectionChanged)_cell.ContextActions).CollectionChanged -= OnContextItemsChanged;
+ _cell = null;
+ }
+ }
+
+ base.Dispose(disposing);
+ }
+
+ void ActivateMore()
+ {
+ var displayed = new HashSet<nint>();
+ for (var i = 0; i < _buttons.Count; i++)
+ {
+ var tag = _buttons[i].Tag;
+ if (tag >= 0)
+ displayed.Add(tag);
+ }
+
+ var frame = _moreButton.Frame;
+ if (!Forms.IsiOS8OrNewer)
+ {
+ var container = _moreButton.Superview;
+ frame = new RectangleF(container.Frame.X, 0, frame.Width, frame.Height);
+ }
+
+ var x = frame.X - _scroller.ContentOffset.X;
+
+ var path = _tableView.IndexPathForCell(this);
+ var rowPosition = _tableView.RectForRowAtIndexPath(path);
+ var sourceRect = new RectangleF(x, rowPosition.Y, rowPosition.Width, rowPosition.Height);
+
+ if (UIDevice.CurrentDevice.CheckSystemVersion(8, 0))
+ {
+ var actionSheet = new MoreActionSheetController();
+
+ for (var i = 0; i < _cell.ContextActions.Count; i++)
+ {
+ if (displayed.Contains(i))
+ continue;
+
+ var item = _cell.ContextActions[i];
+ var weakItem = new WeakReference<MenuItem>(item);
+ var action = UIAlertAction.Create(item.Text, UIAlertActionStyle.Default, a =>
+ {
+ _scroller.SetContentOffset(new PointF(0, 0), true);
+ MenuItem mi;
+ if (weakItem.TryGetTarget(out mi))
+ mi.Activate();
+ });
+ actionSheet.AddAction(action);
+ }
+
+ var controller = GetController();
+ if (controller == null)
+ throw new InvalidOperationException("No UIViewController found to present.");
+
+ if (UIDevice.CurrentDevice.UserInterfaceIdiom == UIUserInterfaceIdiom.Phone)
+ {
+ var cancel = UIAlertAction.Create(StringResources.Cancel, UIAlertActionStyle.Cancel, null);
+ actionSheet.AddAction(cancel);
+ }
+ else
+ {
+ actionSheet.PopoverPresentationController.SourceView = _tableView;
+ actionSheet.PopoverPresentationController.SourceRect = sourceRect;
+ }
+
+ controller.PresentViewController(actionSheet, true, null);
+ }
+ else
+ {
+ var d = new MoreActionSheetDelegate { Scroller = _scroller, Items = new List<MenuItem>() };
+
+ var actionSheet = new UIActionSheet(null, d);
+
+ for (var i = 0; i < _cell.ContextActions.Count; i++)
+ {
+ if (displayed.Contains(i))
+ continue;
+
+ var item = _cell.ContextActions[i];
+ d.Items.Add(item);
+ actionSheet.AddButton(item.Text);
+ }
+
+ if (UIDevice.CurrentDevice.UserInterfaceIdiom == UIUserInterfaceIdiom.Phone)
+ {
+ var index = actionSheet.AddButton(StringResources.Cancel);
+ actionSheet.CancelButtonIndex = index;
+ }
+
+ actionSheet.ShowFrom(sourceRect, _tableView, true);
+ }
+ }
+
+ void CullButtons(nfloat acceptableTotalWidth, ref bool needMoreButton, ref nfloat largestButtonWidth)
+ {
+ while (largestButtonWidth * (_buttons.Count + (needMoreButton ? 1 : 0)) > acceptableTotalWidth && _buttons.Count > 1)
+ {
+ needMoreButton = true;
+
+ var button = _buttons[_buttons.Count - 1];
+ _buttons.RemoveAt(_buttons.Count - 1);
+
+ if (largestButtonWidth == button.Frame.Width)
+ largestButtonWidth = GetLargestWidth();
+ }
+
+ if (needMoreButton && _cell.ContextActions.Count - _buttons.Count == 1)
+ _buttons.RemoveAt(_buttons.Count - 1);
+ }
+
+ UIButton GetButton(MenuItem item)
+ {
+ var button = new UIButton(new RectangleF(0, 0, 1, 1));
+
+ if (!item.IsDestructive)
+ button.SetBackgroundImage(NormalBackground, UIControlState.Normal);
+ else
+ button.SetBackgroundImage(DestructiveBackground, UIControlState.Normal);
+
+ button.SetTitle(item.Text, UIControlState.Normal);
+ button.TitleEdgeInsets = new UIEdgeInsets(0, 15, 0, 15);
+
+ button.Enabled = item.IsEnabled;
+
+ return button;
+ }
+
+ UIViewController GetController()
+ {
+ Element e = _cell;
+ while (e.RealParent != null)
+ {
+ var renderer = Platform.GetRenderer((VisualElement)e.RealParent);
+ if (renderer.ViewController != null)
+ return renderer.ViewController;
+
+ e = e.RealParent;
+ }
+
+ return null;
+ }
+
+ nfloat GetLargestWidth()
+ {
+ nfloat largestWidth = 0;
+ for (var i = 0; i < _buttons.Count; i++)
+ {
+ var frame = _buttons[i].Frame;
+ if (frame.Width > largestWidth)
+ largestWidth = frame.Width;
+ }
+
+ return largestWidth;
+ }
+
+ void OnButtonActivated(object sender, EventArgs e)
+ {
+ var button = (UIButton)sender;
+ if (button.Tag == -1)
+ ActivateMore();
+ else
+ {
+ _scroller.SetContentOffset(new PointF(0, 0), true);
+ _cell.ContextActions[(int)button.Tag].Activate();
+ }
+ }
+
+ void OnCellPropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ if (e.PropertyName == "HasContextActions")
+ {
+ var parentListView = _cell.RealParent as ListView;
+ var recycling = parentListView != null && parentListView.CachingStrategy == ListViewCachingStrategy.RecycleElement;
+ if (!recycling)
+ ReloadRow();
+ }
+ }
+
+ void OnContextItemsChanged(object sender, NotifyCollectionChangedEventArgs e)
+ {
+ var parentListView = _cell.RealParent as ListView;
+ var recycling = parentListView != null && parentListView.CachingStrategy == ListViewCachingStrategy.RecycleElement;
+ if (recycling)
+ Update(_tableView, _cell, ContentCell);
+ else
+ ReloadRow();
+ // TODO: Perhaps make this nicer if it's open while adding
+ }
+
+ void OnMenuItemPropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ var parentListView = _cell.RealParent as ListView;
+ var recycling = parentListView != null && parentListView.CachingStrategy == ListViewCachingStrategy.RecycleElement;
+ if (recycling)
+ Update(_tableView, _cell, ContentCell);
+ else
+ ReloadRow();
+ }
+
+ void ReloadRow()
+ {
+ if (_scroller.ContentOffset.X > 0)
+ {
+ ((ContextScrollViewDelegate)_scroller.Delegate).ClosedCallback = () =>
+ {
+ ReloadRowCore();
+ ((ContextScrollViewDelegate)_scroller.Delegate).ClosedCallback = null;
+ };
+
+ _scroller.SetContentOffset(new PointF(0, 0), true);
+ }
+ else
+ ReloadRowCore();
+ }
+
+ void ReloadRowCore()
+ {
+ if (_cell.RealParent == null)
+ return;
+
+ var path = _cell.GetIndexPath();
+
+ var selected = path.Equals(_tableView.IndexPathForSelectedRow);
+
+ _tableView.ReloadRows(new[] { path }, UITableViewRowAnimation.None);
+
+ if (selected)
+ {
+ _tableView.SelectRow(path, false, UITableViewScrollPosition.None);
+ _tableView.Source.RowSelected(_tableView, path);
+ }
+ }
+
+ UIView SetupButtons(nfloat width, nfloat height)
+ {
+ MenuItem destructive = null;
+ nfloat largestWidth = 0, acceptableSize = width * 0.80f;
+
+ for (var i = 0; i < _cell.ContextActions.Count; i++)
+ {
+ var item = _cell.ContextActions[i];
+
+ if (_buttons.Count == 3)
+ {
+ if (destructive != null)
+ break;
+ if (!item.IsDestructive)
+ continue;
+
+ _buttons.RemoveAt(_buttons.Count - 1);
+ }
+
+ if (item.IsDestructive)
+ destructive = item;
+
+ var button = GetButton(item);
+ button.Tag = i;
+ var buttonWidth = button.TitleLabel.SizeThatFits(new SizeF(width, height)).Width + 30;
+ if (buttonWidth > largestWidth)
+ largestWidth = buttonWidth;
+
+ if (destructive == item)
+ _buttons.Insert(0, button);
+ else
+ _buttons.Add(button);
+ }
+
+ var needMore = _cell.ContextActions.Count > _buttons.Count;
+
+ if (_cell.ContextActions.Count > 2)
+ CullButtons(acceptableSize, ref needMore, ref largestWidth);
+
+ var resize = false;
+ if (needMore)
+ {
+ if (largestWidth * 2 > acceptableSize)
+ {
+ largestWidth = acceptableSize / 2;
+ resize = true;
+ }
+
+ var button = new UIButton(new RectangleF(0, 0, largestWidth, height));
+ button.SetBackgroundImage(NormalBackground, UIControlState.Normal);
+ button.TitleEdgeInsets = new UIEdgeInsets(0, 15, 0, 15);
+ button.SetTitle(StringResources.More, UIControlState.Normal);
+
+ var moreWidth = button.TitleLabel.SizeThatFits(new SizeF(width, height)).Width + 30;
+ if (moreWidth > largestWidth)
+ {
+ largestWidth = moreWidth;
+ CullButtons(acceptableSize, ref needMore, ref largestWidth);
+
+ if (largestWidth * 2 > acceptableSize)
+ {
+ largestWidth = acceptableSize / 2;
+ resize = true;
+ }
+ }
+
+ button.Tag = -1;
+ button.TouchUpInside += OnButtonActivated;
+ if (resize)
+ button.TitleLabel.AdjustsFontSizeToFitWidth = true;
+
+ _moreButton = button;
+ _buttons.Add(button);
+ }
+
+ var handler = new PropertyChangedEventHandler(OnMenuItemPropertyChanged);
+ var totalWidth = _buttons.Count * largestWidth;
+ for (var n = 0; n < _buttons.Count; n++)
+ {
+ var b = _buttons[n];
+
+ if (b.Tag >= 0)
+ {
+ var item = _cell.ContextActions[(int)b.Tag];
+ item.PropertyChanged += handler;
+ }
+
+ var offset = (n + 1) * largestWidth;
+
+ var x = width - offset;
+ if (UIDevice.CurrentDevice.CheckSystemVersion(8, 0))
+ x += totalWidth;
+
+ b.Frame = new RectangleF(x, 0, largestWidth, height);
+ if (resize)
+ b.TitleLabel.AdjustsFontSizeToFitWidth = true;
+
+ b.SetNeedsLayout();
+
+ if (b != _moreButton)
+ b.TouchUpInside += OnButtonActivated;
+ }
+
+ return null;
+ }
+
+ void SetupSelection(UITableView table)
+ {
+ for (var i = 0; i < table.GestureRecognizers.Length; i++)
+ {
+ var r = table.GestureRecognizers[i] as SelectGestureRecognizer;
+ if (r != null)
+ return;
+ }
+
+ _tableView.AddGestureRecognizer(new SelectGestureRecognizer());
+ }
+
+ class SelectGestureRecognizer : UITapGestureRecognizer
+ {
+ NSIndexPath _lastPath;
+
+ public SelectGestureRecognizer() : base(Tapped)
+ {
+ ShouldReceiveTouch = (recognizer, touch) =>
+ {
+ var table = (UITableView)View;
+ var pos = touch.LocationInView(table);
+
+ _lastPath = table.IndexPathForRowAtPoint(pos);
+ if (_lastPath == null)
+ return false;
+
+ var cell = table.CellAt(_lastPath) as ContextActionsCell;
+
+ return cell != null;
+ };
+ }
+
+ static void Tapped(UIGestureRecognizer recognizer)
+ {
+ var selector = (SelectGestureRecognizer)recognizer;
+
+ var table = (UITableView)recognizer.View;
+
+ if (!selector._lastPath.Equals(table.IndexPathForSelectedRow))
+ table.SelectRow(selector._lastPath, false, UITableViewScrollPosition.None);
+ table.Source.RowSelected(table, selector._lastPath);
+ }
+ }
+
+ class MoreActionSheetController : UIAlertController
+ {
+ public override UIAlertControllerStyle PreferredStyle
+ {
+ get { return UIAlertControllerStyle.ActionSheet; }
+ }
+
+ public override void WillRotate(UIInterfaceOrientation toInterfaceOrientation, double duration)
+ {
+ DismissViewController(false, null);
+ }
+ }
+
+ class MoreActionSheetDelegate : UIActionSheetDelegate
+ {
+ public List<MenuItem> Items;
+ public UIScrollView Scroller;
+
+ public override void Clicked(UIActionSheet actionSheet, nint buttonIndex)
+ {
+ if (buttonIndex == Items.Count)
+ return; // Cancel button
+
+ Scroller.SetContentOffset(new PointF(0, 0), true);
+
+ // do not activate a -1 index when dismissing by clicking outside the popover
+ if (buttonIndex >= 0)
+ Items[(int)buttonIndex].Activate();
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.iOS/ContextScrollViewDelegate.cs b/Xamarin.Forms.Platform.iOS/ContextScrollViewDelegate.cs
new file mode 100644
index 00000000..47ce0714
--- /dev/null
+++ b/Xamarin.Forms.Platform.iOS/ContextScrollViewDelegate.cs
@@ -0,0 +1,298 @@
+using System;
+using System.Drawing;
+using System.Collections.Generic;
+#if __UNIFIED__
+using UIKit;
+using Foundation;
+#else
+using MonoTouch.UIKit;
+using MonoTouch.Foundation;
+#endif
+#if __UNIFIED__
+using NSAction = System.Action;
+using RectangleF = CoreGraphics.CGRect;
+using SizeF = CoreGraphics.CGSize;
+using PointF = CoreGraphics.CGPoint;
+
+#else
+using nfloat=System.Single;
+using nint=System.Int32;
+#endif
+
+namespace Xamarin.Forms.Platform.iOS
+{
+ internal class iOS7ButtonContainer : UIView
+ {
+ readonly nfloat _buttonWidth;
+
+ public iOS7ButtonContainer(nfloat buttonWidth) : base(new RectangleF(0, 0, 0, 0))
+ {
+ _buttonWidth = buttonWidth;
+ ClipsToBounds = true;
+ }
+
+ public override void LayoutSubviews()
+ {
+ var width = Frame.Width;
+ nfloat takenSpace = 0;
+
+ for (var i = 0; i < Subviews.Length; i++)
+ {
+ var view = Subviews[i];
+
+ var pos = Subviews.Length - i;
+ var x = width - _buttonWidth * pos;
+ view.Frame = new RectangleF(x, 0, view.Frame.Width, view.Frame.Height);
+
+ takenSpace += view.Frame.Width;
+ }
+ }
+ }
+
+ internal class ContextScrollViewDelegate : UIScrollViewDelegate
+ {
+ readonly nfloat _finalButtonSize;
+ UIView _backgroundView;
+ List<UIButton> _buttons;
+ UITapGestureRecognizer _closer;
+ UIView _container;
+ GlobalCloseContextGestureRecognizer _globalCloser;
+
+ bool _isDisposed;
+
+ UITableView _table;
+
+ public ContextScrollViewDelegate(UIView container, List<UIButton> buttons, bool isOpen)
+ {
+ IsOpen = isOpen;
+ _container = container;
+ _buttons = buttons;
+
+ for (var i = 0; i < buttons.Count; i++)
+ {
+ var b = buttons[i];
+ b.Hidden = !isOpen;
+
+ ButtonsWidth += b.Frame.Width;
+ _finalButtonSize = b.Frame.Width;
+ }
+ }
+
+ public nfloat ButtonsWidth { get; }
+
+ public Action ClosedCallback { get; set; }
+
+ public bool IsOpen { get; private set; }
+
+ public override void DraggingStarted(UIScrollView scrollView)
+ {
+ if (!IsOpen)
+ SetButtonsShowing(true);
+
+ var cell = GetContextCell(scrollView);
+ if (!cell.Selected)
+ return;
+
+ if (!IsOpen)
+ RemoveHighlight(scrollView);
+ }
+
+ public void PrepareForDeselect(UIScrollView scrollView)
+ {
+ RestoreHighlight(scrollView);
+ }
+
+ public override void Scrolled(UIScrollView scrollView)
+ {
+ var width = _finalButtonSize;
+ var count = _buttons.Count;
+
+ if (!UIDevice.CurrentDevice.CheckSystemVersion(8, 0))
+ _container.Frame = new RectangleF(scrollView.Frame.Width, 0, scrollView.ContentOffset.X, scrollView.Frame.Height);
+ else
+ {
+ var ioffset = scrollView.ContentOffset.X / (float)count;
+
+ if (ioffset > width)
+ width = ioffset + 1;
+
+ for (var i = count - 1; i >= 0; i--)
+ {
+ var b = _buttons[i];
+ var rect = b.Frame;
+ b.Frame = new RectangleF(scrollView.Frame.Width + (count - (i + 1)) * ioffset, 0, width, rect.Height);
+ }
+ }
+
+ if (scrollView.ContentOffset.X == 0)
+ {
+ IsOpen = false;
+ SetButtonsShowing(false);
+ RestoreHighlight(scrollView);
+
+ ClearCloserRecognizer(scrollView);
+
+ if (ClosedCallback != null)
+ ClosedCallback();
+ }
+ }
+
+ public void Unhook(UIScrollView scrollView)
+ {
+ RestoreHighlight(scrollView);
+ ClearCloserRecognizer(scrollView);
+ }
+
+ public override void WillEndDragging(UIScrollView scrollView, PointF velocity, ref PointF targetContentOffset)
+ {
+ var width = ButtonsWidth;
+ var x = targetContentOffset.X;
+ var parentThreshold = scrollView.Frame.Width * .4f;
+ var contentThreshold = width * .8f;
+
+ if (x >= parentThreshold || x >= contentThreshold)
+ {
+ IsOpen = true;
+ targetContentOffset = new PointF(width, 0);
+ RemoveHighlight(scrollView);
+
+ if (_globalCloser == null)
+ {
+ UIView view = scrollView;
+ while (view.Superview != null)
+ {
+ view = view.Superview;
+
+ NSAction close = () =>
+ {
+ if (UIDevice.CurrentDevice.CheckSystemVersion(8, 0))
+ RestoreHighlight(scrollView);
+
+ IsOpen = false;
+ scrollView.SetContentOffset(new PointF(0, 0), true);
+
+ ClearCloserRecognizer(scrollView);
+ };
+
+ var table = view as UITableView;
+ if (table != null)
+ {
+ _table = table;
+ _globalCloser = new GlobalCloseContextGestureRecognizer(scrollView, _buttons, close);
+ _globalCloser.ShouldRecognizeSimultaneously = (recognizer, r) => r == _table.PanGestureRecognizer;
+ table.AddGestureRecognizer(_globalCloser);
+
+ _closer = new UITapGestureRecognizer(close);
+ var cell = GetContextCell(scrollView);
+ cell.ContentCell.AddGestureRecognizer(_closer);
+ }
+ }
+ }
+ }
+ else
+ {
+ ClearCloserRecognizer(scrollView);
+
+ IsOpen = false;
+ targetContentOffset = new PointF(0, 0);
+
+ if (UIDevice.CurrentDevice.CheckSystemVersion(8, 0))
+ RestoreHighlight(scrollView);
+ }
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ if (_isDisposed)
+ return;
+
+ _isDisposed = true;
+
+ if (disposing)
+ {
+ ClosedCallback = null;
+
+ _table = null;
+ _backgroundView = null;
+ _container = null;
+
+ _buttons = null;
+ }
+
+ base.Dispose(disposing);
+ }
+
+ void ClearCloserRecognizer(UIScrollView scrollView)
+ {
+ if (_globalCloser == null)
+ return;
+
+ var cell = GetContextCell(scrollView);
+ cell.ContentCell.RemoveGestureRecognizer(_closer);
+ _closer.Dispose();
+ _closer = null;
+
+ _table.RemoveGestureRecognizer(_globalCloser);
+ _table = null;
+ _globalCloser.Dispose();
+ _globalCloser = null;
+ }
+
+ ContextActionsCell GetContextCell(UIScrollView scrollView)
+ {
+ var view = scrollView.Superview.Superview;
+ var cell = view as ContextActionsCell;
+ while (view.Superview != null)
+ {
+ cell = view as ContextActionsCell;
+ if (cell != null)
+ break;
+
+ view = view.Superview;
+ }
+
+ return cell;
+ }
+
+ void RemoveHighlight(UIScrollView scrollView)
+ {
+ var subviews = scrollView.Superview.Superview.Subviews;
+
+ var count = 0;
+ for (var i = 0; i < subviews.Length; i++)
+ {
+ var s = subviews[i];
+ if (s.Frame.Height > 1)
+ count++;
+ }
+
+ if (count <= 1)
+ return;
+
+ _backgroundView = subviews[0];
+ _backgroundView.RemoveFromSuperview();
+
+ var cell = GetContextCell(scrollView);
+ cell.SelectionStyle = UITableViewCellSelectionStyle.None;
+ }
+
+ void RestoreHighlight(UIScrollView scrollView)
+ {
+ if (_backgroundView == null)
+ return;
+
+ var cell = GetContextCell(scrollView);
+ cell.SelectionStyle = UITableViewCellSelectionStyle.Default;
+ cell.SetSelected(true, false);
+
+ scrollView.Superview.Superview.InsertSubview(_backgroundView, 0);
+ _backgroundView = null;
+ }
+
+ void SetButtonsShowing(bool show)
+ {
+ for (var i = 0; i < _buttons.Count; i++)
+ _buttons[i].Hidden = !show;
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.iOS/Deserializer.cs b/Xamarin.Forms.Platform.iOS/Deserializer.cs
new file mode 100644
index 00000000..bdab44df
--- /dev/null
+++ b/Xamarin.Forms.Platform.iOS/Deserializer.cs
@@ -0,0 +1,89 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO.IsolatedStorage;
+using System.Runtime.Serialization;
+using System.Threading.Tasks;
+using System.Xml;
+
+namespace Xamarin.Forms.Platform.iOS
+{
+ internal class Deserializer : IDeserializer
+ {
+ const string PropertyStoreFile = "PropertyStore.forms";
+
+ public Task<IDictionary<string, object>> DeserializePropertiesAsync()
+ {
+ // Deserialize property dictionary to local storage
+ // Make sure to use Internal
+ return Task.Run(() =>
+ {
+ using(var store = IsolatedStorageFile.GetUserStoreForApplication())
+ using(var stream = store.OpenFile(PropertyStoreFile, System.IO.FileMode.OpenOrCreate))
+ using(var reader = XmlDictionaryReader.CreateBinaryReader(stream, XmlDictionaryReaderQuotas.Max))
+ {
+ if (stream.Length == 0)
+ return null;
+
+ try
+ {
+ var dcs = new DataContractSerializer(typeof(Dictionary<string, object>));
+ return (IDictionary<string, object>)dcs.ReadObject(reader);
+ }
+ catch (Exception e)
+ {
+ Debug.WriteLine("Could not deserialize properties: " + e.Message);
+ Log.Warning("Xamarin.Forms PropertyStore", $"Exception while reading Application properties: {e}");
+ }
+ }
+
+ return null;
+ });
+ }
+
+ public Task SerializePropertiesAsync(IDictionary<string, object> properties)
+ {
+ properties = new Dictionary<string, object>(properties);
+ // Serialize property dictionary to local storage
+ // Make sure to use Internal
+ return Task.Run(() =>
+ {
+ var success = false;
+ using(var store = IsolatedStorageFile.GetUserStoreForApplication())
+ using(var stream = store.OpenFile(PropertyStoreFile + ".tmp", System.IO.FileMode.OpenOrCreate))
+ using(var writer = XmlDictionaryWriter.CreateBinaryWriter(stream))
+ {
+ try
+ {
+ var dcs = new DataContractSerializer(typeof(Dictionary<string, object>));
+ dcs.WriteObject(writer, properties);
+ writer.Flush();
+ success = true;
+ }
+ catch (Exception e)
+ {
+ Debug.WriteLine("Could not serialize properties: " + e.Message);
+ Log.Warning("Xamarin.Forms PropertyStore", $"Exception while writing Application properties: {e}");
+ }
+ }
+
+ if (!success)
+ return;
+ using(var store = IsolatedStorageFile.GetUserStoreForApplication())
+ {
+ try
+ {
+ if (store.FileExists(PropertyStoreFile))
+ store.DeleteFile(PropertyStoreFile);
+ store.MoveFile(PropertyStoreFile + ".tmp", PropertyStoreFile);
+ }
+ catch (Exception e)
+ {
+ Debug.WriteLine("Could not move new serialized property file over old: " + e.Message);
+ Log.Warning("Xamarin.Forms PropertyStore", $"Exception while writing Application properties: {e}");
+ }
+ }
+ });
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.iOS/ElementChangedEventArgs.cs b/Xamarin.Forms.Platform.iOS/ElementChangedEventArgs.cs
new file mode 100644
index 00000000..560cfd1c
--- /dev/null
+++ b/Xamarin.Forms.Platform.iOS/ElementChangedEventArgs.cs
@@ -0,0 +1,24 @@
+using System;
+
+namespace Xamarin.Forms.Platform.iOS
+{
+ public class VisualElementChangedEventArgs : ElementChangedEventArgs<VisualElement>
+ {
+ public VisualElementChangedEventArgs(VisualElement oldElement, VisualElement newElement) : base(oldElement, newElement)
+ {
+ }
+ }
+
+ public class ElementChangedEventArgs<TElement> : EventArgs where TElement : Element
+ {
+ public ElementChangedEventArgs(TElement oldElement, TElement newElement)
+ {
+ OldElement = oldElement;
+ NewElement = newElement;
+ }
+
+ public TElement NewElement { get; private set; }
+
+ public TElement OldElement { get; private set; }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.iOS/EventTracker.cs b/Xamarin.Forms.Platform.iOS/EventTracker.cs
new file mode 100644
index 00000000..16ccd505
--- /dev/null
+++ b/Xamarin.Forms.Platform.iOS/EventTracker.cs
@@ -0,0 +1,303 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Collections.Specialized;
+using System.Linq;
+#if __UNIFIED__
+using UIKit;
+#else
+using MonoTouch.UIKit;
+#endif
+#if __UNIFIED__
+using RectangleF = CoreGraphics.CGRect;
+using SizeF = CoreGraphics.CGSize;
+using PointF = CoreGraphics.CGPoint;
+
+#else
+using nfloat=System.Single;
+using nint=System.Int32;
+using nuint=System.UInt32;
+#endif
+
+namespace Xamarin.Forms.Platform.iOS
+{
+ public class EventTracker : IDisposable
+ {
+ readonly NotifyCollectionChangedEventHandler _collectionChangedHandler;
+
+ readonly Dictionary<IGestureRecognizer, UIGestureRecognizer> _gestureRecognizers = new Dictionary<IGestureRecognizer, UIGestureRecognizer>();
+
+ readonly IVisualElementRenderer _renderer;
+ bool _disposed;
+ UIView _handler;
+
+ double _previousScale = 1.0;
+ UITouchEventArgs _shouldReceive;
+
+ public EventTracker(IVisualElementRenderer renderer)
+ {
+ if (renderer == null)
+ throw new ArgumentNullException("renderer");
+
+ _collectionChangedHandler = ModelGestureRecognizersOnCollectionChanged;
+
+ _renderer = renderer;
+ _renderer.ElementChanged += OnElementChanged;
+ }
+
+ ObservableCollection<IGestureRecognizer> ElementGestureRecognizers
+ {
+ get
+ {
+ if (_renderer?.Element is View)
+ return ((View)_renderer.Element).GestureRecognizers as ObservableCollection<IGestureRecognizer>;
+ return null;
+ }
+ }
+
+ public void Dispose()
+ {
+ if (_disposed)
+ return;
+
+ _disposed = true;
+
+ foreach (var kvp in _gestureRecognizers)
+ {
+ _handler.RemoveGestureRecognizer(kvp.Value);
+ kvp.Value.Dispose();
+ }
+
+ _gestureRecognizers.Clear();
+
+ if (ElementGestureRecognizers != null)
+ ElementGestureRecognizers.CollectionChanged -= _collectionChangedHandler;
+
+ _handler = null;
+ }
+
+ public void LoadEvents(UIView handler)
+ {
+ if (_disposed)
+ throw new ObjectDisposedException(null);
+
+ _shouldReceive = (r, t) => t.View is IVisualElementRenderer;
+
+ _handler = handler;
+ OnElementChanged(this, new VisualElementChangedEventArgs(null, _renderer.Element));
+ }
+
+ protected virtual UIGestureRecognizer GetNativeRecognizer(IGestureRecognizer recognizer)
+ {
+ if (recognizer == null)
+ return null;
+
+ var weakRecognizer = new WeakReference(recognizer);
+ var weakEventTracker = new WeakReference(this);
+
+ var tapRecognizer = recognizer as TapGestureRecognizer;
+ if (tapRecognizer != null)
+ {
+ var uiRecognizer = CreateTapRecognizer(1, tapRecognizer.NumberOfTapsRequired, r =>
+ {
+ var tapGestureRecognizer = weakRecognizer.Target as TapGestureRecognizer;
+ var eventTracker = weakEventTracker.Target as EventTracker;
+ var view = eventTracker?._renderer?.Element as View;
+
+ if (tapGestureRecognizer != null && view != null)
+ tapGestureRecognizer.SendTapped(view);
+ });
+ return uiRecognizer;
+ }
+
+ var pinchRecognizer = recognizer as PinchGestureRecognizer;
+ if (pinchRecognizer != null)
+ {
+ double startingScale = 1;
+ var uiRecognizer = CreatePinchRecognizer(r =>
+ {
+ var pinchGestureRecognizer = weakRecognizer.Target as IPinchGestureController;
+ var eventTracker = weakEventTracker.Target as EventTracker;
+ var view = eventTracker?._renderer?.Element as View;
+
+ if (pinchGestureRecognizer != null && eventTracker != null && view != null)
+ {
+ var oldScale = eventTracker._previousScale;
+ var originPoint = r.LocationInView(null);
+ originPoint = UIApplication.SharedApplication.KeyWindow.ConvertPointToView(originPoint, eventTracker._renderer.NativeView);
+ var scaledPoint = new Point(originPoint.X / view.Width, originPoint.Y / view.Height);
+
+ switch (r.State)
+ {
+ case UIGestureRecognizerState.Began:
+ if (r.NumberOfTouches < 2)
+ return;
+ pinchGestureRecognizer.SendPinchStarted(view, scaledPoint);
+ startingScale = view.Scale;
+ break;
+ case UIGestureRecognizerState.Changed:
+ if (r.NumberOfTouches < 2 && pinchGestureRecognizer.IsPinching)
+ {
+ r.State = UIGestureRecognizerState.Ended;
+ pinchGestureRecognizer.SendPinchEnded(view);
+ return;
+ }
+
+ var delta = 1.0;
+ var dif = Math.Abs(r.Scale - oldScale) * startingScale;
+ if (oldScale < r.Scale)
+ delta = 1 + dif;
+ if (oldScale > r.Scale)
+ delta = 1 - dif;
+
+ pinchGestureRecognizer.SendPinch(view, delta, scaledPoint);
+ eventTracker._previousScale = r.Scale;
+ break;
+ case UIGestureRecognizerState.Cancelled:
+ case UIGestureRecognizerState.Failed:
+ if (pinchGestureRecognizer.IsPinching)
+ pinchGestureRecognizer.SendPinchCanceled(view);
+ break;
+ case UIGestureRecognizerState.Ended:
+ if (pinchGestureRecognizer.IsPinching)
+ pinchGestureRecognizer.SendPinchEnded(view);
+ eventTracker._previousScale = 1;
+ break;
+ }
+ }
+ });
+ return uiRecognizer;
+ }
+
+ var panRecognizer = recognizer as PanGestureRecognizer;
+ if (panRecognizer != null)
+ {
+ var uiRecognizer = CreatePanRecognizer(panRecognizer.TouchPoints, r =>
+ {
+ var eventTracker = weakEventTracker.Target as EventTracker;
+ var view = eventTracker?._renderer?.Element as View;
+
+ var panGestureRecognizer = weakRecognizer.Target as IPanGestureController;
+ if (panGestureRecognizer != null && view != null)
+ {
+ switch (r.State)
+ {
+ case UIGestureRecognizerState.Began:
+ if (r.NumberOfTouches != panRecognizer.TouchPoints)
+ return;
+ panGestureRecognizer.SendPanStarted(view, Application.Current.PanGestureId);
+ break;
+ case UIGestureRecognizerState.Changed:
+ if (r.NumberOfTouches != panRecognizer.TouchPoints)
+ {
+ r.State = UIGestureRecognizerState.Ended;
+ panGestureRecognizer.SendPanCompleted(view, Application.Current.PanGestureId);
+ Application.Current.PanGestureId++;
+ return;
+ }
+ var translationInView = r.TranslationInView(_handler);
+ panGestureRecognizer.SendPan(view, translationInView.X, translationInView.Y, Application.Current.PanGestureId);
+ break;
+ case UIGestureRecognizerState.Cancelled:
+ case UIGestureRecognizerState.Failed:
+ panGestureRecognizer.SendPanCanceled(view, Application.Current.PanGestureId);
+ Application.Current.PanGestureId++;
+ break;
+ case UIGestureRecognizerState.Ended:
+ if (r.NumberOfTouches != panRecognizer.TouchPoints)
+ {
+ panGestureRecognizer.SendPanCompleted(view, Application.Current.PanGestureId);
+ Application.Current.PanGestureId++;
+ }
+ break;
+ }
+ }
+ });
+ return uiRecognizer;
+ }
+
+ return null;
+ }
+
+ UIPanGestureRecognizer CreatePanRecognizer(int numTouches, Action<UIPanGestureRecognizer> action)
+ {
+ var result = new UIPanGestureRecognizer(action);
+ result.MinimumNumberOfTouches = result.MaximumNumberOfTouches = (uint)numTouches;
+ return result;
+ }
+
+ UIPinchGestureRecognizer CreatePinchRecognizer(Action<UIPinchGestureRecognizer> action)
+ {
+ var result = new UIPinchGestureRecognizer(action);
+ return result;
+ }
+
+ UITapGestureRecognizer CreateTapRecognizer(int numFingers, int numTaps, Action<UITapGestureRecognizer> action)
+ {
+ var result = new UITapGestureRecognizer(action);
+ result.NumberOfTouchesRequired = (uint)numFingers;
+ result.NumberOfTapsRequired = (uint)numTaps;
+ return result;
+ }
+
+ void LoadRecognizers()
+ {
+ if (ElementGestureRecognizers == null)
+ return;
+
+ foreach (var recognizer in ElementGestureRecognizers)
+ {
+ if (_gestureRecognizers.ContainsKey(recognizer))
+ continue;
+
+ var nativeRecognizer = GetNativeRecognizer(recognizer);
+ if (nativeRecognizer != null)
+ {
+ nativeRecognizer.ShouldReceiveTouch = _shouldReceive;
+ _handler.AddGestureRecognizer(nativeRecognizer);
+
+ _gestureRecognizers[recognizer] = nativeRecognizer;
+ }
+ }
+
+ var toRemove = _gestureRecognizers.Keys.Where(key => !ElementGestureRecognizers.Contains(key)).ToArray();
+ foreach (var gestureRecognizer in toRemove)
+ {
+ var uiRecognizer = _gestureRecognizers[gestureRecognizer];
+ _gestureRecognizers.Remove(gestureRecognizer);
+
+ _handler.RemoveGestureRecognizer(uiRecognizer);
+ uiRecognizer.Dispose();
+ }
+ }
+
+ void ModelGestureRecognizersOnCollectionChanged(object sender, NotifyCollectionChangedEventArgs notifyCollectionChangedEventArgs)
+ {
+ LoadRecognizers();
+ }
+
+ void OnElementChanged(object sender, VisualElementChangedEventArgs e)
+ {
+ if (e.OldElement != null)
+ {
+ // unhook
+ var oldView = e.OldElement as View;
+ if (oldView != null)
+ {
+ var oldRecognizers = (ObservableCollection<IGestureRecognizer>)oldView.GestureRecognizers;
+ oldRecognizers.CollectionChanged -= _collectionChangedHandler;
+ }
+ }
+
+ if (e.NewElement != null)
+ {
+ // hook
+ if (ElementGestureRecognizers != null)
+ {
+ ElementGestureRecognizers.CollectionChanged += _collectionChangedHandler;
+ LoadRecognizers();
+ }
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.iOS/ExportCellAttribute.cs b/Xamarin.Forms.Platform.iOS/ExportCellAttribute.cs
new file mode 100644
index 00000000..96aec6cb
--- /dev/null
+++ b/Xamarin.Forms.Platform.iOS/ExportCellAttribute.cs
@@ -0,0 +1,12 @@
+using System;
+
+namespace Xamarin.Forms
+{
+ [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
+ public sealed class ExportCellAttribute : HandlerAttribute
+ {
+ public ExportCellAttribute(Type handler, Type target) : base(handler, target)
+ {
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.iOS/ExportImageSourceHandlerAttribute.cs b/Xamarin.Forms.Platform.iOS/ExportImageSourceHandlerAttribute.cs
new file mode 100644
index 00000000..4c5c01fb
--- /dev/null
+++ b/Xamarin.Forms.Platform.iOS/ExportImageSourceHandlerAttribute.cs
@@ -0,0 +1,12 @@
+using System;
+
+namespace Xamarin.Forms
+{
+ [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
+ public sealed class ExportImageSourceHandlerAttribute : HandlerAttribute
+ {
+ public ExportImageSourceHandlerAttribute(Type handler, Type target) : base(handler, target)
+ {
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.iOS/ExportRendererAttribute.cs b/Xamarin.Forms.Platform.iOS/ExportRendererAttribute.cs
new file mode 100644
index 00000000..50bba17f
--- /dev/null
+++ b/Xamarin.Forms.Platform.iOS/ExportRendererAttribute.cs
@@ -0,0 +1,29 @@
+using System;
+using UIKit;
+
+namespace Xamarin.Forms
+{
+ [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
+ public sealed class ExportRendererAttribute : HandlerAttribute
+ {
+ public ExportRendererAttribute(Type handler, Type target, UIUserInterfaceIdiom idiom) : base(handler, target)
+ {
+ Idiomatic = true;
+ Idiom = idiom;
+ }
+
+ public ExportRendererAttribute(Type handler, Type target) : base(handler, target)
+ {
+ Idiomatic = false;
+ }
+
+ internal UIUserInterfaceIdiom Idiom { get; }
+
+ internal bool Idiomatic { get; }
+
+ public override bool ShouldRegister()
+ {
+ return !Idiomatic || Idiom == UIDevice.CurrentDevice.UserInterfaceIdiom;
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.iOS/Extensions/ArrayExtensions.cs b/Xamarin.Forms.Platform.iOS/Extensions/ArrayExtensions.cs
new file mode 100644
index 00000000..9adafecb
--- /dev/null
+++ b/Xamarin.Forms.Platform.iOS/Extensions/ArrayExtensions.cs
@@ -0,0 +1,38 @@
+using System;
+
+namespace Xamarin.Forms.Platform.iOS
+{
+ internal static class ArrayExtensions
+ {
+ public static T[] Insert<T>(this T[] self, int index, T item)
+ {
+ var result = new T[self.Length + 1];
+ if (index > 0)
+ Array.Copy(self, result, index);
+
+ result[index] = item;
+
+ if (index < self.Length)
+ Array.Copy(self, index, result, index + 1, result.Length - index - 1);
+
+ return result;
+ }
+
+ public static T[] Remove<T>(this T[] self, T item)
+ {
+ return self.RemoveAt(self.IndexOf(item));
+ }
+
+ public static T[] RemoveAt<T>(this T[] self, int index)
+ {
+ var result = new T[self.Length - 1];
+ if (index > 0)
+ Array.Copy(self, result, index);
+
+ if (index < self.Length - 1)
+ Array.Copy(self, index + 1, result, index, self.Length - index - 1);
+
+ return result;
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.iOS/Extensions/CellExtensions.cs b/Xamarin.Forms.Platform.iOS/Extensions/CellExtensions.cs
new file mode 100644
index 00000000..ca05bc54
--- /dev/null
+++ b/Xamarin.Forms.Platform.iOS/Extensions/CellExtensions.cs
@@ -0,0 +1,41 @@
+using System;
+#if __UNIFIED__
+using Foundation;
+
+#else
+using MonoTouch.Foundation;
+#endif
+
+namespace Xamarin.Forms.Platform.iOS
+{
+ internal static class CellExtensions
+ {
+ internal static NSIndexPath GetIndexPath(this Cell self)
+ {
+ if (self == null)
+ throw new ArgumentNullException("self");
+
+ NSIndexPath path;
+
+ if (self.RealParent is ListView)
+ {
+ var section = 0;
+ var til = TemplatedItemsList<ItemsView<Cell>, Cell>.GetGroup(self);
+ if (til != null)
+ section = TemplatedItemsList<ItemsView<Cell>, Cell>.GetIndex(til.HeaderContent);
+
+ var row = TemplatedItemsList<ItemsView<Cell>, Cell>.GetIndex(self);
+ path = NSIndexPath.FromRowSection(row, section);
+ }
+ else if (self.RealParent is TableView)
+ {
+ var tmPath = TableView.TableSectionModel.GetPath(self);
+ path = NSIndexPath.FromRowSection(tmPath.Item2, tmPath.Item1);
+ }
+ else
+ throw new NotSupportedException("Unknown cell parent type");
+
+ return path;
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.iOS/Extensions/ColorExtensions.cs b/Xamarin.Forms.Platform.iOS/Extensions/ColorExtensions.cs
new file mode 100644
index 00000000..f512e157
--- /dev/null
+++ b/Xamarin.Forms.Platform.iOS/Extensions/ColorExtensions.cs
@@ -0,0 +1,99 @@
+using System;
+using System.ComponentModel;
+using System.Drawing;
+using System.Linq;
+#if __UNIFIED__
+using CoreAnimation;
+using CoreGraphics;
+using Foundation;
+using UIKit;
+#else
+using MonoTouch.CoreAnimation;
+using MonoTouch.CoreGraphics;
+using MonoTouch.Foundation;
+using MonoTouch.UIKit;
+#endif
+#if __UNIFIED__
+using RectangleF = CoreGraphics.CGRect;
+using SizeF = CoreGraphics.CGSize;
+using PointF = CoreGraphics.CGPoint;
+
+#else
+using nfloat=System.Single;
+using nint=System.Int32;
+using nuint=System.UInt32;
+#endif
+
+namespace Xamarin.Forms.Platform.iOS
+{
+ public static class ColorExtensions
+ {
+ internal static readonly UIColor Black = UIColor.Black;
+ internal static readonly UIColor SeventyPercentGrey = new UIColor(0.7f, 0.7f, 0.7f, 1);
+
+ public static CGColor ToCGColor(this Color color)
+ {
+ return new CGColor((float)color.R, (float)color.G, (float)color.B, (float)color.A);
+ }
+
+ public static Color ToColor(this UIColor color)
+ {
+ nfloat red;
+ nfloat green;
+ nfloat blue;
+ nfloat alpha;
+ color.GetRGBA(out red, out green, out blue, out alpha);
+ return new Color(red, green, blue, alpha);
+ }
+
+ public static UIColor ToUIColor(this Color color)
+ {
+ return new UIColor((float)color.R, (float)color.G, (float)color.B, (float)color.A);
+ }
+
+ public static UIColor ToUIColor(this Color color, Color defaultColor)
+ {
+ if (color.IsDefault)
+ return defaultColor.ToUIColor();
+
+ return color.ToUIColor();
+ }
+
+ public static UIColor ToUIColor(this Color color, UIColor defaultColor)
+ {
+ if (color.IsDefault)
+ return defaultColor;
+
+ return color.ToUIColor();
+ }
+ }
+
+ public static class PointExtensions
+ {
+ public static Point ToPoint(this PointF point)
+ {
+ return new Point(point.X, point.Y);
+ }
+ }
+
+ public static class SizeExtensions
+ {
+ public static SizeF ToSizeF(this Size size)
+ {
+ return new SizeF((float)size.Width, (float)size.Height);
+ }
+ }
+
+ public static class RectangleExtensions
+ {
+ public static Rectangle ToRectangle(this RectangleF rect)
+ {
+ return new Rectangle(rect.X, rect.Y, rect.Width, rect.Height);
+ }
+
+ public static RectangleF ToRectangleF(this Rectangle rect)
+ {
+ return new RectangleF((nfloat)rect.X, (nfloat)rect.Y, (nfloat)rect.Width, (nfloat)rect.Height);
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.iOS/Extensions/DateExtensions.cs b/Xamarin.Forms.Platform.iOS/Extensions/DateExtensions.cs
new file mode 100644
index 00000000..a75e7a69
--- /dev/null
+++ b/Xamarin.Forms.Platform.iOS/Extensions/DateExtensions.cs
@@ -0,0 +1,33 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Drawing;
+using System.Linq;
+#if __UNIFIED__
+using CoreAnimation;
+using CoreGraphics;
+using Foundation;
+using UIKit;
+
+#else
+using MonoTouch.CoreAnimation;
+using MonoTouch.CoreGraphics;
+using MonoTouch.Foundation;
+using MonoTouch.UIKit;
+#endif
+
+namespace Xamarin.Forms.Platform.iOS
+{
+ public static class DateExtensions
+ {
+ public static DateTime ToDateTime(this NSDate date)
+ {
+ return new DateTime(2001, 1, 1, 0, 0, 0).AddSeconds(date.SecondsSinceReferenceDate);
+ }
+
+ public static NSDate ToNSDate(this DateTime date)
+ {
+ return NSDate.FromTimeIntervalSinceReferenceDate((date - new DateTime(2001, 1, 1, 0, 0, 0)).TotalSeconds);
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.iOS/Extensions/Extensions.cs b/Xamarin.Forms.Platform.iOS/Extensions/Extensions.cs
new file mode 100644
index 00000000..e74fed8d
--- /dev/null
+++ b/Xamarin.Forms.Platform.iOS/Extensions/Extensions.cs
@@ -0,0 +1,76 @@
+using System;
+using System.Collections.Generic;
+#if __UNIFIED__
+using UIKit;
+
+#else
+using MonoTouch.UIKit;
+#endif
+
+namespace Xamarin.Forms.Platform.iOS
+{
+ public static class Extensions
+ {
+ public static void ApplyKeyboard(this IUITextInput textInput, Keyboard keyboard)
+ {
+ textInput.AutocapitalizationType = UITextAutocapitalizationType.None;
+ textInput.AutocorrectionType = UITextAutocorrectionType.No;
+ textInput.SpellCheckingType = UITextSpellCheckingType.No;
+
+ if (keyboard == Keyboard.Default)
+ {
+ textInput.AutocapitalizationType = UITextAutocapitalizationType.Sentences;
+ textInput.AutocorrectionType = UITextAutocorrectionType.Default;
+ textInput.SpellCheckingType = UITextSpellCheckingType.Default;
+ textInput.KeyboardType = UIKeyboardType.Default;
+ }
+ else if (keyboard == Keyboard.Chat)
+ {
+ textInput.AutocapitalizationType = UITextAutocapitalizationType.Sentences;
+ textInput.AutocorrectionType = UITextAutocorrectionType.Yes;
+ }
+ else if (keyboard == Keyboard.Email)
+ textInput.KeyboardType = UIKeyboardType.EmailAddress;
+ else if (keyboard == Keyboard.Numeric)
+ textInput.KeyboardType = UIKeyboardType.DecimalPad;
+ else if (keyboard == Keyboard.Telephone)
+ textInput.KeyboardType = UIKeyboardType.PhonePad;
+ else if (keyboard == Keyboard.Text)
+ {
+ textInput.AutocapitalizationType = UITextAutocapitalizationType.Sentences;
+ textInput.AutocorrectionType = UITextAutocorrectionType.Yes;
+ textInput.SpellCheckingType = UITextSpellCheckingType.Yes;
+ }
+ else if (keyboard == Keyboard.Url)
+ textInput.KeyboardType = UIKeyboardType.Url;
+ else if (keyboard is CustomKeyboard)
+ {
+ var custom = (CustomKeyboard)keyboard;
+ var capitalizedSentenceEnabled = (custom.Flags & KeyboardFlags.CapitalizeSentence) == KeyboardFlags.CapitalizeSentence;
+ var spellcheckEnabled = (custom.Flags & KeyboardFlags.Spellcheck) == KeyboardFlags.Spellcheck;
+ var suggestionsEnabled = (custom.Flags & KeyboardFlags.Suggestions) == KeyboardFlags.Suggestions;
+
+ textInput.AutocapitalizationType = capitalizedSentenceEnabled ? UITextAutocapitalizationType.Sentences : UITextAutocapitalizationType.None;
+ textInput.AutocorrectionType = suggestionsEnabled ? UITextAutocorrectionType.Yes : UITextAutocorrectionType.No;
+ textInput.SpellCheckingType = spellcheckEnabled ? UITextSpellCheckingType.Yes : UITextSpellCheckingType.No;
+ }
+ }
+
+ internal static DeviceOrientation ToDeviceOrientation(this UIDeviceOrientation orientation)
+ {
+ switch (orientation)
+ {
+ case UIDeviceOrientation.Portrait:
+ return DeviceOrientation.Portrait;
+ case UIDeviceOrientation.PortraitUpsideDown:
+ return DeviceOrientation.PortraitDown;
+ case UIDeviceOrientation.LandscapeLeft:
+ return DeviceOrientation.LandscapeLeft;
+ case UIDeviceOrientation.LandscapeRight:
+ return DeviceOrientation.LandscapeRight;
+ default:
+ return DeviceOrientation.Other;
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.iOS/Extensions/ToolbarItemExtensions.cs b/Xamarin.Forms.Platform.iOS/Extensions/ToolbarItemExtensions.cs
new file mode 100644
index 00000000..71bdbbac
--- /dev/null
+++ b/Xamarin.Forms.Platform.iOS/Extensions/ToolbarItemExtensions.cs
@@ -0,0 +1,228 @@
+using System;
+using System.Drawing;
+using System.ComponentModel;
+#if __UNIFIED__
+using CoreGraphics;
+using UIKit;
+#else
+using MonoTouch.CoreGraphics;
+using MonoTouch.UIKit;
+#endif
+#if __UNIFIED__
+using RectangleF = CoreGraphics.CGRect;
+using SizeF = CoreGraphics.CGSize;
+using PointF = CoreGraphics.CGPoint;
+
+#else
+using nfloat=System.Single;
+using nint=System.Int32;
+using nuint=System.UInt32;
+#endif
+
+namespace Xamarin.Forms.Platform.iOS
+{
+ public static class ToolbarItemExtensions
+ {
+ public static UIBarButtonItem ToUIBarButtonItem(this ToolbarItem item, bool forceName = false)
+ {
+ return item.Order == ToolbarItemOrder.Secondary ? new SecondaryToolbarItem(item) : (UIBarButtonItem)new PrimaryToolbarItem(item, forceName);
+ }
+
+ sealed class PrimaryToolbarItem : UIBarButtonItem
+ {
+ readonly bool _forceName;
+ readonly ToolbarItem _item;
+
+ public PrimaryToolbarItem(ToolbarItem item, bool forceName)
+ {
+ _forceName = forceName;
+ _item = item;
+
+ if (!string.IsNullOrEmpty(item.Icon) && !forceName)
+ UpdateIconAndStyle();
+ else
+ UpdateTextAndStyle();
+ UpdateIsEnabled();
+
+ Clicked += (sender, e) => item.Activate();
+ item.PropertyChanged += OnPropertyChanged;
+
+ if (item != null && !string.IsNullOrEmpty(item.AutomationId))
+ AccessibilityIdentifier = item.AutomationId;
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing)
+ _item.PropertyChanged -= OnPropertyChanged;
+ base.Dispose(disposing);
+ }
+
+ void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ if (e.PropertyName == MenuItem.IsEnabledProperty.PropertyName)
+ UpdateIsEnabled();
+ else if (e.PropertyName == MenuItem.TextProperty.PropertyName)
+ {
+ if (string.IsNullOrEmpty(_item.Icon) || _forceName)
+ UpdateTextAndStyle();
+ }
+ else if (e.PropertyName == MenuItem.IconProperty.PropertyName)
+ {
+ if (!_forceName)
+ {
+ if (!string.IsNullOrEmpty(_item.Icon))
+ UpdateIconAndStyle();
+ else
+ UpdateTextAndStyle();
+ }
+ }
+ }
+
+ void UpdateIconAndStyle()
+ {
+ var image = UIImage.FromBundle(_item.Icon);
+ Image = image;
+ Style = UIBarButtonItemStyle.Plain;
+ }
+
+ void UpdateIsEnabled()
+ {
+ Enabled = _item.IsEnabled;
+ }
+
+ void UpdateTextAndStyle()
+ {
+ Title = _item.Text;
+ Style = UIBarButtonItemStyle.Bordered;
+ Image = null;
+ }
+ }
+
+ sealed class SecondaryToolbarItem : UIBarButtonItem
+ {
+ readonly ToolbarItem _item;
+
+ public SecondaryToolbarItem(ToolbarItem item) : base(new SecondaryToolbarItemContent())
+ {
+ _item = item;
+ UpdateText();
+ UpdateIcon();
+ UpdateIsEnabled();
+
+ ((SecondaryToolbarItemContent)CustomView).TouchUpInside += (sender, e) => item.Activate();
+ item.PropertyChanged += OnPropertyChanged;
+
+ if (item != null && !string.IsNullOrEmpty(item.AutomationId))
+ AccessibilityIdentifier = item.AutomationId;
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing)
+ _item.PropertyChanged -= OnPropertyChanged;
+ base.Dispose(disposing);
+ }
+
+ void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ if (e.PropertyName == MenuItem.TextProperty.PropertyName)
+ UpdateText();
+ else if (e.PropertyName == MenuItem.IconProperty.PropertyName)
+ UpdateIcon();
+ else if (e.PropertyName == MenuItem.IsEnabledProperty.PropertyName)
+ UpdateIsEnabled();
+ }
+
+ void UpdateIcon()
+ {
+ ((SecondaryToolbarItemContent)CustomView).Image = string.IsNullOrEmpty(_item.Icon) ? null : new UIImage(_item.Icon);
+ }
+
+ void UpdateIsEnabled()
+ {
+ ((UIControl)CustomView).Enabled = _item.IsEnabled;
+ }
+
+ void UpdateText()
+ {
+ ((SecondaryToolbarItemContent)CustomView).Text = _item.Text;
+ }
+
+ sealed class SecondaryToolbarItemContent : UIControl
+ {
+ readonly UIImageView _imageView;
+ readonly UILabel _label;
+
+ public SecondaryToolbarItemContent() : base(new RectangleF(0, 0, 75, 20))
+ {
+ BackgroundColor = UIColor.Clear;
+ _imageView = new UIImageView { BackgroundColor = UIColor.Clear };
+ AddSubview(_imageView);
+
+ _label = new UILabel { BackgroundColor = UIColor.Clear, Lines = 1, LineBreakMode = UILineBreakMode.TailTruncation, Font = UIFont.SystemFontOfSize(10) };
+ AddSubview(_label);
+ }
+
+ public override bool Enabled
+ {
+ get { return base.Enabled; }
+ set
+ {
+ base.Enabled = value;
+ _label.Enabled = value;
+ _imageView.Alpha = value ? 1f : 0.25f;
+ }
+ }
+
+ public UIImage Image
+ {
+ get { return _imageView.Image; }
+ set { _imageView.Image = value; }
+ }
+
+ public string Text
+ {
+ get { return _label.Text; }
+ set { _label.Text = value; }
+ }
+
+ public override void LayoutSubviews()
+ {
+ base.LayoutSubviews();
+
+ const float padding = 5f;
+ var imageSize = _imageView.SizeThatFits(Bounds.Size);
+ var fullStringSize = _label.SizeThatFits(Bounds.Size);
+
+ if (imageSize.Width > 0 && (string.IsNullOrEmpty(Text) || fullStringSize.Width > Bounds.Width / 3))
+ {
+ _imageView.Frame = new RectangleF(PointF.Empty, imageSize);
+ _imageView.Center = new PointF(Bounds.GetMidX(), Bounds.GetMidY());
+ _label.Hidden = true;
+ return;
+ }
+
+ _label.Hidden = false;
+ var availableWidth = Bounds.Width - padding * 3 - imageSize.Width;
+ var stringSize = _label.SizeThatFits(new SizeF(availableWidth, Bounds.Height - padding * 2));
+
+ availableWidth = Bounds.Width;
+ availableWidth -= stringSize.Width;
+ availableWidth -= imageSize.Width;
+
+ var x = availableWidth / 2;
+
+ var frame = new RectangleF(new PointF(x, Bounds.GetMidY() - imageSize.Height / 2), imageSize);
+ _imageView.Frame = frame;
+
+ frame.X = frame.Right + (imageSize.Width > 0 ? padding : 0);
+ frame.Size = stringSize;
+ frame.Height = Bounds.Height;
+ frame.Y = 0;
+ _label.Frame = frame;
+ }
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.iOS/Extensions/UIViewExtensions.cs b/Xamarin.Forms.Platform.iOS/Extensions/UIViewExtensions.cs
new file mode 100644
index 00000000..9847867c
--- /dev/null
+++ b/Xamarin.Forms.Platform.iOS/Extensions/UIViewExtensions.cs
@@ -0,0 +1,66 @@
+using System.Collections.Generic;
+using System.Drawing;
+using System.Linq;
+using System;
+#if __UNIFIED__
+using UIKit;
+
+#else
+using MonoTouch.UIKit;
+#endif
+
+namespace Xamarin.Forms.Platform.iOS
+{
+ public static class UIViewExtensions
+ {
+ public static IEnumerable<UIView> Descendants(this UIView self)
+ {
+ if (self.Subviews == null)
+ return Enumerable.Empty<UIView>();
+ return self.Subviews.Concat(self.Subviews.SelectMany(s => s.Descendants()));
+ }
+
+ public static SizeRequest GetSizeRequest(this UIView self, double widthConstraint, double heightConstraint, double minimumWidth = -1, double minimumHeight = -1)
+ {
+ var s = self.SizeThatFits(new SizeF((float)widthConstraint, (float)heightConstraint));
+ var request = new Size(s.Width == float.PositiveInfinity ? double.PositiveInfinity : s.Width, s.Height == float.PositiveInfinity ? double.PositiveInfinity : s.Height);
+ var minimum = new Size(minimumWidth < 0 ? request.Width : minimumWidth, minimumHeight < 0 ? request.Height : minimumHeight);
+ return new SizeRequest(request, minimum);
+ }
+
+ internal static T FindDescendantView<T>(this UIView view) where T : UIView
+ {
+ var queue = new Queue<UIView>();
+ queue.Enqueue(view);
+
+ while (queue.Count > 0)
+ {
+ var descendantView = queue.Dequeue();
+
+ var result = descendantView as T;
+ if (result != null)
+ return result;
+
+ for (var i = 0; i < descendantView.Subviews.Length; i++)
+ queue.Enqueue(descendantView.Subviews[i]);
+ }
+
+ return null;
+ }
+
+ internal static UIView FindFirstResponder(this UIView view)
+ {
+ if (view.IsFirstResponder)
+ return view;
+
+ foreach (var subView in view.Subviews)
+ {
+ var firstResponder = subView.FindFirstResponder();
+ if (firstResponder != null)
+ return firstResponder;
+ }
+
+ return null;
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.iOS/Extensions/ViewExtensions.cs b/Xamarin.Forms.Platform.iOS/Extensions/ViewExtensions.cs
new file mode 100644
index 00000000..9ded0632
--- /dev/null
+++ b/Xamarin.Forms.Platform.iOS/Extensions/ViewExtensions.cs
@@ -0,0 +1,20 @@
+using System.Collections.Generic;
+
+namespace Xamarin.Forms.Platform.iOS
+{
+ public static class ViewExtensions
+ {
+ public static IEnumerable<Page> GetParentPages(this Page target)
+ {
+ var result = new List<Page>();
+ var parent = target.RealParent as Page;
+ while (!Application.IsApplicationOrNull(parent))
+ {
+ result.Add(parent);
+ parent = parent.RealParent as Page;
+ }
+
+ return result;
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.iOS/Forms.cs b/Xamarin.Forms.Platform.iOS/Forms.cs
new file mode 100644
index 00000000..b7a41826
--- /dev/null
+++ b/Xamarin.Forms.Platform.iOS/Forms.cs
@@ -0,0 +1,373 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.IO.IsolatedStorage;
+using System.Linq;
+using System.Linq.Expressions;
+using System.Net.Http;
+using System.Reflection;
+using System.Security.Cryptography;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+#if __UNIFIED__
+using UIKit;
+using CoreFoundation;
+using Foundation;
+#else
+using MonoTouch.UIKit;
+using MonoTouch.CoreFoundation;
+using MonoTouch.Foundation;
+#endif
+using Xamarin.Forms.Platform.iOS;
+
+namespace Xamarin.Forms
+{
+ public static class Forms
+ {
+ //Preserve GetCallingAssembly
+ static readonly bool nevertrue = false;
+
+ static bool? s_isiOS7OrNewer;
+
+ static bool? s_isiOS8OrNewer;
+
+ static bool? s_isiOS9OrNewer;
+
+ static Forms()
+ {
+ if (nevertrue)
+ Assembly.GetCallingAssembly();
+ }
+
+ public static bool IsInitialized { get; private set; }
+
+ internal static bool IsiOS7OrNewer
+ {
+ get
+ {
+ if (!s_isiOS7OrNewer.HasValue)
+ s_isiOS7OrNewer = UIDevice.CurrentDevice.CheckSystemVersion(7, 0);
+ return s_isiOS7OrNewer.Value;
+ }
+ }
+
+ internal static bool IsiOS8OrNewer
+ {
+ get
+ {
+ if (!s_isiOS8OrNewer.HasValue)
+ s_isiOS8OrNewer = UIDevice.CurrentDevice.CheckSystemVersion(8, 0);
+ return s_isiOS8OrNewer.Value;
+ }
+ }
+
+ internal static bool IsiOS9OrNewer
+ {
+ get
+ {
+ if (!s_isiOS9OrNewer.HasValue)
+ s_isiOS9OrNewer = UIDevice.CurrentDevice.CheckSystemVersion(9, 0);
+ return s_isiOS9OrNewer.Value;
+ }
+ }
+
+ public static void Init()
+ {
+ if (IsInitialized)
+ return;
+ IsInitialized = true;
+ Color.Accent = Color.FromRgba(50, 79, 133, 255);
+
+ Log.Listeners.Add(new DelegateLogListener((c, m) => Trace.WriteLine(m, c)));
+
+ Device.OS = TargetPlatform.iOS;
+ Device.PlatformServices = new IOSPlatformServices();
+ Device.Info = new IOSDeviceInfo();
+
+ Ticker.Default = new CADisplayLinkTicker();
+ Registrar.RegisterAll(new[] { typeof(ExportRendererAttribute), typeof(ExportCellAttribute), typeof(ExportImageSourceHandlerAttribute) });
+
+ Device.Idiom = UIDevice.CurrentDevice.UserInterfaceIdiom == UIUserInterfaceIdiom.Pad ? TargetIdiom.Tablet : TargetIdiom.Phone;
+
+ ExpressionSearch.Default = new iOSExpressionSearch();
+ }
+
+ public static event EventHandler<ViewInitializedEventArgs> ViewInitialized;
+
+ internal static void SendViewInitialized(this VisualElement self, UIView nativeView)
+ {
+ var viewInitialized = ViewInitialized;
+ if (viewInitialized != null)
+ viewInitialized(self, new ViewInitializedEventArgs { View = self, NativeView = nativeView });
+ }
+
+ class iOSExpressionSearch : ExpressionVisitor, IExpressionSearch
+ {
+ List<object> _results;
+ Type _targetType;
+
+ public List<T> FindObjects<T>(Expression expression) where T : class
+ {
+ _results = new List<object>();
+ _targetType = typeof(T);
+ Visit(expression);
+ return _results.Select(o => o as T).ToList();
+ }
+
+ protected override Expression VisitMember(MemberExpression node)
+ {
+ if (node.Expression is ConstantExpression && node.Member is FieldInfo)
+ {
+ var container = ((ConstantExpression)node.Expression).Value;
+ var value = ((FieldInfo)node.Member).GetValue(container);
+
+ if (_targetType.IsInstanceOfType(value))
+ _results.Add(value);
+ }
+ return base.VisitMember(node);
+ }
+ }
+
+ internal class IOSDeviceInfo : DeviceInfo
+ {
+ readonly NSObject _notification;
+ readonly Size _scaledScreenSize;
+ readonly double _scalingFactor;
+
+ public IOSDeviceInfo()
+ {
+ _notification = UIDevice.Notifications.ObserveOrientationDidChange((sender, args) => CurrentOrientation = UIDevice.CurrentDevice.Orientation.ToDeviceOrientation());
+
+ _scalingFactor = UIScreen.MainScreen.Scale;
+ _scaledScreenSize = new Size(UIScreen.MainScreen.Bounds.Width, UIScreen.MainScreen.Bounds.Height);
+ PixelScreenSize = new Size(_scaledScreenSize.Width * _scalingFactor, _scaledScreenSize.Height * _scalingFactor);
+ }
+
+ public override Size PixelScreenSize { get; }
+
+ public override Size ScaledScreenSize
+ {
+ get { return _scaledScreenSize; }
+ }
+
+ public override double ScalingFactor
+ {
+ get { return _scalingFactor; }
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ _notification.Dispose();
+ base.Dispose(disposing);
+ }
+ }
+
+ class IOSPlatformServices : IPlatformServices
+ {
+ static readonly MD5CryptoServiceProvider Checksum = new MD5CryptoServiceProvider();
+
+ public void BeginInvokeOnMainThread(Action action)
+ {
+ NSRunLoop.Main.BeginInvokeOnMainThread(action.Invoke);
+ }
+
+ public ITimer CreateTimer(Action<object> callback)
+ {
+ return new _Timer(new Timer(o => callback(o)));
+ }
+
+ public ITimer CreateTimer(Action<object> callback, object state, int dueTime, int period)
+ {
+ return new _Timer(new Timer(o => callback(o), state, dueTime, period));
+ }
+
+ public ITimer CreateTimer(Action<object> callback, object state, long dueTime, long period)
+ {
+ return new _Timer(new Timer(o => callback(o), state, dueTime, period));
+ }
+
+ public ITimer CreateTimer(Action<object> callback, object state, TimeSpan dueTime, TimeSpan period)
+ {
+ return new _Timer(new Timer(o => callback(o), state, dueTime, period));
+ }
+
+ public ITimer CreateTimer(Action<object> callback, object state, uint dueTime, uint period)
+ {
+ return new _Timer(new Timer(o => callback(o), state, dueTime, period));
+ }
+
+ public Assembly[] GetAssemblies()
+ {
+ return AppDomain.CurrentDomain.GetAssemblies();
+ }
+
+ public string GetMD5Hash(string input)
+ {
+ var bytes = Checksum.ComputeHash(Encoding.UTF8.GetBytes(input));
+ var ret = new char[32];
+ for (var i = 0; i < 16; i++)
+ {
+ ret[i * 2] = (char)Hex(bytes[i] >> 4);
+ ret[i * 2 + 1] = (char)Hex(bytes[i] & 0xf);
+ }
+ return new string(ret);
+ }
+
+ public double GetNamedSize(NamedSize size, Type targetElementType, bool useOldSizes)
+ {
+ // We make these up anyway, so new sizes didn't really change
+ // iOS docs say default button font size is 15, default label font size is 17 so we use those as the defaults.
+ switch (size)
+ {
+ case NamedSize.Default:
+ return typeof(Button).IsAssignableFrom(targetElementType) ? 15 : 17;
+ case NamedSize.Micro:
+ return 12;
+ case NamedSize.Small:
+ return 14;
+ case NamedSize.Medium:
+ return 17;
+ case NamedSize.Large:
+ return 22;
+ default:
+ throw new ArgumentOutOfRangeException("size");
+ }
+ }
+
+ public async Task<Stream> GetStreamAsync(Uri uri, CancellationToken cancellationToken)
+ {
+ using(var client = GetHttpClient())
+ using(var response = await client.GetAsync(uri, cancellationToken))
+ return await response.Content.ReadAsStreamAsync();
+ }
+
+ public IIsolatedStorageFile GetUserStoreForApplication()
+ {
+ return new _IsolatedStorageFile(IsolatedStorageFile.GetUserStoreForApplication());
+ }
+
+ public bool IsInvokeRequired
+ {
+ get { return !NSThread.IsMain; }
+ }
+
+ public void OpenUriAction(Uri uri)
+ {
+ UIApplication.SharedApplication.OpenUrl(new NSUrl(uri.AbsoluteUri));
+ }
+
+ public void StartTimer(TimeSpan interval, Func<bool> callback)
+ {
+ NSTimer timer = null;
+#if __UNIFIED__
+ timer = NSTimer.CreateRepeatingScheduledTimer(interval, t =>
+ {
+#else
+ timer = NSTimer.CreateRepeatingScheduledTimer (interval, () => {
+ #endif
+ if (!callback())
+#if __UNIFIED__
+ t.Invalidate();
+#else
+ timer.Invalidate ();
+ #endif
+ });
+ NSRunLoop.Main.AddTimer(timer, NSRunLoopMode.Common);
+ }
+
+ HttpClient GetHttpClient()
+ {
+ var proxy = CFNetwork.GetSystemProxySettings();
+ var handler = new HttpClientHandler();
+ if (!string.IsNullOrEmpty(proxy.HTTPProxy))
+ {
+ handler.Proxy = CFNetwork.GetDefaultProxy();
+ handler.UseProxy = true;
+ }
+ return new HttpClient(handler);
+ }
+
+ static int Hex(int v)
+ {
+ if (v < 10)
+ return '0' + v;
+ return 'a' + v - 10;
+ }
+
+ public class _Timer : ITimer
+ {
+ readonly Timer _timer;
+
+ public _Timer(Timer timer)
+ {
+ _timer = timer;
+ }
+
+ public void Change(int dueTime, int period)
+ {
+ _timer.Change(dueTime, period);
+ }
+
+ public void Change(long dueTime, long period)
+ {
+ _timer.Change(dueTime, period);
+ }
+
+ public void Change(TimeSpan dueTime, TimeSpan period)
+ {
+ _timer.Change(dueTime, period);
+ }
+
+ public void Change(uint dueTime, uint period)
+ {
+ _timer.Change(dueTime, period);
+ }
+ }
+
+ public class _IsolatedStorageFile : IIsolatedStorageFile
+ {
+ readonly IsolatedStorageFile _isolatedStorageFile;
+
+ public _IsolatedStorageFile(IsolatedStorageFile isolatedStorageFile)
+ {
+ _isolatedStorageFile = isolatedStorageFile;
+ }
+
+ public Task CreateDirectoryAsync(string path)
+ {
+ _isolatedStorageFile.CreateDirectory(path);
+ return Task.FromResult(true);
+ }
+
+ public Task<bool> GetDirectoryExistsAsync(string path)
+ {
+ return Task.FromResult(_isolatedStorageFile.DirectoryExists(path));
+ }
+
+ public Task<bool> GetFileExistsAsync(string path)
+ {
+ return Task.FromResult(_isolatedStorageFile.FileExists(path));
+ }
+
+ public Task<DateTimeOffset> GetLastWriteTimeAsync(string path)
+ {
+ return Task.FromResult(_isolatedStorageFile.GetLastWriteTime(path));
+ }
+
+ public Task<Stream> OpenFileAsync(string path, FileMode mode, FileAccess access)
+ {
+ Stream stream = _isolatedStorageFile.OpenFile(path, (System.IO.FileMode)mode, (System.IO.FileAccess)access);
+ return Task.FromResult(stream);
+ }
+
+ public Task<Stream> OpenFileAsync(string path, FileMode mode, FileAccess access, FileShare share)
+ {
+ Stream stream = _isolatedStorageFile.OpenFile(path, (System.IO.FileMode)mode, (System.IO.FileAccess)access, (System.IO.FileShare)share);
+ return Task.FromResult(stream);
+ }
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.iOS/FormsApplicationDelegate.cs b/Xamarin.Forms.Platform.iOS/FormsApplicationDelegate.cs
new file mode 100644
index 00000000..a4342473
--- /dev/null
+++ b/Xamarin.Forms.Platform.iOS/FormsApplicationDelegate.cs
@@ -0,0 +1,151 @@
+using System;
+using System.ComponentModel;
+using System.Linq;
+using System.Text;
+#if __UNIFIED__
+using Foundation;
+using UIKit;
+
+#else
+using MonoTouch.Foundation;
+using MonoTouch.UIKit;
+#endif
+
+namespace Xamarin.Forms.Platform.iOS
+{
+ public class FormsApplicationDelegate : UIApplicationDelegate
+ {
+ Application _application;
+ bool _isSuspended;
+ UIWindow _window;
+
+ protected FormsApplicationDelegate()
+ {
+ }
+
+ public override bool ContinueUserActivity(UIApplication application, NSUserActivity userActivity, UIApplicationRestorationHandler completionHandler)
+ {
+ return true;
+ }
+
+ // now in background
+ public override void DidEnterBackground(UIApplication uiApplication)
+ {
+ // applicationDidEnterBackground
+ }
+
+ // finish initialization before display to user
+ public override bool FinishedLaunching(UIApplication uiApplication, NSDictionary launchOptions)
+ {
+ // check contents of launch options and evaluate why the app was launched and respond
+ // initialize the important data structures
+ // prepare you apps window and views for display
+ // keep lightweight, anything long winded should be executed asynchronously on a secondary thread.
+ // application:didFinishLaunchingWithOptions
+ _window = new UIWindow(UIScreen.MainScreen.Bounds);
+
+ if (_application == null)
+ throw new InvalidOperationException("You MUST invoke LoadApplication () before calling base.FinishedLaunching ()");
+
+ SetMainPage();
+ _application.SendStart();
+ return true;
+ }
+
+ // about to become foreground, last minute preparatuin
+ public override void OnActivated(UIApplication uiApplication)
+ {
+ // applicationDidBecomeActive
+ // execute any OpenGL ES drawing calls
+ if (_application != null && _isSuspended)
+ {
+ _isSuspended = false;
+ _application.SendResume();
+ }
+ }
+
+ // transitioning to background
+ public override async void OnResignActivation(UIApplication uiApplication)
+ {
+ // applicationWillResignActive
+ if (_application != null)
+ {
+ _isSuspended = true;
+ await _application.SendSleepAsync();
+ }
+ }
+
+ public override void UserActivityUpdated(UIApplication application, NSUserActivity userActivity)
+ {
+ }
+
+ // from background to foreground, not yet active
+ public override void WillEnterForeground(UIApplication uiApplication)
+ {
+ // applicationWillEnterForeground
+ }
+
+ // TODO where to execute heavy code, storing state, sending to server, etc
+
+ // first chance to execute code at launch time
+ public override bool WillFinishLaunching(UIApplication uiApplication, NSDictionary launchOptions)
+ {
+ // check contents of launch options and evaluate why the app was launched and respond
+ // initialize the important data structures
+ // prepare you apps window and views for display
+ // keep lightweight, anything long winded should be executed asynchronously on a secondary thread.
+ // application:willFinishLaunchingWithOptions
+ // Restore ui state here
+ return true;
+ }
+
+ // app is being terminated, not called if you app is suspended
+ public override void WillTerminate(UIApplication uiApplication)
+ {
+ // applicationWillTerminate
+ //application.SendTerminate ();
+ }
+
+ 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("application");
+
+ Application.Current = application;
+ _application = application;
+
+ application.PropertyChanged += ApplicationOnPropertyChanged;
+ }
+
+ void ApplicationOnPropertyChanged(object sender, PropertyChangedEventArgs args)
+ {
+ if (args.PropertyName == "MainPage")
+ UpdateMainPage();
+ }
+
+ void SetMainPage()
+ {
+ UpdateMainPage();
+ _window.MakeKeyAndVisible();
+ }
+
+ void UpdateMainPage()
+ {
+ if (_application.MainPage == null)
+ return;
+
+ var platformRenderer = (PlatformRenderer)_window.RootViewController;
+ _window.RootViewController = _application.MainPage.CreateViewController();
+ if (platformRenderer != null)
+ ((IDisposable)platformRenderer.Platform).Dispose();
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.iOS/GlobalCloseContextGestureRecognizer.cs b/Xamarin.Forms.Platform.iOS/GlobalCloseContextGestureRecognizer.cs
new file mode 100644
index 00000000..fa76b982
--- /dev/null
+++ b/Xamarin.Forms.Platform.iOS/GlobalCloseContextGestureRecognizer.cs
@@ -0,0 +1,72 @@
+using System.Collections.Generic;
+using System.Drawing;
+#if __UNIFIED__
+using Foundation;
+using UIKit;
+#else
+using MonoTouch.Foundation;
+using MonoTouch.UIKit;
+#endif
+#if __UNIFIED__
+using RectangleF = CoreGraphics.CGRect;
+using SizeF = CoreGraphics.CGSize;
+using PointF = CoreGraphics.CGPoint;
+using NSAction = System.Action;
+
+#else
+using nfloat=System.Single;
+using nint=System.Int32;
+#endif
+
+namespace Xamarin.Forms.Platform.iOS
+{
+ internal class GlobalCloseContextGestureRecognizer : UIGestureRecognizer
+ {
+ List<UIButton> _buttons;
+ UIScrollView _scrollView;
+
+ public GlobalCloseContextGestureRecognizer(UIScrollView scrollView, List<UIButton> buttons, NSAction activated) : base(activated)
+ {
+ _scrollView = scrollView;
+ _buttons = buttons;
+
+ ShouldReceiveTouch = OnShouldReceiveTouch;
+ }
+
+ public override void TouchesBegan(NSSet touches, UIEvent evt)
+ {
+ State = UIGestureRecognizerState.Began;
+ base.TouchesBegan(touches, evt);
+ }
+
+ public override void TouchesEnded(NSSet touches, UIEvent evt)
+ {
+ State = UIGestureRecognizerState.Recognized;
+ base.TouchesEnded(touches, evt);
+ }
+
+ public override void TouchesMoved(NSSet touches, UIEvent evt)
+ {
+ State = UIGestureRecognizerState.Recognized;
+ base.TouchesMoved(touches, evt);
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ base.Dispose(disposing);
+
+ if (disposing)
+ {
+ _buttons = null;
+ _scrollView = null;
+ }
+ }
+
+ bool OnShouldReceiveTouch(UIGestureRecognizer r, UITouch t)
+ {
+ var scrollPos = t.LocationInView(_scrollView);
+ var rect = new RectangleF(0, 0, _scrollView.ContentSize.Width, _scrollView.ContentSize.Height);
+ return !rect.Contains(scrollPos);
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.iOS/IVisualElementRenderer.cs b/Xamarin.Forms.Platform.iOS/IVisualElementRenderer.cs
new file mode 100644
index 00000000..5ffabf08
--- /dev/null
+++ b/Xamarin.Forms.Platform.iOS/IVisualElementRenderer.cs
@@ -0,0 +1,27 @@
+using System;
+#if __UNIFIED__
+using UIKit;
+
+#else
+using MonoTouch.UIKit;
+#endif
+
+namespace Xamarin.Forms.Platform.iOS
+{
+ public interface IVisualElementRenderer : IDisposable, IRegisterable
+ {
+ VisualElement Element { get; }
+
+ UIView NativeView { get; }
+
+ UIViewController ViewController { get; }
+
+ event EventHandler<VisualElementChangedEventArgs> ElementChanged;
+
+ SizeRequest GetDesiredSize(double widthConstraint, double heightConstraint);
+
+ void SetElement(VisualElement element);
+
+ void SetElementSize(Size size);
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.iOS/LayoutExtensions.cs b/Xamarin.Forms.Platform.iOS/LayoutExtensions.cs
new file mode 100644
index 00000000..6e7f5738
--- /dev/null
+++ b/Xamarin.Forms.Platform.iOS/LayoutExtensions.cs
@@ -0,0 +1,36 @@
+using System.Collections.Generic;
+#if __UNIFIED__
+using CoreGraphics;
+using UIKit;
+
+#else
+using MonoTouch.UIKit;
+#endif
+
+#if !__UNIFIED__
+ // Save ourselves a ton of ugly ifdefs below
+using CGSize = System.Drawing.SizeF;
+#endif
+
+namespace Xamarin.Forms.Platform.iOS
+{
+ public delegate SizeRequest? GetDesiredSizeDelegate(NativeViewWrapperRenderer renderer, double widthConstraint, double heightConstraint);
+
+ public delegate CGSize? SizeThatFitsDelegate(CGSize size);
+
+ public delegate bool LayoutSubviewsDelegate();
+
+ public static class LayoutExtensions
+ {
+ public static void Add(this IList<View> children, UIView view, GetDesiredSizeDelegate getDesiredSizeDelegate = null, SizeThatFitsDelegate sizeThatFitsDelegate = null,
+ LayoutSubviewsDelegate layoutSubViews = null)
+ {
+ children.Add(view.ToView(getDesiredSizeDelegate, sizeThatFitsDelegate, layoutSubViews));
+ }
+
+ public static View ToView(this UIView view, GetDesiredSizeDelegate getDesiredSizeDelegate = null, SizeThatFitsDelegate sizeThatFitsDelegate = null, LayoutSubviewsDelegate layoutSubViews = null)
+ {
+ return new NativeViewWrapper(view, getDesiredSizeDelegate, sizeThatFitsDelegate, layoutSubViews);
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.iOS/NativeViewWrapper.cs b/Xamarin.Forms.Platform.iOS/NativeViewWrapper.cs
new file mode 100644
index 00000000..209aca80
--- /dev/null
+++ b/Xamarin.Forms.Platform.iOS/NativeViewWrapper.cs
@@ -0,0 +1,35 @@
+using System.Collections.Generic;
+#if __UNIFIED__
+using CoreGraphics;
+using UIKit;
+
+#else
+using MonoTouch.UIKit;
+#endif
+
+#if !__UNIFIED__
+ // Save ourselves a ton of ugly ifdefs below
+using CGSize = System.Drawing.SizeF;
+#endif
+
+namespace Xamarin.Forms.Platform.iOS
+{
+ public class NativeViewWrapper : View
+ {
+ public NativeViewWrapper(UIView nativeView, GetDesiredSizeDelegate getDesiredSizeDelegate = null, SizeThatFitsDelegate sizeThatFitsDelegate = null, LayoutSubviewsDelegate layoutSubViews = null)
+ {
+ GetDesiredSizeDelegate = getDesiredSizeDelegate;
+ SizeThatFitsDelegate = sizeThatFitsDelegate;
+ LayoutSubViews = layoutSubViews;
+ NativeView = nativeView;
+ }
+
+ public GetDesiredSizeDelegate GetDesiredSizeDelegate { get; }
+
+ public LayoutSubviewsDelegate LayoutSubViews { get; set; }
+
+ public UIView NativeView { get; }
+
+ public SizeThatFitsDelegate SizeThatFitsDelegate { get; set; }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.iOS/NativeViewWrapperRenderer.cs b/Xamarin.Forms.Platform.iOS/NativeViewWrapperRenderer.cs
new file mode 100644
index 00000000..3d3e6868
--- /dev/null
+++ b/Xamarin.Forms.Platform.iOS/NativeViewWrapperRenderer.cs
@@ -0,0 +1,72 @@
+using System.Collections.Generic;
+#if __UNIFIED__
+using CoreGraphics;
+using UIKit;
+
+#else
+using MonoTouch.UIKit;
+#endif
+
+#if !__UNIFIED__
+ // Save ourselves a ton of ugly ifdefs below
+using CGSize = System.Drawing.SizeF;
+#endif
+
+namespace Xamarin.Forms.Platform.iOS
+{
+ public class NativeViewWrapperRenderer : ViewRenderer<NativeViewWrapper, UIView>
+ {
+ public override SizeRequest GetDesiredSize(double widthConstraint, double heightConstraint)
+ {
+ if (Element?.GetDesiredSizeDelegate == null)
+ return base.GetDesiredSize(widthConstraint, heightConstraint);
+
+ // The user has specified a different implementation of GetDesiredSize
+ var result = Element.GetDesiredSizeDelegate(this, widthConstraint, heightConstraint);
+
+ // If the GetDesiredSize delegate returns a SizeRequest, we use it; if it returns null,
+ // fall back to the default implementation
+ return result ?? base.GetDesiredSize(widthConstraint, heightConstraint);
+ }
+
+ public override void LayoutSubviews()
+ {
+ if (Element?.LayoutSubViews == null)
+ {
+ Element?.InvalidateMeasure(InvalidationTrigger.MeasureChanged);
+ base.LayoutSubviews();
+ return;
+ }
+
+ // The user has specified a different implementation of LayoutSubviews
+ var handled = Element.LayoutSubViews();
+
+ if (!handled)
+ {
+ // If the delegate wasn't able to handle the request, fall back to the default implementation
+ base.LayoutSubviews();
+ }
+ }
+
+ public override CGSize SizeThatFits(CGSize size)
+ {
+ if (Element?.SizeThatFitsDelegate == null)
+ return base.SizeThatFits(size);
+
+ // The user has specified a different implementation of SizeThatFits
+ var result = Element.SizeThatFitsDelegate(size);
+
+ // If the delegate returns a value, we use it;
+ // if it returns null, fall back to the default implementation
+ return result ?? base.SizeThatFits(size);
+ }
+
+ protected override void OnElementChanged(ElementChangedEventArgs<NativeViewWrapper> e)
+ {
+ base.OnElementChanged(e);
+
+ if (e.OldElement == null)
+ SetNativeControl(Element.NativeView);
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.iOS/PageExtensions.cs b/Xamarin.Forms.Platform.iOS/PageExtensions.cs
new file mode 100644
index 00000000..07da048a
--- /dev/null
+++ b/Xamarin.Forms.Platform.iOS/PageExtensions.cs
@@ -0,0 +1,33 @@
+using System;
+#if __UNIFIED__
+using UIKit;
+
+#else
+using MonoTouch.UIKit;
+#endif
+
+namespace Xamarin.Forms
+{
+ public static class PageExtensions
+ {
+ public static UIViewController 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.iOS.Platform();
+ result.SetPage(view);
+ return result.ViewController;
+ }
+
+ class DefaultApplication : Application
+ {
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.iOS/Platform.cs b/Xamarin.Forms.Platform.iOS/Platform.cs
new file mode 100644
index 00000000..0293e75f
--- /dev/null
+++ b/Xamarin.Forms.Platform.iOS/Platform.cs
@@ -0,0 +1,505 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Drawing;
+using System.Linq;
+using System.Runtime.InteropServices;
+using System.Threading.Tasks;
+#if __UNIFIED__
+using CoreGraphics;
+using Foundation;
+using ObjCRuntime;
+using UIKit;
+#else
+using MonoTouch;
+using MonoTouch.CoreAnimation;
+using MonoTouch.CoreFoundation;
+using MonoTouch.CoreGraphics;
+using MonoTouch.UIKit;
+using System.Drawing;
+using MonoTouch.CoreAnimation;
+using MonoTouch.Foundation;
+using MonoTouch.ObjCRuntime;
+using MonoTouch.UIKit;
+#endif
+#if __UNIFIED__
+using RectangleF = CoreGraphics.CGRect;
+using SizeF = CoreGraphics.CGSize;
+using PointF = CoreGraphics.CGPoint;
+
+#else
+using nfloat=System.Single;
+using nint=System.Int32;
+using nuint=System.UInt32;
+#endif
+
+namespace Xamarin.Forms.Platform.iOS
+{
+ public class Platform : BindableObject, IPlatform, INavigation, IDisposable
+ {
+ internal static readonly BindableProperty RendererProperty = BindableProperty.CreateAttached("Renderer", typeof(IVisualElementRenderer), typeof(Platform), default(IVisualElementRenderer),
+ propertyChanged: (bindable, oldvalue, newvalue) =>
+ {
+ var view = bindable as VisualElement;
+ if (view != null)
+ view.IsPlatformEnabled = newvalue != null;
+ });
+
+ readonly int _alertPadding = 10;
+
+ readonly List<Page> _modals;
+ readonly PlatformRenderer _renderer;
+ bool _animateModals = true;
+ bool _appeared;
+
+ bool _disposed;
+
+ internal Platform()
+ {
+ _renderer = new PlatformRenderer(this);
+ _modals = new List<Page>();
+
+ var busyCount = 0;
+ MessagingCenter.Subscribe(this, Page.BusySetSignalName, (Page sender, bool enabled) =>
+ {
+ if (!PageIsChildOfPlatform(sender))
+ return;
+ busyCount = Math.Max(0, enabled ? busyCount + 1 : busyCount - 1);
+ UIApplication.SharedApplication.NetworkActivityIndicatorVisible = busyCount > 0;
+ });
+
+ MessagingCenter.Subscribe(this, Page.AlertSignalName, (Page sender, AlertArguments arguments) =>
+ {
+ if (!PageIsChildOfPlatform(sender))
+ return;
+
+ if (Forms.IsiOS8OrNewer)
+ {
+ var alert = UIAlertController.Create(arguments.Title, arguments.Message, UIAlertControllerStyle.Alert);
+ var oldFrame = alert.View.Frame;
+ alert.View.Frame = new RectangleF(oldFrame.X, oldFrame.Y, oldFrame.Width, oldFrame.Height - _alertPadding * 2);
+ alert.AddAction(UIAlertAction.Create(arguments.Cancel, UIAlertActionStyle.Cancel, a => arguments.SetResult(false)));
+ if (arguments.Accept != null)
+ alert.AddAction(UIAlertAction.Create(arguments.Accept, UIAlertActionStyle.Default, a => arguments.SetResult(true)));
+ var page = _modals.Any() ? _modals.Last() : Page;
+ var vc = GetRenderer(page).ViewController;
+ vc.PresentViewController(alert, true, null);
+ }
+ else
+ {
+ UIAlertView alertView;
+ if (arguments.Accept != null)
+ alertView = new UIAlertView(arguments.Title, arguments.Message, null, arguments.Cancel, arguments.Accept);
+ else
+ alertView = new UIAlertView(arguments.Title, arguments.Message, null, arguments.Cancel);
+
+ alertView.Dismissed += (o, args) => arguments.SetResult(args.ButtonIndex != 0);
+ alertView.Show();
+ }
+ });
+
+ MessagingCenter.Subscribe(this, Page.ActionSheetSignalName, (Page sender, ActionSheetArguments arguments) =>
+ {
+ if (!PageIsChildOfPlatform(sender))
+ return;
+
+ var pageRoot = sender;
+ while (!Application.IsApplicationOrNull(pageRoot.RealParent))
+ pageRoot = (Page)pageRoot.RealParent;
+ var pageRenderer = GetRenderer(pageRoot);
+
+ if (Forms.IsiOS8OrNewer)
+ {
+ var alert = UIAlertController.Create(arguments.Title, null, UIAlertControllerStyle.ActionSheet);
+
+ if (arguments.Cancel != null)
+ {
+ alert.AddAction(UIAlertAction.Create(arguments.Cancel, UIAlertActionStyle.Cancel, a => arguments.SetResult(arguments.Cancel)));
+ }
+
+ if (arguments.Destruction != null)
+ {
+ alert.AddAction(UIAlertAction.Create(arguments.Destruction, UIAlertActionStyle.Destructive, a => arguments.SetResult(arguments.Destruction)));
+ }
+
+ foreach (var label in arguments.Buttons)
+ {
+ if (label == null)
+ continue;
+
+ var blabel = label;
+ alert.AddAction(UIAlertAction.Create(blabel, UIAlertActionStyle.Default, a => arguments.SetResult(blabel)));
+ }
+
+ if (UIDevice.CurrentDevice.UserInterfaceIdiom == UIUserInterfaceIdiom.Pad)
+ {
+ UIDevice.CurrentDevice.BeginGeneratingDeviceOrientationNotifications();
+ var observer = NSNotificationCenter.DefaultCenter.AddObserver(UIDevice.OrientationDidChangeNotification,
+ n => { alert.PopoverPresentationController.SourceRect = pageRenderer.ViewController.View.Bounds; });
+
+ arguments.Result.Task.ContinueWith(t =>
+ {
+ NSNotificationCenter.DefaultCenter.RemoveObserver(observer);
+ UIDevice.CurrentDevice.EndGeneratingDeviceOrientationNotifications();
+ }, TaskScheduler.FromCurrentSynchronizationContext());
+
+ alert.PopoverPresentationController.SourceView = pageRenderer.ViewController.View;
+ alert.PopoverPresentationController.SourceRect = pageRenderer.ViewController.View.Bounds;
+ alert.PopoverPresentationController.PermittedArrowDirections = 0; // No arrow
+ }
+
+ pageRenderer.ViewController.PresentViewController(alert, true, null);
+ }
+ else
+ {
+ var actionSheet = new UIActionSheet(arguments.Title, null, arguments.Cancel, arguments.Destruction, arguments.Buttons.ToArray());
+
+ actionSheet.ShowInView(pageRenderer.NativeView);
+
+ actionSheet.Clicked += (o, args) =>
+ {
+ string title = null;
+ if (args.ButtonIndex != -1)
+ title = actionSheet.ButtonTitle(args.ButtonIndex);
+
+ // iOS 8 always calls WillDismiss twice, once with the real result, and again with -1.
+ arguments.Result.TrySetResult(title);
+ };
+ }
+ });
+ }
+
+ internal UIViewController ViewController
+ {
+ get { return _renderer; }
+ }
+
+ Page Page { get; set; }
+
+ Application TargetApplication
+ {
+ get
+ {
+ if (Page == null)
+ return null;
+ return Page.RealParent as Application;
+ }
+ }
+
+ void IDisposable.Dispose()
+ {
+ if (_disposed)
+ return;
+ _disposed = true;
+
+ Page.DescendantRemoved -= HandleChildRemoved;
+ MessagingCenter.Unsubscribe<Page, ActionSheetArguments>(this, Page.ActionSheetSignalName);
+ MessagingCenter.Unsubscribe<Page, AlertArguments>(this, Page.AlertSignalName);
+ MessagingCenter.Unsubscribe<Page, bool>(this, Page.BusySetSignalName);
+
+ DisposeModelAndChildrenRenderers(Page);
+ foreach (var modal in _modals)
+ DisposeModelAndChildrenRenderers(modal);
+
+ _renderer.Dispose();
+ }
+
+ void INavigation.InsertPageBefore(Page page, Page before)
+ {
+ throw new InvalidOperationException("InsertPageBefore is not supported globally on iOS, please use a NavigationPage.");
+ }
+
+ IReadOnlyList<Page> INavigation.ModalStack
+ {
+ get { return _modals; }
+ }
+
+ IReadOnlyList<Page> INavigation.NavigationStack
+ {
+ get { return new List<Page>(); }
+ }
+
+ Task<Page> INavigation.PopAsync()
+ {
+ return ((INavigation)this).PopAsync(true);
+ }
+
+ Task<Page> INavigation.PopAsync(bool animated)
+ {
+ throw new InvalidOperationException("PopAsync is not supported globally on iOS, please use a NavigationPage.");
+ }
+
+ Task<Page> INavigation.PopModalAsync()
+ {
+ return ((INavigation)this).PopModalAsync(true);
+ }
+
+ async Task<Page> INavigation.PopModalAsync(bool animated)
+ {
+ var modal = _modals.Last();
+ _modals.Remove(modal);
+ modal.DescendantRemoved -= HandleChildRemoved;
+
+ var controller = GetRenderer(modal) as UIViewController;
+
+ if (_modals.Count >= 1 && controller != null)
+ await controller.DismissViewControllerAsync(animated);
+ else
+ await _renderer.DismissViewControllerAsync(animated);
+
+ DisposeModelAndChildrenRenderers(modal);
+
+ return modal;
+ }
+
+ Task INavigation.PopToRootAsync()
+ {
+ return ((INavigation)this).PopToRootAsync(true);
+ }
+
+ Task INavigation.PopToRootAsync(bool animated)
+ {
+ throw new InvalidOperationException("PopToRootAsync is not supported globally on iOS, please use a NavigationPage.");
+ }
+
+ Task INavigation.PushAsync(Page root)
+ {
+ return ((INavigation)this).PushAsync(root, true);
+ }
+
+ Task INavigation.PushAsync(Page root, bool animated)
+ {
+ throw new InvalidOperationException("PushAsync is not supported globally on iOS, please use a NavigationPage.");
+ }
+
+ Task INavigation.PushModalAsync(Page modal)
+ {
+ return ((INavigation)this).PushModalAsync(modal, true);
+ }
+
+ Task INavigation.PushModalAsync(Page modal, bool animated)
+ {
+ _modals.Add(modal);
+ modal.Platform = this;
+
+ modal.DescendantRemoved += HandleChildRemoved;
+
+ if (_appeared)
+ return PresentModal(modal, _animateModals && animated);
+ return Task.FromResult<object>(null);
+ }
+
+ void INavigation.RemovePage(Page page)
+ {
+ throw new InvalidOperationException("RemovePage is not supported globally on iOS, please use a NavigationPage.");
+ }
+
+ SizeRequest IPlatform.GetNativeSize(VisualElement view, double widthConstraint, double heightConstraint)
+ {
+ var renderView = GetRenderer(view);
+ if (renderView == null || renderView.NativeView == null)
+ return new SizeRequest(Size.Zero);
+
+ return renderView.GetDesiredSize(widthConstraint, heightConstraint);
+ }
+
+ public static IVisualElementRenderer CreateRenderer(VisualElement element)
+ {
+ var t = element.GetType();
+ var renderer = Registrar.Registered.GetHandler<IVisualElementRenderer>(t) ?? new DefaultRenderer();
+ renderer.SetElement(element);
+ return renderer;
+ }
+
+ public static IVisualElementRenderer GetRenderer(VisualElement bindable)
+ {
+ return (IVisualElementRenderer)bindable.GetValue(RendererProperty);
+ }
+
+ public static void SetRenderer(VisualElement bindable, IVisualElementRenderer value)
+ {
+ bindable.SetValue(RendererProperty, value);
+ }
+
+ protected override void OnBindingContextChanged()
+ {
+ SetInheritedBindingContext(Page, BindingContext);
+
+ base.OnBindingContextChanged();
+ }
+
+ internal void DidAppear()
+ {
+ _animateModals = false;
+ TargetApplication.NavigationProxy.Inner = this;
+ _animateModals = true;
+ }
+
+ internal void DisposeModelAndChildrenRenderers(Element view)
+ {
+ IVisualElementRenderer renderer;
+ foreach (VisualElement child in view.Descendants())
+ {
+ renderer = GetRenderer(child);
+ child.ClearValue(RendererProperty);
+
+ if (renderer != null)
+ {
+ renderer.NativeView.RemoveFromSuperview();
+ renderer.Dispose();
+ }
+ }
+
+ renderer = GetRenderer((VisualElement)view);
+ if (renderer != null)
+ {
+ if (renderer.ViewController != null)
+ {
+ var modalWrapper = renderer.ViewController.ParentViewController as ModalWrapper;
+ if (modalWrapper != null)
+ modalWrapper.Dispose();
+ }
+
+ renderer.NativeView.RemoveFromSuperview();
+ renderer.Dispose();
+ }
+ view.ClearValue(RendererProperty);
+ }
+
+ internal void DisposeRendererAndChildren(IVisualElementRenderer rendererToRemove)
+ {
+ if (rendererToRemove == null)
+ return;
+
+ if (rendererToRemove.Element != null && GetRenderer(rendererToRemove.Element) == rendererToRemove)
+ rendererToRemove.Element.ClearValue(RendererProperty);
+
+ var subviews = rendererToRemove.NativeView.Subviews;
+ for (var i = 0; i < subviews.Length; i++)
+ {
+ var childRenderer = subviews[i] as IVisualElementRenderer;
+ if (childRenderer != null)
+ DisposeRendererAndChildren(childRenderer);
+ }
+
+ rendererToRemove.NativeView.RemoveFromSuperview();
+ rendererToRemove.Dispose();
+ }
+
+ internal void LayoutSubviews()
+ {
+ if (Page == null)
+ return;
+
+ var rootRenderer = GetRenderer(Page);
+
+ if (rootRenderer == null)
+ return;
+
+ rootRenderer.SetElementSize(new Size(_renderer.View.Bounds.Width, _renderer.View.Bounds.Height));
+ }
+
+ internal void SetPage(Page newRoot)
+ {
+ if (newRoot == null)
+ return;
+ if (Page != null)
+ throw new NotImplementedException();
+ Page = newRoot;
+
+ if (_appeared == false)
+ return;
+
+ Page.Platform = this;
+ AddChild(Page);
+
+ Page.DescendantRemoved += HandleChildRemoved;
+
+ TargetApplication.NavigationProxy.Inner = this;
+ }
+
+ internal void WillAppear()
+ {
+ if (_appeared)
+ return;
+
+ _renderer.View.BackgroundColor = UIColor.White;
+ _renderer.View.ContentMode = UIViewContentMode.Redraw;
+
+ Page.Platform = this;
+ AddChild(Page);
+
+ Page.DescendantRemoved += HandleChildRemoved;
+
+ _appeared = true;
+ }
+
+ void AddChild(VisualElement view)
+ {
+ if (!Application.IsApplicationOrNull(view.RealParent))
+ Console.Error.WriteLine("Tried to add parented view to canvas directly");
+
+ if (GetRenderer(view) == null)
+ {
+ var viewRenderer = CreateRenderer(view);
+ SetRenderer(view, viewRenderer);
+
+ _renderer.View.AddSubview(viewRenderer.NativeView);
+ if (viewRenderer.ViewController != null)
+ _renderer.AddChildViewController(viewRenderer.ViewController);
+ viewRenderer.NativeView.Frame = new RectangleF(0, 0, _renderer.View.Bounds.Width, _renderer.View.Bounds.Height);
+ viewRenderer.SetElementSize(new Size(_renderer.View.Bounds.Width, _renderer.View.Bounds.Height));
+ }
+ else
+ Console.Error.WriteLine("Potential view double add");
+ }
+
+ void HandleChildRemoved(object sender, ElementEventArgs e)
+ {
+ var view = e.Element;
+ DisposeModelAndChildrenRenderers(view);
+ }
+
+ bool PageIsChildOfPlatform(Page page)
+ {
+ while (!Application.IsApplicationOrNull(page.RealParent))
+ page = (Page)page.RealParent;
+
+ return Page == page || _modals.Contains(page);
+ }
+
+ async Task PresentModal(Page modal, bool animated)
+ {
+ var modalRenderer = GetRenderer(modal);
+ if (modalRenderer == null)
+ {
+ modalRenderer = CreateRenderer(modal);
+ SetRenderer(modal, modalRenderer);
+ }
+
+ var wrapper = new ModalWrapper(modalRenderer);
+
+ if (_modals.Count > 1)
+ {
+ var topPage = _modals[_modals.Count - 2];
+ var controller = GetRenderer(topPage) as UIViewController;
+ if (controller != null)
+ {
+ await controller.PresentViewControllerAsync(wrapper, animated);
+ await Task.Delay(5);
+ return;
+ }
+ }
+
+ // One might wonder why these delays are here... well thats a great question. It turns out iOS will claim the
+ // presentation is complete before it really is. It does not however inform you when it is really done (and thus
+ // would be safe to dismiss the VC). Fortunately this is almost never an issue
+ await _renderer.PresentViewControllerAsync(wrapper, animated);
+ await Task.Delay(5);
+ }
+
+ internal class DefaultRenderer : VisualElementRenderer<VisualElement>
+ {
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.iOS/PlatformEffect.cs b/Xamarin.Forms.Platform.iOS/PlatformEffect.cs
new file mode 100644
index 00000000..436ef6d7
--- /dev/null
+++ b/Xamarin.Forms.Platform.iOS/PlatformEffect.cs
@@ -0,0 +1,14 @@
+using System.ComponentModel;
+#if __UNIFIED__
+using UIKit;
+
+#else
+using MonoTouch.UIKit;
+#endif
+
+namespace Xamarin.Forms.Platform.iOS
+{
+ public abstract class PlatformEffect : PlatformEffect<UIView, UIView>
+ {
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.iOS/PlatformRenderer.cs b/Xamarin.Forms.Platform.iOS/PlatformRenderer.cs
new file mode 100644
index 00000000..43534b2b
--- /dev/null
+++ b/Xamarin.Forms.Platform.iOS/PlatformRenderer.cs
@@ -0,0 +1,85 @@
+using System;
+#if __UNIFIED__
+using UIKit;
+
+#else
+using MonoTouch.UIKit;
+#endif
+
+namespace Xamarin.Forms.Platform.iOS
+{
+ internal class ModalWrapper : UIViewController
+ {
+ IVisualElementRenderer _modal;
+
+ internal ModalWrapper(IVisualElementRenderer modal)
+ {
+ _modal = modal;
+
+ View.BackgroundColor = UIColor.White;
+ View.AddSubview(modal.ViewController.View);
+ AddChildViewController(modal.ViewController);
+
+ modal.ViewController.DidMoveToParentViewController(this);
+ }
+
+ public override UIInterfaceOrientationMask GetSupportedInterfaceOrientations()
+ {
+ return UIInterfaceOrientationMask.All;
+ }
+
+ public override void ViewDidLayoutSubviews()
+ {
+ base.ViewDidLayoutSubviews();
+ if (_modal != null)
+ _modal.SetElementSize(new Size(View.Bounds.Width, View.Bounds.Height));
+ }
+
+ public override void ViewWillAppear(bool animated)
+ {
+ View.BackgroundColor = UIColor.White;
+ base.ViewWillAppear(animated);
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing)
+ _modal = null;
+ base.Dispose(disposing);
+ }
+ }
+
+ internal class PlatformRenderer : UIViewController
+ {
+ internal PlatformRenderer(Platform platform)
+ {
+ Platform = platform;
+ }
+
+ public Platform Platform { get; set; }
+
+ public override UIInterfaceOrientationMask GetSupportedInterfaceOrientations()
+ {
+ return UIInterfaceOrientationMask.All;
+ }
+
+ public override void ViewDidAppear(bool animated)
+ {
+ Platform.DidAppear();
+ base.ViewDidAppear(animated);
+ }
+
+ public override void ViewDidLayoutSubviews()
+ {
+ base.ViewDidLayoutSubviews();
+ Platform.LayoutSubviews();
+ }
+
+ public override void ViewWillAppear(bool animated)
+ {
+ View.BackgroundColor = UIColor.White;
+ Platform.WillAppear();
+ base.ViewWillAppear(animated);
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.iOS/Properties/AssemblyInfo.cs b/Xamarin.Forms.Platform.iOS/Properties/AssemblyInfo.cs
new file mode 100644
index 00000000..8ebe9ba1
--- /dev/null
+++ b/Xamarin.Forms.Platform.iOS/Properties/AssemblyInfo.cs
@@ -0,0 +1,69 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+// Information about this assembly is defined by the following attributes.
+// Change them to the values specific to your project.
+using Xamarin.Forms;
+using Xamarin.Forms.Platform.iOS;
+#if __UNIFIED__
+using UIKit;
+
+#else
+using MonoTouch.UIKit;
+#endif
+
+[assembly: AssemblyTitle("Xamarin.Forms.Platform.iOS")]
+[assembly: AssemblyDescription("iOS Backend for Xamarin.Forms")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCulture("")]
+
+// The following attributes are used to specify the signing key for the assembly,
+// if desired. See the Mono documentation for more information about signing.
+
+//[assembly: AssemblyDelaySign(false)]
+//[assembly: AssemblyKeyFile("")]
+
+[assembly: ExportRenderer(typeof(BoxView), typeof(BoxRenderer))]
+[assembly: ExportRenderer(typeof(Entry), typeof(EntryRenderer))]
+[assembly: ExportRenderer(typeof(Editor), typeof(EditorRenderer))]
+[assembly: ExportRenderer(typeof(Label), typeof(LabelRenderer))]
+[assembly: ExportRenderer(typeof(Image), typeof(ImageRenderer))]
+[assembly: ExportRenderer(typeof(Button), typeof(ButtonRenderer))]
+[assembly: ExportRenderer(typeof(TableView), typeof(TableViewRenderer))]
+[assembly: ExportRenderer(typeof(Slider), typeof(SliderRenderer))]
+[assembly: ExportRenderer(typeof(WebView), typeof(WebViewRenderer))]
+[assembly: ExportRenderer(typeof(SearchBar), typeof(SearchBarRenderer))]
+[assembly: ExportRenderer(typeof(Switch), typeof(SwitchRenderer))]
+[assembly: ExportRenderer(typeof(DatePicker), typeof(DatePickerRenderer))]
+[assembly: ExportRenderer(typeof(TimePicker), typeof(TimePickerRenderer))]
+[assembly: ExportRenderer(typeof(Picker), typeof(PickerRenderer))]
+[assembly: ExportRenderer(typeof(Stepper), typeof(StepperRenderer))]
+[assembly: ExportRenderer(typeof(ProgressBar), typeof(ProgressBarRenderer))]
+[assembly: ExportRenderer(typeof(ScrollView), typeof(ScrollViewRenderer))]
+[assembly: ExportRenderer(typeof(Toolbar), typeof(ToolbarRenderer))]
+[assembly: ExportRenderer(typeof(ActivityIndicator), typeof(ActivityIndicatorRenderer))]
+[assembly: ExportRenderer(typeof(Frame), typeof(FrameRenderer))]
+[assembly: ExportRenderer(typeof(NavigationMenu), typeof(NavigationMenuRenderer))]
+[assembly: ExportRenderer(typeof(ListView), typeof(ListViewRenderer))]
+[assembly: ExportRenderer(typeof(CarouselView), typeof(CarouselViewRenderer))]
+[assembly: ExportRenderer(typeof(OpenGLView), typeof(OpenGLViewRenderer))]
+[assembly: ExportRenderer(typeof(NativeViewWrapper), typeof(NativeViewWrapperRenderer))]
+[assembly: ExportRenderer(typeof(TabbedPage), typeof(TabbedRenderer))]
+[assembly: ExportRenderer(typeof(NavigationPage), typeof(NavigationRenderer))]
+[assembly: ExportRenderer(typeof(CarouselPage), typeof(CarouselPageRenderer))]
+[assembly: ExportRenderer(typeof(Page), typeof(PageRenderer))]
+[assembly: ExportRenderer(typeof(MasterDetailPage), typeof(PhoneMasterDetailRenderer), UIUserInterfaceIdiom.Phone)]
+[assembly: ExportRenderer(typeof(MasterDetailPage), typeof(TabletMasterDetailRenderer), UIUserInterfaceIdiom.Pad)]
+[assembly: ExportCell(typeof(Cell), typeof(CellRenderer))]
+[assembly: ExportCell(typeof(ImageCell), typeof(ImageCellRenderer))]
+[assembly: ExportCell(typeof(EntryCell), typeof(EntryCellRenderer))]
+[assembly: ExportCell(typeof(TextCell), typeof(TextCellRenderer))]
+[assembly: ExportCell(typeof(ViewCell), typeof(ViewCellRenderer))]
+[assembly: ExportCell(typeof(SwitchCell), typeof(SwitchCellRenderer))]
+[assembly: ExportImageSourceHandler(typeof(FileImageSource), typeof(FileImageSourceHandler))]
+[assembly: ExportImageSourceHandler(typeof(StreamImageSource), typeof(StreamImagesourceHandler))]
+[assembly: ExportImageSourceHandler(typeof(UriImageSource), typeof(ImageLoaderSourceHandler))]
+[assembly: InternalsVisibleTo("iOSUnitTests")]
+[assembly: InternalsVisibleTo("Xamarin.Forms.Platform")]
+[assembly: Xamarin.Forms.Dependency(typeof(Deserializer))]
+[assembly: Xamarin.Forms.Dependency(typeof(ResourcesProvider))]
+[assembly: Preserve] \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.iOS/RendererFactory.cs b/Xamarin.Forms.Platform.iOS/RendererFactory.cs
new file mode 100644
index 00000000..ff37905a
--- /dev/null
+++ b/Xamarin.Forms.Platform.iOS/RendererFactory.cs
@@ -0,0 +1,13 @@
+using System;
+
+namespace Xamarin.Forms.Platform.iOS
+{
+ public static class RendererFactory
+ {
+ [Obsolete("Use Platform.CreateRenderer")]
+ public static IVisualElementRenderer GetRenderer(VisualElement view)
+ {
+ return Platform.CreateRenderer(view);
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.iOS/RendererPool.cs b/Xamarin.Forms.Platform.iOS/RendererPool.cs
new file mode 100644
index 00000000..25df358d
--- /dev/null
+++ b/Xamarin.Forms.Platform.iOS/RendererPool.cs
@@ -0,0 +1,154 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+#if __UNIFIED__
+using UIKit;
+
+#else
+using MonoTouch.UIKit;
+#endif
+
+namespace Xamarin.Forms.Platform.iOS
+{
+ public sealed class RendererPool
+ {
+ readonly Dictionary<Type, Stack<IVisualElementRenderer>> _freeRenderers = new Dictionary<Type, Stack<IVisualElementRenderer>>();
+
+ readonly VisualElement _oldElement;
+
+ readonly IVisualElementRenderer _parent;
+
+ public RendererPool(IVisualElementRenderer renderer, VisualElement oldElement)
+ {
+ if (renderer == null)
+ throw new ArgumentNullException("renderer");
+
+ if (oldElement == null)
+ throw new ArgumentNullException("oldElement");
+
+ _oldElement = oldElement;
+ _parent = renderer;
+ }
+
+ public IVisualElementRenderer GetFreeRenderer(VisualElement view)
+ {
+ if (view == null)
+ throw new ArgumentNullException("view");
+
+ var rendererType = Registrar.Registered.GetHandlerType(view.GetType()) ?? typeof(ViewRenderer);
+
+ Stack<IVisualElementRenderer> renderers;
+ if (!_freeRenderers.TryGetValue(rendererType, out renderers) || renderers.Count == 0)
+ return null;
+
+ var renderer = renderers.Pop();
+ renderer.SetElement(view);
+ return renderer;
+ }
+
+ public void UpdateNewElement(VisualElement newElement)
+ {
+ if (newElement == null)
+ throw new ArgumentNullException("newElement");
+
+ var sameChildrenTypes = true;
+
+ var oldChildren = _oldElement.LogicalChildren;
+ var newChildren = newElement.LogicalChildren;
+
+ if (oldChildren.Count == newChildren.Count)
+ {
+ for (var i = 0; i < oldChildren.Count; i++)
+ {
+ if (oldChildren[i].GetType() != newChildren[i].GetType())
+ {
+ sameChildrenTypes = false;
+ break;
+ }
+ }
+ }
+ else
+ sameChildrenTypes = false;
+
+ if (!sameChildrenTypes)
+ {
+ ClearRenderers(_parent);
+ FillChildrenWithRenderers(newElement);
+ }
+ else
+ UpdateRenderers(newElement);
+ }
+
+ void ClearRenderers(IVisualElementRenderer renderer)
+ {
+ if (renderer == null)
+ return;
+
+ var subviews = renderer.NativeView.Subviews;
+ for (var i = 0; i < subviews.Length; i++)
+ {
+ var childRenderer = subviews[i] as IVisualElementRenderer;
+ if (childRenderer != null)
+ {
+ PushRenderer(childRenderer);
+
+ if (ReferenceEquals(childRenderer, Platform.GetRenderer(childRenderer.Element)))
+ childRenderer.Element.ClearValue(Platform.RendererProperty);
+ }
+
+ subviews[i].RemoveFromSuperview();
+ }
+ }
+
+ void FillChildrenWithRenderers(VisualElement element)
+ {
+ foreach (var logicalChild in element.LogicalChildren)
+ {
+ var child = logicalChild as VisualElement;
+ if (child != null)
+ {
+ var renderer = GetFreeRenderer(child) ?? Platform.CreateRenderer(child);
+ Platform.SetRenderer(child, renderer);
+
+ _parent.NativeView.AddSubview(renderer.NativeView);
+ }
+ }
+ }
+
+ void PushRenderer(IVisualElementRenderer renderer)
+ {
+ var rendererType = renderer.GetType();
+
+ Stack<IVisualElementRenderer> renderers;
+ if (!_freeRenderers.TryGetValue(rendererType, out renderers))
+ _freeRenderers[rendererType] = renderers = new Stack<IVisualElementRenderer>();
+
+ renderers.Push(renderer);
+ }
+
+ void UpdateRenderers(Element newElement)
+ {
+ if (newElement.LogicalChildren.Count == 0)
+ return;
+
+ var subviews = _parent.NativeView.Subviews;
+ for (var i = 0; i < subviews.Length; i++)
+ {
+ var childRenderer = subviews[i] as IVisualElementRenderer;
+ if (childRenderer == null)
+ continue;
+
+ var x = (int)childRenderer.NativeView.Layer.ZPosition / 1000;
+ var element = newElement.LogicalChildren[x] as VisualElement;
+ if (element == null)
+ continue;
+
+ if (childRenderer.Element != null && ReferenceEquals(childRenderer, Platform.GetRenderer(childRenderer.Element)))
+ childRenderer.Element.ClearValue(Platform.RendererProperty);
+
+ childRenderer.SetElement(element);
+ Platform.SetRenderer(element, childRenderer);
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.iOS/Renderers/ActivityIndicatorRenderer.cs b/Xamarin.Forms.Platform.iOS/Renderers/ActivityIndicatorRenderer.cs
new file mode 100644
index 00000000..0ef8741f
--- /dev/null
+++ b/Xamarin.Forms.Platform.iOS/Renderers/ActivityIndicatorRenderer.cs
@@ -0,0 +1,53 @@
+using System.Drawing;
+using System.ComponentModel;
+#if __UNIFIED__
+using UIKit;
+
+#else
+using MonoTouch.UIKit;
+#endif
+
+namespace Xamarin.Forms.Platform.iOS
+{
+ public class ActivityIndicatorRenderer : ViewRenderer<ActivityIndicator, UIActivityIndicatorView>
+ {
+ protected override void OnElementChanged(ElementChangedEventArgs<ActivityIndicator> e)
+ {
+ if (e.NewElement != null)
+ {
+ if (Control == null)
+ {
+ SetNativeControl(new UIActivityIndicatorView(RectangleF.Empty) { ActivityIndicatorViewStyle = UIActivityIndicatorViewStyle.Gray });
+ }
+
+ 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()
+ {
+ Control.Color = Element.Color == Color.Default ? null : Element.Color.ToUIColor();
+ }
+
+ void UpdateIsRunning()
+ {
+ if (Element.IsRunning)
+ Control.StartAnimating();
+ else
+ Control.StopAnimating();
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.iOS/Renderers/AlignmentExtensions.cs b/Xamarin.Forms.Platform.iOS/Renderers/AlignmentExtensions.cs
new file mode 100644
index 00000000..01d0bf66
--- /dev/null
+++ b/Xamarin.Forms.Platform.iOS/Renderers/AlignmentExtensions.cs
@@ -0,0 +1,25 @@
+#if __UNIFIED__
+using UIKit;
+
+#else
+using MonoTouch.UIKit;
+#endif
+
+namespace Xamarin.Forms.Platform.iOS
+{
+ internal static class AlignmentExtensions
+ {
+ internal static UITextAlignment ToNativeTextAlignment(this TextAlignment alignment)
+ {
+ switch (alignment)
+ {
+ case TextAlignment.Center:
+ return UITextAlignment.Center;
+ case TextAlignment.End:
+ return UITextAlignment.Right;
+ default:
+ return UITextAlignment.Left;
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.iOS/Renderers/BoxRenderer.cs b/Xamarin.Forms.Platform.iOS/Renderers/BoxRenderer.cs
new file mode 100644
index 00000000..f60ed6c1
--- /dev/null
+++ b/Xamarin.Forms.Platform.iOS/Renderers/BoxRenderer.cs
@@ -0,0 +1,78 @@
+using System.ComponentModel;
+using System.Drawing;
+#if __UNIFIED__
+using UIKit;
+using CoreGraphics;
+#else
+using MonoTouch.UIKit;
+using MonoTouch.CoreGraphics;
+#endif
+#if __UNIFIED__
+using RectangleF = CoreGraphics.CGRect;
+using SizeF = CoreGraphics.CGSize;
+using PointF = CoreGraphics.CGPoint;
+
+#else
+using nfloat=System.Single;
+using nint=System.Int32;
+using nuint=System.UInt32;
+#endif
+
+namespace Xamarin.Forms.Platform.iOS
+{
+ public class BoxRenderer : VisualElementRenderer<BoxView>
+ {
+ UIColor _colorToRenderer;
+
+ SizeF _previousSize;
+
+ public override void Draw(RectangleF rect)
+ {
+ using(var context = UIGraphics.GetCurrentContext())
+ {
+ _colorToRenderer.SetFill();
+ context.FillRect(rect);
+ }
+ base.Draw(rect);
+
+ _previousSize = Bounds.Size;
+ }
+
+ public override void LayoutSubviews()
+ {
+ if (_previousSize != Bounds.Size)
+ SetNeedsDisplay();
+ }
+
+ protected override void OnElementChanged(ElementChangedEventArgs<BoxView> e)
+ {
+ base.OnElementChanged(e);
+
+ if (Element != null)
+ SetBackgroundColor(Element.BackgroundColor);
+ }
+
+ 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)
+ SetNeedsDisplay();
+ }
+
+ protected override void SetBackgroundColor(Color color)
+ {
+ if (Element == null)
+ return;
+
+ var elementColor = Element.Color;
+ if (!elementColor.IsDefault)
+ _colorToRenderer = elementColor.ToUIColor();
+ else
+ _colorToRenderer = color.ToUIColor();
+
+ SetNeedsDisplay();
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.iOS/Renderers/ButtonRenderer.cs b/Xamarin.Forms.Platform.iOS/Renderers/ButtonRenderer.cs
new file mode 100644
index 00000000..3fa84d1d
--- /dev/null
+++ b/Xamarin.Forms.Platform.iOS/Renderers/ButtonRenderer.cs
@@ -0,0 +1,190 @@
+using System;
+using System.Drawing;
+using System.Linq;
+using System.ComponentModel;
+#if __UNIFIED__
+using UIKit;
+using CoreGraphics;
+#else
+using MonoTouch.UIKit;
+using MonoTouch.CoreGraphics;
+#endif
+#if __UNIFIED__
+using RectangleF = CoreGraphics.CGRect;
+using SizeF = CoreGraphics.CGSize;
+using PointF = CoreGraphics.CGPoint;
+
+#else
+using nfloat=System.Single;
+using nint=System.Int32;
+using nuint=System.UInt32;
+#endif
+
+namespace Xamarin.Forms.Platform.iOS
+{
+ public class ButtonRenderer : ViewRenderer<Button, UIButton>
+ {
+ UIColor _buttonTextColorDefaultDisabled;
+ UIColor _buttonTextColorDefaultHighlighted;
+ UIColor _buttonTextColorDefaultNormal;
+
+ public override SizeF SizeThatFits(SizeF size)
+ {
+ var result = base.SizeThatFits(size);
+ result.Height = 44; // Apple docs
+ //Compensate for the insets
+ if (!Control.ImageView.Hidden)
+ result.Width += 10;
+ return result;
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ if (Control != null)
+ Control.TouchUpInside -= OnButtonTouchUpInside;
+
+ base.Dispose(disposing);
+ }
+
+ protected override void OnElementChanged(ElementChangedEventArgs<Button> e)
+ {
+ base.OnElementChanged(e);
+
+ if (e.NewElement != null)
+ {
+ if (Control == null)
+ {
+ SetNativeControl(new UIButton(UIButtonType.RoundedRect));
+
+ _buttonTextColorDefaultNormal = Control.TitleColor(UIControlState.Normal);
+ _buttonTextColorDefaultHighlighted = Control.TitleColor(UIControlState.Highlighted);
+ _buttonTextColorDefaultDisabled = Control.TitleColor(UIControlState.Disabled);
+
+ Control.TouchUpInside += OnButtonTouchUpInside;
+ }
+
+ UpdateText();
+ UpdateFont();
+ UpdateBorder();
+ UpdateImage();
+ UpdateTextColor();
+ }
+ }
+
+ protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ base.OnElementPropertyChanged(sender, e);
+
+ if (e.PropertyName == Button.TextProperty.PropertyName)
+ UpdateText();
+ else if (e.PropertyName == Button.TextColorProperty.PropertyName)
+ UpdateTextColor();
+ 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 OnButtonTouchUpInside(object sender, EventArgs eventArgs)
+ {
+ if (Element != null)
+ ((IButtonController)Element).SendClicked();
+ }
+
+ void UpdateBackgroundVisibility()
+ {
+ if (Forms.IsiOS7OrNewer)
+ return;
+
+ var model = Element;
+ var shouldDrawImage = model.BackgroundColor == Color.Default;
+
+ foreach (var control in Control.Subviews.Where(sv => !(sv is UILabel)))
+ control.Alpha = shouldDrawImage ? 1.0f : 0.0f;
+ }
+
+ 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.TitleLabel.Font = Element.ToUIFont();
+ }
+
+ async void UpdateImage()
+ {
+ IImageSourceHandler handler;
+ var source = Element.Image;
+ if (source != null && (handler = Registrar.Registered.GetHandler<IImageSourceHandler>(source.GetType())) != null)
+ {
+ UIImage uiimage;
+ try
+ {
+ uiimage = await handler.LoadImageAsync(source, scale: (float)UIScreen.MainScreen.Scale);
+ }
+ catch (OperationCanceledException)
+ {
+ uiimage = null;
+ }
+ var button = Control;
+ if (button != null && uiimage != null)
+ {
+ if (Forms.IsiOS7OrNewer)
+ button.SetImage(uiimage.ImageWithRenderingMode(UIImageRenderingMode.AlwaysOriginal), UIControlState.Normal);
+ else
+ button.SetImage(uiimage, UIControlState.Normal);
+ button.ImageView.ContentMode = UIViewContentMode.ScaleAspectFit;
+
+ Control.ImageEdgeInsets = new UIEdgeInsets(0, 0, 0, 10);
+ Control.TitleEdgeInsets = new UIEdgeInsets(0, 10, 0, 0);
+ }
+ }
+ else
+ {
+ Control.SetImage(null, UIControlState.Normal);
+ Control.ImageEdgeInsets = new UIEdgeInsets(0, 0, 0, 0);
+ Control.TitleEdgeInsets = new UIEdgeInsets(0, 0, 0, 0);
+ }
+ ((IVisualElementController)Element).NativeSizeChanged();
+ }
+
+ void UpdateText()
+ {
+ Control.SetTitle(Element.Text, UIControlState.Normal);
+ }
+
+ void UpdateTextColor()
+ {
+ if (Element.TextColor == Color.Default)
+ {
+ Control.SetTitleColor(_buttonTextColorDefaultNormal, UIControlState.Normal);
+ Control.SetTitleColor(_buttonTextColorDefaultHighlighted, UIControlState.Highlighted);
+ Control.SetTitleColor(_buttonTextColorDefaultDisabled, UIControlState.Disabled);
+ }
+ else
+ {
+ Control.SetTitleColor(Element.TextColor.ToUIColor(), UIControlState.Normal);
+ Control.SetTitleColor(Element.TextColor.ToUIColor(), UIControlState.Highlighted);
+ Control.SetTitleColor(_buttonTextColorDefaultDisabled, UIControlState.Disabled);
+
+ if (Forms.IsiOS7OrNewer)
+ Control.TintColor = Element.TextColor.ToUIColor();
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.iOS/Renderers/CarouselPageRenderer.cs b/Xamarin.Forms.Platform.iOS/Renderers/CarouselPageRenderer.cs
new file mode 100644
index 00000000..16e97cd2
--- /dev/null
+++ b/Xamarin.Forms.Platform.iOS/Renderers/CarouselPageRenderer.cs
@@ -0,0 +1,395 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.Specialized;
+using System.ComponentModel;
+using System.Drawing;
+#if __UNIFIED__
+using UIKit;
+#else
+using MonoTouch.UIKit;
+#endif
+#if __UNIFIED__
+using RectangleF = CoreGraphics.CGRect;
+using SizeF = CoreGraphics.CGSize;
+using PointF = CoreGraphics.CGPoint;
+
+#else
+using nfloat = System.Single;
+using nint = System.Int32;
+using nuint = System.UInt32;
+#endif
+
+namespace Xamarin.Forms.Platform.iOS
+{
+ public class CarouselPageRenderer : UIViewController, IVisualElementRenderer
+ {
+ bool _appeared;
+ Dictionary<Page, UIView> _containerMap;
+ bool _disposed;
+ EventTracker _events;
+ bool _ignoreNativeScrolling;
+ UIScrollView _scrollView;
+ VisualElementTracker _tracker;
+
+ public CarouselPageRenderer()
+ {
+ if (!Forms.IsiOS7OrNewer)
+ WantsFullScreenLayout = true;
+ }
+
+ protected CarouselPage Carousel
+ {
+ get { return (CarouselPage)Element; }
+ }
+
+ protected int SelectedIndex
+ {
+ get { return (int)(_scrollView.ContentOffset.X / _scrollView.Frame.Width); }
+ set { ScrollToPage(value); }
+ }
+
+ public VisualElement Element { get; private set; }
+
+ public event EventHandler<VisualElementChangedEventArgs> ElementChanged;
+
+ public SizeRequest GetDesiredSize(double widthConstraint, double heightConstraint)
+ {
+ return NativeView.GetSizeRequest(widthConstraint, heightConstraint);
+ }
+
+ public UIView NativeView
+ {
+ get { return View; }
+ }
+
+ public void SetElement(VisualElement element)
+ {
+ VisualElement oldElement = Element;
+ Element = element;
+ _containerMap = new Dictionary<Page, UIView>();
+
+ OnElementChanged(new VisualElementChangedEventArgs(oldElement, element));
+
+ if (element != null)
+ element.SendViewInitialized(NativeView);
+ }
+
+ public void SetElementSize(Size size)
+ {
+ Element.Layout(new Rectangle(Element.X, Element.Y, size.Width, size.Height));
+ }
+
+ public UIViewController ViewController
+ {
+ get { return this; }
+ }
+
+ public override void DidRotate(UIInterfaceOrientation fromInterfaceOrientation)
+ {
+ _ignoreNativeScrolling = false;
+ View.SetNeedsLayout();
+ }
+
+ public override void ViewDidAppear(bool animated)
+ {
+ base.ViewDidAppear(animated);
+
+ if (_appeared || _disposed)
+ return;
+
+ _appeared = true;
+ Carousel.SendAppearing();
+ }
+
+ public override void ViewDidDisappear(bool animated)
+ {
+ base.ViewDidDisappear(animated);
+
+ if (!_appeared || _disposed)
+ return;
+
+ _appeared = false;
+ Carousel.SendDisappearing();
+ }
+
+ public override void ViewDidLayoutSubviews()
+ {
+ base.ViewDidLayoutSubviews();
+
+ View.Frame = View.Superview.Bounds;
+ _scrollView.Frame = View.Bounds;
+
+ PositionChildren();
+ UpdateCurrentPage(false);
+ }
+
+ public override void ViewDidLoad()
+ {
+ base.ViewDidLoad();
+
+ _tracker = new VisualElementTracker(this);
+ _events = new EventTracker(this);
+ _events.LoadEvents(View);
+
+ _scrollView = new UIScrollView { ShowsHorizontalScrollIndicator = false };
+
+ _scrollView.DecelerationEnded += OnDecelerationEnded;
+
+ UpdateBackground();
+
+ View.Add(_scrollView);
+
+ for (var i = 0; i < Element.LogicalChildren.Count; i++)
+ {
+ Element element = Element.LogicalChildren[i];
+ var child = element as ContentPage;
+ if (child != null)
+ InsertPage(child, i);
+ }
+
+ PositionChildren();
+
+ Carousel.PropertyChanged += OnPropertyChanged;
+ Carousel.PagesChanged += OnPagesChanged;
+ }
+
+ public override void ViewDidUnload()
+ {
+ base.ViewDidUnload();
+
+ if (_scrollView != null)
+ _scrollView.DecelerationEnded -= OnDecelerationEnded;
+
+ if (Carousel != null)
+ {
+ Carousel.PropertyChanged -= OnPropertyChanged;
+ Carousel.PagesChanged -= OnPagesChanged;
+ }
+ }
+
+ public override void WillRotate(UIInterfaceOrientation toInterfaceOrientation, double duration)
+ {
+ _ignoreNativeScrolling = true;
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing && !_disposed)
+ {
+ if (_scrollView != null)
+ _scrollView.DecelerationEnded -= OnDecelerationEnded;
+
+ if (Carousel != null)
+ {
+ Carousel.PropertyChanged -= OnPropertyChanged;
+ Carousel.PagesChanged -= OnPagesChanged;
+ }
+
+ Platform.SetRenderer(Element, null);
+
+ Clear();
+
+ if (_scrollView != null)
+ {
+ _scrollView.DecelerationEnded -= OnDecelerationEnded;
+ _scrollView.RemoveFromSuperview();
+ _scrollView = null;
+ }
+
+ if (_appeared)
+ {
+ _appeared = false;
+ Carousel.SendDisappearing();
+ }
+
+ if (_events != null)
+ {
+ _events.Dispose();
+ _events = null;
+ }
+
+ if (_tracker != null)
+ {
+ _tracker.Dispose();
+ _tracker = null;
+ }
+
+ Element = null;
+ _disposed = true;
+ }
+
+ base.Dispose(disposing);
+ }
+
+ protected virtual void OnElementChanged(VisualElementChangedEventArgs e)
+ {
+ EventHandler<VisualElementChangedEventArgs> changed = ElementChanged;
+ if (changed != null)
+ changed(this, e);
+ }
+
+ void Clear()
+ {
+ foreach (KeyValuePair<Page, UIView> kvp in _containerMap)
+ {
+ kvp.Value.RemoveFromSuperview();
+ IVisualElementRenderer renderer = Platform.GetRenderer(kvp.Key);
+ if (renderer != null)
+ {
+ renderer.ViewController.RemoveFromParentViewController();
+ renderer.NativeView.RemoveFromSuperview();
+ Platform.SetRenderer(kvp.Key, null);
+ }
+ }
+ _containerMap.Clear();
+ }
+
+ void InsertPage(ContentPage page, int index)
+ {
+ IVisualElementRenderer renderer = Platform.GetRenderer(page);
+ if (renderer == null)
+ {
+ renderer = Platform.CreateRenderer(page);
+ Platform.SetRenderer(page, renderer);
+ }
+
+ UIView container = new PageContainer(page);
+ container.AddSubview(renderer.NativeView);
+ _containerMap[page] = container;
+
+ AddChildViewController(renderer.ViewController);
+ _scrollView.InsertSubview(container, index);
+
+ if ((index == 0 && SelectedIndex == 0) || (index < SelectedIndex))
+ ScrollToPage(SelectedIndex + 1, false);
+ }
+
+ void OnDecelerationEnded(object sender, EventArgs eventArgs)
+ {
+ if (_ignoreNativeScrolling || SelectedIndex >= Element.LogicalChildren.Count)
+ return;
+
+ Carousel.CurrentPage = (ContentPage)Element.LogicalChildren[SelectedIndex];
+ }
+
+ void OnPagesChanged(object sender, NotifyCollectionChangedEventArgs e)
+ {
+ _ignoreNativeScrolling = true;
+
+ NotifyCollectionChangedAction action = e.Apply((o, i, c) => InsertPage((ContentPage)o, i), (o, i) => RemovePage((ContentPage)o, i), Reset);
+ PositionChildren();
+
+ _ignoreNativeScrolling = false;
+
+ if (action == NotifyCollectionChangedAction.Reset)
+ {
+ int index = Carousel.CurrentPage != null ? CarouselPage.GetIndex(Carousel.CurrentPage) : 0;
+ if (index < 0)
+ index = 0;
+
+ ScrollToPage(index);
+ }
+ }
+
+ void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ if (e.PropertyName == "CurrentPage")
+ UpdateCurrentPage();
+ else if (e.PropertyName == VisualElement.BackgroundColorProperty.PropertyName)
+ UpdateBackground();
+ else if (e.PropertyName == Page.BackgroundImageProperty.PropertyName)
+ UpdateBackground();
+ }
+
+ void PositionChildren()
+ {
+ nfloat x = 0;
+ RectangleF bounds = View.Bounds;
+ foreach (ContentPage child in ((CarouselPage)Element).Children)
+ {
+ UIView container = _containerMap[child];
+
+ container.Frame = new RectangleF(x, bounds.Y, bounds.Width, bounds.Height);
+ x += bounds.Width;
+ }
+
+ _scrollView.PagingEnabled = true;
+ _scrollView.ContentSize = new SizeF(bounds.Width * ((CarouselPage)Element).Children.Count, bounds.Height);
+ }
+
+ void RemovePage(ContentPage page, int index)
+ {
+ UIView container = _containerMap[page];
+ container.RemoveFromSuperview();
+ _containerMap.Remove(page);
+
+ IVisualElementRenderer renderer = Platform.GetRenderer(page);
+ if (renderer == null)
+ return;
+
+ renderer.ViewController.RemoveFromParentViewController();
+ renderer.NativeView.RemoveFromSuperview();
+ }
+
+ void Reset()
+ {
+ Clear();
+
+ for (var i = 0; i < Element.LogicalChildren.Count; i++)
+ {
+ Element element = Element.LogicalChildren[i];
+ var child = element as ContentPage;
+ if (child != null)
+ InsertPage(child, i);
+ }
+ }
+
+ void ScrollToPage(int index, bool animated = true)
+ {
+ if (_scrollView.ContentOffset.X == index * _scrollView.Frame.Width)
+ return;
+
+ _scrollView.SetContentOffset(new PointF(index * _scrollView.Frame.Width, 0), animated);
+ }
+
+ void UpdateBackground()
+ {
+ string bgImage = ((Page)Element).BackgroundImage;
+ if (!string.IsNullOrEmpty(bgImage))
+ {
+ View.BackgroundColor = UIColor.FromPatternImage(UIImage.FromBundle(bgImage));
+ return;
+ }
+ Color bgColor = Element.BackgroundColor;
+ if (bgColor.IsDefault)
+ View.BackgroundColor = UIColor.White;
+ else
+ View.BackgroundColor = bgColor.ToUIColor();
+ }
+
+ void UpdateCurrentPage(bool animated = true)
+ {
+ ContentPage current = Carousel.CurrentPage;
+ if (current != null)
+ ScrollToPage(CarouselPage.GetIndex(current), animated);
+ }
+
+ class PageContainer : UIView
+ {
+ public PageContainer(VisualElement element)
+ {
+ Element = element;
+ }
+
+ public VisualElement Element { get; }
+
+ public override void LayoutSubviews()
+ {
+ base.LayoutSubviews();
+
+ if (Subviews.Length > 0)
+ Subviews[0].Frame = new RectangleF(0, 0, (float)Element.Width, (float)Element.Height);
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.iOS/Renderers/CarouselViewRenderer.cs b/Xamarin.Forms.Platform.iOS/Renderers/CarouselViewRenderer.cs
new file mode 100644
index 00000000..26ef16cc
--- /dev/null
+++ b/Xamarin.Forms.Platform.iOS/Renderers/CarouselViewRenderer.cs
@@ -0,0 +1,454 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Collections.Specialized;
+using System.ComponentModel;
+using System.Drawing;
+using System.Linq;
+#if __UNIFIED__
+using UIKit;
+using Foundation;
+#else
+using MonoTouch.UIKit;
+using MonoTouch.Foundation;
+#endif
+#if __UNIFIED__
+using RectangleF = CoreGraphics.CGRect;
+using SizeF = CoreGraphics.CGSize;
+using PointF = CoreGraphics.CGPoint;
+
+#else
+using nfloat=System.Single;
+using nint=System.Int32;
+using nuint=System.UInt32;
+#endif
+
+namespace Xamarin.Forms.Platform.iOS
+{
+ /// <summary>
+ /// UICollectionView visualizes a collection of data. UICollectionViews are created indirectly by first creating a
+ /// CarouselViewController from which the CollectionView is accessed via the CollectionView property.
+ /// The CarouselViewController functionality is exposed through a set of interfaces (aka "conforms to" in the Apple
+ /// docs).
+ /// When Xamarin exposed CarouselViewRenderer the following interfaces where implemented as virtual methods:
+ /// UICollectionViewSource
+ /// UIScrollViewDelegate
+ /// UICollectionViewDelegate Allow you to manage the selection and highlighting of items in a collection view
+ /// UICollectionViewDataSource Creation and configuration of cells and supplementary views used to display data
+ /// The interfaces only implement required method while the UICollectionView exposes optional methods via
+ /// ExportAttribute.
+ /// The C# method name may be aliased. For example, C# "GetCell" maps to obj-C "CellForItemAtIndexPath".
+ /// <seealso cref="https://developer.apple.com/library/ios/documentation/UIKit/Reference/UICollectionView_class/" />
+ /// </summary>
+ public class CarouselViewRenderer : ViewRenderer<CarouselView, UICollectionView>
+ {
+ const int DefaultMinimumDimension = 44;
+ static readonly UIColor DefaultBackgroundColor = UIColor.White;
+
+ CarouselViewController.Layout _layout;
+ int _position;
+
+ CarouselViewController CarouselViewController { get; set; }
+
+ new UIScrollView Control
+ {
+ get
+ {
+ Initialize();
+ return base.Control;
+ }
+ }
+
+ ICarouselViewController Controller
+ {
+ get { return Element; }
+ }
+
+ public override SizeRequest GetDesiredSize(double widthConstraint, double heightConstraint)
+ {
+ return Control.GetSizeRequest(widthConstraint, heightConstraint, DefaultMinimumDimension, DefaultMinimumDimension);
+ }
+
+ protected override void OnElementChanged(ElementChangedEventArgs<CarouselView> e)
+ {
+ base.OnElementChanged(e);
+ Initialize();
+ }
+
+ protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ if (e.PropertyName == "Position")
+ // not ideal; the event is raised before the animation to move completes (or even starts)
+ ScrollToPosition(Element.Position);
+
+ base.OnElementPropertyChanged(sender, e);
+ }
+
+ void Initialize()
+ {
+ // cache hit?
+ var carouselView = base.Control;
+ if (carouselView != null)
+ return;
+
+ CarouselViewController = new CarouselViewController(this, _layout = new CarouselViewController.Layout(UICollectionViewScrollDirection.Horizontal), Element.Position);
+
+ // hook up on position changed event
+ // not ideal; the event is raised upon releasing the swipe instead of animation completion
+ _layout.OnSwipeOffsetChosen += o => OnPositionChange(o);
+
+ // hook up crud events
+ Element.CollectionChanged += OnCollectionChanged;
+
+ // populate cache
+ SetNativeControl(CarouselViewController.CollectionView);
+ }
+
+ void OnCollectionChanged(object source, NotifyCollectionChangedEventArgs e)
+ {
+ switch (e.Action)
+ {
+ case NotifyCollectionChangedAction.Add:
+ CarouselViewController.ReloadData();
+
+ if (e.NewStartingIndex <= _position)
+ ShiftPosition(e.NewItems.Count);
+
+ break;
+
+ case NotifyCollectionChangedAction.Move:
+ for (var i = 0; i < e.NewItems.Count; i++)
+ {
+ CarouselViewController.MoveItem(e.OldStartingIndex + i, e.NewStartingIndex + i);
+ }
+ break;
+
+ case NotifyCollectionChangedAction.Remove:
+ if (Element.Count == 0)
+ throw new InvalidOperationException("CarouselView must retain a least one item.");
+
+ if (e.OldStartingIndex == _position)
+ {
+ CarouselViewController.DeleteItems(Enumerable.Range(e.OldStartingIndex, e.OldItems.Count));
+ if (_position == Element.Count)
+ _position--;
+ OnItemChange(_position);
+ }
+
+ else
+ {
+ CarouselViewController.ReloadData();
+
+ if (e.OldStartingIndex < _position)
+ ShiftPosition(-e.OldItems.Count);
+ }
+
+ break;
+
+ case NotifyCollectionChangedAction.Replace:
+ CarouselViewController.ReloadItems(Enumerable.Range(e.OldStartingIndex, e.OldItems.Count));
+ break;
+
+ case NotifyCollectionChangedAction.Reset:
+ CarouselViewController.ReloadData();
+ break;
+
+ default:
+ throw new Exception();
+ }
+ }
+
+ void OnItemChange(int position)
+ {
+ var item = Controller.GetItem(position);
+ Controller.SendSelectedItemChanged(item);
+ }
+
+ bool OnPositionChange(int position)
+ {
+ if (position == _position)
+ return false;
+
+ _position = position;
+
+ Controller.SendSelectedPositionChanged(position);
+ OnItemChange(position);
+ return true;
+ }
+
+ void ScrollToPosition(int position, bool animated = true)
+ {
+ if (!OnPositionChange(position))
+ return;
+
+ CarouselViewController.ScrollToPosition(position, animated);
+ }
+
+ void ShiftPosition(int offset)
+ {
+ // By default the position remains the same which causes an animation in the case
+ // of the added/removed position preceding the current position. I prefer the constructed
+ // Android behavior whereby the item remains the same and the position changes.
+ ScrollToPosition(_position + offset, false);
+ }
+ }
+
+ internal sealed class CarouselViewController : UICollectionViewController
+ {
+ readonly Dictionary<object, int> _typeIdByType;
+ UICollectionViewLayout _layout;
+ int _nextItemTypeId;
+ int _originPosition;
+
+ public Action<int> OnBind;
+ public Action<int> OnSwipeTargetChosen;
+
+ public CarouselViewController(CarouselViewRenderer renderer, UICollectionViewLayout layout, int originPosition) : base(layout)
+ {
+ Renderer = renderer;
+ _typeIdByType = new Dictionary<object, int>();
+ _nextItemTypeId = 0;
+ _layout = layout;
+ _originPosition = originPosition;
+ }
+
+ ICarouselViewController Controller
+ {
+ get { return Element; }
+ }
+
+ CarouselView Element
+ {
+ get { return Renderer.Element; }
+ }
+
+ CarouselViewRenderer Renderer { get; }
+
+ public void DeleteItems(IEnumerable<int> positions)
+ {
+ var indices = positions.Select(o => NSIndexPath.FromRowSection(o, 0)).ToArray();
+ CollectionView.DeleteItems(indices);
+ }
+
+ public override UICollectionViewCell GetCell(UICollectionView collectionView, NSIndexPath indexPath)
+ {
+ var index = indexPath.Row;
+
+ if (_originPosition != 0)
+ index = _originPosition;
+
+ var item = Controller.GetItem(index);
+ var itemType = Controller.GetItemType(item);
+
+ var itemTypeId = default(int);
+ if (!_typeIdByType.TryGetValue(itemType, out itemTypeId))
+ {
+ _typeIdByType[itemType] = itemTypeId = _nextItemTypeId++;
+ CollectionView.RegisterClassForCell(typeof(Cell), itemTypeId.ToString());
+ }
+
+ var cell = (Cell)CollectionView.DequeueReusableCell(itemTypeId.ToString(), indexPath);
+ cell.Initialize(Element, itemType, item, index);
+
+ // a semantically weak approach to OnAppearing; decided not to expose as such
+ if (cell.OnBind == null)
+ cell.OnBind += o => OnBind?.Invoke(o);
+
+ return cell;
+ }
+
+ public override nint GetItemsCount(UICollectionView collectionView, nint section)
+ {
+ var result = Element.Count;
+ return result;
+ }
+
+ public void MoveItem(int oldPosition, int newPosition)
+ {
+ base.MoveItem(CollectionView, NSIndexPath.FromRowSection(oldPosition, 0), NSIndexPath.FromRowSection(newPosition, 0));
+ }
+
+ public override nint NumberOfSections(UICollectionView collectionView)
+ {
+ return 1;
+ }
+
+ public void ReloadData() => CollectionView.ReloadData();
+
+ public void ReloadItems(IEnumerable<int> positions)
+ {
+ var indices = positions.Select(o => NSIndexPath.FromRowSection(o, 0)).ToArray();
+ CollectionView.ReloadItems(indices);
+ }
+
+ public void ScrollToPosition(int position, bool animated = true)
+ {
+ CollectionView.ScrollToItem(NSIndexPath.FromRowSection(position, 0), UICollectionViewScrollPosition.CenteredHorizontally, animated);
+ }
+
+ public override void ViewDidLoad()
+ {
+ base.ViewDidLoad();
+
+ CollectionView.PagingEnabled = true;
+ CollectionView.BackgroundColor = UIColor.Clear;
+ }
+
+ public override void WillDisplayCell(UICollectionView collectionView, UICollectionViewCell cell, NSIndexPath indexPath)
+ {
+ if (_originPosition == 0)
+ return;
+
+ // Ideally position zero would not be rendered in memory however it is.
+ // Thankfully, position zero is not displyed; position originPosition is rendered and displayed.
+ ScrollToPosition(_originPosition, false);
+ _originPosition = 0;
+ }
+
+ [Export("collectionView:layout:sizeForItemAtIndexPath:")]
+ SizeF GetSizeForItem(UICollectionView collectionView, UICollectionViewLayout layout, NSIndexPath indexPath)
+ {
+ return collectionView.Frame.Size;
+ }
+
+ internal new sealed class Layout : UICollectionViewFlowLayout
+ {
+ static readonly nfloat ZeroMinimumInteritemSpacing = 0;
+ static readonly nfloat ZeroMinimumLineSpacing = 0;
+
+ int _width;
+
+ public Action<int> OnSwipeOffsetChosen;
+
+ public Layout(UICollectionViewScrollDirection scrollDirection)
+ {
+ ScrollDirection = scrollDirection;
+ MinimumInteritemSpacing = ZeroMinimumInteritemSpacing;
+ MinimumLineSpacing = ZeroMinimumLineSpacing;
+ }
+
+ public override SizeF CollectionViewContentSize
+ {
+ get
+ {
+ var result = base.CollectionViewContentSize;
+ return result;
+ }
+ }
+
+ public override UICollectionViewLayoutAttributes FinalLayoutAttributesForDisappearingItem(NSIndexPath itemIndexPath)
+ {
+ return base.FinalLayoutAttributesForDisappearingItem(itemIndexPath);
+ }
+
+ public override NSIndexPath GetTargetIndexPathForInteractivelyMovingItem(NSIndexPath previousIndexPath, PointF position)
+ {
+ var result = base.GetTargetIndexPathForInteractivelyMovingItem(previousIndexPath, position);
+ return result;
+ }
+
+ public override UICollectionViewLayoutAttributes InitialLayoutAttributesForAppearingItem(NSIndexPath itemIndexPath)
+ {
+ return base.InitialLayoutAttributesForAppearingItem(itemIndexPath);
+ }
+
+ public override UICollectionViewLayoutAttributes[] LayoutAttributesForElementsInRect(RectangleF rect)
+ {
+ // couldn't figure a way to use these values to compute when an element appeared to disappeared. YMMV
+ var result = base.LayoutAttributesForElementsInRect(rect);
+ foreach (var item in result)
+ {
+ var index = item.IndexPath;
+ var category = item.RepresentedElementCategory;
+ var kind = item.RepresentedElementKind;
+
+ var hidden = item.Hidden;
+ var bounds = item.Bounds;
+ var frame = item.Frame;
+ var center = item.Center;
+
+ _width = (int)item.Bounds.Width;
+ }
+ return result;
+ }
+
+ public override bool ShouldInvalidateLayoutForBoundsChange(RectangleF newBounds)
+ {
+ return true;
+ }
+
+ public override PointF TargetContentOffset(PointF proposedContentOffset, PointF scrollingVelocity)
+ {
+ var result = base.TargetContentOffset(proposedContentOffset, scrollingVelocity);
+ OnSwipeOffsetChosen?.Invoke((int)result.X / _width);
+ return result;
+ }
+
+ public override PointF TargetContentOffsetForProposedContentOffset(PointF proposedContentOffset)
+ {
+ var result = base.TargetContentOffsetForProposedContentOffset(proposedContentOffset);
+ return result;
+ }
+ }
+
+ sealed class Cell : UICollectionViewCell
+ {
+ IItemViewController _controller;
+ int _position;
+ IVisualElementRenderer _renderer;
+ View _view;
+
+ public Action<int> OnBind;
+
+ [Export("initWithFrame:")]
+ internal Cell(RectangleF frame) : base(frame)
+ {
+ _position = -1;
+ }
+
+ public override void LayoutSubviews()
+ {
+ base.LayoutSubviews();
+
+ _renderer.Element.Layout(new Rectangle(0, 0, ContentView.Frame.Width, ContentView.Frame.Height));
+ }
+
+ internal void Initialize(IItemViewController controller, object itemType, object item, int position)
+ {
+ _position = position;
+
+ if (_controller == null)
+ {
+ _controller = controller;
+
+ // create view
+ _view = controller.CreateView(itemType);
+
+ // bind view
+ Bind(item, position);
+
+ // render view
+ _renderer = Platform.CreateRenderer(_view);
+ Platform.SetRenderer(_view, _renderer);
+
+ // attach view
+ var uiView = _renderer.NativeView;
+ ContentView.AddSubview(uiView);
+ }
+ else
+ Bind(item, position);
+ }
+
+ void Bind(object item, int position)
+ {
+ //if (position != this.position)
+ // controller.SendPositionDisappearing (this.position);
+
+ _position = position;
+ OnBind?.Invoke(position);
+
+ _controller.BindView(_view, item);
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.iOS/Renderers/DatePickerRenderer.cs b/Xamarin.Forms.Platform.iOS/Renderers/DatePickerRenderer.cs
new file mode 100644
index 00000000..fd2b1549
--- /dev/null
+++ b/Xamarin.Forms.Platform.iOS/Renderers/DatePickerRenderer.cs
@@ -0,0 +1,122 @@
+using System;
+using System.Drawing;
+using System.ComponentModel;
+#if __UNIFIED__
+using UIKit;
+using Foundation;
+#else
+using MonoTouch.UIKit;
+using MonoTouch.Foundation;
+#endif
+#if __UNIFIED__
+using RectangleF = CoreGraphics.CGRect;
+using SizeF = CoreGraphics.CGSize;
+using PointF = CoreGraphics.CGPoint;
+
+#else
+using nfloat=System.Single;
+using nint=System.Int32;
+using nuint=System.UInt32;
+#endif
+
+namespace Xamarin.Forms.Platform.iOS
+{
+ internal class NoCaretField : UITextField
+ {
+ public NoCaretField() : base(new RectangleF())
+ {
+ }
+
+ public override RectangleF GetCaretRectForPosition(UITextPosition position)
+ {
+ return new RectangleF();
+ }
+ }
+
+ public class DatePickerRenderer : ViewRenderer<DatePicker, UITextField>
+ {
+ UIDatePicker _picker;
+
+ protected override void OnElementChanged(ElementChangedEventArgs<DatePicker> e)
+ {
+ base.OnElementChanged(e);
+
+ if (e.OldElement == null)
+ {
+ var entry = new NoCaretField { BorderStyle = UITextBorderStyle.RoundedRect };
+
+ entry.Started += OnStarted;
+ entry.Ended += OnEnded;
+
+ _picker = new UIDatePicker { Mode = UIDatePickerMode.Date, TimeZone = new NSTimeZone("UTC") };
+
+ _picker.ValueChanged += HandleValueChanged;
+
+ var width = UIScreen.MainScreen.Bounds.Width;
+ var toolbar = new UIToolbar(new RectangleF(0, 0, width, 44)) { BarStyle = UIBarStyle.Default, Translucent = true };
+ var spacer = new UIBarButtonItem(UIBarButtonSystemItem.FlexibleSpace);
+ var doneButton = new UIBarButtonItem(UIBarButtonSystemItem.Done, (o, a) => entry.ResignFirstResponder());
+
+ toolbar.SetItems(new[] { spacer, doneButton }, false);
+
+ entry.InputView = _picker;
+ entry.InputAccessoryView = toolbar;
+
+ SetNativeControl(entry);
+ }
+
+ if (e.NewElement != null)
+ {
+ UpdateDateFromModel(false);
+ UpdateMaximumDate();
+ UpdateMinimumDate();
+ }
+ }
+
+ protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ base.OnElementPropertyChanged(sender, e);
+
+ if (e.PropertyName == DatePicker.DateProperty.PropertyName || e.PropertyName == DatePicker.FormatProperty.PropertyName)
+ UpdateDateFromModel(true);
+ else if (e.PropertyName == DatePicker.MinimumDateProperty.PropertyName)
+ UpdateMinimumDate();
+ else if (e.PropertyName == DatePicker.MaximumDateProperty.PropertyName)
+ UpdateMaximumDate();
+ }
+
+ void HandleValueChanged(object sender, EventArgs e)
+ {
+ if (Element != null)
+ ((IElementController)Element).SetValueFromRenderer(DatePicker.DateProperty, _picker.Date.ToDateTime().Date);
+ }
+
+ void OnEnded(object sender, EventArgs eventArgs)
+ {
+ ((IElementController)Element).SetValueFromRenderer(VisualElement.IsFocusedPropertyKey, false);
+ }
+
+ void OnStarted(object sender, EventArgs eventArgs)
+ {
+ ((IElementController)Element).SetValueFromRenderer(VisualElement.IsFocusedPropertyKey, true);
+ }
+
+ void UpdateDateFromModel(bool animate)
+ {
+ if (_picker.Date.ToDateTime().Date != Element.Date.Date)
+ _picker.SetDate(Element.Date.ToNSDate(), animate);
+
+ Control.Text = Element.Date.ToString(Element.Format);
+ }
+
+ void UpdateMaximumDate()
+ {
+ _picker.MaximumDate = Element.MaximumDate.ToNSDate();
+ }
+
+ void UpdateMinimumDate()
+ {
+ _picker.MinimumDate = Element.MinimumDate.ToNSDate();
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.iOS/Renderers/EditorRenderer.cs b/Xamarin.Forms.Platform.iOS/Renderers/EditorRenderer.cs
new file mode 100644
index 00000000..4d5a3f99
--- /dev/null
+++ b/Xamarin.Forms.Platform.iOS/Renderers/EditorRenderer.cs
@@ -0,0 +1,159 @@
+using System;
+using System.Drawing;
+using System.ComponentModel;
+#if __UNIFIED__
+using UIKit;
+#else
+using MonoTouch.UIKit;
+#endif
+#if __UNIFIED__
+using RectangleF = CoreGraphics.CGRect;
+using SizeF = CoreGraphics.CGSize;
+using PointF = CoreGraphics.CGPoint;
+
+#else
+using nfloat=System.Single;
+using nint=System.Int32;
+using nuint=System.UInt32;
+#endif
+
+namespace Xamarin.Forms.Platform.iOS
+{
+ public class EditorRenderer : ViewRenderer<Editor, UITextView>
+ {
+ UIToolbar _accessoryView;
+
+ public override SizeRequest GetDesiredSize(double widthConstraint, double heightConstraint)
+ {
+ if (!Forms.IsiOS7OrNewer)
+ {
+ // Avoid crash iOS 6. iOS 6, I hate you. Why you no like Infinite size?
+ return base.GetDesiredSize(Math.Min(widthConstraint, 2000), Math.Min(heightConstraint, 2000));
+ }
+ return base.GetDesiredSize(widthConstraint, heightConstraint);
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ Control.Changed -= HandleChanged;
+ Control.Started -= OnStarted;
+ Control.Ended -= OnEnded;
+ }
+
+ base.Dispose(disposing);
+ }
+
+ protected override void OnElementChanged(ElementChangedEventArgs<Editor> e)
+ {
+ base.OnElementChanged(e);
+
+ if (Control == null)
+ {
+ SetNativeControl(new UITextView(RectangleF.Empty));
+
+ if (Device.Idiom == TargetIdiom.Phone)
+ {
+ // iPhone does not have a dismiss keyboard button
+ var keyboardWidth = UIScreen.MainScreen.Bounds.Width;
+ _accessoryView = new UIToolbar(new RectangleF(0, 0, keyboardWidth, 44)) { BarStyle = UIBarStyle.Default, Translucent = true };
+
+ var spacer = new UIBarButtonItem(UIBarButtonSystemItem.FlexibleSpace);
+ var doneButton = new UIBarButtonItem(UIBarButtonSystemItem.Done, (o, a) =>
+ {
+ Control.ResignFirstResponder();
+ Element.SendCompleted();
+ });
+ _accessoryView.SetItems(new[] { spacer, doneButton }, false);
+ Control.InputAccessoryView = _accessoryView;
+ }
+
+ Control.Changed += HandleChanged;
+ Control.Started += OnStarted;
+ Control.Ended += OnEnded;
+ }
+
+ if (e.NewElement != null)
+ {
+ UpdateText();
+ UpdateFont();
+ UpdateTextColor();
+ UpdateKeyboard();
+ UpdateEditable();
+ }
+ }
+
+ protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ base.OnElementPropertyChanged(sender, e);
+
+ if (e.PropertyName == Editor.TextProperty.PropertyName)
+ UpdateText();
+ else if (e.PropertyName == Xamarin.Forms.InputView.KeyboardProperty.PropertyName)
+ UpdateKeyboard();
+ 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();
+ }
+
+ void HandleChanged(object sender, EventArgs e)
+ {
+ ((IElementController)Element).SetValueFromRenderer(Editor.TextProperty, Control.Text);
+ }
+
+ void OnEnded(object sender, EventArgs eventArgs)
+ {
+ Element.SetValue(VisualElement.IsFocusedPropertyKey, false);
+ Element.SendCompleted();
+ }
+
+ void OnStarted(object sender, EventArgs eventArgs)
+ {
+ ((IElementController)Element).SetValueFromRenderer(VisualElement.IsFocusedPropertyKey, true);
+ }
+
+ void UpdateEditable()
+ {
+ Control.Editable = Element.IsEnabled;
+ Control.UserInteractionEnabled = Element.IsEnabled;
+
+ if (Control.InputAccessoryView != null)
+ Control.InputAccessoryView.Hidden = !Element.IsEnabled;
+ }
+
+ void UpdateFont()
+ {
+ Control.Font = Element.ToUIFont();
+ }
+
+ void UpdateKeyboard()
+ {
+ Control.ApplyKeyboard(Element.Keyboard);
+ }
+
+ void UpdateText()
+ {
+ // ReSharper disable once RedundantCheckBeforeAssignment
+ if (Control.Text != Element.Text)
+ Control.Text = Element.Text;
+ }
+
+ void UpdateTextColor()
+ {
+ var textColor = Element.TextColor;
+
+ if (textColor.IsDefault)
+ Control.TextColor = UIColor.Black;
+ else
+ Control.TextColor = textColor.ToUIColor();
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.iOS/Renderers/EntryRenderer.cs b/Xamarin.Forms.Platform.iOS/Renderers/EntryRenderer.cs
new file mode 100644
index 00000000..734a56be
--- /dev/null
+++ b/Xamarin.Forms.Platform.iOS/Renderers/EntryRenderer.cs
@@ -0,0 +1,184 @@
+using System;
+using System.Drawing;
+using System.Runtime.Remoting.Channels;
+using System.ComponentModel;
+#if __UNIFIED__
+using UIKit;
+
+#else
+using MonoTouch.UIKit;
+#endif
+
+namespace Xamarin.Forms.Platform.iOS
+{
+ public class EntryRenderer : ViewRenderer<Entry, UITextField>
+ {
+ UIColor _defaultTextColor;
+
+ public EntryRenderer()
+ {
+ Frame = new RectangleF(0, 20, 320, 40);
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ if (Control != null)
+ {
+ Control.EditingDidBegin -= OnEditingBegan;
+ Control.EditingChanged -= OnEditingChanged;
+ Control.EditingDidEnd -= OnEditingEnded;
+ }
+ }
+
+ base.Dispose(disposing);
+ }
+
+ protected override void OnElementChanged(ElementChangedEventArgs<Entry> e)
+ {
+ base.OnElementChanged(e);
+
+ var textField = Control;
+
+ if (Control == null)
+ {
+ SetNativeControl(textField = new UITextField(RectangleF.Empty));
+
+ _defaultTextColor = textField.TextColor;
+ textField.BorderStyle = UITextBorderStyle.RoundedRect;
+
+ textField.EditingChanged += OnEditingChanged;
+
+ textField.ShouldReturn = OnShouldReturn;
+
+ textField.EditingDidBegin += OnEditingBegan;
+ textField.EditingDidEnd += OnEditingEnded;
+ }
+
+ if (e.NewElement != null)
+ {
+ UpdatePlaceholder();
+ UpdatePassword();
+ UpdateText();
+ UpdateColor();
+ UpdateFont();
+ UpdateKeyboard();
+ 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 == Xamarin.Forms.InputView.KeyboardProperty.PropertyName)
+ UpdateKeyboard();
+ 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);
+ }
+
+ void OnEditingBegan(object sender, EventArgs e)
+ {
+ ((IElementController)Element).SetValueFromRenderer(VisualElement.IsFocusedPropertyKey, true);
+ }
+
+ void OnEditingChanged(object sender, EventArgs eventArgs)
+ {
+ ((IElementController)Element).SetValueFromRenderer(Entry.TextProperty, Control.Text);
+ }
+
+ void OnEditingEnded(object sender, EventArgs e)
+ {
+ ((IElementController)Element).SetValueFromRenderer(VisualElement.IsFocusedPropertyKey, false);
+ }
+
+ bool OnShouldReturn(UITextField view)
+ {
+ Control.ResignFirstResponder();
+ Element.SendCompleted();
+ return true;
+ }
+
+ void UpdateAlignment()
+ {
+ Control.TextAlignment = Element.HorizontalTextAlignment.ToNativeTextAlignment();
+ }
+
+ void UpdateColor()
+ {
+ var textColor = Element.TextColor;
+
+ if (textColor.IsDefault || !Element.IsEnabled)
+ Control.TextColor = _defaultTextColor;
+ else
+ Control.TextColor = textColor.ToUIColor();
+ }
+
+ void UpdateFont()
+ {
+ Control.Font = Element.ToUIFont();
+ }
+
+ void UpdateKeyboard()
+ {
+ Control.ApplyKeyboard(Element.Keyboard);
+ }
+
+ void UpdatePassword()
+ {
+ if (Element.IsPassword && Control.IsFirstResponder)
+ {
+ Control.Enabled = false;
+ Control.SecureTextEntry = true;
+ Control.Enabled = Element.IsEnabled;
+ Control.BecomeFirstResponder();
+ }
+ else
+ Control.SecureTextEntry = Element.IsPassword;
+ }
+
+ 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.AttributedPlaceholder = formatted.ToAttributed(Element, color);
+ }
+
+ void UpdateText()
+ {
+ // ReSharper disable once RedundantCheckBeforeAssignment
+ if (Control.Text != Element.Text)
+ Control.Text = Element.Text;
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.iOS/Renderers/ExportCellAttribute.cs b/Xamarin.Forms.Platform.iOS/Renderers/ExportCellAttribute.cs
new file mode 100644
index 00000000..1abf0d7f
--- /dev/null
+++ b/Xamarin.Forms.Platform.iOS/Renderers/ExportCellAttribute.cs
@@ -0,0 +1,11 @@
+using System;
+
+namespace Xamarin.Forms
+{
+ [AttributeUsage (AttributeTargets.Assembly, AllowMultiple = true)]
+ public sealed class ExportCellAttribute : HandlerAttribute {
+ public ExportCellAttribute (Type handler, Type target)
+ : base (handler, target) {
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.iOS/Renderers/ExportImageSourceHandlerAttribute.cs b/Xamarin.Forms.Platform.iOS/Renderers/ExportImageSourceHandlerAttribute.cs
new file mode 100644
index 00000000..59e96e1f
--- /dev/null
+++ b/Xamarin.Forms.Platform.iOS/Renderers/ExportImageSourceHandlerAttribute.cs
@@ -0,0 +1,11 @@
+using System;
+
+namespace Xamarin.Forms
+{
+ [AttributeUsage (AttributeTargets.Assembly, AllowMultiple = true)]
+ public sealed class ExportImageSourceHandlerAttribute : HandlerAttribute {
+ public ExportImageSourceHandlerAttribute (Type handler, Type target)
+ : base (handler, target) {
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.iOS/Renderers/ExportRendererAttribute.cs b/Xamarin.Forms.Platform.iOS/Renderers/ExportRendererAttribute.cs
new file mode 100644
index 00000000..899056da
--- /dev/null
+++ b/Xamarin.Forms.Platform.iOS/Renderers/ExportRendererAttribute.cs
@@ -0,0 +1,30 @@
+using System;
+#if __UNIFIED__
+using UIKit;
+#else
+using MonoTouch.UIKit;
+#endif
+
+namespace Xamarin.Forms
+{
+ [AttributeUsage (AttributeTargets.Assembly, AllowMultiple = true)]
+ public sealed class ExportRendererAttribute : HandlerAttribute {
+ internal bool Idiomatic { get; private set; }
+ internal UIUserInterfaceIdiom Idiom { get; private set; }
+
+ public ExportRendererAttribute (Type handler, Type target, UIUserInterfaceIdiom idiom)
+ : base (handler, target) {
+ Idiomatic = true;
+ Idiom = idiom;
+ }
+
+ public ExportRendererAttribute (Type handler, Type target)
+ : base (handler, target) {
+ Idiomatic = false;
+ }
+
+ public override bool ShouldRegister () {
+ return !Idiomatic || Idiom == UIDevice.CurrentDevice.UserInterfaceIdiom;
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.iOS/Renderers/FontExtensions.cs b/Xamarin.Forms.Platform.iOS/Renderers/FontExtensions.cs
new file mode 100644
index 00000000..07afcfaf
--- /dev/null
+++ b/Xamarin.Forms.Platform.iOS/Renderers/FontExtensions.cs
@@ -0,0 +1,205 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+#if __UNIFIED__
+using UIKit;
+
+#else
+using MonoTouch.UIKit;
+#endif
+
+namespace Xamarin.Forms.Platform.iOS
+{
+ public static class FontExtensions
+ {
+ static readonly Dictionary<ToUIFontKey, UIFont> ToUiFont = new Dictionary<ToUIFontKey, UIFont>();
+
+ public static UIFont ToUIFont(this Font self)
+ {
+ var size = (float)self.FontSize;
+ if (self.UseNamedSize)
+ {
+ switch (self.NamedSize)
+ {
+ case NamedSize.Micro:
+ size = 12;
+ break;
+ case NamedSize.Small:
+ size = 14;
+ break;
+ case NamedSize.Medium:
+ size = 17; // as defined by iOS documentation
+ break;
+ case NamedSize.Large:
+ size = 22;
+ break;
+ default:
+ size = 17;
+ break;
+ }
+ }
+
+ var bold = self.FontAttributes.HasFlag(FontAttributes.Bold);
+ var italic = self.FontAttributes.HasFlag(FontAttributes.Italic);
+
+ if (self.FontFamily != null)
+ {
+ try
+ {
+ if (UIFont.FamilyNames.Contains(self.FontFamily) && Forms.IsiOS7OrNewer)
+ {
+ var descriptor = new UIFontDescriptor().CreateWithFamily(self.FontFamily);
+
+ if (bold || italic)
+ {
+ var traits = (UIFontDescriptorSymbolicTraits)0;
+ if (bold)
+ traits = traits | UIFontDescriptorSymbolicTraits.Bold;
+ if (italic)
+ traits = traits | UIFontDescriptorSymbolicTraits.Italic;
+
+ descriptor = descriptor.CreateWithTraits(traits);
+ return UIFont.FromDescriptor(descriptor, size);
+ }
+ }
+
+ return UIFont.FromName(self.FontFamily, size);
+ }
+ catch
+ {
+ Debug.WriteLine("Could not load font named: {0}", self.FontFamily);
+ }
+ }
+ if (bold && italic)
+ {
+ if (!Forms.IsiOS7OrNewer)
+ {
+ // not sure how to make a font both bold and italic in iOS 6, default to bold
+ return UIFont.BoldSystemFontOfSize(size);
+ }
+
+ var defaultFont = UIFont.SystemFontOfSize(size);
+ var descriptor = defaultFont.FontDescriptor.CreateWithTraits(UIFontDescriptorSymbolicTraits.Bold | UIFontDescriptorSymbolicTraits.Italic);
+ return UIFont.FromDescriptor(descriptor, 0);
+ }
+ if (bold)
+ return UIFont.BoldSystemFontOfSize(size);
+ if (italic)
+ return UIFont.ItalicSystemFontOfSize(size);
+ return UIFont.SystemFontOfSize(size);
+ }
+
+ internal static bool IsDefault(this Span self)
+ {
+ return self.FontFamily == null && self.FontSize == Device.GetNamedSize(NamedSize.Default, typeof(Label), true) && self.FontAttributes == FontAttributes.None;
+ }
+
+ internal static UIFont ToUIFont(this Label label)
+ {
+ var values = label.GetValues(Label.FontFamilyProperty, Label.FontSizeProperty, Label.FontAttributesProperty);
+ return ToUIFont((string)values[0], (float)(double)values[1], (FontAttributes)values[2]) ?? UIFont.SystemFontOfSize(UIFont.LabelFontSize);
+ }
+
+ internal static UIFont ToUIFont(this IFontElement element)
+ {
+ return ToUIFont(element.FontFamily, (float)element.FontSize, element.FontAttributes);
+ }
+
+ static UIFont _ToUIFont(string family, float size, FontAttributes attributes)
+ {
+ var bold = (attributes & FontAttributes.Bold) != 0;
+ var italic = (attributes & FontAttributes.Italic) != 0;
+
+ if (family != null)
+ {
+ try
+ {
+ UIFont result;
+ if (UIFont.FamilyNames.Contains(family) && Forms.IsiOS7OrNewer)
+ {
+ var descriptor = new UIFontDescriptor().CreateWithFamily(family);
+
+ if (bold || italic)
+ {
+ var traits = (UIFontDescriptorSymbolicTraits)0;
+ if (bold)
+ traits = traits | UIFontDescriptorSymbolicTraits.Bold;
+ if (italic)
+ traits = traits | UIFontDescriptorSymbolicTraits.Italic;
+
+ descriptor = descriptor.CreateWithTraits(traits);
+ result = UIFont.FromDescriptor(descriptor, size);
+ if (result != null)
+ return result;
+ }
+ }
+
+ result = UIFont.FromName(family, size);
+ if (result != null)
+ return result;
+ }
+ catch
+ {
+ Debug.WriteLine("Could not load font named: {0}", family);
+ }
+ }
+
+ if (bold && italic)
+ {
+ var defaultFont = UIFont.SystemFontOfSize(size);
+
+ if (!Forms.IsiOS7OrNewer)
+ {
+ // not sure how to make a font both bold and italic in iOS 6, default to bold
+ return UIFont.BoldSystemFontOfSize(size);
+ }
+
+ var descriptor = defaultFont.FontDescriptor.CreateWithTraits(UIFontDescriptorSymbolicTraits.Bold | UIFontDescriptorSymbolicTraits.Italic);
+ return UIFont.FromDescriptor(descriptor, 0);
+ }
+ if (bold)
+ return UIFont.BoldSystemFontOfSize(size);
+ if (italic)
+ return UIFont.ItalicSystemFontOfSize(size);
+
+ return UIFont.SystemFontOfSize(size);
+ }
+
+ static UIFont ToUIFont(string family, float size, FontAttributes attributes)
+ {
+ var key = new ToUIFontKey(family, size, attributes);
+
+ lock(ToUiFont)
+ {
+ UIFont value;
+ if (ToUiFont.TryGetValue(key, out value))
+ return value;
+ }
+
+ var generatedValue = _ToUIFont(family, size, attributes);
+
+ lock(ToUiFont)
+ {
+ UIFont value;
+ if (!ToUiFont.TryGetValue(key, out value))
+ ToUiFont.Add(key, value = generatedValue);
+ return value;
+ }
+ }
+
+ struct ToUIFontKey
+ {
+ internal ToUIFontKey(string family, float size, FontAttributes attributes)
+ {
+ _family = family;
+ _size = size;
+ _attributes = attributes;
+ }
+
+ string _family;
+ float _size;
+ FontAttributes _attributes;
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.iOS/Renderers/FormattedStringExtensions.cs b/Xamarin.Forms.Platform.iOS/Renderers/FormattedStringExtensions.cs
new file mode 100644
index 00000000..0dfa38cd
--- /dev/null
+++ b/Xamarin.Forms.Platform.iOS/Renderers/FormattedStringExtensions.cs
@@ -0,0 +1,82 @@
+#if __UNIFIED__
+using Foundation;
+using UIKit;
+
+#else
+using MonoTouch.Foundation;
+using MonoTouch.UIKit;
+#endif
+
+namespace Xamarin.Forms.Platform.iOS
+{
+ public static class FormattedStringExtensions
+ {
+ public static NSAttributedString ToAttributed(this Span span, Font defaultFont, Color defaultForegroundColor)
+ {
+ if (span == null)
+ return null;
+
+ var font = span.Font != Font.Default ? span.Font : defaultFont;
+
+ var fgcolor = span.ForegroundColor;
+ if (fgcolor.IsDefault)
+ fgcolor = defaultForegroundColor;
+ if (fgcolor.IsDefault)
+ fgcolor = Color.Black; // as defined by apple docs
+
+ return new NSAttributedString(span.Text, font == Font.Default ? null : font.ToUIFont(), fgcolor.ToUIColor(), span.BackgroundColor.ToUIColor());
+ }
+
+ public static NSAttributedString ToAttributed(this FormattedString formattedString, Font defaultFont, Color defaultForegroundColor)
+ {
+ if (formattedString == null)
+ return null;
+ var attributed = new NSMutableAttributedString();
+ foreach (var span in formattedString.Spans)
+ {
+ if (span.Text == null)
+ continue;
+
+ attributed.Append(span.ToAttributed(defaultFont, defaultForegroundColor));
+ }
+
+ return attributed;
+ }
+
+ internal static NSAttributedString ToAttributed(this Span span, Element owner, Color defaultForegroundColor)
+ {
+ if (span == null)
+ return null;
+
+ UIFont targetFont;
+ if (span.IsDefault())
+ targetFont = ((IFontElement)owner).ToUIFont();
+ else
+ targetFont = span.ToUIFont();
+
+ var fgcolor = span.ForegroundColor;
+ if (fgcolor.IsDefault)
+ fgcolor = defaultForegroundColor;
+ if (fgcolor.IsDefault)
+ fgcolor = Color.Black; // as defined by apple docs
+
+ return new NSAttributedString(span.Text, targetFont, fgcolor.ToUIColor(), span.BackgroundColor.ToUIColor());
+ }
+
+ internal static NSAttributedString ToAttributed(this FormattedString formattedString, Element owner, Color defaultForegroundColor)
+ {
+ if (formattedString == null)
+ return null;
+ var attributed = new NSMutableAttributedString();
+ foreach (var span in formattedString.Spans)
+ {
+ if (span.Text == null)
+ continue;
+
+ attributed.Append(span.ToAttributed(owner, defaultForegroundColor));
+ }
+
+ return attributed;
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.iOS/Renderers/FrameRenderer.cs b/Xamarin.Forms.Platform.iOS/Renderers/FrameRenderer.cs
new file mode 100644
index 00000000..3f2f1ccb
--- /dev/null
+++ b/Xamarin.Forms.Platform.iOS/Renderers/FrameRenderer.cs
@@ -0,0 +1,61 @@
+using System.ComponentModel;
+using System.Drawing;
+#if __UNIFIED__
+using UIKit;
+
+#else
+using MonoTouch.UIKit;
+#endif
+
+namespace Xamarin.Forms.Platform.iOS
+{
+ 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 = UIColor.White.CGColor;
+ else
+ Layer.BackgroundColor = Element.BackgroundColor.ToCGColor();
+
+ if (Element.HasShadow)
+ {
+ Layer.ShadowRadius = 5;
+ Layer.ShadowColor = UIColor.Black.CGColor;
+ Layer.ShadowOpacity = 0.8f;
+ Layer.ShadowOffset = new SizeF();
+ }
+ else
+ Layer.ShadowOpacity = 0;
+
+ if (Element.OutlineColor == Color.Default)
+ Layer.BorderColor = UIColor.Clear.CGColor;
+ else
+ {
+ Layer.BorderColor = Element.OutlineColor.ToCGColor();
+ Layer.BorderWidth = 1;
+ }
+
+ Layer.RasterizationScale = UIScreen.MainScreen.Scale;
+ Layer.ShouldRasterize = true;
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.iOS/Renderers/ImageRenderer.cs b/Xamarin.Forms.Platform.iOS/Renderers/ImageRenderer.cs
new file mode 100644
index 00000000..f227f9a5
--- /dev/null
+++ b/Xamarin.Forms.Platform.iOS/Renderers/ImageRenderer.cs
@@ -0,0 +1,209 @@
+using System.Drawing;
+using System.ComponentModel;
+using System.IO;
+using System.Threading.Tasks;
+using System.Threading;
+using System;
+#if __UNIFIED__
+using UIKit;
+using Foundation;
+#else
+using MonoTouch.UIKit;
+using MonoTouch.Foundation;
+#endif
+#if __UNIFIED__
+using RectangleF = CoreGraphics.CGRect;
+using SizeF = CoreGraphics.CGSize;
+using PointF = CoreGraphics.CGPoint;
+
+#else
+using nfloat=System.Single;
+using nint=System.Int32;
+using nuint=System.UInt32;
+#endif
+
+namespace Xamarin.Forms.Platform.iOS
+{
+ public static class ImageExtensions
+ {
+ public static UIViewContentMode ToUIViewContentMode(this Aspect aspect)
+ {
+ switch (aspect)
+ {
+ case Aspect.AspectFill:
+ return UIViewContentMode.ScaleAspectFill;
+ case Aspect.Fill:
+ return UIViewContentMode.ScaleToFill;
+ case Aspect.AspectFit:
+ default:
+ return UIViewContentMode.ScaleAspectFit;
+ }
+ }
+ }
+
+ public class ImageRenderer : ViewRenderer<Image, UIImageView>
+ {
+ bool _isDisposed;
+
+ protected override void Dispose(bool disposing)
+ {
+ if (_isDisposed)
+ return;
+
+ if (disposing)
+ {
+ UIImage oldUIImage;
+ if (Control != null && (oldUIImage = Control.Image) != null)
+ {
+ oldUIImage.Dispose();
+ oldUIImage = null;
+ }
+ }
+
+ _isDisposed = true;
+
+ base.Dispose(disposing);
+ }
+
+ protected override void OnElementChanged(ElementChangedEventArgs<Image> e)
+ {
+ if (Control == null)
+ {
+ var imageView = new UIImageView(RectangleF.Empty);
+ imageView.ContentMode = UIViewContentMode.ScaleAspectFit;
+ imageView.ClipsToBounds = true;
+ 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()
+ {
+ 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;
+
+ if (oldSource is FileImageSource && source is FileImageSource && ((FileImageSource)oldSource).File == ((FileImageSource)source).File)
+ return;
+
+ Control.Image = null;
+ }
+
+ IImageSourceHandler handler;
+ ((IElementController)Element).SetValueFromRenderer(Image.IsLoadingPropertyKey, true);
+
+ if (source != null && (handler = Registrar.Registered.GetHandler<IImageSourceHandler>(source.GetType())) != null)
+ {
+ UIImage uiimage;
+ try
+ {
+ uiimage = await handler.LoadImageAsync(source, scale: (float)UIScreen.MainScreen.Scale);
+ }
+ catch (OperationCanceledException)
+ {
+ uiimage = null;
+ }
+
+ var imageView = Control;
+ if (imageView != null)
+ imageView.Image = uiimage;
+
+ if (!_isDisposed)
+ ((IVisualElementController)Element).NativeSizeChanged();
+ }
+ else
+ Control.Image = null;
+
+ if (!_isDisposed)
+ ((IElementController)Element).SetValueFromRenderer(Image.IsLoadingPropertyKey, false);
+ }
+
+ void SetOpacity()
+ {
+ Control.Opaque = Element.IsOpaque;
+ }
+ }
+
+ public interface IImageSourceHandler : IRegisterable
+ {
+ Task<UIImage> LoadImageAsync(ImageSource imagesource, CancellationToken cancelationToken = default(CancellationToken), float scale = 1);
+ }
+
+ public sealed class FileImageSourceHandler : IImageSourceHandler
+ {
+ public Task<UIImage> LoadImageAsync(ImageSource imagesource, CancellationToken cancelationToken = default(CancellationToken), float scale = 1f)
+ {
+ UIImage image = null;
+ var filesource = imagesource as FileImageSource;
+ if (filesource != null)
+ {
+ var file = filesource.File;
+ if (!string.IsNullOrEmpty(file))
+ image = File.Exists(file) ? new UIImage(file) : UIImage.FromBundle(file);
+ }
+ return Task.FromResult(image);
+ }
+ }
+
+ public sealed class StreamImagesourceHandler : IImageSourceHandler
+ {
+ public async Task<UIImage> LoadImageAsync(ImageSource imagesource, CancellationToken cancelationToken = default(CancellationToken), float scale = 1f)
+ {
+ UIImage image = null;
+ var streamsource = imagesource as StreamImageSource;
+ if (streamsource != null && streamsource.Stream != null)
+ {
+ var streamImage = await streamsource.GetStreamAsync(cancelationToken).ConfigureAwait(false);
+ if (streamImage != null)
+ image = UIImage.LoadFromData(NSData.FromStream(streamImage), scale);
+ }
+ return image;
+ }
+ }
+
+ public sealed class ImageLoaderSourceHandler : IImageSourceHandler
+ {
+ public async Task<UIImage> LoadImageAsync(ImageSource imagesource, CancellationToken cancelationToken = default(CancellationToken), float scale = 1f)
+ {
+ UIImage 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 = UIImage.LoadFromData(NSData.FromStream(streamImage), scale);
+ }
+ }
+ return image;
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.iOS/Renderers/KeyboardInsetTracker.cs b/Xamarin.Forms.Platform.iOS/Renderers/KeyboardInsetTracker.cs
new file mode 100644
index 00000000..d5759947
--- /dev/null
+++ b/Xamarin.Forms.Platform.iOS/Renderers/KeyboardInsetTracker.cs
@@ -0,0 +1,109 @@
+using System;
+using System.Drawing;
+#if __UNIFIED__
+using UIKit;
+#else
+using MonoTouch.UIKit;
+#endif
+#if __UNIFIED__
+using RectangleF = CoreGraphics.CGRect;
+using SizeF = CoreGraphics.CGSize;
+using PointF = CoreGraphics.CGPoint;
+
+#else
+using nfloat=System.Single;
+using nint=System.Int32;
+using nuint=System.UInt32;
+#endif
+
+namespace Xamarin.Forms.Platform.iOS
+{
+ internal class KeyboardInsetTracker : IDisposable
+ {
+ readonly Func<UIWindow> _fetchWindow;
+ readonly Action<PointF> _setContentOffset;
+ readonly Action<UIEdgeInsets> _setInsetAction;
+ readonly UIView _targetView;
+ bool _disposed;
+
+ RectangleF _lastKeyboardRect;
+
+ public KeyboardInsetTracker(UIView targetView, Func<UIWindow> fetchWindow, Action<UIEdgeInsets> setInsetAction) : this(targetView, fetchWindow, setInsetAction, null)
+ {
+ }
+
+ public KeyboardInsetTracker(UIView targetView, Func<UIWindow> fetchWindow, Action<UIEdgeInsets> setInsetAction, Action<PointF> setContentOffset)
+ {
+ _setContentOffset = setContentOffset;
+ _targetView = targetView;
+ _fetchWindow = fetchWindow;
+ _setInsetAction = setInsetAction;
+ KeyboardObserver.KeyboardWillShow += OnKeyboardShown;
+ KeyboardObserver.KeyboardWillHide += OnKeyboardHidden;
+ }
+
+ public void Dispose()
+ {
+ if (_disposed)
+ return;
+ _disposed = true;
+
+ KeyboardObserver.KeyboardWillShow -= OnKeyboardShown;
+ KeyboardObserver.KeyboardWillHide -= OnKeyboardHidden;
+ }
+
+ //This method allows us to update the insets if the Frame changes
+ internal void UpdateInsets()
+ {
+ //being called from LayoutSubviews but keyboard wasn't shown yet
+ if (_lastKeyboardRect.IsEmpty)
+ return;
+
+ var window = _fetchWindow();
+ // Code left verbose to make its operation more obvious
+ if (window == null)
+ {
+ // we are not currently displayed and can safely ignore this
+ // most likely this renderer is on a page which is currently not displayed (e.g. in NavController)
+ return;
+ }
+
+ var field = _targetView.FindFirstResponder();
+
+ //the view that is triggering the keyboard is not inside our UITableView?
+ //if (field == null)
+ // return;
+
+ var boundsSize = _targetView.Frame.Size;
+
+ //since our keyboard frame is RVC CoordinateSpace, lets convert it to our targetView CoordinateSpace
+ var rect = _targetView.Superview.ConvertRectFromView(_lastKeyboardRect, null);
+ //let's see how much does it cover our target view
+ var overlay = RectangleF.Intersect(rect, _targetView.Frame);
+
+ _setInsetAction(new UIEdgeInsets(0, 0, overlay.Height, 0));
+
+ if (field is UITextView && _setContentOffset != null)
+ {
+ var keyboardTop = boundsSize.Height - overlay.Height;
+ var fieldPosition = field.ConvertPointToView(field.Frame.Location, _targetView.Superview);
+ var fieldBottom = fieldPosition.Y + field.Frame.Height;
+ var offset = fieldBottom - keyboardTop;
+ if (offset > 0)
+ _setContentOffset(new PointF(0, offset));
+ }
+ }
+
+ void OnKeyboardHidden(object sender, UIKeyboardEventArgs args)
+ {
+ _setInsetAction(new UIEdgeInsets(0, 0, 0, 0));
+ _lastKeyboardRect = RectangleF.Empty;
+ }
+
+ void OnKeyboardShown(object sender, UIKeyboardEventArgs args)
+ {
+ _lastKeyboardRect = args.FrameEnd;
+ UpdateInsets();
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.iOS/Renderers/KeyboardObserver.cs b/Xamarin.Forms.Platform.iOS/Renderers/KeyboardObserver.cs
new file mode 100644
index 00000000..4ebb75d7
--- /dev/null
+++ b/Xamarin.Forms.Platform.iOS/Renderers/KeyboardObserver.cs
@@ -0,0 +1,37 @@
+using System;
+#if __UNIFIED__
+using UIKit;
+
+#else
+using MonoTouch.UIKit;
+#endif
+
+namespace Xamarin.Forms.Platform.iOS
+{
+ internal static class KeyboardObserver
+ {
+ static KeyboardObserver()
+ {
+ UIKeyboard.Notifications.ObserveWillShow(OnKeyboardShown);
+ UIKeyboard.Notifications.ObserveWillHide(OnKeyboardHidden);
+ }
+
+ public static event EventHandler<UIKeyboardEventArgs> KeyboardWillHide;
+
+ public static event EventHandler<UIKeyboardEventArgs> KeyboardWillShow;
+
+ static void OnKeyboardHidden(object sender, UIKeyboardEventArgs args)
+ {
+ var handler = KeyboardWillHide;
+ if (handler != null)
+ handler(sender, args);
+ }
+
+ static void OnKeyboardShown(object sender, UIKeyboardEventArgs args)
+ {
+ var handler = KeyboardWillShow;
+ if (handler != null)
+ handler(sender, args);
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.iOS/Renderers/LabelRenderer.cs b/Xamarin.Forms.Platform.iOS/Renderers/LabelRenderer.cs
new file mode 100644
index 00000000..f886cb35
--- /dev/null
+++ b/Xamarin.Forms.Platform.iOS/Renderers/LabelRenderer.cs
@@ -0,0 +1,183 @@
+using System;
+using System.Drawing;
+using System.ComponentModel;
+#if __UNIFIED__
+using UIKit;
+using CoreText;
+#else
+using MonoTouch.UIKit;
+using MonoTouch.CoreText;
+#endif
+#if __UNIFIED__
+using RectangleF = CoreGraphics.CGRect;
+using SizeF = CoreGraphics.CGSize;
+using PointF = CoreGraphics.CGPoint;
+
+#else
+using nfloat=System.Single;
+using nint=System.Int32;
+using nuint=System.UInt32;
+#endif
+
+namespace Xamarin.Forms.Platform.iOS
+{
+ public class LabelRenderer : ViewRenderer<Label, UILabel>
+ {
+ SizeRequest _perfectSize;
+
+ bool _perfectSizeValid;
+
+ public override SizeRequest GetDesiredSize(double widthConstraint, double heightConstraint)
+ {
+ if (!_perfectSizeValid)
+ {
+ _perfectSize = base.GetDesiredSize(double.PositiveInfinity, double.PositiveInfinity);
+ _perfectSize.Minimum = new Size(Math.Min(10, _perfectSize.Request.Width), _perfectSize.Request.Height);
+ _perfectSizeValid = true;
+ }
+
+ if (widthConstraint >= _perfectSize.Request.Width && heightConstraint >= _perfectSize.Request.Height)
+ return _perfectSize;
+
+ var result = base.GetDesiredSize(widthConstraint, heightConstraint);
+ result.Minimum = new Size(Math.Min(10, result.Request.Width), result.Request.Height);
+ if ((Element.LineBreakMode & (LineBreakMode.TailTruncation | LineBreakMode.HeadTruncation | LineBreakMode.MiddleTruncation)) != 0)
+ {
+ if (result.Request.Width > widthConstraint)
+ result.Request = new Size(Math.Max(result.Minimum.Width, widthConstraint), result.Request.Height);
+ }
+
+ return result;
+ }
+
+ public override void LayoutSubviews()
+ {
+ base.LayoutSubviews();
+ if (Control == null)
+ return;
+
+ SizeF fitSize;
+ nfloat labelHeight;
+ switch (Element.VerticalTextAlignment)
+ {
+ case TextAlignment.Start:
+ fitSize = Control.SizeThatFits(Element.Bounds.Size.ToSizeF());
+ labelHeight = (nfloat)Math.Min(Bounds.Height, fitSize.Height);
+ Control.Frame = new RectangleF(0, 0, (nfloat)Element.Width, labelHeight);
+ break;
+ case TextAlignment.Center:
+ Control.Frame = new RectangleF(0, 0, (nfloat)Element.Width, (nfloat)Element.Height);
+ break;
+ case TextAlignment.End:
+ nfloat yOffset = 0;
+ fitSize = Control.SizeThatFits(Element.Bounds.Size.ToSizeF());
+ labelHeight = (nfloat)Math.Min(Bounds.Height, fitSize.Height);
+ yOffset = (nfloat)(Element.Height - labelHeight);
+ Control.Frame = new RectangleF(0, yOffset, (nfloat)Element.Width, labelHeight);
+ break;
+ }
+ }
+
+ protected override void OnElementChanged(ElementChangedEventArgs<Label> e)
+ {
+ if (e.NewElement != null)
+ {
+ if (Control == null)
+ {
+ SetNativeControl(new UILabel(RectangleF.Empty) { BackgroundColor = UIColor.Clear });
+ }
+
+ UpdateText();
+ UpdateLineBreakMode();
+ UpdateAlignment();
+ }
+
+ base.OnElementChanged(e);
+ }
+
+ protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ base.OnElementPropertyChanged(sender, e);
+
+ if (e.PropertyName == Label.HorizontalTextAlignmentProperty.PropertyName)
+ UpdateAlignment();
+ else if (e.PropertyName == Label.VerticalTextAlignmentProperty.PropertyName)
+ LayoutSubviews();
+ else if (e.PropertyName == Label.TextColorProperty.PropertyName)
+ UpdateText();
+ else if (e.PropertyName == Label.FontProperty.PropertyName)
+ UpdateText();
+ else if (e.PropertyName == Label.TextProperty.PropertyName)
+ UpdateText();
+ else if (e.PropertyName == Label.FormattedTextProperty.PropertyName)
+ UpdateText();
+ else if (e.PropertyName == Label.LineBreakModeProperty.PropertyName)
+ UpdateLineBreakMode();
+ }
+
+ protected override void SetBackgroundColor(Color color)
+ {
+ if (color == Color.Default)
+ BackgroundColor = UIColor.Clear;
+ else
+ BackgroundColor = color.ToUIColor();
+ }
+
+ void UpdateAlignment()
+ {
+ Control.TextAlignment = Element.HorizontalTextAlignment.ToNativeTextAlignment();
+ }
+
+ void UpdateLineBreakMode()
+ {
+ _perfectSizeValid = false;
+
+ switch (Element.LineBreakMode)
+ {
+ case LineBreakMode.NoWrap:
+ Control.LineBreakMode = UILineBreakMode.Clip;
+ Control.Lines = 1;
+ break;
+ case LineBreakMode.WordWrap:
+ Control.LineBreakMode = UILineBreakMode.WordWrap;
+ Control.Lines = 0;
+ break;
+ case LineBreakMode.CharacterWrap:
+ Control.LineBreakMode = UILineBreakMode.CharacterWrap;
+ Control.Lines = 0;
+ break;
+ case LineBreakMode.HeadTruncation:
+ Control.LineBreakMode = UILineBreakMode.HeadTruncation;
+ Control.Lines = 1;
+ break;
+ case LineBreakMode.MiddleTruncation:
+ Control.LineBreakMode = UILineBreakMode.MiddleTruncation;
+ Control.Lines = 1;
+ break;
+ case LineBreakMode.TailTruncation:
+ Control.LineBreakMode = UILineBreakMode.TailTruncation;
+ Control.Lines = 1;
+ break;
+ }
+ }
+
+ void UpdateText()
+ {
+ _perfectSizeValid = false;
+
+ var values = Element.GetValues(Label.FormattedTextProperty, Label.TextProperty, Label.TextColorProperty);
+ var formatted = (FormattedString)values[0];
+ if (formatted != null)
+ Control.AttributedText = formatted.ToAttributed(Element, (Color)values[2]);
+ else
+ {
+ Control.Text = (string)values[1];
+ // default value of color documented to be black in iOS docs
+ Control.Font = Element.ToUIFont();
+ Control.TextColor = ((Color)values[2]).ToUIColor(ColorExtensions.Black);
+ }
+
+ LayoutSubviews();
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.iOS/Renderers/ListViewRenderer.cs b/Xamarin.Forms.Platform.iOS/Renderers/ListViewRenderer.cs
new file mode 100644
index 00000000..2151d446
--- /dev/null
+++ b/Xamarin.Forms.Platform.iOS/Renderers/ListViewRenderer.cs
@@ -0,0 +1,1119 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Collections.Specialized;
+using System.ComponentModel;
+using System.Drawing;
+using System.Linq;
+#if __UNIFIED__
+using UIKit;
+using Foundation;
+#else
+using MonoTouch.UIKit;
+using MonoTouch.Foundation;
+#endif
+#if __UNIFIED__
+using RectangleF = CoreGraphics.CGRect;
+using SizeF = CoreGraphics.CGSize;
+using PointF = CoreGraphics.CGPoint;
+
+#else
+using nfloat=System.Single;
+using nint=System.Int32;
+using nuint=System.UInt32;
+#endif
+
+namespace Xamarin.Forms.Platform.iOS
+{
+ public class ListViewRenderer : ViewRenderer<ListView, UITableView>
+ {
+ const int DefaultRowHeight = 44;
+ ListViewDataSource _dataSource;
+ bool _estimatedRowHeight;
+ IVisualElementRenderer _headerRenderer;
+ IVisualElementRenderer _footerRenderer;
+
+ KeyboardInsetTracker _insetTracker;
+ RectangleF _previousFrame;
+ ScrollToRequestedEventArgs _requestedScroll;
+ bool _shouldEstimateRowHeight = true;
+ FormsUITableViewController _tableViewController;
+
+ public override SizeRequest GetDesiredSize(double widthConstraint, double heightConstraint)
+ {
+ return Control.GetSizeRequest(widthConstraint, heightConstraint, 44, 44);
+ }
+
+ public override void LayoutSubviews()
+ {
+ base.LayoutSubviews();
+
+ double height = Bounds.Height;
+ double width = Bounds.Width;
+ if (_headerRenderer != null)
+ {
+ var e = _headerRenderer.Element;
+ var request = e.Measure(width, double.PositiveInfinity, MeasureFlags.IncludeMargins);
+
+ // Time for another story with Jason. Gather round children because the following Math.Ceiling will look like it's completely useless.
+ // You will remove it and test and find everything is fiiiiiine, but it is not fine, no it is far from fine. See iOS, or at least iOS 8
+ // has an issue where-by if the TableHeaderView happens to NOT be an integer height, it will add padding to the space between the content
+ // of the UITableView and the TableHeaderView to the tune of the difference between Math.Ceiling (height) - height. Now this seems fine
+ // and when you test it will be, EXCEPT that it does this every time you toggle the visibility of the UITableView causing the spacing to
+ // grow a little each time, which you weren't testing at all were you? So there you have it, the stupid reason we integer align here.
+ //
+ // The same technically applies to the footer, though that could hardly matter less. We just do it for fun.
+ Layout.LayoutChildIntoBoundingRegion(e, new Rectangle(0, 0, width, Math.Ceiling(request.Request.Height)));
+
+ Device.BeginInvokeOnMainThread(() =>
+ {
+ if (_headerRenderer != null)
+ Control.TableHeaderView = _headerRenderer.NativeView;
+ });
+ }
+
+ if (_footerRenderer != null)
+ {
+ var e = _footerRenderer.Element;
+ var request = e.Measure(width, height, MeasureFlags.IncludeMargins);
+ Layout.LayoutChildIntoBoundingRegion(e, new Rectangle(0, 0, width, Math.Ceiling(request.Request.Height)));
+
+ Device.BeginInvokeOnMainThread(() =>
+ {
+ if (_footerRenderer != null)
+ Control.TableFooterView = _footerRenderer.NativeView;
+ });
+ }
+
+ if (_requestedScroll != null && Superview != null)
+ {
+ var request = _requestedScroll;
+ _requestedScroll = null;
+ OnScrollToRequested(this, request);
+ }
+
+ if (_previousFrame != Frame)
+ {
+ _previousFrame = Frame;
+ _insetTracker?.UpdateInsets();
+ }
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ // check inset tracker for null to
+ if (disposing && _insetTracker != null)
+ {
+ _insetTracker.Dispose();
+ _insetTracker = null;
+
+ var viewsToLookAt = new Stack<UIView>(Subviews);
+ while (viewsToLookAt.Count > 0)
+ {
+ var view = viewsToLookAt.Pop();
+ var viewCellRenderer = view as ViewCellRenderer.ViewTableCell;
+ if (viewCellRenderer != null)
+ viewCellRenderer.Dispose();
+ else
+ {
+ foreach (var child in view.Subviews)
+ viewsToLookAt.Push(child);
+ }
+ }
+
+ if (Element != null)
+ {
+ Element.TemplatedItems.CollectionChanged -= OnCollectionChanged;
+ Element.TemplatedItems.GroupedCollectionChanged -= OnGroupedCollectionChanged;
+ }
+
+ if (_tableViewController != null)
+ {
+ _tableViewController.Dispose();
+ _tableViewController = null;
+ }
+ }
+
+ if (disposing)
+ {
+ if (_headerRenderer != null)
+ {
+ var platform = _headerRenderer.Element.Platform as Platform;
+ if (platform != null)
+ platform.DisposeModelAndChildrenRenderers(_headerRenderer.Element);
+ _headerRenderer = null;
+ }
+ if (_footerRenderer != null)
+ {
+ var platform = _footerRenderer.Element.Platform as Platform;
+ if (platform != null)
+ platform.DisposeModelAndChildrenRenderers(_footerRenderer.Element);
+ _footerRenderer = null;
+ }
+
+ var controller = Element as IListViewController;
+
+ var headerView = controller?.HeaderElement as VisualElement;
+ if (headerView != null)
+ headerView.MeasureInvalidated -= OnHeaderMeasureInvalidated;
+ Control?.TableHeaderView?.Dispose();
+
+ var footerView = controller?.FooterElement as VisualElement;
+ if (footerView != null)
+ footerView.MeasureInvalidated -= OnFooterMeasureInvalidated;
+ Control?.TableFooterView?.Dispose();
+ }
+
+ base.Dispose(disposing);
+ }
+
+ protected override void OnElementChanged(ElementChangedEventArgs<ListView> e)
+ {
+ _requestedScroll = null;
+
+ if (e.OldElement != null)
+ {
+ var controller = (IListViewController)e.OldElement;
+ var headerView = (VisualElement)controller.HeaderElement;
+ if (headerView != null)
+ headerView.MeasureInvalidated -= OnHeaderMeasureInvalidated;
+
+ var footerView = (VisualElement)controller.FooterElement;
+ if (footerView != null)
+ footerView.MeasureInvalidated -= OnFooterMeasureInvalidated;
+
+ e.OldElement.ScrollToRequested -= OnScrollToRequested;
+ e.OldElement.TemplatedItems.CollectionChanged -= OnCollectionChanged;
+ e.OldElement.TemplatedItems.GroupedCollectionChanged -= OnGroupedCollectionChanged;
+ }
+
+ if (e.NewElement != null)
+ {
+ if (Control == null)
+ {
+ _tableViewController = new FormsUITableViewController(e.NewElement);
+ SetNativeControl(_tableViewController.TableView);
+ if (Forms.IsiOS9OrNewer)
+ Control.CellLayoutMarginsFollowReadableWidth = false;
+
+ _insetTracker = new KeyboardInsetTracker(_tableViewController.TableView, () => Control.Window, insets => Control.ContentInset = Control.ScrollIndicatorInsets = insets, point =>
+ {
+ var offset = Control.ContentOffset;
+ offset.Y += point.Y;
+ Control.SetContentOffset(offset, true);
+ });
+ }
+ _shouldEstimateRowHeight = true;
+ //if the user specifies he wants to sacrifice performance we will do things like:
+ // - don't EstimateRowHeight anymore
+ if (e.NewElement.TakePerformanceHit)
+ _shouldEstimateRowHeight = false;
+
+ e.NewElement.ScrollToRequested += OnScrollToRequested;
+ e.NewElement.TemplatedItems.CollectionChanged += OnCollectionChanged;
+ e.NewElement.TemplatedItems.GroupedCollectionChanged += OnGroupedCollectionChanged;
+
+ UpdateRowHeight();
+
+ Control.Source = _dataSource = e.NewElement.HasUnevenRows ? new UnevenListViewDataSource(e.NewElement, _tableViewController) : new ListViewDataSource(e.NewElement, _tableViewController);
+
+ UpdateEstimatedRowHeight();
+ 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)
+ _dataSource.UpdateGrouping();
+ else if (e.PropertyName == ListView.HasUnevenRowsProperty.PropertyName)
+ {
+ _estimatedRowHeight = false;
+ Control.Source = _dataSource = Element.HasUnevenRows ? new UnevenListViewDataSource(_dataSource) : new ListViewDataSource(_dataSource);
+ }
+ 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();
+ }
+
+ NSIndexPath[] GetPaths(int section, int index, int count)
+ {
+ var paths = new NSIndexPath[count];
+ for (var i = 0; i < paths.Length; i++)
+ paths[i] = NSIndexPath.FromRowSection(index + i, section);
+
+ return paths;
+ }
+
+ UITableViewScrollPosition GetScrollPosition(ScrollToPosition position)
+ {
+ switch (position)
+ {
+ case ScrollToPosition.Center:
+ return UITableViewScrollPosition.Middle;
+ case ScrollToPosition.End:
+ return UITableViewScrollPosition.Bottom;
+ case ScrollToPosition.Start:
+ return UITableViewScrollPosition.Top;
+ case ScrollToPosition.MakeVisible:
+ default:
+ return UITableViewScrollPosition.None;
+ }
+ }
+
+ void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
+ {
+ UpdateItems(e, 0, true);
+ }
+
+ void OnFooterMeasureInvalidated(object sender, EventArgs eventArgs)
+ {
+ double width = Bounds.Width;
+ if (width == 0)
+ return;
+
+ var footerView = (VisualElement)sender;
+ var request = footerView.Measure(width, double.PositiveInfinity, MeasureFlags.IncludeMargins);
+ Layout.LayoutChildIntoBoundingRegion(footerView, new Rectangle(0, 0, width, request.Request.Height));
+
+ Control.TableFooterView = _footerRenderer.NativeView;
+ }
+
+ void OnGroupedCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
+ {
+ var til = (TemplatedItemsList<ItemsView<Cell>, Cell>)sender;
+
+ var groupIndex = Element.TemplatedItems.IndexOf(til.HeaderContent);
+ UpdateItems(e, groupIndex, false);
+ }
+
+ void OnHeaderMeasureInvalidated(object sender, EventArgs eventArgs)
+ {
+ double width = Bounds.Width;
+ if (width == 0)
+ return;
+
+ var headerView = (VisualElement)sender;
+ var request = headerView.Measure(width, double.PositiveInfinity, MeasureFlags.IncludeMargins);
+ Layout.LayoutChildIntoBoundingRegion(headerView, new Rectangle(0, 0, width, request.Request.Height));
+
+ Control.TableHeaderView = _headerRenderer.NativeView;
+ }
+
+ void OnScrollToRequested(object sender, ScrollToRequestedEventArgs e)
+ {
+ if (Superview == null)
+ {
+ _requestedScroll = e;
+ return;
+ }
+
+ var position = GetScrollPosition(e.Position);
+
+ if (Element.IsGroupingEnabled)
+ {
+ var result = Element.TemplatedItems.GetGroupAndIndexOfItem(e.Group, e.Item);
+ if (result.Item1 != -1 && result.Item2 != -1)
+ Control.ScrollToRow(NSIndexPath.FromRowSection(result.Item2, result.Item1), position, e.ShouldAnimate);
+ }
+ else
+ {
+ var index = Element.TemplatedItems.GetGlobalIndexOfItem(e.Item);
+ if (index != -1)
+ Control.ScrollToRow(NSIndexPath.FromRowSection(index, 0), position, e.ShouldAnimate);
+ }
+ }
+
+ void UpdateEstimatedRowHeight()
+ {
+ if (_estimatedRowHeight)
+ return;
+
+ var rowHeight = Element.RowHeight;
+ if (Element.HasUnevenRows && rowHeight == -1)
+ {
+ var source = _dataSource as UnevenListViewDataSource;
+ if (_shouldEstimateRowHeight)
+ {
+ if (Element.TemplatedItems.Count > 0 && source != null)
+ {
+ var estimatedHeightFromFirstCell = source.CalculateHeightForCell(Control, Element.TemplatedItems.First());
+ Control.EstimatedRowHeight = estimatedHeightFromFirstCell;
+ _estimatedRowHeight = true;
+ }
+ else
+ {
+ //We need to set a default estimated row height, because re-setting it later(when we have items on the TIL)
+ //will cause the UITableView to reload, and throw a Excepetion
+ Control.EstimatedRowHeight = DefaultRowHeight;
+ }
+ }
+ }
+ else
+ {
+ if (Forms.IsiOS7OrNewer)
+ Control.EstimatedRowHeight = 0;
+ _estimatedRowHeight = true;
+ }
+ }
+
+ void UpdateFooter()
+ {
+ var footer = ((IListViewController)Element).FooterElement;
+ var footerView = (View)footer;
+
+ if (footerView != null)
+ {
+ if (_footerRenderer != null)
+ {
+ _footerRenderer.Element.MeasureInvalidated -= OnFooterMeasureInvalidated;
+ if (footer != null && _footerRenderer.GetType() == Registrar.Registered.GetHandlerType(footer.GetType()))
+ {
+ _footerRenderer.SetElement(footerView);
+ return;
+ }
+ Control.TableFooterView = null;
+ var platform = _footerRenderer.Element.Platform as Platform;
+ if (platform != null)
+ platform.DisposeModelAndChildrenRenderers(_footerRenderer.Element);
+ _footerRenderer.Dispose();
+ _footerRenderer = null;
+ }
+
+ _footerRenderer = Platform.CreateRenderer(footerView);
+ Platform.SetRenderer(footerView, _footerRenderer);
+
+ double width = Bounds.Width;
+ var request = footerView.Measure(width, double.PositiveInfinity, MeasureFlags.IncludeMargins);
+ Layout.LayoutChildIntoBoundingRegion(footerView, new Rectangle(0, 0, width, request.Request.Height));
+
+ Control.TableFooterView = _footerRenderer.NativeView;
+ footerView.MeasureInvalidated += OnFooterMeasureInvalidated;
+ }
+ else if (_footerRenderer != null)
+ {
+ Control.TableFooterView = null;
+ var platform = _footerRenderer.Element.Platform as Platform;
+ if (platform != null)
+ platform.DisposeModelAndChildrenRenderers(_footerRenderer.Element);
+ _footerRenderer.Dispose();
+ _footerRenderer = null;
+ }
+ }
+
+ void UpdateHeader()
+ {
+ var header = ((IListViewController)Element).HeaderElement;
+ var headerView = (View)header;
+
+ if (headerView != null)
+ {
+ if (_headerRenderer != null)
+ {
+ _headerRenderer.Element.MeasureInvalidated -= OnHeaderMeasureInvalidated;
+ if (header != null && _headerRenderer.GetType() == Registrar.Registered.GetHandlerType(header.GetType()))
+ {
+ _headerRenderer.SetElement(headerView);
+ return;
+ }
+ Control.TableHeaderView = null;
+ var platform = _headerRenderer.Element.Platform as Platform;
+ if (platform != null)
+ platform.DisposeModelAndChildrenRenderers(_headerRenderer.Element);
+ _headerRenderer = null;
+ }
+
+ _headerRenderer = Platform.CreateRenderer(headerView);
+ // This will force measure to invalidate, which we haven't hooked up to yet because we are smarter!
+ Platform.SetRenderer(headerView, _headerRenderer);
+
+ double width = Bounds.Width;
+ var request = headerView.Measure(width, double.PositiveInfinity, MeasureFlags.IncludeMargins);
+ Layout.LayoutChildIntoBoundingRegion(headerView, new Rectangle(0, 0, width, request.Request.Height));
+
+ Control.TableHeaderView = _headerRenderer.NativeView;
+ headerView.MeasureInvalidated += OnHeaderMeasureInvalidated;
+ }
+ else if (_headerRenderer != null)
+ {
+ Control.TableHeaderView = null;
+ var platform = _headerRenderer.Element.Platform as Platform;
+ if (platform != null)
+ platform.DisposeModelAndChildrenRenderers(_headerRenderer.Element);
+ _headerRenderer.Dispose();
+ _headerRenderer = null;
+ }
+ }
+
+ void UpdateIsRefreshing()
+ {
+ var refreshing = Element.IsRefreshing;
+ if (_tableViewController != null)
+ _tableViewController.UpdateIsRefreshing(refreshing);
+ }
+
+ 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:
+ UpdateEstimatedRowHeight();
+ if (e.NewStartingIndex == -1 || groupReset)
+ goto case NotifyCollectionChangedAction.Reset;
+ Control.BeginUpdates();
+ Control.InsertRows(GetPaths(section, e.NewStartingIndex, e.NewItems.Count), UITableViewRowAnimation.Automatic);
+
+ Control.EndUpdates();
+
+ break;
+
+ case NotifyCollectionChangedAction.Remove:
+ if (e.OldStartingIndex == -1 || groupReset)
+ goto case NotifyCollectionChangedAction.Reset;
+ Control.BeginUpdates();
+ Control.DeleteRows(GetPaths(section, e.OldStartingIndex, e.OldItems.Count), UITableViewRowAnimation.Automatic);
+
+ Control.EndUpdates();
+
+ if (_estimatedRowHeight && Element.TemplatedItems.Count == 0)
+ _estimatedRowHeight = false;
+
+ break;
+
+ case NotifyCollectionChangedAction.Move:
+ if (e.OldStartingIndex == -1 || e.NewStartingIndex == -1 || groupReset)
+ goto case NotifyCollectionChangedAction.Reset;
+ Control.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;
+ }
+
+ Control.MoveRow(NSIndexPath.FromRowSection(oldi, section), NSIndexPath.FromRowSection(newi, section));
+ }
+ Control.EndUpdates();
+
+ if (_estimatedRowHeight && e.OldStartingIndex == 0)
+ _estimatedRowHeight = false;
+
+ break;
+
+ case NotifyCollectionChangedAction.Replace:
+ if (e.OldStartingIndex == -1 || groupReset)
+ goto case NotifyCollectionChangedAction.Reset;
+ Control.BeginUpdates();
+ Control.ReloadRows(GetPaths(section, e.OldStartingIndex, e.OldItems.Count), UITableViewRowAnimation.Automatic);
+ Control.EndUpdates();
+
+ if (_estimatedRowHeight && e.OldStartingIndex == 0)
+ _estimatedRowHeight = false;
+
+ break;
+
+ case NotifyCollectionChangedAction.Reset:
+ _estimatedRowHeight = false;
+ Control.ReloadData();
+ return;
+ }
+ }
+
+ void UpdatePullToRefreshEnabled()
+ {
+ if (_tableViewController != null)
+ {
+ var isPullToRequestEnabled = Element.IsPullToRefreshEnabled && (Element as IListViewController).RefreshAllowed;
+ _tableViewController.UpdatePullToRefreshEnabled(isPullToRequestEnabled);
+ }
+ }
+
+ void UpdateRowHeight()
+ {
+ var rowHeight = Element.RowHeight;
+ if (Element.HasUnevenRows && rowHeight == -1 && Forms.IsiOS7OrNewer)
+ {
+ if (Forms.IsiOS8OrNewer)
+ Control.RowHeight = UITableView.AutomaticDimension;
+ }
+ else
+ Control.RowHeight = rowHeight <= 0 ? DefaultRowHeight : rowHeight;
+ }
+
+ void UpdateSeparatorColor()
+ {
+ var color = Element.SeparatorColor;
+ // ...and Steve said to the unbelievers the separator shall be gray, and gray it was. The unbelievers looked on, and saw that it was good, and
+ // they went forth and documented the default color. The holy scripture still reflects this default.
+ // Defined here: https://developer.apple.com/library/ios/documentation/UIKit/Reference/UITableView_Class/#//apple_ref/occ/instp/UITableView/separatorColor
+ Control.SeparatorColor = color.ToUIColor(UIColor.Gray);
+ }
+
+ void UpdateSeparatorVisibility()
+ {
+ var visibility = Element.SeparatorVisibility;
+ switch (visibility)
+ {
+ case SeparatorVisibility.Default:
+ Control.SeparatorStyle = UITableViewCellSeparatorStyle.SingleLine;
+ break;
+ case SeparatorVisibility.None:
+ Control.SeparatorStyle = UITableViewCellSeparatorStyle.None;
+ break;
+ default:
+ throw new ArgumentOutOfRangeException();
+ }
+ }
+
+ internal class UnevenListViewDataSource : ListViewDataSource
+ {
+ IVisualElementRenderer _prototype;
+
+ public UnevenListViewDataSource(ListView list, FormsUITableViewController uiTableViewController) : base(list, uiTableViewController)
+ {
+ }
+
+ public UnevenListViewDataSource(ListViewDataSource source) : base(source)
+ {
+ }
+
+ public override nfloat GetHeightForRow(UITableView tableView, NSIndexPath indexPath)
+ {
+ var cell = GetCellForPath(indexPath);
+
+ if (List.RowHeight == -1 && cell.Height == -1 && cell is ViewCell)
+ {
+ // only doing ViewCell because its the only one that matters (the others dont adjust ANYWAY)
+ if (Forms.IsiOS8OrNewer)
+ return UITableView.AutomaticDimension;
+ return CalculateHeightForCell(tableView, cell);
+ }
+
+ var renderHeight = cell.RenderHeight;
+ return renderHeight > 0 ? (nfloat)renderHeight : DefaultRowHeight;
+ }
+
+ internal nfloat CalculateHeightForCell(UITableView tableView, Cell cell)
+ {
+ var viewCell = cell as ViewCell;
+ if (viewCell != null && 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);
+
+ return (nfloat)req.Request.Height;
+ }
+ var renderHeight = cell.RenderHeight;
+ return renderHeight > 0 ? (nfloat)renderHeight : DefaultRowHeight;
+ }
+ }
+
+ internal class ListViewDataSource : UITableViewSource
+ {
+ const int DefaultItemTemplateId = 1;
+ static int s_dataTemplateIncrementer = 2; // lets start at not 0 because
+ readonly nfloat _defaultSectionHeight;
+ readonly Dictionary<DataTemplate, int> _templateToId = new Dictionary<DataTemplate, int>();
+ readonly UITableView _uiTableView;
+ readonly FormsUITableViewController _uiTableViewController;
+ protected readonly ListView List;
+ bool _isDragging;
+ bool _selectionFromNative;
+
+ public ListViewDataSource(ListViewDataSource source)
+ {
+ _uiTableViewController = source._uiTableViewController;
+ List = source.List;
+ _uiTableView = source._uiTableView;
+ _defaultSectionHeight = source._defaultSectionHeight;
+ _selectionFromNative = source._selectionFromNative;
+
+ Counts = new Dictionary<int, int>();
+ }
+
+ public ListViewDataSource(ListView list, FormsUITableViewController uiTableViewController)
+ {
+ _uiTableViewController = uiTableViewController;
+ _uiTableView = uiTableViewController.TableView;
+ _defaultSectionHeight = Forms.IsiOS8OrNewer ? DefaultRowHeight : _uiTableView.SectionHeaderHeight;
+ List = list;
+ List.ItemSelected += OnItemSelected;
+ UpdateShortNameListener();
+
+ Counts = new Dictionary<int, int>();
+ }
+
+ public Dictionary<int, int> Counts { get; set; }
+
+ UIColor DefaultBackgroundColor
+ {
+ get { return UIColor.Clear; }
+ }
+
+ public override void DraggingEnded(UIScrollView scrollView, bool willDecelerate)
+ {
+ _isDragging = false;
+ _uiTableViewController.UpdateShowHideRefresh(false);
+ }
+
+ public override void DraggingStarted(UIScrollView scrollView)
+ {
+ _isDragging = true;
+ }
+
+ public override UITableViewCell GetCell(UITableView tableView, NSIndexPath indexPath)
+ {
+ UITableViewCell nativeCell = null;
+
+ var cachingStrategy = List.CachingStrategy;
+ if (cachingStrategy == ListViewCachingStrategy.RetainElement)
+ {
+ var cell = GetCellForPath(indexPath);
+ nativeCell = CellTableViewCell.GetNativeCell(tableView, cell);
+ }
+ else if (cachingStrategy == ListViewCachingStrategy.RecycleElement)
+ {
+ var id = TemplateIdForPath(indexPath);
+ nativeCell = tableView.DequeueReusableCell(ContextActionsCell.Key + id);
+ if (nativeCell == null)
+ {
+ var cell = GetCellForPath(indexPath);
+ nativeCell = CellTableViewCell.GetNativeCell(tableView, cell, true, id.ToString());
+ }
+ else
+ {
+ var templatedList = List.TemplatedItems.GetGroup(indexPath.Section);
+ var cell = (Cell)((INativeElementView)nativeCell).Element;
+ cell.SendDisappearing();
+ templatedList.UpdateContent(cell, indexPath.Row);
+ cell.SendAppearing();
+ }
+ }
+ else
+ throw new NotSupportedException();
+
+ var bgColor = tableView.IndexPathForSelectedRow != null && tableView.IndexPathForSelectedRow.Equals(indexPath) ? UIColor.Clear : DefaultBackgroundColor;
+
+ SetCellBackgroundColor(nativeCell, bgColor);
+
+ return nativeCell;
+ }
+
+ public override nfloat GetHeightForHeader(UITableView tableView, nint section)
+ {
+ if (List.IsGroupingEnabled)
+ {
+ var cell = List.TemplatedItems[(int)section];
+ nfloat height = (float)cell.RenderHeight;
+ if (height == -1)
+ height = _defaultSectionHeight;
+
+ return height;
+ }
+
+ return 0;
+ }
+
+ public override UIView GetViewForHeader(UITableView tableView, nint section)
+ {
+ if (List.IsGroupingEnabled && List.GroupHeaderTemplate != null)
+ {
+ var cell = List.TemplatedItems[(int)section];
+ if (cell.HasContextActions)
+ throw new NotSupportedException("Header cells do not support context actions");
+
+ var renderer = (CellRenderer)Registrar.Registered.GetHandler(cell.GetType());
+
+ var view = new HeaderWrapperView();
+ view.AddSubview(renderer.GetCell(cell, null, tableView));
+
+ return view;
+ }
+
+ return null;
+ }
+
+ public override nint NumberOfSections(UITableView tableView)
+ {
+ if (List.IsGroupingEnabled)
+ return List.TemplatedItems.Count;
+
+ return 1;
+ }
+
+ public void OnItemSelected(object sender, SelectedItemChangedEventArgs eventArg)
+ {
+ if (_selectionFromNative)
+ {
+ _selectionFromNative = false;
+ return;
+ }
+
+ var location = List.TemplatedItems.GetGroupAndIndexOfItem(eventArg.SelectedItem);
+ if (location.Item1 == -1 || location.Item2 == -1)
+ {
+ var selectedIndexPath = _uiTableView.IndexPathForSelectedRow;
+
+ var animate = true;
+
+ if (selectedIndexPath != null)
+ {
+ var cell = _uiTableView.CellAt(selectedIndexPath) as ContextActionsCell;
+ if (cell != null)
+ {
+ cell.PrepareForDeselect();
+ if (cell.IsOpen)
+ animate = false;
+ }
+ }
+
+ if (selectedIndexPath != null)
+ _uiTableView.DeselectRow(selectedIndexPath, animate);
+ return;
+ }
+
+ _uiTableView.SelectRow(NSIndexPath.FromRowSection(location.Item2, location.Item1), true, UITableViewScrollPosition.Middle);
+ }
+
+ public override void RowDeselected(UITableView tableView, NSIndexPath indexPath)
+ {
+ var cell = tableView.CellAt(indexPath);
+ if (cell == null)
+ return;
+
+ SetCellBackgroundColor(cell, DefaultBackgroundColor);
+ }
+
+ public override void RowSelected(UITableView tableView, NSIndexPath indexPath)
+ {
+ var cell = tableView.CellAt(indexPath);
+
+ if (cell == null)
+ return;
+
+ Cell formsCell = null;
+ if (List.CachingStrategy == ListViewCachingStrategy.RecycleElement)
+ formsCell = (Cell)((INativeElementView)cell).Element;
+
+ SetCellBackgroundColor(cell, UIColor.Clear);
+
+ _selectionFromNative = true;
+
+ tableView.EndEditing(true);
+ List.NotifyRowTapped(indexPath.Section, indexPath.Row, formsCell);
+ }
+
+ public override nint RowsInSection(UITableView tableview, nint section)
+ {
+ int countOverride;
+ if (Counts.TryGetValue((int)section, out countOverride))
+ {
+ Counts.Remove((int)section);
+ return countOverride;
+ }
+
+ if (List.IsGroupingEnabled)
+ {
+ var group = (IList)((IList)List.TemplatedItems)[(int)section];
+ return group.Count;
+ }
+
+ return List.TemplatedItems.Count;
+ }
+
+ public override void Scrolled(UIScrollView scrollView)
+ {
+ if (_isDragging && scrollView.ContentOffset.Y < 0)
+ _uiTableViewController.UpdateShowHideRefresh(true);
+ }
+
+ public override string[] SectionIndexTitles(UITableView tableView)
+ {
+ if (List.TemplatedItems.ShortNames == null)
+ return null;
+
+ return List.TemplatedItems.ShortNames.ToArray();
+ }
+
+ public override string TitleForHeader(UITableView tableView, nint section)
+ {
+ if (!List.IsGroupingEnabled)
+ return null;
+
+ var sl = GetSectionList((int)section);
+ sl.PropertyChanged -= OnSectionPropertyChanged;
+ sl.PropertyChanged += OnSectionPropertyChanged;
+
+ return sl.Name;
+ }
+
+ public void UpdateGrouping()
+ {
+ UpdateShortNameListener();
+ _uiTableView.ReloadData();
+ }
+
+ protected Cell GetCellForPath(NSIndexPath indexPath)
+ {
+ var templatedList = List.TemplatedItems;
+ if (List.IsGroupingEnabled)
+ templatedList = (TemplatedItemsList<ItemsView<Cell>, Cell>)((IList)templatedList)[indexPath.Section];
+
+ var cell = templatedList[indexPath.Row];
+ return cell;
+ }
+
+ TemplatedItemsList<ItemsView<Cell>, Cell> GetSectionList(int section)
+ {
+ return (TemplatedItemsList<ItemsView<Cell>, Cell>)((IList)List.TemplatedItems)[section];
+ }
+
+ void OnSectionPropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ var currentSelected = _uiTableView.IndexPathForSelectedRow;
+
+ var til = (TemplatedItemsList<ItemsView<Cell>, Cell>)sender;
+ var groupIndex = ((IList)List.TemplatedItems).IndexOf(til);
+ if (groupIndex == -1)
+ {
+ til.PropertyChanged -= OnSectionPropertyChanged;
+ return;
+ }
+
+ _uiTableView.ReloadSections(NSIndexSet.FromIndex(groupIndex), UITableViewRowAnimation.Automatic);
+ _uiTableView.SelectRow(currentSelected, false, UITableViewScrollPosition.None);
+ }
+
+ void OnShortNamesCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
+ {
+ _uiTableView.ReloadSectionIndexTitles();
+ }
+
+ static void SetCellBackgroundColor(UITableViewCell cell, UIColor color)
+ {
+ var contextCell = cell as ContextActionsCell;
+ cell.BackgroundColor = color;
+ if (contextCell != null)
+ contextCell.ContentCell.BackgroundColor = color;
+ }
+
+ int TemplateIdForPath(NSIndexPath indexPath)
+ {
+ var itemTemplate = List.ItemTemplate;
+ var selector = itemTemplate as DataTemplateSelector;
+ if (selector == null)
+ return DefaultItemTemplateId;
+
+ var templatedList = List.TemplatedItems;
+ if (List.IsGroupingEnabled)
+ templatedList = (TemplatedItemsList<ItemsView<Cell>, Cell>)((IList)templatedList)[indexPath.Section];
+
+ var item = templatedList.ListProxy[indexPath.Row];
+
+ itemTemplate = selector.SelectTemplate(item, List);
+ int key;
+ if (!_templateToId.TryGetValue(itemTemplate, out key))
+ {
+ s_dataTemplateIncrementer++;
+ key = s_dataTemplateIncrementer;
+ _templateToId[itemTemplate] = key;
+ }
+ return key;
+ }
+
+ void UpdateShortNameListener()
+ {
+ if (List.IsGroupingEnabled)
+ {
+ if (List.TemplatedItems.ShortNames != null)
+ ((INotifyCollectionChanged)List.TemplatedItems.ShortNames).CollectionChanged += OnShortNamesCollectionChanged;
+ }
+ else
+ {
+ if (List.TemplatedItems.ShortNames != null)
+ ((INotifyCollectionChanged)List.TemplatedItems.ShortNames).CollectionChanged -= OnShortNamesCollectionChanged;
+ }
+ }
+ }
+ }
+
+ internal class HeaderWrapperView : UIView
+ {
+ public override void LayoutSubviews()
+ {
+ base.LayoutSubviews();
+ foreach (var item in Subviews)
+ item.Frame = Bounds;
+ }
+ }
+
+ internal class FormsUITableViewController : UITableViewController
+ {
+ readonly ListView _list;
+ UIRefreshControl _refresh;
+
+ bool _refreshAdded;
+
+ public FormsUITableViewController(ListView element)
+ {
+ if (Forms.IsiOS9OrNewer)
+ TableView.CellLayoutMarginsFollowReadableWidth = false;
+ _refresh = new UIRefreshControl();
+ _refresh.ValueChanged += OnRefreshingChanged;
+ _list = element;
+ }
+
+ public void UpdateIsRefreshing(bool refreshing)
+ {
+ if (refreshing)
+ {
+ if (!_refreshAdded)
+ {
+ RefreshControl = _refresh;
+ _refreshAdded = true;
+ }
+
+ if (!_refresh.Refreshing)
+ {
+ _refresh.BeginRefreshing();
+
+ //hack: when we don't have cells in our UITableView the spinner fails to appear
+ CheckContentSize();
+
+ TableView.ScrollRectToVisible(new RectangleF(0, 0, _refresh.Bounds.Width, _refresh.Bounds.Height), true);
+ }
+ }
+ else
+ {
+ _refresh.EndRefreshing();
+
+ if (!_list.IsPullToRefreshEnabled)
+ RemoveRefresh();
+ }
+ }
+
+ public void UpdatePullToRefreshEnabled(bool pullToRefreshEnabled)
+ {
+ if (pullToRefreshEnabled)
+ {
+ if (!_refreshAdded)
+ {
+ _refreshAdded = true;
+ RefreshControl = _refresh;
+ }
+ }
+ else if (_refreshAdded)
+ {
+ if (_refresh.Refreshing)
+ _refresh.EndRefreshing();
+
+ RefreshControl = null;
+ _refreshAdded = false;
+ }
+ }
+
+ public void UpdateShowHideRefresh(bool shouldHide)
+ {
+ if (_list.IsPullToRefreshEnabled)
+ return;
+
+ if (shouldHide)
+ RemoveRefresh();
+ else
+ UpdateIsRefreshing(_list.IsRefreshing);
+ }
+
+ public override void ViewWillAppear(bool animated)
+ {
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ base.Dispose(disposing);
+
+ if (disposing && _refresh != null)
+ {
+ _refresh.ValueChanged -= OnRefreshingChanged;
+ _refresh.EndRefreshing();
+ _refresh.Dispose();
+ _refresh = null;
+ }
+ }
+
+ void CheckContentSize()
+ {
+ //adding a default height of at least 1 pixel tricks iOS to show the spinner
+ var contentSize = TableView.ContentSize;
+ if (contentSize.Height == 0)
+ TableView.ContentSize = new SizeF(contentSize.Width, 1);
+ }
+
+ void OnRefreshingChanged(object sender, EventArgs eventArgs)
+ {
+ if (_refresh.Refreshing)
+ (_list as IListViewController).SendRefreshing();
+ }
+
+ void RemoveRefresh()
+ {
+ if (!_refreshAdded)
+ return;
+
+ if (_refresh.Refreshing)
+ _refresh.EndRefreshing();
+
+ RefreshControl = null;
+ _refreshAdded = false;
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.iOS/Renderers/NavigationMenuRenderer.cs b/Xamarin.Forms.Platform.iOS/Renderers/NavigationMenuRenderer.cs
new file mode 100644
index 00000000..7136a61d
--- /dev/null
+++ b/Xamarin.Forms.Platform.iOS/Renderers/NavigationMenuRenderer.cs
@@ -0,0 +1,152 @@
+using System;
+using System.Drawing;
+using System.Linq;
+#if __UNIFIED__
+using UIKit;
+using Foundation;
+#else
+using MonoTouch.UIKit;
+using MonoTouch.Foundation;
+#endif
+#if __UNIFIED__
+using RectangleF = CoreGraphics.CGRect;
+using SizeF = CoreGraphics.CGSize;
+using PointF = CoreGraphics.CGPoint;
+
+#else
+using nfloat=System.Single;
+using nint=System.Int32;
+using nuint=System.UInt32;
+#endif
+
+namespace Xamarin.Forms.Platform.iOS
+{
+ public class NavigationMenuRenderer : ViewRenderer
+ {
+ UICollectionView _collectionView;
+
+ protected override void OnElementChanged(ElementChangedEventArgs<View> e)
+ {
+ base.OnElementChanged(e);
+ var pad = UIDevice.CurrentDevice.UserInterfaceIdiom == UIUserInterfaceIdiom.Pad;
+ var size = pad ? 220 : 142;
+ var margin = pad ? 27 : 12;
+ var bottomMargin = (int)(margin * 0.8);
+
+ _collectionView = new UICollectionView(new RectangleF(0, 0, 100, 100),
+ new UICollectionViewFlowLayout
+ {
+ ItemSize = new SizeF(size, size + 30),
+ ScrollDirection = UICollectionViewScrollDirection.Vertical,
+ SectionInset = new UIEdgeInsets(margin, margin, bottomMargin, margin),
+ MinimumInteritemSpacing = margin,
+ MinimumLineSpacing = margin
+ }) { DataSource = new DataSource((NavigationMenu)Element), BackgroundColor = UIColor.White };
+
+ using(var navigationCellId = new NSString("NavigationCell"))
+ _collectionView.RegisterClassForCell(typeof(NavigationCell), navigationCellId);
+
+ SetNativeControl(_collectionView);
+ }
+
+ sealed class NavigationCell : UICollectionViewCell
+ {
+ readonly UIButton _image = new UIButton(RectangleF.Empty);
+
+ readonly UILabel _nameLabel = new UILabel(RectangleF.Empty) { BackgroundColor = UIColor.Clear, TextAlignment = UITextAlignment.Center, Font = UIFont.SystemFontOfSize(14) };
+
+ string _icon;
+
+ [Export("initWithFrame:")]
+ public NavigationCell(RectangleF frame) : base(frame)
+ {
+ SetupLayer();
+ _image.TouchUpInside += (object sender, EventArgs e) =>
+ {
+ if (Selected != null)
+ Selected();
+ };
+ _image.ContentMode = UIViewContentMode.ScaleAspectFit;
+ _image.Center = ContentView.Center;
+
+ ContentView.AddSubview(_image);
+ ContentView.AddSubview(_nameLabel);
+ }
+
+ public string Icon
+ {
+ get { return _icon; }
+ set
+ {
+ _icon = value;
+ _image.SetImage(new UIImage(_icon), UIControlState.Normal);
+ }
+ }
+
+ public string Name
+ {
+ get { return _nameLabel.Text; }
+ set { _nameLabel.Text = value; }
+ }
+
+ public Action Selected { get; set; }
+
+ public override void LayoutSubviews()
+ {
+ base.LayoutSubviews();
+ _image.Frame = new RectangleF(0, 0, ContentView.Frame.Width, ContentView.Frame.Height - 30);
+ var sizeThatFits = _nameLabel.SizeThatFits(ContentView.Frame.Size);
+ _nameLabel.Frame = new RectangleF(0, ContentView.Frame.Height - 15 - sizeThatFits.Height / 2, ContentView.Frame.Width, sizeThatFits.Height);
+ }
+
+ void SetupLayer()
+ {
+ var layer = _image.Layer;
+
+ layer.ShadowRadius = 6;
+ layer.ShadowColor = UIColor.Black.CGColor;
+ layer.ShadowOpacity = 0.2f;
+ layer.ShadowOffset = new SizeF();
+
+ layer.RasterizationScale = UIScreen.MainScreen.Scale;
+ layer.ShouldRasterize = true;
+ }
+ }
+
+ class DataSource : UICollectionViewDataSource
+ {
+ readonly NavigationMenu _menu;
+
+ public DataSource(NavigationMenu menu)
+ {
+ _menu = menu;
+ }
+
+ public override UICollectionViewCell GetCell(UICollectionView collectionView, NSIndexPath indexPath)
+ {
+ var cell = (NavigationCell)collectionView.DequeueReusableCell(new NSString("NavigationCell"), indexPath);
+ var target = _menu.Targets.Skip(indexPath.Row).FirstOrDefault();
+
+ if (target != null)
+ {
+ cell.Name = target.Title;
+ cell.Icon = target.Icon;
+ cell.Selected = () => _menu.SendTargetSelected(target);
+ }
+ else
+ {
+ cell.Selected = null;
+ cell.Icon = "";
+ cell.Name = "";
+ }
+
+ return cell;
+ }
+
+ public override nint GetItemsCount(UICollectionView collectionView, nint section)
+ {
+ return _menu.Targets.Count();
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.iOS/Renderers/NavigationRenderer.cs b/Xamarin.Forms.Platform.iOS/Renderers/NavigationRenderer.cs
new file mode 100644
index 00000000..63330564
--- /dev/null
+++ b/Xamarin.Forms.Platform.iOS/Renderers/NavigationRenderer.cs
@@ -0,0 +1,918 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Diagnostics;
+using System.Drawing;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+#if __UNIFIED__
+using UIKit;
+using CoreGraphics;
+#else
+using MonoTouch.UIKit;
+using MonoTouch.CoreGraphics;
+#endif
+#if __UNIFIED__
+using RectangleF = CoreGraphics.CGRect;
+using SizeF = CoreGraphics.CGSize;
+using PointF = CoreGraphics.CGPoint;
+
+#else
+using nfloat=System.Single;
+using nint=System.Int32;
+using nuint=System.UInt32;
+#endif
+
+namespace Xamarin.Forms.Platform.iOS
+{
+ public class NavigationRenderer : UINavigationController, IVisualElementRenderer
+ {
+ internal const string UpdateToolbarButtons = "Xamarin.UpdateToolbarButtons";
+ bool _appeared;
+ bool _ignorePopCall;
+ bool _loaded;
+ MasterDetailPage _parentMasterDetailPage;
+ Size _queuedSize;
+ UIViewController[] _removeControllers;
+ UIToolbar _secondaryToolbar;
+
+ VisualElementTracker _tracker;
+
+ public NavigationRenderer()
+ {
+ MessagingCenter.Subscribe<IVisualElementRenderer>(this, UpdateToolbarButtons, sender =>
+ {
+ if (!ViewControllers.Any())
+ return;
+ var parentingViewController = (ParentingViewController)ViewControllers.Last();
+ UpdateLeftBarButtonItem(parentingViewController);
+ });
+ }
+
+ Page Current { get; set; }
+
+ public VisualElement Element { get; private set; }
+
+ public event EventHandler<VisualElementChangedEventArgs> ElementChanged;
+
+ public SizeRequest GetDesiredSize(double widthConstraint, double heightConstraint)
+ {
+ return NativeView.GetSizeRequest(widthConstraint, heightConstraint);
+ }
+
+ public UIView NativeView
+ {
+ get { return View; }
+ }
+
+ public void SetElement(VisualElement element)
+ {
+ var oldElement = Element;
+ Element = (NavigationPage)element;
+ OnElementChanged(new VisualElementChangedEventArgs(oldElement, element));
+
+ if (element != null)
+ element.SendViewInitialized(NativeView);
+ }
+
+ public void SetElementSize(Size size)
+ {
+ if (_loaded)
+ Element.Layout(new Rectangle(Element.X, Element.Y, size.Width, size.Height));
+ else
+ _queuedSize = size;
+ }
+
+ public UIViewController ViewController
+ {
+ get { return this; }
+ }
+
+ public override void DidRotate(UIInterfaceOrientation fromInterfaceOrientation)
+ {
+ base.DidRotate(fromInterfaceOrientation);
+
+ View.SetNeedsLayout();
+
+ var parentingViewController = (ParentingViewController)ViewControllers.Last();
+ UpdateLeftBarButtonItem(parentingViewController);
+ }
+
+ public Task<bool> PopToRootAsync(Page page, bool animated = true)
+ {
+ return OnPopToRoot(page, animated);
+ }
+
+ public override UIViewController[] PopToRootViewController(bool animated)
+ {
+ if (!_ignorePopCall && ViewControllers.Length > 1)
+ RemoveViewControllers(animated);
+
+ return base.PopToRootViewController(animated);
+ }
+
+ public Task<bool> PopViewAsync(Page page, bool animated = true)
+ {
+ return OnPopViewAsync(page, animated);
+ }
+
+#if __UNIFIED__
+ public override UIViewController PopViewController(bool animated)
+#else
+ public override UIViewController PopViewControllerAnimated (bool animated)
+ #endif
+ {
+ RemoveViewControllers(animated);
+#if __UNIFIED__
+ return base.PopViewController(animated);
+#else
+ return base.PopViewControllerAnimated (animated);
+ #endif
+ }
+
+ public Task<bool> PushPageAsync(Page page, bool animated = true)
+ {
+ return OnPushAsync(page, animated);
+ }
+
+ public override void ViewDidAppear(bool animated)
+ {
+ if (!_appeared)
+ {
+ _appeared = true;
+ ((NavigationPage)Element)?.SendAppearing();
+ }
+
+ base.ViewDidAppear(animated);
+
+ View.SetNeedsLayout();
+ }
+
+ public override void ViewDidDisappear(bool animated)
+ {
+ base.ViewDidDisappear(animated);
+
+ if (!_appeared || Element == null)
+ return;
+
+ _appeared = false;
+ ((NavigationPage)Element).SendDisappearing();
+ }
+
+ public override void ViewDidLayoutSubviews()
+ {
+ base.ViewDidLayoutSubviews();
+ UpdateToolBarVisible();
+
+ var navBarFrame = NavigationBar.Frame;
+
+ var toolbar = _secondaryToolbar;
+ // Use 0 if the NavBar is hidden or will be hidden
+ var toolbarY = NavigationBarHidden || !NavigationPage.GetHasNavigationBar(Current) ? 0 : navBarFrame.Bottom;
+ toolbar.Frame = new RectangleF(0, toolbarY, View.Frame.Width, toolbar.Frame.Height);
+
+ double trueBottom = toolbar.Hidden ? toolbarY : toolbar.Frame.Bottom;
+ var modelSize = _queuedSize.IsZero ? Element.Bounds.Size : _queuedSize;
+ ((NavigationPage)Element).ContainerArea = new Rectangle(0, toolbar.Hidden ? 0 : toolbar.Frame.Height, modelSize.Width, modelSize.Height - trueBottom);
+
+ if (!_queuedSize.IsZero)
+ {
+ Element.Layout(new Rectangle(Element.X, Element.Y, _queuedSize.Width, _queuedSize.Height));
+ _queuedSize = Size.Zero;
+ }
+
+ _loaded = true;
+
+ foreach (var view in View.Subviews)
+ {
+ if (view == NavigationBar || view == _secondaryToolbar)
+ continue;
+ view.Frame = View.Bounds;
+ }
+ }
+
+ public override void ViewDidLoad()
+ {
+ base.ViewDidLoad();
+
+ if (Forms.IsiOS7OrNewer)
+ NavigationBar.Translucent = false;
+ else
+ WantsFullScreenLayout = false;
+
+ _secondaryToolbar = new SecondaryToolbar { Frame = new RectangleF(0, 0, 320, 44) };
+ View.Add(_secondaryToolbar);
+ _secondaryToolbar.Hidden = true;
+
+ FindParentMasterDetail();
+
+ 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.");
+ }
+
+ navPage.PushRequested += OnPushRequested;
+ navPage.PopRequested += OnPopRequested;
+ navPage.PopToRootRequested += OnPopToRootRequested;
+ navPage.RemovePageRequested += OnRemovedPageRequested;
+ navPage.InsertPageBeforeRequested += OnInsertPageBeforeRequested;
+
+ UpdateTint();
+ UpdateBarBackgroundColor();
+ UpdateBarTextColor();
+
+ // If there is already stuff on the stack we need to push it
+ navPage.StackCopy.Reverse().ForEach(async p => await PushPageAsync(p, false));
+
+ _tracker = new VisualElementTracker(this);
+
+ Element.PropertyChanged += HandlePropertyChanged;
+
+ UpdateToolBarVisible();
+ UpdateBackgroundColor();
+ Current = navPage.CurrentPage;
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ MessagingCenter.Unsubscribe<IVisualElementRenderer>(this, UpdateToolbarButtons);
+
+ foreach (var childViewController in ViewControllers)
+ childViewController.Dispose();
+
+ if (_tracker != null)
+ _tracker.Dispose();
+
+ _secondaryToolbar.RemoveFromSuperview();
+ _secondaryToolbar.Dispose();
+ _secondaryToolbar = null;
+
+ _parentMasterDetailPage = null;
+ Current = null; // unhooks events
+
+ var navPage = (NavigationPage)Element;
+ navPage.PropertyChanged -= HandlePropertyChanged;
+ navPage.PushRequested -= OnPushRequested;
+ navPage.PopRequested -= OnPopRequested;
+ navPage.PopToRootRequested -= OnPopToRootRequested;
+ navPage.RemovePageRequested -= OnRemovedPageRequested;
+ navPage.InsertPageBeforeRequested -= OnInsertPageBeforeRequested;
+ }
+
+ base.Dispose(disposing);
+ if (_appeared)
+ {
+ ((Page)Element).SendDisappearing();
+
+ _appeared = false;
+ }
+ }
+
+ protected virtual void OnElementChanged(VisualElementChangedEventArgs e)
+ {
+ var changed = ElementChanged;
+ if (changed != null)
+ changed(this, e);
+ }
+
+ protected virtual async Task<bool> OnPopToRoot(Page page, bool animated)
+ {
+ _ignorePopCall = true;
+ var renderer = Platform.GetRenderer(page);
+ if (renderer == null || renderer.ViewController == null)
+ return false;
+
+ var task = GetAppearedOrDisappearedTask(page);
+
+ PopToRootViewController(animated);
+
+ _ignorePopCall = false;
+ var success = !await task;
+
+ UpdateToolBarVisible();
+ return success;
+ }
+
+ protected virtual async Task<bool> OnPopViewAsync(Page page, bool animated)
+ {
+ if (_ignorePopCall)
+ return true;
+
+ var renderer = Platform.GetRenderer(page);
+ if (renderer == null || renderer.ViewController == null)
+ return false;
+
+ var actuallyRemoved = false;
+
+ if (page != ((ParentingViewController)TopViewController).Child)
+ throw new NotSupportedException("Popped page does not appear on top of current navigation stack, please file a bug.");
+
+ var task = GetAppearedOrDisappearedTask(page);
+
+ UIViewController poppedViewController;
+#if __UNIFIED__
+ poppedViewController = base.PopViewController(animated);
+#else
+ poppedViewController = base.PopViewControllerAnimated (animated);
+ #endif
+
+ if (poppedViewController == null)
+ {
+ // this happens only when the user does something REALLY dumb like pop right after putting the page as visible.
+ poppedViewController = TopViewController;
+ var newControllers = ViewControllers.Remove(poppedViewController);
+ ViewControllers = newControllers;
+ actuallyRemoved = true;
+ }
+ else
+ actuallyRemoved = !await task;
+
+ poppedViewController.Dispose();
+
+ UpdateToolBarVisible();
+ return actuallyRemoved;
+ }
+
+ protected virtual async Task<bool> OnPushAsync(Page page, bool animated)
+ {
+ var pack = CreateViewControllerForPage(page);
+ var task = GetAppearedOrDisappearedTask(page);
+
+ PushViewController(pack, animated);
+
+ var shown = await task;
+ UpdateToolBarVisible();
+ return shown;
+ }
+
+ ParentingViewController CreateViewControllerForPage(Page page)
+ {
+ if (Platform.GetRenderer(page) == null)
+ Platform.SetRenderer(page, Platform.CreateRenderer(page));
+
+ // must pack into container so padding can work
+ // otherwise the view controller is forced to 0,0
+ var pack = new ParentingViewController(this) { Child = page };
+ if (!string.IsNullOrWhiteSpace(page.Title))
+ pack.NavigationItem.Title = page.Title;
+
+ // First page and we have a master detail to contend with
+ UpdateLeftBarButtonItem(pack);
+
+ //var pack = Platform.GetRenderer (view).ViewController;
+
+ var titleIcon = NavigationPage.GetTitleIcon(page);
+ if (!string.IsNullOrEmpty(titleIcon))
+ {
+ try
+ {
+ //UIImage ctor throws on file not found if MonoTouch.ObjCRuntime.Class.ThrowOnInitFailure is true;
+ pack.NavigationItem.TitleView = new UIImageView(new UIImage(titleIcon));
+ }
+ catch
+ {
+ }
+ }
+
+ var titleText = NavigationPage.GetBackButtonTitle(page);
+ if (titleText != null)
+ {
+ pack.NavigationItem.BackBarButtonItem = new UIBarButtonItem(titleText, UIBarButtonItemStyle.Plain, async (o, e) => await PopViewAsync(page));
+ }
+
+ var pageRenderer = Platform.GetRenderer(page);
+ pack.View.AddSubview(pageRenderer.ViewController.View);
+ pack.AddChildViewController(pageRenderer.ViewController);
+ pageRenderer.ViewController.DidMoveToParentViewController(pack);
+
+ return pack;
+ }
+
+ void FindParentMasterDetail()
+ {
+ var parentPages = ((Page)Element).GetParentPages();
+ var masterDetail = parentPages.OfType<MasterDetailPage>().FirstOrDefault();
+
+ if (masterDetail != null && parentPages.Append((Page)Element).Contains(masterDetail.Detail))
+ _parentMasterDetailPage = masterDetail;
+ }
+
+ Task<bool> GetAppearedOrDisappearedTask(Page page)
+ {
+ var tcs = new TaskCompletionSource<bool>();
+
+ var parentViewController = Platform.GetRenderer(page).ViewController.ParentViewController as ParentingViewController;
+ if (parentViewController == null)
+ throw new NotSupportedException("ParentingViewController parent could not be found. Please file a bug.");
+
+ EventHandler appearing = null, disappearing = null;
+ appearing = (s, e) =>
+ {
+ parentViewController.Appearing -= appearing;
+ parentViewController.Disappearing -= disappearing;
+
+ Device.BeginInvokeOnMainThread(() => { tcs.SetResult(true); });
+ };
+
+ disappearing = (s, e) =>
+ {
+ parentViewController.Appearing -= appearing;
+ parentViewController.Disappearing -= disappearing;
+
+ Device.BeginInvokeOnMainThread(() => { tcs.SetResult(false); });
+ };
+
+ parentViewController.Appearing += appearing;
+ parentViewController.Disappearing += disappearing;
+
+ return tcs.Task;
+ }
+
+ void HandlePropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ if (e.PropertyName == NavigationPage.TintProperty.PropertyName)
+ UpdateTint();
+ if (e.PropertyName == NavigationPage.BarBackgroundColorProperty.PropertyName)
+ UpdateBarBackgroundColor();
+ else if (e.PropertyName == NavigationPage.BarTextColorProperty.PropertyName)
+ UpdateBarTextColor();
+ else if (e.PropertyName == VisualElement.BackgroundColorProperty.PropertyName)
+ UpdateBackgroundColor();
+ else if (e.PropertyName == NavigationPage.CurrentPageProperty.PropertyName)
+ Current = ((NavigationPage)Element).CurrentPage;
+ }
+
+ void InsertPageBefore(Page page, Page before)
+ {
+ if (before == null)
+ throw new ArgumentNullException("before");
+ if (page == null)
+ throw new ArgumentNullException("page");
+
+ var pageContainer = CreateViewControllerForPage(page);
+ var target = Platform.GetRenderer(before).ViewController.ParentViewController;
+ ViewControllers = ViewControllers.Insert(ViewControllers.IndexOf(target), pageContainer);
+ }
+
+ 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);
+ }
+
+ void RemovePage(Page page)
+ {
+ if (page == null)
+ throw new ArgumentNullException("page");
+ if (page == Current)
+ throw new NotSupportedException(); // should never happen as NavPage protecs against this
+
+ var target = Platform.GetRenderer(page).ViewController.ParentViewController;
+
+ // So the ViewControllers property is not very property like on iOS. Assigning to it doesn't cause it to be
+ // immediately reflected into the property. The change will not be reflected until there has been sufficient time
+ // to process it (it ends up on the event queue). So to resolve this issue we keep our own stack until we
+ // know iOS has processed it, and make sure any updates use that.
+
+ // In the future we may want to make RemovePageAsync and deprecate RemovePage to handle cases where Push/Pop is called
+ // during a remove cycle.
+
+ if (_removeControllers == null)
+ {
+ _removeControllers = ViewControllers.Remove(target);
+ ViewControllers = _removeControllers;
+ Device.BeginInvokeOnMainThread(() => { _removeControllers = null; });
+ }
+ else
+ {
+ _removeControllers = _removeControllers.Remove(target);
+ ViewControllers = _removeControllers;
+ }
+ }
+
+ void RemoveViewControllers(bool animated)
+ {
+ var controller = TopViewController as ParentingViewController;
+ if (controller == null || controller.Child == null)
+ return;
+
+ // Gesture in progress, lets not be proactive and just wait for it to finish
+ var count = ViewControllers.Length;
+ var task = GetAppearedOrDisappearedTask(controller.Child);
+ task.ContinueWith(async t =>
+ {
+ // task returns true if the user lets go of the page and is not popped
+ // however at this point the renderer is already off the visual stack so we just need to update the NavigationPage
+ // Also worth noting this task returns on the main thread
+ if (t.Result)
+ return;
+ _ignorePopCall = true;
+ // because iOS will just chain multiple animations together...
+ var removed = count - ViewControllers.Length;
+ for (var i = 0; i < removed; i++)
+ {
+ // lets just pop these suckers off, do not await, the true is there to make this fast
+ await ((NavigationPage)Element).PopAsyncInner(animated, true);
+ }
+ // because we skip the normal pop process we need to dispose ourselves
+ controller.Dispose();
+ _ignorePopCall = false;
+ }, TaskScheduler.FromCurrentSynchronizationContext());
+ }
+
+ void UpdateBackgroundColor()
+ {
+ var color = Element.BackgroundColor == Color.Default ? Color.White : Element.BackgroundColor;
+ View.BackgroundColor = color.ToUIColor();
+ }
+
+ void UpdateBarBackgroundColor()
+ {
+ var barBackgroundColor = ((NavigationPage)Element).BarBackgroundColor;
+ // Set navigation bar background color
+ if (Forms.IsiOS7OrNewer)
+ {
+ NavigationBar.BarTintColor = barBackgroundColor == Color.Default ? UINavigationBar.Appearance.BarTintColor : barBackgroundColor.ToUIColor();
+ }
+ else
+ {
+ NavigationBar.TintColor = barBackgroundColor == Color.Default ? UINavigationBar.Appearance.TintColor : barBackgroundColor.ToUIColor();
+ }
+ }
+
+ void UpdateBarTextColor()
+ {
+ var barTextColor = ((NavigationPage)Element).BarTextColor;
+
+ var globalAttributes = UINavigationBar.Appearance.GetTitleTextAttributes();
+
+ if (barTextColor == Color.Default)
+ {
+ if (NavigationBar.TitleTextAttributes != null)
+ {
+ var attributes = new UIStringAttributes();
+ attributes.ForegroundColor = globalAttributes.TextColor;
+ attributes.Font = globalAttributes.Font;
+ NavigationBar.TitleTextAttributes = attributes;
+ }
+ }
+ else
+ {
+ var titleAttributes = new UIStringAttributes();
+ titleAttributes.Font = globalAttributes.Font;
+ titleAttributes.ForegroundColor = barTextColor == Color.Default ? titleAttributes.ForegroundColor ?? UINavigationBar.Appearance.TintColor : barTextColor.ToUIColor();
+ NavigationBar.TitleTextAttributes = titleAttributes;
+ }
+
+ // set Tint color (i. e. Back Button arrow and Text)
+ if (Forms.IsiOS7OrNewer)
+ {
+ NavigationBar.TintColor = barTextColor == Color.Default ? UINavigationBar.Appearance.TintColor : barTextColor.ToUIColor();
+ }
+
+ if (barTextColor.Luminosity > 0.5)
+ {
+ // Use light text color for status bar
+ UIApplication.SharedApplication.StatusBarStyle = UIStatusBarStyle.LightContent;
+ }
+ else
+ {
+ // Use dark text color for status bar
+ UIApplication.SharedApplication.StatusBarStyle = UIStatusBarStyle.Default;
+ }
+ }
+
+ void UpdateLeftBarButtonItem(ParentingViewController containerController)
+ {
+ var currentChild = containerController.Child;
+ var firstPage = ((NavigationPage)Element).StackCopy.LastOrDefault();
+ if ((currentChild != firstPage && NavigationPage.GetHasBackButton(currentChild)) || _parentMasterDetailPage == null)
+ return;
+
+ if (!_parentMasterDetailPage.ShouldShowToolbarButton())
+ {
+ containerController.NavigationItem.LeftBarButtonItem = null;
+ return;
+ }
+
+ var shouldUseIcon = _parentMasterDetailPage.Master.Icon != null;
+ if (shouldUseIcon)
+ {
+ try
+ {
+ containerController.NavigationItem.LeftBarButtonItem = new UIBarButtonItem(new UIImage(_parentMasterDetailPage.Master.Icon), UIBarButtonItemStyle.Plain,
+ (o, e) => _parentMasterDetailPage.IsPresented = !_parentMasterDetailPage.IsPresented);
+ }
+ catch (Exception)
+ {
+ // Throws Exception otherwise would catch more specific exception type
+ shouldUseIcon = false;
+ }
+ }
+
+ if (!shouldUseIcon)
+ {
+ containerController.NavigationItem.LeftBarButtonItem = new UIBarButtonItem(_parentMasterDetailPage.Master.Title, UIBarButtonItemStyle.Plain,
+ (o, e) => _parentMasterDetailPage.IsPresented = !_parentMasterDetailPage.IsPresented);
+ }
+ }
+
+ void UpdateTint()
+ {
+ var tintColor = ((NavigationPage)Element).Tint;
+
+ if (Forms.IsiOS7OrNewer)
+ {
+ NavigationBar.BarTintColor = tintColor == Color.Default ? UINavigationBar.Appearance.BarTintColor : tintColor.ToUIColor();
+ if (tintColor == Color.Default)
+ NavigationBar.TintColor = UINavigationBar.Appearance.TintColor;
+ else
+ NavigationBar.TintColor = tintColor.Luminosity > 0.5 ? UIColor.Black : UIColor.White;
+ }
+ else
+ NavigationBar.TintColor = tintColor == Color.Default ? null : tintColor.ToUIColor();
+ }
+
+ void UpdateToolBarVisible()
+ {
+ if (_secondaryToolbar == null)
+ return;
+ if (TopViewController != null && TopViewController.ToolbarItems != null && TopViewController.ToolbarItems.Any())
+ {
+ _secondaryToolbar.Hidden = false;
+ _secondaryToolbar.Items = TopViewController.ToolbarItems;
+ }
+ else
+ {
+ _secondaryToolbar.Hidden = true;
+ //secondaryToolbar.Items = null;
+ }
+ }
+
+ class SecondaryToolbar : UIToolbar
+ {
+ readonly List<UIView> _lines = new List<UIView>();
+
+ public SecondaryToolbar()
+ {
+ TintColor = UIColor.White;
+ }
+
+ public override UIBarButtonItem[] Items
+ {
+ get { return base.Items; }
+ set
+ {
+ base.Items = value;
+ SetupLines();
+ }
+ }
+
+ public override void LayoutSubviews()
+ {
+ base.LayoutSubviews();
+ if (Items == null || Items.Length == 0)
+ return;
+ nfloat padding = 11f;
+ var itemWidth = (Bounds.Width - padding) / Items.Length - padding;
+ var x = padding;
+ var itemH = Bounds.Height - 10;
+ foreach (var item in Items)
+ {
+ var frame = new RectangleF(x, 5, itemWidth, itemH);
+ item.CustomView.Frame = frame;
+ x += itemWidth + padding;
+ }
+ x = itemWidth + padding * 1.5f;
+ var y = Bounds.GetMidY();
+ foreach (var l in _lines)
+ {
+ l.Center = new PointF(x, y);
+ x += itemWidth + padding;
+ }
+ }
+
+ void SetupLines()
+ {
+ _lines.ForEach(l => l.RemoveFromSuperview());
+ _lines.Clear();
+ if (Items == null)
+ return;
+ for (var i = 1; i < Items.Length; i++)
+ {
+ var l = new UIView(new RectangleF(0, 0, 1, 24)) { BackgroundColor = new UIColor(0, 0, 0, 0.2f) };
+ AddSubview(l);
+ _lines.Add(l);
+ }
+ }
+ }
+
+ class ParentingViewController : UIViewController
+ {
+ readonly WeakReference<NavigationRenderer> _navigation;
+
+ Page _child;
+ ToolbarTracker _tracker = new ToolbarTracker();
+
+ public ParentingViewController(NavigationRenderer navigation)
+ {
+ if (Forms.IsiOS7OrNewer)
+ AutomaticallyAdjustsScrollViewInsets = false;
+
+ _navigation = new WeakReference<NavigationRenderer>(navigation);
+ }
+
+ public Page Child
+ {
+ get { return _child; }
+ set
+ {
+ if (_child == value)
+ return;
+
+ if (_child != null)
+ _child.PropertyChanged -= HandleChildPropertyChanged;
+
+ _child = value;
+
+ if (_child != null)
+ _child.PropertyChanged += HandleChildPropertyChanged;
+
+ UpdateHasBackButton();
+ }
+ }
+
+ public event EventHandler Appearing;
+
+ public override void DidRotate(UIInterfaceOrientation fromInterfaceOrientation)
+ {
+ base.DidRotate(fromInterfaceOrientation);
+
+ View.SetNeedsLayout();
+ }
+
+ public event EventHandler Disappearing;
+
+ public override void ViewDidAppear(bool animated)
+ {
+ base.ViewDidAppear(animated);
+
+ var handler = Appearing;
+ if (handler != null)
+ handler(this, EventArgs.Empty);
+ }
+
+ public override void ViewDidDisappear(bool animated)
+ {
+ base.ViewDidDisappear(animated);
+
+ var handler = Disappearing;
+ if (handler != null)
+ handler(this, EventArgs.Empty);
+ }
+
+ public override void ViewDidLayoutSubviews()
+ {
+ IVisualElementRenderer childRenderer;
+ if (Child != null && (childRenderer = Platform.GetRenderer(Child)) != null)
+ childRenderer.NativeView.Frame = Child.Bounds.ToRectangleF();
+ base.ViewDidLayoutSubviews();
+ }
+
+ public override void ViewDidLoad()
+ {
+ base.ViewDidLoad();
+
+ _tracker.Target = Child;
+ _tracker.AdditionalTargets = Child.GetParentPages();
+ _tracker.CollectionChanged += TrackerOnCollectionChanged;
+
+ UpdateToolbarItems();
+ }
+
+ public override void ViewWillAppear(bool animated)
+ {
+ UpdateNavigationBarVisibility(animated);
+ base.ViewWillAppear(animated);
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ Child.SendDisappearing();
+ Child = null;
+ _tracker.Target = null;
+ _tracker.CollectionChanged -= TrackerOnCollectionChanged;
+ _tracker = null;
+
+ if (NavigationItem.RightBarButtonItems != null)
+ {
+ for (var i = 0; i < NavigationItem.RightBarButtonItems.Length; i++)
+ NavigationItem.RightBarButtonItems[i].Dispose();
+ }
+
+ if (ToolbarItems != null)
+ {
+ for (var i = 0; i < ToolbarItems.Length; i++)
+ ToolbarItems[i].Dispose();
+ }
+ }
+ base.Dispose(disposing);
+ }
+
+ void HandleChildPropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ if (e.PropertyName == NavigationPage.HasNavigationBarProperty.PropertyName)
+ UpdateNavigationBarVisibility(true);
+ else if (e.PropertyName == Page.TitleProperty.PropertyName)
+ NavigationItem.Title = Child.Title;
+ else if (e.PropertyName == NavigationPage.HasBackButtonProperty.PropertyName)
+ UpdateHasBackButton();
+ }
+
+ void TrackerOnCollectionChanged(object sender, EventArgs eventArgs)
+ {
+ UpdateToolbarItems();
+ }
+
+ void UpdateHasBackButton()
+ {
+ if (Child == null)
+ return;
+
+ NavigationItem.HidesBackButton = !NavigationPage.GetHasBackButton(Child);
+ }
+
+ void UpdateNavigationBarVisibility(bool animated)
+ {
+ var current = Child;
+
+ if (current == null || NavigationController == null)
+ return;
+
+ var hasNavBar = NavigationPage.GetHasNavigationBar(current);
+
+ if (NavigationController.NavigationBarHidden == hasNavBar)
+ NavigationController.SetNavigationBarHidden(!hasNavBar, animated);
+ }
+
+ void UpdateToolbarItems()
+ {
+ if (NavigationItem.RightBarButtonItems != null)
+ {
+ for (var i = 0; i < NavigationItem.RightBarButtonItems.Length; i++)
+ NavigationItem.RightBarButtonItems[i].Dispose();
+ }
+ if (ToolbarItems != null)
+ {
+ for (var i = 0; i < ToolbarItems.Length; i++)
+ ToolbarItems[i].Dispose();
+ }
+
+ List<UIBarButtonItem> primaries = null;
+ List<UIBarButtonItem> secondaries = null;
+ foreach (var item in _tracker.ToolbarItems)
+ {
+ if (item.Order == ToolbarItemOrder.Secondary)
+ (secondaries = secondaries ?? new List<UIBarButtonItem>()).Add(item.ToUIBarButtonItem(true));
+ else
+ (primaries = primaries ?? new List<UIBarButtonItem>()).Add(item.ToUIBarButtonItem());
+ }
+
+ if (primaries != null)
+ primaries.Reverse();
+ NavigationItem.SetRightBarButtonItems(primaries == null ? new UIBarButtonItem[0] : primaries.ToArray(), false);
+ ToolbarItems = secondaries == null ? new UIBarButtonItem[0] : secondaries.ToArray();
+
+ NavigationRenderer n;
+ if (_navigation.TryGetTarget(out n))
+ n.UpdateToolBarVisible();
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.iOS/Renderers/OpenGLViewRenderer.cs b/Xamarin.Forms.Platform.iOS/Renderers/OpenGLViewRenderer.cs
new file mode 100644
index 00000000..84c67744
--- /dev/null
+++ b/Xamarin.Forms.Platform.iOS/Renderers/OpenGLViewRenderer.cs
@@ -0,0 +1,122 @@
+using System;
+using System.Drawing;
+using System.ComponentModel;
+#if __UNIFIED__
+using GLKit;
+using OpenGLES;
+using Foundation;
+using CoreAnimation;
+#else
+using MonoTouch.GLKit;
+using MonoTouch.OpenGLES;
+using MonoTouch.Foundation;
+using MonoTouch.CoreAnimation;
+#endif
+#if __UNIFIED__
+using RectangleF = CoreGraphics.CGRect;
+using SizeF = CoreGraphics.CGSize;
+using PointF = CoreGraphics.CGPoint;
+
+#else
+using nfloat=System.Single;
+using nint=System.Int32;
+using nuint=System.UInt32;
+#endif
+
+namespace Xamarin.Forms.Platform.iOS
+{
+ internal class OpenGLViewRenderer : ViewRenderer<OpenGLView, GLKView>
+ {
+ CADisplayLink _displayLink;
+
+ public void Display(object sender, EventArgs eventArgs)
+ {
+ if (Element.HasRenderLoop)
+ return;
+ SetupRenderLoop(true);
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ if (_displayLink != null)
+ {
+ _displayLink.Invalidate();
+ _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) };
+ 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 = CADisplayLink.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.AddToRunLoop(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.iOS/Renderers/PageRenderer.cs b/Xamarin.Forms.Platform.iOS/Renderers/PageRenderer.cs
new file mode 100644
index 00000000..ddc0d39e
--- /dev/null
+++ b/Xamarin.Forms.Platform.iOS/Renderers/PageRenderer.cs
@@ -0,0 +1,218 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+#if __UNIFIED__
+using UIKit;
+
+#else
+using MonoTouch.UIKit;
+#endif
+
+namespace Xamarin.Forms.Platform.iOS
+{
+ public class PageRenderer : UIViewController, IVisualElementRenderer
+ {
+ bool _appeared;
+ bool _disposed;
+ EventTracker _events;
+ VisualElementPackager _packager;
+ VisualElementTracker _tracker;
+
+ public PageRenderer()
+ {
+ if (!Forms.IsiOS7OrNewer)
+ WantsFullScreenLayout = 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 UIView NativeView
+ {
+ get { return _disposed ? null : View; }
+ }
+
+ public void SetElement(VisualElement element)
+ {
+ var oldElement = Element;
+ Element = element;
+ UpdateTitle();
+
+ OnElementChanged(new VisualElementChangedEventArgs(oldElement, element));
+
+ if (Element != null && !string.IsNullOrEmpty(Element.AutomationId))
+ SetAutomationId(Element.AutomationId);
+
+ if (element != null)
+ element.SendViewInitialized(NativeView);
+ }
+
+ public void SetElementSize(Size size)
+ {
+ Element.Layout(new Rectangle(Element.X, Element.Y, size.Width, size.Height));
+ }
+
+ public UIViewController ViewController
+ {
+ get { return _disposed ? null : this; }
+ }
+
+ public override void ViewDidAppear(bool animated)
+ {
+ base.ViewDidAppear(animated);
+
+ if (_appeared || _disposed)
+ return;
+
+ _appeared = true;
+ ((Page)Element).SendAppearing();
+ }
+
+ public override void ViewDidDisappear(bool animated)
+ {
+ base.ViewDidDisappear(animated);
+
+ if (!_appeared || _disposed)
+ return;
+
+ _appeared = false;
+ ((Page)Element).SendDisappearing();
+ }
+
+ public override void ViewDidLoad()
+ {
+ base.ViewDidLoad();
+
+ var uiTapGestureRecognizer = new UITapGestureRecognizer(a => View.EndEditing(true));
+
+ uiTapGestureRecognizer.ShouldRecognizeSimultaneously = (recognizer, gestureRecognizer) => true;
+ uiTapGestureRecognizer.ShouldReceiveTouch = OnShouldReceiveTouch;
+ uiTapGestureRecognizer.DelaysTouchesBegan = uiTapGestureRecognizer.DelaysTouchesEnded = false;
+ View.AddGestureRecognizer(uiTapGestureRecognizer);
+
+ UpdateBackground();
+
+ _packager = new VisualElementPackager(this);
+ _packager.Load();
+
+ Element.PropertyChanged += OnHandlePropertyChanged;
+ _tracker = new VisualElementTracker(this);
+
+ _events = new EventTracker(this);
+ _events.LoadEvents(View);
+
+ Element.SendViewInitialized(View);
+ }
+
+ public override void ViewWillDisappear(bool animated)
+ {
+ base.ViewWillDisappear(animated);
+
+ if (View.Window != null)
+ View.Window.EndEditing(true);
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing && !_disposed)
+ {
+ Element.PropertyChanged -= OnHandlePropertyChanged;
+ Platform.SetRenderer(Element, null);
+ if (_appeared)
+ ((Page)Element).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);
+ }
+
+ protected virtual void OnElementChanged(VisualElementChangedEventArgs e)
+ {
+ var changed = ElementChanged;
+ if (changed != null)
+ changed(this, e);
+ }
+
+ protected virtual void SetAutomationId(string id)
+ {
+ if (NativeView != null)
+ NativeView.AccessibilityIdentifier = id;
+ }
+
+ 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();
+ }
+
+ bool OnShouldReceiveTouch(UIGestureRecognizer recognizer, UITouch touch)
+ {
+ if (ViewAndSuperviewsOfView(touch.View).Any(v => v is UITableView || v is UITableViewCell || v.CanBecomeFirstResponder))
+ return false;
+ return true;
+ }
+
+ void UpdateBackground()
+ {
+ var bgImage = ((Page)Element).BackgroundImage;
+ if (!string.IsNullOrEmpty(bgImage))
+ {
+ View.BackgroundColor = UIColor.FromPatternImage(UIImage.FromBundle(bgImage));
+ return;
+ }
+ var bgColor = Element.BackgroundColor;
+ if (bgColor.IsDefault)
+ View.BackgroundColor = UIColor.White;
+ else
+ View.BackgroundColor = bgColor.ToUIColor();
+ }
+
+ void UpdateTitle()
+ {
+ if (!string.IsNullOrWhiteSpace(((Page)Element).Title))
+ Title = ((Page)Element).Title;
+ }
+
+ IEnumerable<UIView> ViewAndSuperviewsOfView(UIView view)
+ {
+ while (view != null)
+ {
+ yield return view;
+ view = view.Superview;
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.iOS/Renderers/PhoneMasterDetailRenderer.cs b/Xamarin.Forms.Platform.iOS/Renderers/PhoneMasterDetailRenderer.cs
new file mode 100644
index 00000000..070a0389
--- /dev/null
+++ b/Xamarin.Forms.Platform.iOS/Renderers/PhoneMasterDetailRenderer.cs
@@ -0,0 +1,401 @@
+using System;
+using System.Linq;
+using System.Drawing;
+using System.ComponentModel;
+#if __UNIFIED__
+using UIKit;
+#else
+using MonoTouch.UIKit;
+#endif
+#if __UNIFIED__
+using RectangleF = CoreGraphics.CGRect;
+using SizeF = CoreGraphics.CGSize;
+using PointF = CoreGraphics.CGPoint;
+
+#else
+using nfloat=System.Single;
+using nint=System.Int32;
+using nuint=System.UInt32;
+#endif
+
+namespace Xamarin.Forms.Platform.iOS
+{
+ public class PhoneMasterDetailRenderer : UIViewController, IVisualElementRenderer
+ {
+ UIView _clickOffView;
+ UIViewController _detailController;
+
+ bool _disposed;
+ EventTracker _events;
+
+ UIViewController _masterController;
+
+ UIPanGestureRecognizer _panGesture;
+
+ bool _presented;
+ UIGestureRecognizer _tapGesture;
+
+ VisualElementTracker _tracker;
+
+ public PhoneMasterDetailRenderer()
+ {
+ if (!Forms.IsiOS7OrNewer)
+ WantsFullScreenLayout = true;
+ }
+
+ bool Presented
+ {
+ get { return _presented; }
+ set
+ {
+ if (_presented == value)
+ return;
+ _presented = value;
+ LayoutChildren(true);
+ if (value)
+ AddClickOffView();
+ else
+ RemoveClickOffView();
+
+ ((IElementController)Element).SetValueFromRenderer(MasterDetailPage.IsPresentedProperty, value);
+ }
+ }
+
+ public VisualElement Element { get; private set; }
+
+ public event EventHandler<VisualElementChangedEventArgs> ElementChanged;
+
+ public SizeRequest GetDesiredSize(double widthConstraint, double heightConstraint)
+ {
+ return NativeView.GetSizeRequest(widthConstraint, heightConstraint);
+ }
+
+ public UIView NativeView
+ {
+ get { return View; }
+ }
+
+ public void SetElement(VisualElement element)
+ {
+ var oldElement = Element;
+ Element = element;
+ Element.SizeChanged += PageOnSizeChanged;
+
+ _masterController = new ChildViewController();
+ _detailController = new ChildViewController();
+
+ _clickOffView = new UIView();
+ _clickOffView.BackgroundColor = new Color(0, 0, 0, 0).ToUIColor();
+
+ Presented = ((MasterDetailPage)Element).IsPresented;
+
+ OnElementChanged(new VisualElementChangedEventArgs(oldElement, element));
+
+ if (element != null)
+ element.SendViewInitialized(NativeView);
+ }
+
+ public void SetElementSize(Size size)
+ {
+ Element.Layout(new Rectangle(Element.X, Element.Y, size.Width, size.Height));
+ }
+
+ public UIViewController ViewController
+ {
+ get { return this; }
+ }
+
+ public override void ViewDidAppear(bool animated)
+ {
+ base.ViewDidAppear(animated);
+ ((Page)Element).SendAppearing();
+ }
+
+ public override void ViewDidDisappear(bool animated)
+ {
+ base.ViewDidDisappear(animated);
+ ((Page)Element).SendDisappearing();
+ }
+
+ public override void ViewDidLayoutSubviews()
+ {
+ base.ViewDidLayoutSubviews();
+
+ LayoutChildren(false);
+ }
+
+ public override void ViewDidLoad()
+ {
+ base.ViewDidLoad();
+
+ _tracker = new VisualElementTracker(this);
+ _events = new EventTracker(this);
+ _events.LoadEvents(View);
+
+ ((MasterDetailPage)Element).PropertyChanged += HandlePropertyChanged;
+
+ _tapGesture = new UITapGestureRecognizer(() =>
+ {
+ if (Presented)
+ Presented = false;
+ });
+ _clickOffView.AddGestureRecognizer(_tapGesture);
+
+ PackContainers();
+ UpdateMasterDetailContainers();
+
+ UpdateBackground();
+
+ UpdatePanGesture();
+ }
+
+ public override void WillRotate(UIInterfaceOrientation toInterfaceOrientation, double duration)
+ {
+ if (!((MasterDetailPage)Element).ShouldShowSplitMode && _presented)
+ Presented = false;
+
+ base.WillRotate(toInterfaceOrientation, duration);
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing && !_disposed)
+ {
+ Element.SizeChanged -= PageOnSizeChanged;
+ Element.PropertyChanged -= HandlePropertyChanged;
+
+ if (_tracker != null)
+ {
+ _tracker.Dispose();
+ _tracker = null;
+ }
+
+ if (_events != null)
+ {
+ _events.Dispose();
+ _events = null;
+ }
+
+ if (_tapGesture != null)
+ {
+ if (_clickOffView != null && _clickOffView.GestureRecognizers.Contains(_panGesture))
+ {
+ _clickOffView.GestureRecognizers.Remove(_tapGesture);
+ _clickOffView.Dispose();
+ }
+ _tapGesture.Dispose();
+ }
+ if (_panGesture != null)
+ {
+ if (View != null && View.GestureRecognizers.Contains(_panGesture))
+ View.GestureRecognizers.Remove(_panGesture);
+ _panGesture.Dispose();
+ }
+
+ EmptyContainers();
+
+ ((Page)Element).SendDisappearing();
+
+ _disposed = true;
+ }
+
+ base.Dispose(disposing);
+ }
+
+ protected virtual void OnElementChanged(VisualElementChangedEventArgs e)
+ {
+ var changed = ElementChanged;
+ if (changed != null)
+ changed(this, e);
+ }
+
+ void AddClickOffView()
+ {
+ View.Add(_clickOffView);
+ _clickOffView.Frame = _detailController.View.Frame;
+ }
+
+ void EmptyContainers()
+ {
+ foreach (var child in _detailController.View.Subviews.Concat(_masterController.View.Subviews))
+ child.RemoveFromSuperview();
+
+ foreach (var vc in _detailController.ChildViewControllers.Concat(_masterController.ChildViewControllers))
+ vc.RemoveFromParentViewController();
+ }
+
+ void HandleMasterPropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ if (e.PropertyName == Page.IconProperty.PropertyName || e.PropertyName == Page.TitleProperty.PropertyName)
+ MessagingCenter.Send<IVisualElementRenderer>(this, NavigationRenderer.UpdateToolbarButtons);
+ }
+
+ void HandlePropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ if (e.PropertyName == "Master" || e.PropertyName == "Detail")
+ UpdateMasterDetailContainers();
+ else if (e.PropertyName == MasterDetailPage.IsPresentedProperty.PropertyName)
+ Presented = ((MasterDetailPage)Element).IsPresented;
+ else if (e.PropertyName == MasterDetailPage.IsGestureEnabledProperty.PropertyName)
+ UpdatePanGesture();
+ else if (e.PropertyName == VisualElement.BackgroundColorProperty.PropertyName)
+ UpdateBackground();
+ else if (e.PropertyName == Page.BackgroundImageProperty.PropertyName)
+ UpdateBackground();
+ }
+
+ void LayoutChildren(bool animated)
+ {
+ var frame = Element.Bounds.ToRectangleF();
+ var masterFrame = frame;
+ masterFrame.Width = (int)(Math.Min(masterFrame.Width, masterFrame.Height) * 0.8);
+
+ _masterController.View.Frame = masterFrame;
+
+ var target = frame;
+ if (Presented)
+ target.X += masterFrame.Width;
+
+ if (animated)
+ {
+ UIView.BeginAnimations("Flyout");
+ var view = _detailController.View;
+ view.Frame = target;
+ UIView.SetAnimationCurve(UIViewAnimationCurve.EaseOut);
+ UIView.SetAnimationDuration(250);
+ UIView.CommitAnimations();
+ }
+ else
+ _detailController.View.Frame = target;
+
+ ((MasterDetailPage)Element).MasterBounds = new Rectangle(0, 0, masterFrame.Width, masterFrame.Height);
+ ((MasterDetailPage)Element).DetailBounds = new Rectangle(0, 0, frame.Width, frame.Height);
+
+ if (Presented)
+ _clickOffView.Frame = _detailController.View.Frame;
+ }
+
+ void PackContainers()
+ {
+ _detailController.View.BackgroundColor = new UIColor(1, 1, 1, 1);
+ View.AddSubview(_masterController.View);
+ View.AddSubview(_detailController.View);
+
+ AddChildViewController(_masterController);
+ AddChildViewController(_detailController);
+ }
+
+ void PageOnSizeChanged(object sender, EventArgs eventArgs)
+ {
+ LayoutChildren(false);
+ }
+
+ void RemoveClickOffView()
+ {
+ _clickOffView.RemoveFromSuperview();
+ }
+
+ void UpdateBackground()
+ {
+ if (!string.IsNullOrEmpty(((Page)Element).BackgroundImage))
+ View.BackgroundColor = UIColor.FromPatternImage(UIImage.FromBundle(((Page)Element).BackgroundImage));
+ else if (Element.BackgroundColor == Color.Default)
+ View.BackgroundColor = UIColor.White;
+ else
+ View.BackgroundColor = Element.BackgroundColor.ToUIColor();
+ }
+
+ void UpdateMasterDetailContainers()
+ {
+ ((MasterDetailPage)Element).Master.PropertyChanged -= HandleMasterPropertyChanged;
+
+ EmptyContainers();
+
+ if (Platform.GetRenderer(((MasterDetailPage)Element).Master) == null)
+ Platform.SetRenderer(((MasterDetailPage)Element).Master, Platform.CreateRenderer(((MasterDetailPage)Element).Master));
+ if (Platform.GetRenderer(((MasterDetailPage)Element).Detail) == null)
+ Platform.SetRenderer(((MasterDetailPage)Element).Detail, Platform.CreateRenderer(((MasterDetailPage)Element).Detail));
+
+ var masterRenderer = Platform.GetRenderer(((MasterDetailPage)Element).Master);
+ var detailRenderer = Platform.GetRenderer(((MasterDetailPage)Element).Detail);
+
+ ((MasterDetailPage)Element).Master.PropertyChanged += HandleMasterPropertyChanged;
+
+ _masterController.View.AddSubview(masterRenderer.NativeView);
+ _masterController.AddChildViewController(masterRenderer.ViewController);
+
+ _detailController.View.AddSubview(detailRenderer.NativeView);
+ _detailController.AddChildViewController(detailRenderer.ViewController);
+ }
+
+ void UpdatePanGesture()
+ {
+ var model = (MasterDetailPage)Element;
+ if (!model.IsGestureEnabled)
+ {
+ if (_panGesture != null)
+ View.RemoveGestureRecognizer(_panGesture);
+ return;
+ }
+
+ if (_panGesture != null)
+ {
+ View.AddGestureRecognizer(_panGesture);
+ return;
+ }
+
+ UITouchEventArgs shouldRecieve = (g, t) => !(t.View is UISlider);
+ var center = new PointF();
+ _panGesture = new UIPanGestureRecognizer(g =>
+ {
+ switch (g.State)
+ {
+ case UIGestureRecognizerState.Began:
+ center = g.LocationInView(g.View);
+ break;
+ case UIGestureRecognizerState.Changed:
+ var currentPosition = g.LocationInView(g.View);
+ var motion = currentPosition.X - center.X;
+ var detailView = _detailController.View;
+ var targetFrame = detailView.Frame;
+ if (Presented)
+ targetFrame.X = (nfloat)Math.Max(0, _masterController.View.Frame.Width + Math.Min(0, motion));
+ else
+ targetFrame.X = (nfloat)Math.Min(_masterController.View.Frame.Width, Math.Max(0, motion));
+ detailView.Frame = targetFrame;
+ break;
+ case UIGestureRecognizerState.Ended:
+ var detailFrame = _detailController.View.Frame;
+ var masterFrame = _masterController.View.Frame;
+ if (Presented)
+ {
+ if (detailFrame.X < masterFrame.Width * .75)
+ Presented = false;
+ else
+ LayoutChildren(true);
+ }
+ else
+ {
+ if (detailFrame.X > masterFrame.Width * .25)
+ Presented = true;
+ else
+ LayoutChildren(true);
+ }
+ break;
+ }
+ });
+ _panGesture.ShouldReceiveTouch = shouldRecieve;
+ _panGesture.MaximumNumberOfTouches = 2;
+ View.AddGestureRecognizer(_panGesture);
+ }
+
+ class ChildViewController : UIViewController
+ {
+ public override void ViewDidLayoutSubviews()
+ {
+ foreach (var vc in ChildViewControllers)
+ vc.View.Frame = View.Bounds;
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.iOS/Renderers/PickerRenderer.cs b/Xamarin.Forms.Platform.iOS/Renderers/PickerRenderer.cs
new file mode 100644
index 00000000..e47d2fa4
--- /dev/null
+++ b/Xamarin.Forms.Platform.iOS/Renderers/PickerRenderer.cs
@@ -0,0 +1,180 @@
+using System;
+using System.ComponentModel;
+using System.Drawing;
+#if __UNIFIED__
+using UIKit;
+#else
+using MonoTouch.UIKit;
+#endif
+#if __UNIFIED__
+using RectangleF = CoreGraphics.CGRect;
+using SizeF = CoreGraphics.CGSize;
+using PointF = CoreGraphics.CGPoint;
+
+#else
+using nfloat=System.Single;
+using nint=System.Int32;
+using nuint=System.UInt32;
+#endif
+
+namespace Xamarin.Forms.Platform.iOS
+{
+ public class PickerRenderer : ViewRenderer<Picker, UITextField>
+ {
+ UIPickerView _picker;
+
+ protected override void OnElementChanged(ElementChangedEventArgs<Picker> e)
+ {
+ if (e.OldElement != null)
+ ((ObservableList<string>)e.OldElement.Items).CollectionChanged -= RowsCollectionChanged;
+
+ if (e.NewElement != null)
+ {
+ if (Control == null)
+ {
+ var entry = new NoCaretField { BorderStyle = UITextBorderStyle.RoundedRect };
+
+ entry.Started += OnStarted;
+ entry.Ended += OnEnded;
+
+ _picker = new UIPickerView();
+
+ var width = UIScreen.MainScreen.Bounds.Width;
+ var toolbar = new UIToolbar(new RectangleF(0, 0, width, 44)) { BarStyle = UIBarStyle.Default, Translucent = true };
+ var spacer = new UIBarButtonItem(UIBarButtonSystemItem.FlexibleSpace);
+ var doneButton = new UIBarButtonItem(UIBarButtonSystemItem.Done, (o, a) =>
+ {
+ var s = (PickerSource)_picker.Model;
+ if (s.SelectedIndex == -1 && Element.Items != null && Element.Items.Count > 0)
+ UpdatePickerSelectedIndex(0);
+ UpdatePickerFromModel(s);
+ entry.ResignFirstResponder();
+ });
+
+ toolbar.SetItems(new[] { spacer, doneButton }, false);
+
+ entry.InputView = _picker;
+ entry.InputAccessoryView = toolbar;
+
+ SetNativeControl(entry);
+ }
+
+ _picker.Model = new PickerSource(this);
+
+ UpdatePicker();
+
+ ((ObservableList<string>)e.NewElement.Items).CollectionChanged += RowsCollectionChanged;
+ }
+
+ 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();
+ }
+
+ void OnEnded(object sender, EventArgs eventArgs)
+ {
+ ((IElementController)Element).SetValueFromRenderer(VisualElement.IsFocusedPropertyKey, false);
+ }
+
+ void OnStarted(object sender, EventArgs eventArgs)
+ {
+ ((IElementController)Element).SetValueFromRenderer(VisualElement.IsFocusedPropertyKey, true);
+ }
+
+ void RowsCollectionChanged(object sender, EventArgs e)
+ {
+ UpdatePicker();
+ }
+
+ void UpdatePicker()
+ {
+ var selectedIndex = Element.SelectedIndex;
+ var items = Element.Items;
+ Control.Placeholder = Element.Title;
+ var oldText = Control.Text;
+ Control.Text = selectedIndex == -1 || items == null ? "" : items[selectedIndex];
+ UpdatePickerNativeSize(oldText);
+ _picker.ReloadAllComponents();
+ if (items == null || items.Count == 0)
+ return;
+
+ UpdatePickerSelectedIndex(selectedIndex);
+ }
+
+ void UpdatePickerFromModel(PickerSource s)
+ {
+ if (Element != null)
+ {
+ var oldText = Control.Text;
+ ((IElementController)Element).SetValueFromRenderer(Picker.SelectedIndexProperty, s.SelectedIndex);
+ Control.Text = s.SelectedItem;
+ UpdatePickerNativeSize(oldText);
+ }
+ }
+
+ void UpdatePickerNativeSize(string oldText)
+ {
+ if (oldText != Control.Text)
+ ((IVisualElementController)Element).NativeSizeChanged();
+ }
+
+ void UpdatePickerSelectedIndex(int formsIndex)
+ {
+ var source = (PickerSource)_picker.Model;
+ source.SelectedIndex = formsIndex;
+ source.SelectedItem = formsIndex >= 0 ? Element.Items[formsIndex] : null;
+ _picker.Select(Math.Max(formsIndex, 0), 0, true);
+ }
+
+ class PickerSource : UIPickerViewModel
+ {
+ readonly PickerRenderer _renderer;
+
+ public PickerSource(PickerRenderer model)
+ {
+ _renderer = model;
+ }
+
+ public int SelectedIndex { get; internal set; }
+
+ public string SelectedItem { get; internal set; }
+
+ public override nint GetComponentCount(UIPickerView picker)
+ {
+ return 1;
+ }
+
+ public override nint GetRowsInComponent(UIPickerView pickerView, nint component)
+ {
+ return _renderer.Element.Items != null ? _renderer.Element.Items.Count : 0;
+ }
+
+ public override string GetTitle(UIPickerView picker, nint row, nint component)
+ {
+ return _renderer.Element.Items[(int)row];
+ }
+
+ public override void Selected(UIPickerView picker, nint row, nint component)
+ {
+ if (_renderer.Element.Items.Count == 0)
+ {
+ SelectedItem = null;
+ SelectedIndex = -1;
+ }
+ else
+ {
+ SelectedItem = _renderer.Element.Items[(int)row];
+ SelectedIndex = (int)row;
+ }
+ _renderer.UpdatePickerFromModel(this);
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.iOS/Renderers/ProgressBarRenderer.cs b/Xamarin.Forms.Platform.iOS/Renderers/ProgressBarRenderer.cs
new file mode 100644
index 00000000..37091720
--- /dev/null
+++ b/Xamarin.Forms.Platform.iOS/Renderers/ProgressBarRenderer.cs
@@ -0,0 +1,67 @@
+using System.Drawing;
+using System.ComponentModel;
+#if __UNIFIED__
+using UIKit;
+#else
+using MonoTouch.UIKit;
+#endif
+#if __UNIFIED__
+using RectangleF = CoreGraphics.CGRect;
+using SizeF = CoreGraphics.CGSize;
+using PointF = CoreGraphics.CGPoint;
+
+#else
+using nfloat=System.Single;
+using nint=System.Int32;
+using nuint=System.UInt32;
+#endif
+
+namespace Xamarin.Forms.Platform.iOS
+{
+ public class ProgressBarRenderer : ViewRenderer<ProgressBar, UIProgressView>
+ {
+ public override SizeF SizeThatFits(SizeF size)
+ {
+ // progress bar will size itself to be as wide as the request, even if its inifinite
+ // we want the minimum need size
+ var result = base.SizeThatFits(size);
+ return new SizeF(10, result.Height);
+ }
+
+ protected override void OnElementChanged(ElementChangedEventArgs<ProgressBar> e)
+ {
+ if (e.NewElement != null)
+ {
+ if (Control == null)
+ SetNativeControl(new UIProgressView(UIProgressViewStyle.Default));
+
+ UpdateProgress();
+ }
+
+ base.OnElementChanged(e);
+ }
+
+ 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)
+ {
+ base.SetBackgroundColor(color);
+
+ if (Control == null)
+ return;
+
+ Control.TrackTintColor = color != Color.Default ? color.ToUIColor() : null;
+ }
+
+ void UpdateProgress()
+ {
+ Control.Progress = (float)Element.Progress;
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.iOS/Renderers/ScrollViewRenderer.cs b/Xamarin.Forms.Platform.iOS/Renderers/ScrollViewRenderer.cs
new file mode 100644
index 00000000..1e8ea1fc
--- /dev/null
+++ b/Xamarin.Forms.Platform.iOS/Renderers/ScrollViewRenderer.cs
@@ -0,0 +1,246 @@
+using System;
+using System.ComponentModel;
+using System.Drawing;
+#if __UNIFIED__
+using UIKit;
+using Foundation;
+using ObjCRuntime;
+using PointF = CoreGraphics.CGPoint;
+using RectangleF = CoreGraphics.CGRect;
+
+#else
+using MonoTouch.Foundation;
+using MonoTouch.ObjCRuntime;
+using MonoTouch.UIKit;
+using nfloat=System.Single;
+
+#endif
+
+namespace Xamarin.Forms.Platform.iOS
+{
+ public class ScrollViewRenderer : UIScrollView, IVisualElementRenderer
+ {
+ EventTracker _events;
+ KeyboardInsetTracker _insetTracker;
+
+ VisualElementPackager _packager;
+
+ RectangleF _previousFrame;
+ ScrollToRequestedEventArgs _requestedScroll;
+ VisualElementTracker _tracker;
+
+ public ScrollViewRenderer() : base(RectangleF.Empty)
+ {
+ ScrollAnimationEnded += HandleScrollAnimationEnded;
+ Scrolled += HandleScrolled;
+ }
+
+ protected IScrollViewController Controller
+ {
+ get { return (IScrollViewController)Element; }
+ }
+
+ ScrollView ScrollView
+ {
+ get { return Element as ScrollView; }
+ }
+
+ 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 UIView NativeView
+ {
+ get { return 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 (_packager == null)
+ {
+ DelaysContentTouches = true;
+
+ _packager = new VisualElementPackager(this);
+ _packager.Load();
+
+ _tracker = new VisualElementTracker(this);
+ _tracker.NativeControlUpdated += OnNativeControlUpdated;
+ _events = new EventTracker(this);
+ _events.LoadEvents(this);
+
+ _insetTracker = new KeyboardInsetTracker(this, () => Window, insets => ContentInset = ScrollIndicatorInsets = insets, point =>
+ {
+ var offset = ContentOffset;
+ offset.Y += point.Y;
+ SetContentOffset(offset, true);
+ });
+ }
+
+ UpdateContentSize();
+ UpdateBackgroundColor();
+
+ OnElementChanged(new VisualElementChangedEventArgs(oldElement, element));
+
+ if (element != null)
+ element.SendViewInitialized(this);
+
+ if (!string.IsNullOrEmpty(element.AutomationId))
+ AccessibilityIdentifier = element.AutomationId;
+ }
+ }
+
+ public void SetElementSize(Size size)
+ {
+ Layout.LayoutChildIntoBoundingRegion(Element, new Rectangle(Element.X, Element.Y, size.Width, size.Height));
+ }
+
+ public UIViewController ViewController
+ {
+ get { return null; }
+ }
+
+ public override void LayoutSubviews()
+ {
+ base.LayoutSubviews();
+
+ if (_requestedScroll != null && Superview != null)
+ {
+ var request = _requestedScroll;
+ _requestedScroll = null;
+ OnScrollToRequested(this, request);
+ }
+
+ if (_previousFrame != Frame)
+ {
+ _previousFrame = Frame;
+ _insetTracker?.UpdateInsets();
+ }
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ if (_packager == null)
+ return;
+
+ SetElement(null);
+
+ _packager.Dispose();
+ _packager = null;
+
+ _tracker.NativeControlUpdated -= OnNativeControlUpdated;
+ _tracker.Dispose();
+ _tracker = null;
+
+ _events.Dispose();
+ _events = null;
+
+ _insetTracker.Dispose();
+ _insetTracker = null;
+
+ ScrollAnimationEnded -= HandleScrollAnimationEnded;
+ Scrolled -= HandleScrolled;
+ }
+
+ base.Dispose(disposing);
+ }
+
+ protected virtual void OnElementChanged(VisualElementChangedEventArgs e)
+ {
+ var changed = ElementChanged;
+ if (changed != null)
+ changed(this, e);
+ }
+
+ 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)
+ {
+ ContentSize = Bounds.Size;
+ UpdateContentSize();
+ }
+
+ void OnScrollToRequested(object sender, ScrollToRequestedEventArgs e)
+ {
+ if (Superview == null)
+ {
+ _requestedScroll = e;
+ return;
+ }
+ if (e.Mode == ScrollToMode.Position)
+ SetContentOffset(new PointF((nfloat)e.ScrollX, (nfloat)e.ScrollY), e.ShouldAnimate);
+ else
+ {
+ var positionOnScroll = Controller.GetScrollPositionForElement(e.Element as VisualElement, e.Position);
+ switch (ScrollView.Orientation)
+ {
+ case ScrollOrientation.Horizontal:
+ SetContentOffset(new PointF((nfloat)positionOnScroll.X, ContentOffset.Y), e.ShouldAnimate);
+ break;
+ case ScrollOrientation.Vertical:
+ SetContentOffset(new PointF(ContentOffset.X, (nfloat)positionOnScroll.Y), e.ShouldAnimate);
+ break;
+ case ScrollOrientation.Both:
+ SetContentOffset(new PointF((nfloat)positionOnScroll.X, (nfloat)positionOnScroll.Y), e.ShouldAnimate);
+ break;
+ }
+ }
+ if (!e.ShouldAnimate)
+ Controller.SendScrollFinished();
+ }
+
+ void UpdateBackgroundColor()
+ {
+ BackgroundColor = Element.BackgroundColor.ToUIColor(Color.Transparent);
+ }
+
+ void UpdateContentSize()
+ {
+ var contentSize = ((ScrollView)Element).ContentSize.ToSizeF();
+ if (!contentSize.IsEmpty)
+ ContentSize = contentSize;
+ }
+
+ void UpdateScrollPosition()
+ {
+ if (ScrollView != null)
+ Controller.SetScrolledPosition(ContentOffset.X, ContentOffset.Y);
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.iOS/Renderers/SearchBarRenderer.cs b/Xamarin.Forms.Platform.iOS/Renderers/SearchBarRenderer.cs
new file mode 100644
index 00000000..e5e156c4
--- /dev/null
+++ b/Xamarin.Forms.Platform.iOS/Renderers/SearchBarRenderer.cs
@@ -0,0 +1,249 @@
+using System;
+using System.Drawing;
+using System.ComponentModel;
+#if __UNIFIED__
+using UIKit;
+
+#else
+using MonoTouch.UIKit;
+#endif
+
+namespace Xamarin.Forms.Platform.iOS
+{
+ public class SearchBarRenderer : ViewRenderer<SearchBar, UISearchBar>
+ {
+ UIColor _cancelButtonTextColorDefaultDisabled;
+ UIColor _cancelButtonTextColorDefaultHighlighted;
+ UIColor _cancelButtonTextColorDefaultNormal;
+
+ UIColor _defaultTextColor;
+ UIColor _defaultTintColor;
+ UITextField _textField;
+
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ if (Control != null)
+ {
+ Control.CancelButtonClicked -= OnCancelClicked;
+ Control.SearchButtonClicked -= OnSearchButtonClicked;
+ Control.TextChanged -= OnTextChanged;
+
+ Control.OnEditingStarted -= OnEditingEnded;
+ Control.OnEditingStopped -= OnEditingStarted;
+ }
+ }
+
+ base.Dispose(disposing);
+ }
+
+ protected override void OnElementChanged(ElementChangedEventArgs<SearchBar> e)
+ {
+ if (e.NewElement != null)
+ {
+ if (Control == null)
+ {
+ var searchBar = new UISearchBar(RectangleF.Empty) { ShowsCancelButton = true, BarStyle = UIBarStyle.Default };
+
+ var cancelButton = searchBar.FindDescendantView<UIButton>();
+ _cancelButtonTextColorDefaultNormal = cancelButton.TitleColor(UIControlState.Normal);
+ _cancelButtonTextColorDefaultHighlighted = cancelButton.TitleColor(UIControlState.Highlighted);
+ _cancelButtonTextColorDefaultDisabled = cancelButton.TitleColor(UIControlState.Disabled);
+
+ SetNativeControl(searchBar);
+
+ Control.CancelButtonClicked += OnCancelClicked;
+ Control.SearchButtonClicked += OnSearchButtonClicked;
+ Control.TextChanged += OnTextChanged;
+
+ Control.OnEditingStarted += OnEditingStarted;
+ Control.OnEditingStopped += 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;
+
+ if (_defaultTintColor == null)
+ {
+ if (Forms.IsiOS7OrNewer)
+ _defaultTintColor = Control.BarTintColor;
+ else
+ _defaultTintColor = Control.TintColor;
+ }
+
+ if (Forms.IsiOS7OrNewer)
+ Control.BarTintColor = color.ToUIColor(_defaultTintColor);
+ else
+ Control.TintColor = color.ToUIColor(_defaultTintColor);
+
+ if (color.A < 1)
+ Control.SetBackgroundImage(new UIImage(), UIBarPosition.Any, UIBarMetrics.Default);
+
+ // updating BarTintColor resets the button color so we need to update the button color again
+ UpdateCancelButton();
+ }
+
+ void OnCancelClicked(object sender, EventArgs args)
+ {
+ ((IElementController)Element).SetValueFromRenderer(SearchBar.TextProperty, null);
+ Control.ResignFirstResponder();
+ }
+
+ void OnEditingEnded(object sender, EventArgs e)
+ {
+ if (Element != null)
+ ((IElementController)Element).SetValueFromRenderer(VisualElement.IsFocusedPropertyKey, false);
+ }
+
+ void OnEditingStarted(object sender, EventArgs e)
+ {
+ if (Element != null)
+ ((IElementController)Element).SetValueFromRenderer(VisualElement.IsFocusedPropertyKey, true);
+ }
+
+ void OnSearchButtonClicked(object sender, EventArgs e)
+ {
+ Element.OnSearchButtonPressed();
+ Control.ResignFirstResponder();
+ }
+
+ void OnTextChanged(object sender, UISearchBarTextChangedEventArgs a)
+ {
+ ((IElementController)Element).SetValueFromRenderer(SearchBar.TextProperty, Control.Text);
+ }
+
+ void UpdateAlignment()
+ {
+ _textField = _textField ?? Control.FindDescendantView<UITextField>();
+
+ if (_textField == null)
+ return;
+
+ _textField.TextAlignment = Element.HorizontalTextAlignment.ToNativeTextAlignment();
+ }
+
+ void UpdateCancelButton()
+ {
+ Control.ShowsCancelButton = !string.IsNullOrEmpty(Control.Text);
+
+ // We can't cache the cancel button reference because iOS drops it when it's not displayed
+ // and creates a brand new one when necessary, so we have to look for it each time
+ var cancelButton = Control.FindDescendantView<UIButton>();
+
+ if (cancelButton == null)
+ return;
+
+ if (Element.CancelButtonColor == Color.Default)
+ {
+ cancelButton.SetTitleColor(_cancelButtonTextColorDefaultNormal, UIControlState.Normal);
+ cancelButton.SetTitleColor(_cancelButtonTextColorDefaultHighlighted, UIControlState.Highlighted);
+ cancelButton.SetTitleColor(_cancelButtonTextColorDefaultDisabled, UIControlState.Disabled);
+ }
+ else
+ {
+ cancelButton.SetTitleColor(Element.CancelButtonColor.ToUIColor(), UIControlState.Normal);
+ cancelButton.SetTitleColor(Element.CancelButtonColor.ToUIColor(), UIControlState.Highlighted);
+ cancelButton.SetTitleColor(_cancelButtonTextColorDefaultDisabled, UIControlState.Disabled);
+ }
+ }
+
+ void UpdateFont()
+ {
+ _textField = _textField ?? Control.FindDescendantView<UITextField>();
+
+ if (_textField == null)
+ return;
+
+ _textField.Font = Element.ToUIFont();
+ }
+
+ void UpdateIsEnabled()
+ {
+ Control.UserInteractionEnabled = Element.IsEnabled;
+ }
+
+ void UpdatePlaceholder()
+ {
+ _textField = _textField ?? Control.FindDescendantView<UITextField>();
+
+ if (_textField == null)
+ return;
+
+ var formatted = (FormattedString)Element.Placeholder ?? string.Empty;
+ 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();
+
+ _textField.AttributedPlaceholder = formatted.ToAttributed(Element, color);
+ }
+
+ void UpdateText()
+ {
+ Control.Text = Element.Text;
+ UpdateCancelButton();
+ }
+
+ void UpdateTextColor()
+ {
+ _textField = _textField ?? Control.FindDescendantView<UITextField>();
+
+ if (_textField == null)
+ return;
+
+ _defaultTextColor = _defaultTextColor ?? _textField.TextColor;
+ var targetColor = Element.TextColor;
+
+ var color = Element.IsEnabled && !targetColor.IsDefault ? targetColor : _defaultTextColor.ToColor();
+
+ _textField.TextColor = color.ToUIColor();
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.iOS/Renderers/SliderRenderer.cs b/Xamarin.Forms.Platform.iOS/Renderers/SliderRenderer.cs
new file mode 100644
index 00000000..2df61f2f
--- /dev/null
+++ b/Xamarin.Forms.Platform.iOS/Renderers/SliderRenderer.cs
@@ -0,0 +1,99 @@
+using System;
+using System.Drawing;
+using System.ComponentModel;
+#if __UNIFIED__
+using UIKit;
+#else
+using MonoTouch.UIKit;
+#endif
+#if __UNIFIED__
+using RectangleF = CoreGraphics.CGRect;
+using SizeF = CoreGraphics.CGSize;
+using PointF = CoreGraphics.CGPoint;
+
+#else
+using nfloat=System.Single;
+using nint=System.Int32;
+using nuint=System.UInt32;
+#endif
+
+namespace Xamarin.Forms.Platform.iOS
+{
+ public class SliderRenderer : ViewRenderer<Slider, UISlider>
+ {
+ SizeF _fitSize;
+
+ public override SizeF SizeThatFits(SizeF size)
+ {
+ return _fitSize;
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ if (Control != null)
+ Control.ValueChanged -= OnControlValueChanged;
+
+ base.Dispose(disposing);
+ }
+
+ protected override void OnElementChanged(ElementChangedEventArgs<Slider> e)
+ {
+ if (e.NewElement != null)
+ {
+ if (Control == null)
+ {
+ SetNativeControl(new UISlider { Continuous = true });
+ Control.ValueChanged += OnControlValueChanged;
+
+ // sliders SizeThatFits method returns non-useful answers
+ // this however gives a very useful answer
+ Control.SizeToFit();
+ _fitSize = Control.Bounds.Size;
+
+ // except if your not running iOS 7... then it fails...
+ if (_fitSize.Width <= 0 || _fitSize.Height <= 0)
+ _fitSize = new SizeF(22, 22); // Per the glorious documentation known as the SDK docs
+ }
+
+ 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();
+ }
+
+ void OnControlValueChanged(object sender, EventArgs eventArgs)
+ {
+ ((IElementController)Element).SetValueFromRenderer(Slider.ValueProperty, Control.Value);
+ }
+
+ void UpdateMaximum()
+ {
+ Control.MaxValue = (float)Element.Maximum;
+ }
+
+ void UpdateMinimum()
+ {
+ Control.MinValue = (float)Element.Minimum;
+ }
+
+ void UpdateValue()
+ {
+ if ((float)Element.Value != Control.Value)
+ Control.Value = (float)Element.Value;
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.iOS/Renderers/StepperRenderer.cs b/Xamarin.Forms.Platform.iOS/Renderers/StepperRenderer.cs
new file mode 100644
index 00000000..0cb0fcb7
--- /dev/null
+++ b/Xamarin.Forms.Platform.iOS/Renderers/StepperRenderer.cs
@@ -0,0 +1,82 @@
+using System;
+using System.Drawing;
+using System.ComponentModel;
+#if __UNIFIED__
+using UIKit;
+
+#else
+using MonoTouch.UIKit;
+#endif
+
+namespace Xamarin.Forms.Platform.iOS
+{
+ public class StepperRenderer : ViewRenderer<Stepper, UIStepper>
+ {
+ protected override void Dispose(bool disposing)
+ {
+ if (Control != null)
+ Control.ValueChanged -= OnValueChanged;
+
+ base.Dispose(disposing);
+ }
+
+ protected override void OnElementChanged(ElementChangedEventArgs<Stepper> e)
+ {
+ if (e.NewElement != null)
+ {
+ if (Control == null)
+ {
+ SetNativeControl(new UIStepper(RectangleF.Empty));
+ Control.ValueChanged += OnValueChanged;
+ }
+
+ 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();
+ }
+
+ void OnValueChanged(object sender, EventArgs e)
+ {
+ ((IElementController)Element).SetValueFromRenderer(Stepper.ValueProperty, Control.Value);
+ }
+
+ void UpdateIncrement()
+ {
+ Control.StepValue = Element.Increment;
+ }
+
+ void UpdateMaximum()
+ {
+ Control.MaximumValue = Element.Maximum;
+ }
+
+ void UpdateMinimum()
+ {
+ Control.MinimumValue = Element.Minimum;
+ }
+
+ void UpdateValue()
+ {
+ if (Control.Value != Element.Value)
+ Control.Value = Element.Value;
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.iOS/Renderers/SwitchRenderer.cs b/Xamarin.Forms.Platform.iOS/Renderers/SwitchRenderer.cs
new file mode 100644
index 00000000..0bb7fbef
--- /dev/null
+++ b/Xamarin.Forms.Platform.iOS/Renderers/SwitchRenderer.cs
@@ -0,0 +1,52 @@
+using System;
+using System.Drawing;
+#if __UNIFIED__
+using UIKit;
+
+#else
+using MonoTouch.UIKit;
+#endif
+
+namespace Xamarin.Forms.Platform.iOS
+{
+ public class SwitchRenderer : ViewRenderer<Switch, UISwitch>
+ {
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing)
+ Control.ValueChanged -= OnControlValueChanged;
+
+ base.Dispose(disposing);
+ }
+
+ protected override void OnElementChanged(ElementChangedEventArgs<Switch> e)
+ {
+ if (e.OldElement != null)
+ e.OldElement.Toggled -= OnElementToggled;
+
+ if (e.NewElement != null)
+ {
+ if (Control == null)
+ {
+ SetNativeControl(new UISwitch(RectangleF.Empty));
+ Control.ValueChanged += OnControlValueChanged;
+ }
+
+ Control.On = Element.IsToggled;
+ e.NewElement.Toggled += OnElementToggled;
+ }
+
+ base.OnElementChanged(e);
+ }
+
+ void OnControlValueChanged(object sender, EventArgs e)
+ {
+ ((IElementController)Element).SetValueFromRenderer(Switch.IsToggledProperty, Control.On);
+ }
+
+ void OnElementToggled(object sender, EventArgs e)
+ {
+ Control.SetState(Element.IsToggled, true);
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.iOS/Renderers/TabbedRenderer.cs b/Xamarin.Forms.Platform.iOS/Renderers/TabbedRenderer.cs
new file mode 100644
index 00000000..7e5121fd
--- /dev/null
+++ b/Xamarin.Forms.Platform.iOS/Renderers/TabbedRenderer.cs
@@ -0,0 +1,311 @@
+using System;
+using System.Collections.Specialized;
+using System.ComponentModel;
+using System.Linq;
+using System.Collections.Generic;
+#if __UNIFIED__
+using UIKit;
+#else
+using MonoTouch.UIKit;
+#endif
+#if __UNIFIED__
+using RectangleF = CoreGraphics.CGRect;
+using SizeF = CoreGraphics.CGSize;
+using PointF = CoreGraphics.CGPoint;
+
+#else
+using nfloat=System.Single;
+using nint=System.Int32;
+using nuint=System.UInt32;
+#endif
+
+namespace Xamarin.Forms.Platform.iOS
+{
+ public class TabbedRenderer : UITabBarController, IVisualElementRenderer
+ {
+ bool _loaded;
+ Size _queuedSize;
+
+ public override UIViewController SelectedViewController
+ {
+ get { return base.SelectedViewController; }
+ set
+ {
+ base.SelectedViewController = value;
+ UpdateCurrentPage();
+ }
+ }
+
+ protected TabbedPage Tabbed
+ {
+ get { return (TabbedPage)Element; }
+ }
+
+ public VisualElement Element { get; private set; }
+
+ public event EventHandler<VisualElementChangedEventArgs> ElementChanged;
+
+ public SizeRequest GetDesiredSize(double widthConstraint, double heightConstraint)
+ {
+ return NativeView.GetSizeRequest(widthConstraint, heightConstraint);
+ }
+
+ public UIView NativeView
+ {
+ get { return View; }
+ }
+
+ public void SetElement(VisualElement element)
+ {
+ var oldElement = Element;
+ Element = element;
+
+ FinishedCustomizingViewControllers += HandleFinishedCustomizingViewControllers;
+ Tabbed.PropertyChanged += OnPropertyChanged;
+ Tabbed.PagesChanged += OnPagesChanged;
+
+ OnElementChanged(new VisualElementChangedEventArgs(oldElement, element));
+
+ OnPagesChanged(null, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
+
+ if (element != null)
+ element.SendViewInitialized(NativeView);
+
+ //disable edit/reorder of tabs
+ CustomizableViewControllers = null;
+ }
+
+ public void SetElementSize(Size size)
+ {
+ if (_loaded)
+ Element.Layout(new Rectangle(Element.X, Element.Y, size.Width, size.Height));
+ else
+ _queuedSize = size;
+ }
+
+ public UIViewController ViewController
+ {
+ get { return this; }
+ }
+
+ public override void DidRotate(UIInterfaceOrientation fromInterfaceOrientation)
+ {
+ base.DidRotate(fromInterfaceOrientation);
+
+ View.SetNeedsLayout();
+ }
+
+ public override void ViewDidAppear(bool animated)
+ {
+ ((TabbedPage)Element).SendAppearing();
+ base.ViewDidAppear(animated);
+ }
+
+ public override void ViewDidDisappear(bool animated)
+ {
+ base.ViewDidDisappear(animated);
+ ((TabbedPage)Element).SendDisappearing();
+ }
+
+ public override void ViewDidLayoutSubviews()
+ {
+ base.ViewDidLayoutSubviews();
+
+ 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;
+ var tabBarFrame = TabBar.Frame;
+ ((TabbedPage)Element).ContainerArea = new Rectangle(0, 0, frame.Width, frame.Height - tabBarFrame.Height);
+
+ if (!_queuedSize.IsZero)
+ {
+ Element.Layout(new Rectangle(Element.X, Element.Y, _queuedSize.Width, _queuedSize.Height));
+ _queuedSize = Size.Zero;
+ }
+
+ _loaded = true;
+ }
+
+ public override void ViewDidLoad()
+ {
+ base.ViewDidLoad();
+
+ if (!Forms.IsiOS7OrNewer)
+ WantsFullScreenLayout = false;
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ ((TabbedPage)Element).SendDisappearing();
+ Tabbed.PropertyChanged -= OnPropertyChanged;
+ Tabbed.PagesChanged -= OnPagesChanged;
+ FinishedCustomizingViewControllers -= HandleFinishedCustomizingViewControllers;
+ }
+
+ base.Dispose(disposing);
+ }
+
+ protected virtual void OnElementChanged(VisualElementChangedEventArgs e)
+ {
+ var changed = ElementChanged;
+ if (changed != null)
+ changed(this, e);
+ }
+
+ UIViewController GetViewController(Page page)
+ {
+ var renderer = Platform.GetRenderer(page);
+ if (renderer == null)
+ return null;
+
+ return renderer.ViewController;
+ }
+
+ void HandleFinishedCustomizingViewControllers(object sender, UITabBarCustomizeChangeEventArgs e)
+ {
+ if (e.Changed)
+ UpdateChildrenOrderIndex(e.ViewControllers);
+ }
+
+ void OnPagePropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ if (e.PropertyName == Page.TitleProperty.PropertyName)
+ {
+ var page = (Page)sender;
+ var renderer = Platform.GetRenderer(page);
+ if (renderer == null)
+ return;
+
+ if (renderer.ViewController.TabBarItem != null)
+ renderer.ViewController.TabBarItem.Title = page.Title;
+ }
+ else if (e.PropertyName == Page.IconProperty.PropertyName)
+ {
+ var page = (Page)sender;
+
+ var renderer = Platform.GetRenderer(page);
+ if (renderer == null)
+ return;
+
+ if (renderer.ViewController.TabBarItem == null)
+ return;
+
+ UIImage image = null;
+ if (!string.IsNullOrEmpty(page.Icon))
+ image = new UIImage(page.Icon);
+
+ // the new UITabBarItem forces redraw, setting the UITabBarItem.Image does not
+ renderer.ViewController.TabBarItem = new UITabBarItem(page.Title, image, 0);
+
+ if (image != null)
+ image.Dispose();
+ }
+ }
+
+ void OnPagesChanged(object sender, NotifyCollectionChangedEventArgs e)
+ {
+ e.Apply((o, i, c) => SetupPage((Page)o, i), (o, i) => TeardownPage((Page)o, i), Reset);
+
+ SetControllers();
+
+ UIViewController controller = null;
+ if (Tabbed.CurrentPage != null)
+ controller = GetViewController(Tabbed.CurrentPage);
+ if (controller != null && controller != base.SelectedViewController)
+ base.SelectedViewController = controller;
+ }
+
+ void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ if (e.PropertyName == "CurrentPage")
+ {
+ var current = Tabbed.CurrentPage;
+ if (current == null)
+ return;
+
+ var controller = GetViewController(current);
+ if (controller == null)
+ return;
+
+ SelectedViewController = controller;
+ }
+ }
+
+ void Reset()
+ {
+ var i = 0;
+ foreach (var page in Tabbed.Children)
+ SetupPage(page, i++);
+ }
+
+ void SetControllers()
+ {
+ var list = new List<UIViewController>();
+ for (var i = 0; i < Element.LogicalChildren.Count; i++)
+ {
+ var child = Element.LogicalChildren[i];
+ var v = child as VisualElement;
+ if (v == null)
+ continue;
+ if (Platform.GetRenderer(v) != null)
+ list.Add(Platform.GetRenderer(v).ViewController);
+ }
+ ViewControllers = list.ToArray();
+ }
+
+ void SetupPage(Page page, int index)
+ {
+ var renderer = Platform.GetRenderer(page);
+ if (renderer == null)
+ {
+ renderer = Platform.CreateRenderer(page);
+ Platform.SetRenderer(page, renderer);
+ }
+
+ page.PropertyChanged += OnPagePropertyChanged;
+
+ UIImage icon = null;
+ if (page.Icon != null)
+ icon = new UIImage(page.Icon);
+
+ renderer.ViewController.TabBarItem = new UITabBarItem(page.Title, icon, 0);
+ if (icon != null)
+ icon.Dispose();
+
+ renderer.ViewController.TabBarItem.Tag = index;
+ }
+
+ void TeardownPage(Page page, int index)
+ {
+ page.PropertyChanged -= OnPagePropertyChanged;
+
+ Platform.SetRenderer(page, null);
+ }
+
+ void UpdateChildrenOrderIndex(UIViewController[] viewControllers)
+ {
+ for (var i = 0; i < viewControllers.Length; i++)
+ {
+ var originalIndex = -1;
+ if (int.TryParse(viewControllers[i].TabBarItem.Tag.ToString(), out originalIndex))
+ {
+ var page = (Page)Tabbed.InternalChildren[originalIndex];
+ TabbedPage.SetIndex(page, i);
+ }
+ }
+ }
+
+ void UpdateCurrentPage()
+ {
+ ((TabbedPage)Element).CurrentPage = SelectedIndex >= 0 && SelectedIndex < Tabbed.InternalChildren.Count ? Tabbed.GetPageByIndex((int)SelectedIndex) : null;
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.iOS/Renderers/TableViewModelRenderer.cs b/Xamarin.Forms.Platform.iOS/Renderers/TableViewModelRenderer.cs
new file mode 100644
index 00000000..0e9e15f4
--- /dev/null
+++ b/Xamarin.Forms.Platform.iOS/Renderers/TableViewModelRenderer.cs
@@ -0,0 +1,157 @@
+using System;
+using System.Collections.Generic;
+using Xamarin.Forms;
+#if __UNIFIED__
+using UIKit;
+using Foundation;
+#else
+using MonoTouch.UIKit;
+using MonoTouch.Foundation;
+#endif
+#if __UNIFIED__
+using RectangleF = CoreGraphics.CGRect;
+using SizeF = CoreGraphics.CGSize;
+using PointF = CoreGraphics.CGPoint;
+
+#else
+using nfloat=System.Single;
+using nint=System.Int32;
+using nuint=System.UInt32;
+#endif
+
+namespace Xamarin.Forms.Platform.iOS
+{
+ public class TableViewModelRenderer : UITableViewSource
+ {
+ readonly Dictionary<nint, Cell> _headerCells = new Dictionary<nint, Cell>();
+
+ protected bool HasBoundGestures;
+ protected UITableView Table;
+
+ protected TableView View;
+
+ public TableViewModelRenderer(TableView model)
+ {
+ View = model;
+ View.ModelChanged += (s, e) =>
+ {
+ if (Table != null)
+ Table.ReloadData();
+ };
+ AutomaticallyDeselect = true;
+ }
+
+ public bool AutomaticallyDeselect { get; set; }
+
+ public override UITableViewCell GetCell(UITableView tableView, NSIndexPath indexPath)
+ {
+ var cell = View.Model.GetCell(indexPath.Section, indexPath.Row);
+
+ var nativeCell = CellTableViewCell.GetNativeCell(tableView, cell);
+ return nativeCell;
+ }
+
+ public override nfloat GetHeightForHeader(UITableView tableView, nint section)
+ {
+ if (!_headerCells.ContainsKey((int)section))
+ _headerCells[section] = View.Model.GetHeaderCell((int)section);
+
+ var result = _headerCells[section];
+
+ return result == null ? UITableView.AutomaticDimension : (nfloat)result.Height;
+ }
+
+ public override UIView GetViewForHeader(UITableView tableView, nint section)
+ {
+ if (!_headerCells.ContainsKey((int)section))
+ _headerCells[section] = View.Model.GetHeaderCell((int)section);
+
+ var result = _headerCells[section];
+
+ if (result != null)
+ {
+ var reusable = tableView.DequeueReusableCell(result.GetType().FullName);
+
+ var cellRenderer = Registrar.Registered.GetHandler<CellRenderer>(result.GetType());
+ return cellRenderer.GetCell(result, reusable, Table);
+ }
+ return null;
+ }
+
+ public void LongPress(UILongPressGestureRecognizer gesture)
+ {
+ var point = gesture.LocationInView(Table);
+ var indexPath = Table.IndexPathForRowAtPoint(point);
+ if (indexPath == null)
+ return;
+
+ View.Model.RowLongPressed(indexPath.Section, indexPath.Row);
+ }
+
+ public override nint NumberOfSections(UITableView tableView)
+ {
+ BindGestures(tableView);
+ return View.Model.GetSectionCount();
+ }
+
+ public override void RowSelected(UITableView tableView, NSIndexPath indexPath)
+ {
+ View.Model.RowSelected(indexPath.Section, indexPath.Row);
+ if (AutomaticallyDeselect)
+ tableView.DeselectRow(indexPath, true);
+ }
+
+ public override nint RowsInSection(UITableView tableview, nint section)
+ {
+ return View.Model.GetRowCount((int)section);
+ }
+
+ public override string[] SectionIndexTitles(UITableView tableView)
+ {
+ return View.Model.GetSectionIndexTitles();
+ }
+
+ public override string TitleForHeader(UITableView tableView, nint section)
+ {
+ return View.Model.GetSectionTitle((int)section);
+ }
+
+ void BindGestures(UITableView tableview)
+ {
+ if (HasBoundGestures)
+ return;
+
+ HasBoundGestures = true;
+
+ var gesture = new UILongPressGestureRecognizer(LongPress);
+ gesture.MinimumPressDuration = 2;
+ tableview.AddGestureRecognizer(gesture);
+
+ var dismissGesture = new UITapGestureRecognizer(Tap);
+ dismissGesture.CancelsTouchesInView = false;
+ tableview.AddGestureRecognizer(dismissGesture);
+
+ Table = tableview;
+ }
+
+ void Tap(UITapGestureRecognizer gesture)
+ {
+ gesture.View.EndEditing(true);
+ }
+ }
+
+ public class UnEvenTableViewModelRenderer : TableViewModelRenderer
+ {
+ public UnEvenTableViewModelRenderer(TableView model) : base(model)
+ {
+ }
+
+ public override nfloat GetHeightForRow(UITableView tableView, NSIndexPath indexPath)
+ {
+ var h = View.Model.GetCell(indexPath.Section, indexPath.Row).Height;
+ if (h == -1)
+ return tableView.RowHeight;
+ return (nfloat)h;
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.iOS/Renderers/TableViewRenderer.cs b/Xamarin.Forms.Platform.iOS/Renderers/TableViewRenderer.cs
new file mode 100644
index 00000000..a77b32cc
--- /dev/null
+++ b/Xamarin.Forms.Platform.iOS/Renderers/TableViewRenderer.cs
@@ -0,0 +1,150 @@
+using System;
+using System.ComponentModel;
+using System.Drawing;
+using System.Collections.Generic;
+#if __UNIFIED__
+using UIKit;
+using RectangleF = CoreGraphics.CGRect;
+
+#else
+using MonoTouch.UIKit;
+#endif
+
+namespace Xamarin.Forms.Platform.iOS
+{
+ public class TableViewRenderer : ViewRenderer<TableView, UITableView>
+ {
+ KeyboardInsetTracker _insetTracker;
+ UIView _originalBackgroundView;
+ RectangleF _previousFrame;
+
+ public override SizeRequest GetDesiredSize(double widthConstraint, double heightConstraint)
+ {
+ return Control.GetSizeRequest(widthConstraint, heightConstraint, 44, 44);
+ }
+
+ public override void LayoutSubviews()
+ {
+ base.LayoutSubviews();
+
+ if (_previousFrame != Frame)
+ {
+ _previousFrame = Frame;
+ _insetTracker?.UpdateInsets();
+ }
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing && _insetTracker != null)
+ {
+ _insetTracker.Dispose();
+ _insetTracker = null;
+
+ var viewsToLookAt = new Stack<UIView>(Subviews);
+ while (viewsToLookAt.Count > 0)
+ {
+ var view = viewsToLookAt.Pop();
+ var viewCellRenderer = view as ViewCellRenderer.ViewTableCell;
+ if (viewCellRenderer != null)
+ viewCellRenderer.Dispose();
+ else
+ {
+ foreach (var child in view.Subviews)
+ viewsToLookAt.Push(child);
+ }
+ }
+ }
+
+ base.Dispose(disposing);
+ }
+
+ protected override void OnElementChanged(ElementChangedEventArgs<TableView> e)
+ {
+ if (e.NewElement != null)
+ {
+ var style = UITableViewStyle.Plain;
+ if (e.NewElement.Intent != TableIntent.Data)
+ style = UITableViewStyle.Grouped;
+
+ if (Control == null || Control.Style != style)
+ {
+ if (Control != null)
+ {
+ _insetTracker.Dispose();
+ Control.Dispose();
+ }
+
+ var tv = new UITableView(RectangleF.Empty, style);
+ _originalBackgroundView = tv.BackgroundView;
+
+ SetNativeControl(tv);
+ if (Forms.IsiOS9OrNewer)
+ tv.CellLayoutMarginsFollowReadableWidth = false;
+
+ _insetTracker = new KeyboardInsetTracker(tv, () => Control.Window, insets => Control.ContentInset = Control.ScrollIndicatorInsets = insets, point =>
+ {
+ var offset = Control.ContentOffset;
+ offset.Y += point.Y;
+ Control.SetContentOffset(offset, true);
+ });
+ }
+
+ SetSource();
+ UpdateRowHeight();
+
+ UpdateBackgroundView();
+ }
+
+ base.OnElementChanged(e);
+ }
+
+ protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ base.OnElementPropertyChanged(sender, e);
+
+ if (e.PropertyName == "RowHeight")
+ UpdateRowHeight();
+ else if (e.PropertyName == "HasUnevenRows")
+ SetSource();
+ else if (e.PropertyName == "BackgroundColor")
+ UpdateBackgroundView();
+ }
+
+ protected override void UpdateNativeWidget()
+ {
+ if (Element.Opacity < 1)
+ {
+ if (!Control.Layer.ShouldRasterize)
+ {
+ Control.Layer.RasterizationScale = UIScreen.MainScreen.Scale;
+ Control.Layer.ShouldRasterize = true;
+ }
+ }
+ else
+ Control.Layer.ShouldRasterize = false;
+ base.UpdateNativeWidget();
+ }
+
+ void SetSource()
+ {
+ var modeledView = Element;
+ Control.Source = modeledView.HasUnevenRows ? new UnEvenTableViewModelRenderer(modeledView) : new TableViewModelRenderer(modeledView);
+ }
+
+ void UpdateBackgroundView()
+ {
+ Control.BackgroundView = Element.BackgroundColor == Color.Default ? _originalBackgroundView : null;
+ }
+
+ void UpdateRowHeight()
+ {
+ var height = Element.RowHeight;
+
+ if (height <= 0)
+ height = 44;
+
+ Control.RowHeight = height;
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.iOS/Renderers/TabletMasterDetailRenderer.cs b/Xamarin.Forms.Platform.iOS/Renderers/TabletMasterDetailRenderer.cs
new file mode 100644
index 00000000..3561322c
--- /dev/null
+++ b/Xamarin.Forms.Platform.iOS/Renderers/TabletMasterDetailRenderer.cs
@@ -0,0 +1,371 @@
+using System;
+using System.ComponentModel;
+#if __UNIFIED__
+using UIKit;
+
+#else
+using MonoTouch.UIKit;
+#endif
+
+namespace Xamarin.Forms.Platform.iOS
+{
+ internal class ChildViewController : UIViewController
+ {
+ public override void ViewDidLayoutSubviews()
+ {
+ foreach (var vc in ChildViewControllers)
+ vc.View.Frame = View.Bounds;
+ }
+ }
+
+ internal class EventedViewController : ChildViewController
+ {
+ public override void ViewWillAppear(bool animated)
+ {
+ base.ViewWillAppear(animated);
+
+ var eh = WillAppear;
+ if (eh != null)
+ eh(this, EventArgs.Empty);
+ }
+
+ public override void ViewWillDisappear(bool animated)
+ {
+ base.ViewWillDisappear(animated);
+
+ var eh = WillDisappear;
+ if (eh != null)
+ eh(this, EventArgs.Empty);
+ }
+
+ public event EventHandler WillAppear;
+
+ public event EventHandler WillDisappear;
+ }
+
+ public class TabletMasterDetailRenderer : UISplitViewController, IVisualElementRenderer
+ {
+ UIViewController _detailController;
+
+ bool _disposed;
+ EventTracker _events;
+ InnerDelegate _innerDelegate;
+
+ EventedViewController _masterController;
+
+ MasterDetailPage _masterDetailPage;
+
+ bool _masterVisible;
+
+ VisualElementTracker _tracker;
+
+ protected MasterDetailPage MasterDetailPage
+ {
+ get { return _masterDetailPage ?? (_masterDetailPage = (MasterDetailPage)Element); }
+ }
+
+ UIBarButtonItem PresentButton
+ {
+ get { return _innerDelegate == null ? null : _innerDelegate.PresentButton; }
+ }
+
+ public void Dispose()
+ {
+ if (_disposed)
+ return;
+
+ if (Element != null)
+ {
+ ((Page)Element).SendDisappearing();
+ Element.PropertyChanged -= HandlePropertyChanged;
+ Element = null;
+ }
+
+ if (_tracker != null)
+ {
+ _tracker.Dispose();
+ _tracker = null;
+ }
+
+ if (_events != null)
+ {
+ _events.Dispose();
+ _events = null;
+ }
+
+ if (_masterController != null)
+ {
+ _masterController.WillAppear -= MasterControllerWillAppear;
+ _masterController.WillDisappear -= MasterControllerWillDisappear;
+ }
+
+ _disposed = 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 UIView NativeView
+ {
+ get { return View; }
+ }
+
+ public void SetElement(VisualElement element)
+ {
+ var oldElement = Element;
+ Element = element;
+
+ ViewControllers = new[] { _masterController = new EventedViewController(), _detailController = new ChildViewController() };
+
+ Delegate = _innerDelegate = new InnerDelegate(MasterDetailPage.MasterBehavior);
+
+ UpdateControllers();
+
+ _masterController.WillAppear += MasterControllerWillAppear;
+ _masterController.WillDisappear += MasterControllerWillDisappear;
+
+ PresentsWithGesture = MasterDetailPage.IsGestureEnabled;
+
+ OnElementChanged(new VisualElementChangedEventArgs(oldElement, element));
+
+ if (element != null)
+ element.SendViewInitialized(NativeView);
+ }
+
+ public void SetElementSize(Size size)
+ {
+ Element.Layout(new Rectangle(Element.X, Element.Width, size.Width, size.Height));
+ }
+
+ public UIViewController ViewController
+ {
+ get { return this; }
+ }
+
+ public override void ViewDidAppear(bool animated)
+ {
+ ((Page)Element).SendAppearing();
+ base.ViewDidAppear(animated);
+ ToggleMaster();
+ }
+
+ public override void ViewDidDisappear(bool animated)
+ {
+ base.ViewDidDisappear(animated);
+ ((Page)Element).SendDisappearing();
+ }
+
+ public override void ViewDidLayoutSubviews()
+ {
+ base.ViewDidLayoutSubviews();
+
+ if (View.Subviews.Length < 2)
+ return;
+
+ var detailsBounds = _detailController.View.Frame;
+ var masterBounds = _masterController.View.Frame;
+
+ if (!masterBounds.IsEmpty)
+ MasterDetailPage.MasterBounds = new Rectangle(0, 0, masterBounds.Width, masterBounds.Height);
+
+ if (!detailsBounds.IsEmpty)
+ MasterDetailPage.DetailBounds = new Rectangle(0, 0, detailsBounds.Width, detailsBounds.Height);
+ }
+
+ public override void ViewDidLoad()
+ {
+ base.ViewDidLoad();
+ UpdateBackground();
+ _tracker = new VisualElementTracker(this);
+ _events = new EventTracker(this);
+ _events.LoadEvents(NativeView);
+ }
+
+ public override void ViewWillDisappear(bool animated)
+ {
+ if (_masterVisible)
+ PerformButtonSelector();
+
+ base.ViewWillDisappear(animated);
+ }
+
+ public override void ViewWillLayoutSubviews()
+ {
+ base.ViewWillLayoutSubviews();
+ _masterController.View.BackgroundColor = UIColor.White;
+ }
+
+ public override void WillRotate(UIInterfaceOrientation toInterfaceOrientation, double duration)
+ {
+ // On IOS8 the MasterViewController ViewAppear/Disappear weren't being called correctly after rotation
+ // We now close the Master by using the new SplitView API, basicly we set it to hidden and right back to the Normal/AutomaticMode
+ if (!MasterDetailPage.ShouldShowSplitMode && _masterVisible)
+ {
+ MasterDetailPage.CanChangeIsPresented = true;
+ if (Forms.IsiOS8OrNewer)
+ {
+ PreferredDisplayMode = UISplitViewControllerDisplayMode.PrimaryHidden;
+ PreferredDisplayMode = UISplitViewControllerDisplayMode.Automatic;
+ }
+ }
+ MasterDetailPage.UpdateMasterBehavior(MasterDetailPage);
+ MessagingCenter.Send<IVisualElementRenderer>(this, NavigationRenderer.UpdateToolbarButtons);
+ base.WillRotate(toInterfaceOrientation, duration);
+ }
+
+ protected virtual void OnElementChanged(VisualElementChangedEventArgs e)
+ {
+ if (e.OldElement != null)
+ e.OldElement.PropertyChanged -= HandlePropertyChanged;
+
+ if (e.NewElement != null)
+ e.NewElement.PropertyChanged += HandlePropertyChanged;
+
+ var changed = ElementChanged;
+ if (changed != null)
+ changed(this, e);
+ }
+
+ void ClearControllers()
+ {
+ foreach (var controller in _masterController.ChildViewControllers)
+ {
+ controller.View.RemoveFromSuperview();
+ controller.RemoveFromParentViewController();
+ }
+
+ foreach (var controller in _detailController.ChildViewControllers)
+ {
+ controller.View.RemoveFromSuperview();
+ controller.RemoveFromParentViewController();
+ }
+ }
+
+ void HandleMasterPropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ if (e.PropertyName == Page.IconProperty.PropertyName || e.PropertyName == Page.TitleProperty.PropertyName)
+ MessagingCenter.Send<IVisualElementRenderer>(this, NavigationRenderer.UpdateToolbarButtons);
+ }
+
+ void HandlePropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ if (_tracker == null)
+ return;
+
+ if (e.PropertyName == "Master" || e.PropertyName == "Detail")
+ UpdateControllers();
+ else if (e.PropertyName == MasterDetailPage.IsPresentedProperty.PropertyName)
+ ToggleMaster();
+ else if (e.PropertyName == MasterDetailPage.IsGestureEnabledProperty.PropertyName)
+ PresentsWithGesture = MasterDetailPage.IsGestureEnabled;
+ MessagingCenter.Send<IVisualElementRenderer>(this, NavigationRenderer.UpdateToolbarButtons);
+ }
+
+ void MasterControllerWillAppear(object sender, EventArgs e)
+ {
+ _masterVisible = true;
+ if (MasterDetailPage.CanChangeIsPresented)
+ ((IElementController)Element).SetValueFromRenderer(MasterDetailPage.IsPresentedProperty, true);
+ }
+
+ void MasterControllerWillDisappear(object sender, EventArgs e)
+ {
+ _masterVisible = false;
+ if (MasterDetailPage.CanChangeIsPresented)
+ ((IElementController)Element).SetValueFromRenderer(MasterDetailPage.IsPresentedProperty, false);
+ }
+
+ void PerformButtonSelector()
+ {
+ if (Forms.IsiOS8OrNewer)
+ DisplayModeButtonItem.Target.PerformSelector(DisplayModeButtonItem.Action, DisplayModeButtonItem, 0);
+ else
+ PresentButton.Target.PerformSelector(PresentButton.Action, PresentButton, 0);
+ }
+
+ void ToggleMaster()
+ {
+ if (_masterVisible == MasterDetailPage.IsPresented || MasterDetailPage.ShouldShowSplitMode)
+ return;
+
+ PerformButtonSelector();
+ }
+
+ void UpdateBackground()
+ {
+ if (!string.IsNullOrEmpty(((Page)Element).BackgroundImage))
+ View.BackgroundColor = UIColor.FromPatternImage(UIImage.FromBundle(((Page)Element).BackgroundImage));
+ else if (Element.BackgroundColor == Color.Default)
+ View.BackgroundColor = UIColor.White;
+ else
+ View.BackgroundColor = Element.BackgroundColor.ToUIColor();
+ }
+
+ void UpdateControllers()
+ {
+ MasterDetailPage.Master.PropertyChanged -= HandleMasterPropertyChanged;
+
+ if (Platform.GetRenderer(MasterDetailPage.Master) == null)
+ Platform.SetRenderer(MasterDetailPage.Master, Platform.CreateRenderer(MasterDetailPage.Master));
+ if (Platform.GetRenderer(MasterDetailPage.Detail) == null)
+ Platform.SetRenderer(MasterDetailPage.Detail, Platform.CreateRenderer(MasterDetailPage.Detail));
+
+ ClearControllers();
+
+ MasterDetailPage.Master.PropertyChanged += HandleMasterPropertyChanged;
+
+ var master = Platform.GetRenderer(MasterDetailPage.Master).ViewController;
+ var detail = Platform.GetRenderer(MasterDetailPage.Detail).ViewController;
+
+ _masterController.View.AddSubview(master.View);
+ _masterController.AddChildViewController(master);
+
+ _detailController.View.AddSubview(detail.View);
+ _detailController.AddChildViewController(detail);
+ }
+
+ class InnerDelegate : UISplitViewControllerDelegate
+ {
+ readonly MasterBehavior _masterPresentedDefaultState;
+
+ public InnerDelegate(MasterBehavior masterPresentedDefaultState)
+ {
+ _masterPresentedDefaultState = masterPresentedDefaultState;
+ }
+
+ public UIBarButtonItem PresentButton { get; set; }
+
+ public override bool ShouldHideViewController(UISplitViewController svc, UIViewController viewController, UIInterfaceOrientation inOrientation)
+ {
+ bool willHideViewController;
+ switch (_masterPresentedDefaultState)
+ {
+ case MasterBehavior.Split:
+ willHideViewController = false;
+ break;
+ case MasterBehavior.Popover:
+ willHideViewController = true;
+ break;
+ case MasterBehavior.SplitOnPortrait:
+ willHideViewController = !(inOrientation == UIInterfaceOrientation.Portrait || inOrientation == UIInterfaceOrientation.PortraitUpsideDown);
+ break;
+ default:
+ willHideViewController = inOrientation == UIInterfaceOrientation.Portrait || inOrientation == UIInterfaceOrientation.PortraitUpsideDown;
+ break;
+ }
+ return willHideViewController;
+ }
+
+ public override void WillHideViewController(UISplitViewController svc, UIViewController aViewController, UIBarButtonItem barButtonItem, UIPopoverController pc)
+ {
+ PresentButton = barButtonItem;
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.iOS/Renderers/TimePickerRenderer.cs b/Xamarin.Forms.Platform.iOS/Renderers/TimePickerRenderer.cs
new file mode 100644
index 00000000..85d3ad35
--- /dev/null
+++ b/Xamarin.Forms.Platform.iOS/Renderers/TimePickerRenderer.cs
@@ -0,0 +1,104 @@
+using System;
+using System.Drawing;
+using System.ComponentModel;
+#if __UNIFIED__
+using UIKit;
+using Foundation;
+#else
+using MonoTouch.UIKit;
+using MonoTouch.Foundation;
+#endif
+#if __UNIFIED__
+using RectangleF = CoreGraphics.CGRect;
+using SizeF = CoreGraphics.CGSize;
+using PointF = CoreGraphics.CGPoint;
+
+#else
+using nfloat=System.Single;
+using nint=System.Int32;
+using nuint=System.UInt32;
+#endif
+
+namespace Xamarin.Forms.Platform.iOS
+{
+ public class TimePickerRenderer : ViewRenderer<TimePicker, UITextField>
+ {
+ UIDatePicker _picker;
+
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ Control.Started -= OnStarted;
+ Control.Ended -= OnEnded;
+
+ _picker.ValueChanged -= OnValueChanged;
+ }
+
+ base.Dispose(disposing);
+ }
+
+ protected override void OnElementChanged(ElementChangedEventArgs<TimePicker> e)
+ {
+ if (e.NewElement != null)
+ {
+ if (Control == null)
+ {
+ var entry = new NoCaretField { BorderStyle = UITextBorderStyle.RoundedRect };
+
+ entry.Started += OnStarted;
+ entry.Ended += OnEnded;
+
+ _picker = new UIDatePicker { Mode = UIDatePickerMode.Time, TimeZone = new NSTimeZone("UTC") };
+
+ var width = UIScreen.MainScreen.Bounds.Width;
+ var toolbar = new UIToolbar(new RectangleF(0, 0, width, 44)) { BarStyle = UIBarStyle.Default, Translucent = true };
+ var spacer = new UIBarButtonItem(UIBarButtonSystemItem.FlexibleSpace);
+ var doneButton = new UIBarButtonItem(UIBarButtonSystemItem.Done, (o, a) => entry.ResignFirstResponder());
+
+ toolbar.SetItems(new[] { spacer, doneButton }, false);
+
+ entry.InputView = _picker;
+ entry.InputAccessoryView = toolbar;
+
+ _picker.ValueChanged += OnValueChanged;
+
+ SetNativeControl(entry);
+ }
+
+ UpdateTime();
+ }
+
+ base.OnElementChanged(e);
+ }
+
+ protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ base.OnElementPropertyChanged(sender, e);
+
+ if (e.PropertyName == TimePicker.TimeProperty.PropertyName || e.PropertyName == TimePicker.FormatProperty.PropertyName)
+ UpdateTime();
+ }
+
+ void OnEnded(object sender, EventArgs eventArgs)
+ {
+ ((IElementController)Element).SetValueFromRenderer(VisualElement.IsFocusedPropertyKey, false);
+ }
+
+ void OnStarted(object sender, EventArgs eventArgs)
+ {
+ ((IElementController)Element).SetValueFromRenderer(VisualElement.IsFocusedPropertyKey, true);
+ }
+
+ void OnValueChanged(object sender, EventArgs e)
+ {
+ ((IElementController)Element).SetValueFromRenderer(TimePicker.TimeProperty, _picker.Date.ToDateTime() - new DateTime(1, 1, 1));
+ }
+
+ void UpdateTime()
+ {
+ _picker.Date = new DateTime(1, 1, 1).Add(Element.Time).ToNSDate();
+ Control.Text = DateTime.Today.Add(Element.Time).ToString(Element.Format);
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.iOS/Renderers/ToolbarRenderer.cs b/Xamarin.Forms.Platform.iOS/Renderers/ToolbarRenderer.cs
new file mode 100644
index 00000000..705f95d8
--- /dev/null
+++ b/Xamarin.Forms.Platform.iOS/Renderers/ToolbarRenderer.cs
@@ -0,0 +1,66 @@
+using System.Linq;
+using System.Drawing;
+#if __UNIFIED__
+using UIKit;
+
+#else
+using MonoTouch.UIKit;
+#endif
+
+namespace Xamarin.Forms.Platform.iOS
+{
+ public class ToolbarRenderer : ViewRenderer
+ {
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ ((Toolbar)Element).ItemAdded -= OnItemAdded;
+ ((Toolbar)Element).ItemRemoved -= OnItemRemoved;
+
+ if (((UIToolbar)Control).Items != null)
+ {
+ foreach (var item in ((UIToolbar)Control).Items)
+ item.Dispose();
+ }
+ }
+
+ base.Dispose(disposing);
+ }
+
+ protected override void OnElementChanged(ElementChangedEventArgs<View> e)
+ {
+ base.OnElementChanged(e);
+
+ SetNativeControl(new UIToolbar(RectangleF.Empty));
+ UpdateItems(false);
+
+ ((Toolbar)Element).ItemAdded += OnItemAdded;
+ ((Toolbar)Element).ItemRemoved += OnItemRemoved;
+ }
+
+ void OnItemAdded(object sender, ToolbarItemEventArgs eventArg)
+ {
+ UpdateItems(true);
+ }
+
+ void OnItemRemoved(object sender, ToolbarItemEventArgs eventArg)
+ {
+ UpdateItems(true);
+ }
+
+ void UpdateItems(bool animated)
+ {
+ if (((UIToolbar)Control).Items != null)
+ {
+ for (var i = 0; i < ((UIToolbar)Control).Items.Length; i++)
+ ((UIToolbar)Control).Items[i].Dispose();
+ }
+ var items = new UIBarButtonItem[((Toolbar)Element).Items.Count];
+ for (var i = 0; i < ((Toolbar)Element).Items.Count; i++)
+ items[i] = ((Toolbar)Element).Items[i].ToUIBarButtonItem();
+
+ ((UIToolbar)Control).SetItems(items, animated);
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.iOS/Renderers/WebViewRenderer.cs b/Xamarin.Forms.Platform.iOS/Renderers/WebViewRenderer.cs
new file mode 100644
index 00000000..538bc2d0
--- /dev/null
+++ b/Xamarin.Forms.Platform.iOS/Renderers/WebViewRenderer.cs
@@ -0,0 +1,270 @@
+using System;
+using System.Drawing;
+using System.ComponentModel;
+#if __UNIFIED__
+using UIKit;
+using Foundation;
+
+#else
+using MonoTouch.UIKit;
+using MonoTouch.Foundation;
+#endif
+
+namespace Xamarin.Forms.Platform.iOS
+{
+ public class WebViewRenderer : UIWebView, IVisualElementRenderer, IWebViewRenderer
+ {
+ EventTracker _events;
+ bool _ignoreSourceChanges;
+ WebNavigationEvent _lastBackForwardEvent;
+ VisualElementPackager _packager;
+
+ VisualElementTracker _tracker;
+
+ public WebViewRenderer() : base(RectangleF.Empty)
+ {
+ }
+
+ 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 void SetElement(VisualElement element)
+ {
+ var oldElement = Element;
+ Element = element;
+ Element.PropertyChanged += HandlePropertyChanged;
+ ((WebView)Element).EvalRequested += OnEvalRequested;
+ ((WebView)Element).GoBackRequested += OnGoBackRequested;
+ ((WebView)Element).GoForwardRequested += OnGoForwardRequested;
+ Delegate = new CustomWebViewDelegate(this);
+
+ BackgroundColor = UIColor.Clear;
+
+ AutosizesSubviews = true;
+ AutoresizingMask = UIViewAutoresizing.FlexibleWidth | UIViewAutoresizing.FlexibleHeight;
+
+ _tracker = new VisualElementTracker(this);
+
+ _packager = new VisualElementPackager(this);
+ _packager.Load();
+
+ _events = new EventTracker(this);
+ _events.LoadEvents(this);
+
+ Load();
+
+ OnElementChanged(new VisualElementChangedEventArgs(oldElement, element));
+
+ if (Element != null && !string.IsNullOrEmpty(Element.AutomationId))
+ AccessibilityIdentifier = Element.AutomationId;
+
+ if (element != null)
+ element.SendViewInitialized(this);
+
+ if (Element != null && !string.IsNullOrEmpty(Element.AutomationId))
+ AccessibilityIdentifier = Element.AutomationId;
+ }
+
+ public void SetElementSize(Size size)
+ {
+ Layout.LayoutChildIntoBoundingRegion(Element, new Rectangle(Element.X, Element.Y, size.Width, size.Height));
+ }
+
+ public void LoadHtml(string html, string baseUrl)
+ {
+ if (html != null)
+ LoadHtmlString(html, baseUrl == null ? new NSUrl(NSBundle.MainBundle.BundlePath, true) : new NSUrl(baseUrl, true));
+ }
+
+ public void LoadUrl(string url)
+ {
+ LoadRequest(new NSUrlRequest(new NSUrl(url)));
+ }
+
+ public override void LayoutSubviews()
+ {
+ base.LayoutSubviews();
+
+ // ensure that inner scrollview properly resizes when frame of webview updated
+ ScrollView.Frame = Bounds;
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ if (IsLoading)
+ StopLoading();
+
+ Element.PropertyChanged -= HandlePropertyChanged;
+ ((WebView)Element).EvalRequested -= OnEvalRequested;
+ ((WebView)Element).GoBackRequested -= OnGoBackRequested;
+ ((WebView)Element).GoForwardRequested -= OnGoForwardRequested;
+ }
+
+ base.Dispose(disposing);
+ }
+
+ protected virtual void OnElementChanged(VisualElementChangedEventArgs e)
+ {
+ var changed = ElementChanged;
+ if (changed != null)
+ changed(this, e);
+ }
+
+ void HandlePropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ if (e.PropertyName == WebView.SourceProperty.PropertyName)
+ Load();
+ }
+
+ void Load()
+ {
+ if (_ignoreSourceChanges)
+ return;
+
+ if (((WebView)Element).Source != null)
+ ((WebView)Element).Source.Load(this);
+
+ UpdateCanGoBackForward();
+ }
+
+ void OnEvalRequested(object sender, EventArg<string> eventArg)
+ {
+ EvaluateJavascript(eventArg.Data);
+ }
+
+ void OnGoBackRequested(object sender, EventArgs eventArgs)
+ {
+ if (CanGoBack)
+ {
+ _lastBackForwardEvent = WebNavigationEvent.Back;
+ GoBack();
+ }
+
+ UpdateCanGoBackForward();
+ }
+
+ void OnGoForwardRequested(object sender, EventArgs eventArgs)
+ {
+ if (CanGoForward)
+ {
+ _lastBackForwardEvent = WebNavigationEvent.Forward;
+ GoForward();
+ }
+
+ UpdateCanGoBackForward();
+ }
+
+ void UpdateCanGoBackForward()
+ {
+ ((WebView)Element).CanGoBack = CanGoBack;
+ ((WebView)Element).CanGoForward = CanGoForward;
+ }
+
+ class CustomWebViewDelegate : UIWebViewDelegate
+ {
+ readonly WebViewRenderer _renderer;
+ WebNavigationEvent _lastEvent;
+
+ public CustomWebViewDelegate(WebViewRenderer renderer)
+ {
+ if (renderer == null)
+ throw new ArgumentNullException("renderer");
+ _renderer = renderer;
+ }
+
+ WebView WebView
+ {
+ get { return (WebView)_renderer.Element; }
+ }
+
+ public override void LoadFailed(UIWebView webView, NSError error)
+ {
+ var url = GetCurrentUrl();
+ WebView.SendNavigated(new WebNavigatedEventArgs(_lastEvent, new UrlWebViewSource { Url = url }, url, WebNavigationResult.Failure));
+
+ _renderer.UpdateCanGoBackForward();
+ }
+
+ public override void LoadingFinished(UIWebView webView)
+ {
+ if (webView.IsLoading)
+ return;
+
+ _renderer._ignoreSourceChanges = true;
+ var url = GetCurrentUrl();
+ ((IElementController)WebView).SetValueFromRenderer(WebView.SourceProperty, new UrlWebViewSource { Url = url });
+ _renderer._ignoreSourceChanges = false;
+
+ var args = new WebNavigatedEventArgs(_lastEvent, WebView.Source, url, WebNavigationResult.Success);
+ WebView.SendNavigated(args);
+
+ _renderer.UpdateCanGoBackForward();
+ }
+
+ public override void LoadStarted(UIWebView webView)
+ {
+ }
+
+ public override bool ShouldStartLoad(UIWebView webView, NSUrlRequest request, UIWebViewNavigationType navigationType)
+ {
+ var navEvent = WebNavigationEvent.NewPage;
+ switch (navigationType)
+ {
+ case UIWebViewNavigationType.LinkClicked:
+ navEvent = WebNavigationEvent.NewPage;
+ break;
+ case UIWebViewNavigationType.FormSubmitted:
+ navEvent = WebNavigationEvent.NewPage;
+ break;
+ case UIWebViewNavigationType.BackForward:
+ navEvent = _renderer._lastBackForwardEvent;
+ break;
+ case UIWebViewNavigationType.Reload:
+ navEvent = WebNavigationEvent.Refresh;
+ break;
+ case UIWebViewNavigationType.FormResubmitted:
+ navEvent = WebNavigationEvent.NewPage;
+ break;
+ case UIWebViewNavigationType.Other:
+ navEvent = WebNavigationEvent.NewPage;
+ break;
+ }
+
+ _lastEvent = navEvent;
+ var lastUrl = request.Url.ToString();
+ var args = new WebNavigatingEventArgs(navEvent, new UrlWebViewSource { Url = lastUrl }, lastUrl);
+
+ WebView.SendNavigating(args);
+ _renderer.UpdateCanGoBackForward();
+ return !args.Cancel;
+ }
+
+ string GetCurrentUrl()
+ {
+ return _renderer?.Request?.Url?.AbsoluteUrl?.ToString();
+ }
+ }
+
+ #region IPlatformRenderer implementation
+
+ public UIView NativeView
+ {
+ get { return this; }
+ }
+
+ public UIViewController ViewController
+ {
+ get { return null; }
+ }
+
+ #endregion
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.iOS/Resources/StringResources.Designer.cs b/Xamarin.Forms.Platform.iOS/Resources/StringResources.Designer.cs
new file mode 100644
index 00000000..096da604
--- /dev/null
+++ b/Xamarin.Forms.Platform.iOS/Resources/StringResources.Designer.cs
@@ -0,0 +1,81 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+// This code was generated by a tool.
+// Runtime Version:4.0.30319.42000
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace Xamarin.Forms.Platform.iOS.Resources {
+ using System;
+
+
+ /// <summary>
+ /// A strongly-typed resource class, for looking up localized strings, etc.
+ /// </summary>
+ // This class was auto-generated by the StronglyTypedResourceBuilder
+ // class via a tool like ResGen or Visual Studio.
+ // To add or remove a member, edit your .ResX file then rerun ResGen
+ // with the /str option, or rebuild your VS project.
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ internal class StringResources {
+
+ private static global::System.Resources.ResourceManager resourceMan;
+
+ private static global::System.Globalization.CultureInfo resourceCulture;
+
+ [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ internal StringResources() {
+ }
+
+ /// <summary>
+ /// Returns the cached ResourceManager instance used by this class.
+ /// </summary>
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Resources.ResourceManager ResourceManager {
+ get {
+ if (object.ReferenceEquals(resourceMan, null)) {
+ global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Xamarin.Forms.Platform.iOS.Resources.StringResources", typeof(StringResources).Assembly);
+ resourceMan = temp;
+ }
+ return resourceMan;
+ }
+ }
+
+ /// <summary>
+ /// Overrides the current thread's CurrentUICulture property for all
+ /// resource lookups using this strongly typed resource class.
+ /// </summary>
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Globalization.CultureInfo Culture {
+ get {
+ return resourceCulture;
+ }
+ set {
+ resourceCulture = value;
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Cancel.
+ /// </summary>
+ internal static string Cancel {
+ get {
+ return ResourceManager.GetString("Cancel", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to More.
+ /// </summary>
+ internal static string More {
+ get {
+ return ResourceManager.GetString("More", resourceCulture);
+ }
+ }
+ }
+}
diff --git a/Xamarin.Forms.Platform.iOS/Resources/StringResources.ar.resx b/Xamarin.Forms.Platform.iOS/Resources/StringResources.ar.resx
new file mode 100644
index 00000000..2382ee59
--- /dev/null
+++ b/Xamarin.Forms.Platform.iOS/Resources/StringResources.ar.resx
@@ -0,0 +1,128 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+ <!--
+ Microsoft ResX Schema
+
+ Version 2.0
+
+ The primary goals of this format is to allow a simple XML format
+ that is mostly human readable. The generation and parsing of the
+ various data types are done through the TypeConverter classes
+ associated with the data types.
+
+ Example:
+
+ ... ado.net/XML headers & schema ...
+ <resheader name="resmimetype">text/microsoft-resx</resheader>
+ <resheader name="version">2.0</resheader>
+ <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+ <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+ <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+ <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+ <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+ <value>[base64 mime encoded serialized .NET Framework object]</value>
+ </data>
+ <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+ <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+ <comment>This is a comment</comment>
+ </data>
+
+ There are any number of "resheader" rows that contain simple
+ name/value pairs.
+
+ Each data row contains a name, and value. The row also contains a
+ type or mimetype. Type corresponds to a .NET class that support
+ text/value conversion through the TypeConverter architecture.
+ Classes that don't support this are serialized and stored with the
+ mimetype set.
+
+ The mimetype is used for serialized objects, and tells the
+ ResXResourceReader how to depersist the object. This is currently not
+ extensible. For a given mimetype the value must be set accordingly:
+
+ Note - application/x-microsoft.net.object.binary.base64 is the format
+ that the ResXResourceWriter will generate, however the reader can
+ read any of the formats listed below.
+
+ mimetype: application/x-microsoft.net.object.binary.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.soap.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.bytearray.base64
+ value : The object must be serialized into a byte array
+ : using a System.ComponentModel.TypeConverter
+ : and then encoded with base64 encoding.
+ -->
+ <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+ <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+ <xsd:element name="root" msdata:IsDataSet="true">
+ <xsd:complexType>
+ <xsd:choice maxOccurs="unbounded">
+ <xsd:element name="metadata">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" />
+ </xsd:sequence>
+ <xsd:attribute name="name" use="required" type="xsd:string" />
+ <xsd:attribute name="type" type="xsd:string" />
+ <xsd:attribute name="mimetype" type="xsd:string" />
+ <xsd:attribute ref="xml:space" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="assembly">
+ <xsd:complexType>
+ <xsd:attribute name="alias" type="xsd:string" />
+ <xsd:attribute name="name" type="xsd:string" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="data">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+ <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+ <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+ <xsd:attribute ref="xml:space" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="resheader">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" />
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:choice>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:schema>
+ <resheader name="resmimetype">
+ <value>text/microsoft-resx</value>
+ </resheader>
+ <resheader name="version">
+ <value>2.0</value>
+ </resheader>
+ <resheader name="reader">
+ <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <resheader name="writer">
+ <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <data name="Cancel" xml:space="preserve">
+ <value>إلخاء</value>
+ <comment>ContextActionCell Cancel button</comment>
+ </data>
+ <data name="More" xml:space="preserve">
+ <value>إضافي</value>
+ <comment>ContextActionCell More button</comment>
+ </data>
+</root> \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.iOS/Resources/StringResources.ca.resx b/Xamarin.Forms.Platform.iOS/Resources/StringResources.ca.resx
new file mode 100644
index 00000000..f8c683b4
--- /dev/null
+++ b/Xamarin.Forms.Platform.iOS/Resources/StringResources.ca.resx
@@ -0,0 +1,128 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+ <!--
+ Microsoft ResX Schema
+
+ Version 2.0
+
+ The primary goals of this format is to allow a simple XML format
+ that is mostly human readable. The generation and parsing of the
+ various data types are done through the TypeConverter classes
+ associated with the data types.
+
+ Example:
+
+ ... ado.net/XML headers & schema ...
+ <resheader name="resmimetype">text/microsoft-resx</resheader>
+ <resheader name="version">2.0</resheader>
+ <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+ <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+ <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+ <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+ <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+ <value>[base64 mime encoded serialized .NET Framework object]</value>
+ </data>
+ <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+ <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+ <comment>This is a comment</comment>
+ </data>
+
+ There are any number of "resheader" rows that contain simple
+ name/value pairs.
+
+ Each data row contains a name, and value. The row also contains a
+ type or mimetype. Type corresponds to a .NET class that support
+ text/value conversion through the TypeConverter architecture.
+ Classes that don't support this are serialized and stored with the
+ mimetype set.
+
+ The mimetype is used for serialized objects, and tells the
+ ResXResourceReader how to depersist the object. This is currently not
+ extensible. For a given mimetype the value must be set accordingly:
+
+ Note - application/x-microsoft.net.object.binary.base64 is the format
+ that the ResXResourceWriter will generate, however the reader can
+ read any of the formats listed below.
+
+ mimetype: application/x-microsoft.net.object.binary.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.soap.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.bytearray.base64
+ value : The object must be serialized into a byte array
+ : using a System.ComponentModel.TypeConverter
+ : and then encoded with base64 encoding.
+ -->
+ <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+ <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+ <xsd:element name="root" msdata:IsDataSet="true">
+ <xsd:complexType>
+ <xsd:choice maxOccurs="unbounded">
+ <xsd:element name="metadata">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" />
+ </xsd:sequence>
+ <xsd:attribute name="name" use="required" type="xsd:string" />
+ <xsd:attribute name="type" type="xsd:string" />
+ <xsd:attribute name="mimetype" type="xsd:string" />
+ <xsd:attribute ref="xml:space" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="assembly">
+ <xsd:complexType>
+ <xsd:attribute name="alias" type="xsd:string" />
+ <xsd:attribute name="name" type="xsd:string" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="data">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+ <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+ <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+ <xsd:attribute ref="xml:space" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="resheader">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" />
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:choice>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:schema>
+ <resheader name="resmimetype">
+ <value>text/microsoft-resx</value>
+ </resheader>
+ <resheader name="version">
+ <value>2.0</value>
+ </resheader>
+ <resheader name="reader">
+ <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <resheader name="writer">
+ <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <data name="Cancel" xml:space="preserve">
+ <value>Cancel·lar</value>
+ <comment>ContextActionCell Cancel button</comment>
+ </data>
+ <data name="More" xml:space="preserve">
+ <value>Més</value>
+ <comment>ContextActionCell More button</comment>
+ </data>
+</root> \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.iOS/Resources/StringResources.cs.resx b/Xamarin.Forms.Platform.iOS/Resources/StringResources.cs.resx
new file mode 100644
index 00000000..55c6946a
--- /dev/null
+++ b/Xamarin.Forms.Platform.iOS/Resources/StringResources.cs.resx
@@ -0,0 +1,128 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+ <!--
+ Microsoft ResX Schema
+
+ Version 2.0
+
+ The primary goals of this format is to allow a simple XML format
+ that is mostly human readable. The generation and parsing of the
+ various data types are done through the TypeConverter classes
+ associated with the data types.
+
+ Example:
+
+ ... ado.net/XML headers & schema ...
+ <resheader name="resmimetype">text/microsoft-resx</resheader>
+ <resheader name="version">2.0</resheader>
+ <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+ <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+ <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+ <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+ <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+ <value>[base64 mime encoded serialized .NET Framework object]</value>
+ </data>
+ <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+ <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+ <comment>This is a comment</comment>
+ </data>
+
+ There are any number of "resheader" rows that contain simple
+ name/value pairs.
+
+ Each data row contains a name, and value. The row also contains a
+ type or mimetype. Type corresponds to a .NET class that support
+ text/value conversion through the TypeConverter architecture.
+ Classes that don't support this are serialized and stored with the
+ mimetype set.
+
+ The mimetype is used for serialized objects, and tells the
+ ResXResourceReader how to depersist the object. This is currently not
+ extensible. For a given mimetype the value must be set accordingly:
+
+ Note - application/x-microsoft.net.object.binary.base64 is the format
+ that the ResXResourceWriter will generate, however the reader can
+ read any of the formats listed below.
+
+ mimetype: application/x-microsoft.net.object.binary.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.soap.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.bytearray.base64
+ value : The object must be serialized into a byte array
+ : using a System.ComponentModel.TypeConverter
+ : and then encoded with base64 encoding.
+ -->
+ <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+ <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+ <xsd:element name="root" msdata:IsDataSet="true">
+ <xsd:complexType>
+ <xsd:choice maxOccurs="unbounded">
+ <xsd:element name="metadata">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" />
+ </xsd:sequence>
+ <xsd:attribute name="name" use="required" type="xsd:string" />
+ <xsd:attribute name="type" type="xsd:string" />
+ <xsd:attribute name="mimetype" type="xsd:string" />
+ <xsd:attribute ref="xml:space" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="assembly">
+ <xsd:complexType>
+ <xsd:attribute name="alias" type="xsd:string" />
+ <xsd:attribute name="name" type="xsd:string" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="data">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+ <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+ <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+ <xsd:attribute ref="xml:space" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="resheader">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" />
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:choice>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:schema>
+ <resheader name="resmimetype">
+ <value>text/microsoft-resx</value>
+ </resheader>
+ <resheader name="version">
+ <value>2.0</value>
+ </resheader>
+ <resheader name="reader">
+ <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <resheader name="writer">
+ <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <data name="Cancel" xml:space="preserve">
+ <value>Zrušit</value>
+ <comment>ContextActionCell Cancel button</comment>
+ </data>
+ <data name="More" xml:space="preserve">
+ <value>Další</value>
+ <comment>ContextActionCell More button</comment>
+ </data>
+</root> \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.iOS/Resources/StringResources.da.resx b/Xamarin.Forms.Platform.iOS/Resources/StringResources.da.resx
new file mode 100644
index 00000000..0ff5a6fa
--- /dev/null
+++ b/Xamarin.Forms.Platform.iOS/Resources/StringResources.da.resx
@@ -0,0 +1,128 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+ <!--
+ Microsoft ResX Schema
+
+ Version 2.0
+
+ The primary goals of this format is to allow a simple XML format
+ that is mostly human readable. The generation and parsing of the
+ various data types are done through the TypeConverter classes
+ associated with the data types.
+
+ Example:
+
+ ... ado.net/XML headers & schema ...
+ <resheader name="resmimetype">text/microsoft-resx</resheader>
+ <resheader name="version">2.0</resheader>
+ <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+ <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+ <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+ <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+ <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+ <value>[base64 mime encoded serialized .NET Framework object]</value>
+ </data>
+ <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+ <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+ <comment>This is a comment</comment>
+ </data>
+
+ There are any number of "resheader" rows that contain simple
+ name/value pairs.
+
+ Each data row contains a name, and value. The row also contains a
+ type or mimetype. Type corresponds to a .NET class that support
+ text/value conversion through the TypeConverter architecture.
+ Classes that don't support this are serialized and stored with the
+ mimetype set.
+
+ The mimetype is used for serialized objects, and tells the
+ ResXResourceReader how to depersist the object. This is currently not
+ extensible. For a given mimetype the value must be set accordingly:
+
+ Note - application/x-microsoft.net.object.binary.base64 is the format
+ that the ResXResourceWriter will generate, however the reader can
+ read any of the formats listed below.
+
+ mimetype: application/x-microsoft.net.object.binary.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.soap.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.bytearray.base64
+ value : The object must be serialized into a byte array
+ : using a System.ComponentModel.TypeConverter
+ : and then encoded with base64 encoding.
+ -->
+ <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+ <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+ <xsd:element name="root" msdata:IsDataSet="true">
+ <xsd:complexType>
+ <xsd:choice maxOccurs="unbounded">
+ <xsd:element name="metadata">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" />
+ </xsd:sequence>
+ <xsd:attribute name="name" use="required" type="xsd:string" />
+ <xsd:attribute name="type" type="xsd:string" />
+ <xsd:attribute name="mimetype" type="xsd:string" />
+ <xsd:attribute ref="xml:space" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="assembly">
+ <xsd:complexType>
+ <xsd:attribute name="alias" type="xsd:string" />
+ <xsd:attribute name="name" type="xsd:string" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="data">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+ <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+ <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+ <xsd:attribute ref="xml:space" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="resheader">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" />
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:choice>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:schema>
+ <resheader name="resmimetype">
+ <value>text/microsoft-resx</value>
+ </resheader>
+ <resheader name="version">
+ <value>2.0</value>
+ </resheader>
+ <resheader name="reader">
+ <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <resheader name="writer">
+ <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <data name="Cancel" xml:space="preserve">
+ <value>Annuller</value>
+ <comment>ContextActionCell Cancel button</comment>
+ </data>
+ <data name="More" xml:space="preserve">
+ <value>Mere</value>
+ <comment>ContextActionCell More button</comment>
+ </data>
+</root> \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.iOS/Resources/StringResources.de.resx b/Xamarin.Forms.Platform.iOS/Resources/StringResources.de.resx
new file mode 100644
index 00000000..fa10a5a3
--- /dev/null
+++ b/Xamarin.Forms.Platform.iOS/Resources/StringResources.de.resx
@@ -0,0 +1,128 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+ <!--
+ Microsoft ResX Schema
+
+ Version 2.0
+
+ The primary goals of this format is to allow a simple XML format
+ that is mostly human readable. The generation and parsing of the
+ various data types are done through the TypeConverter classes
+ associated with the data types.
+
+ Example:
+
+ ... ado.net/XML headers & schema ...
+ <resheader name="resmimetype">text/microsoft-resx</resheader>
+ <resheader name="version">2.0</resheader>
+ <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+ <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+ <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+ <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+ <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+ <value>[base64 mime encoded serialized .NET Framework object]</value>
+ </data>
+ <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+ <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+ <comment>This is a comment</comment>
+ </data>
+
+ There are any number of "resheader" rows that contain simple
+ name/value pairs.
+
+ Each data row contains a name, and value. The row also contains a
+ type or mimetype. Type corresponds to a .NET class that support
+ text/value conversion through the TypeConverter architecture.
+ Classes that don't support this are serialized and stored with the
+ mimetype set.
+
+ The mimetype is used for serialized objects, and tells the
+ ResXResourceReader how to depersist the object. This is currently not
+ extensible. For a given mimetype the value must be set accordingly:
+
+ Note - application/x-microsoft.net.object.binary.base64 is the format
+ that the ResXResourceWriter will generate, however the reader can
+ read any of the formats listed below.
+
+ mimetype: application/x-microsoft.net.object.binary.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.soap.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.bytearray.base64
+ value : The object must be serialized into a byte array
+ : using a System.ComponentModel.TypeConverter
+ : and then encoded with base64 encoding.
+ -->
+ <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+ <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+ <xsd:element name="root" msdata:IsDataSet="true">
+ <xsd:complexType>
+ <xsd:choice maxOccurs="unbounded">
+ <xsd:element name="metadata">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" />
+ </xsd:sequence>
+ <xsd:attribute name="name" use="required" type="xsd:string" />
+ <xsd:attribute name="type" type="xsd:string" />
+ <xsd:attribute name="mimetype" type="xsd:string" />
+ <xsd:attribute ref="xml:space" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="assembly">
+ <xsd:complexType>
+ <xsd:attribute name="alias" type="xsd:string" />
+ <xsd:attribute name="name" type="xsd:string" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="data">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+ <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+ <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+ <xsd:attribute ref="xml:space" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="resheader">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" />
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:choice>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:schema>
+ <resheader name="resmimetype">
+ <value>text/microsoft-resx</value>
+ </resheader>
+ <resheader name="version">
+ <value>2.0</value>
+ </resheader>
+ <resheader name="reader">
+ <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <resheader name="writer">
+ <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <data name="Cancel" xml:space="preserve">
+ <value>Abbrechen</value>
+ <comment>ContextActionCell Cancel button</comment>
+ </data>
+ <data name="More" xml:space="preserve">
+ <value>Mehr</value>
+ <comment>ContextActionCell More button</comment>
+ </data>
+</root> \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.iOS/Resources/StringResources.el.resx b/Xamarin.Forms.Platform.iOS/Resources/StringResources.el.resx
new file mode 100644
index 00000000..d360a038
--- /dev/null
+++ b/Xamarin.Forms.Platform.iOS/Resources/StringResources.el.resx
@@ -0,0 +1,128 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+ <!--
+ Microsoft ResX Schema
+
+ Version 2.0
+
+ The primary goals of this format is to allow a simple XML format
+ that is mostly human readable. The generation and parsing of the
+ various data types are done through the TypeConverter classes
+ associated with the data types.
+
+ Example:
+
+ ... ado.net/XML headers & schema ...
+ <resheader name="resmimetype">text/microsoft-resx</resheader>
+ <resheader name="version">2.0</resheader>
+ <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+ <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+ <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+ <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+ <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+ <value>[base64 mime encoded serialized .NET Framework object]</value>
+ </data>
+ <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+ <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+ <comment>This is a comment</comment>
+ </data>
+
+ There are any number of "resheader" rows that contain simple
+ name/value pairs.
+
+ Each data row contains a name, and value. The row also contains a
+ type or mimetype. Type corresponds to a .NET class that support
+ text/value conversion through the TypeConverter architecture.
+ Classes that don't support this are serialized and stored with the
+ mimetype set.
+
+ The mimetype is used for serialized objects, and tells the
+ ResXResourceReader how to depersist the object. This is currently not
+ extensible. For a given mimetype the value must be set accordingly:
+
+ Note - application/x-microsoft.net.object.binary.base64 is the format
+ that the ResXResourceWriter will generate, however the reader can
+ read any of the formats listed below.
+
+ mimetype: application/x-microsoft.net.object.binary.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.soap.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.bytearray.base64
+ value : The object must be serialized into a byte array
+ : using a System.ComponentModel.TypeConverter
+ : and then encoded with base64 encoding.
+ -->
+ <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+ <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+ <xsd:element name="root" msdata:IsDataSet="true">
+ <xsd:complexType>
+ <xsd:choice maxOccurs="unbounded">
+ <xsd:element name="metadata">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" />
+ </xsd:sequence>
+ <xsd:attribute name="name" use="required" type="xsd:string" />
+ <xsd:attribute name="type" type="xsd:string" />
+ <xsd:attribute name="mimetype" type="xsd:string" />
+ <xsd:attribute ref="xml:space" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="assembly">
+ <xsd:complexType>
+ <xsd:attribute name="alias" type="xsd:string" />
+ <xsd:attribute name="name" type="xsd:string" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="data">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+ <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+ <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+ <xsd:attribute ref="xml:space" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="resheader">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" />
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:choice>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:schema>
+ <resheader name="resmimetype">
+ <value>text/microsoft-resx</value>
+ </resheader>
+ <resheader name="version">
+ <value>2.0</value>
+ </resheader>
+ <resheader name="reader">
+ <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <resheader name="writer">
+ <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <data name="Cancel" xml:space="preserve">
+ <value>Ακύρωση</value>
+ <comment>ContextActionCell Cancel button</comment>
+ </data>
+ <data name="More" xml:space="preserve">
+ <value>Λοιπά</value>
+ <comment>ContextActionCell More button</comment>
+ </data>
+</root> \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.iOS/Resources/StringResources.es.resx b/Xamarin.Forms.Platform.iOS/Resources/StringResources.es.resx
new file mode 100644
index 00000000..a8bc4fd8
--- /dev/null
+++ b/Xamarin.Forms.Platform.iOS/Resources/StringResources.es.resx
@@ -0,0 +1,128 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+ <!--
+ Microsoft ResX Schema
+
+ Version 2.0
+
+ The primary goals of this format is to allow a simple XML format
+ that is mostly human readable. The generation and parsing of the
+ various data types are done through the TypeConverter classes
+ associated with the data types.
+
+ Example:
+
+ ... ado.net/XML headers & schema ...
+ <resheader name="resmimetype">text/microsoft-resx</resheader>
+ <resheader name="version">2.0</resheader>
+ <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+ <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+ <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+ <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+ <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+ <value>[base64 mime encoded serialized .NET Framework object]</value>
+ </data>
+ <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+ <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+ <comment>This is a comment</comment>
+ </data>
+
+ There are any number of "resheader" rows that contain simple
+ name/value pairs.
+
+ Each data row contains a name, and value. The row also contains a
+ type or mimetype. Type corresponds to a .NET class that support
+ text/value conversion through the TypeConverter architecture.
+ Classes that don't support this are serialized and stored with the
+ mimetype set.
+
+ The mimetype is used for serialized objects, and tells the
+ ResXResourceReader how to depersist the object. This is currently not
+ extensible. For a given mimetype the value must be set accordingly:
+
+ Note - application/x-microsoft.net.object.binary.base64 is the format
+ that the ResXResourceWriter will generate, however the reader can
+ read any of the formats listed below.
+
+ mimetype: application/x-microsoft.net.object.binary.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.soap.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.bytearray.base64
+ value : The object must be serialized into a byte array
+ : using a System.ComponentModel.TypeConverter
+ : and then encoded with base64 encoding.
+ -->
+ <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+ <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+ <xsd:element name="root" msdata:IsDataSet="true">
+ <xsd:complexType>
+ <xsd:choice maxOccurs="unbounded">
+ <xsd:element name="metadata">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" />
+ </xsd:sequence>
+ <xsd:attribute name="name" use="required" type="xsd:string" />
+ <xsd:attribute name="type" type="xsd:string" />
+ <xsd:attribute name="mimetype" type="xsd:string" />
+ <xsd:attribute ref="xml:space" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="assembly">
+ <xsd:complexType>
+ <xsd:attribute name="alias" type="xsd:string" />
+ <xsd:attribute name="name" type="xsd:string" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="data">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+ <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+ <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+ <xsd:attribute ref="xml:space" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="resheader">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" />
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:choice>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:schema>
+ <resheader name="resmimetype">
+ <value>text/microsoft-resx</value>
+ </resheader>
+ <resheader name="version">
+ <value>2.0</value>
+ </resheader>
+ <resheader name="reader">
+ <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <resheader name="writer">
+ <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <data name="Cancel" xml:space="preserve">
+ <value>Cancelar</value>
+ <comment>ContextActionCell Cancel button</comment>
+ </data>
+ <data name="More" xml:space="preserve">
+ <value>Más</value>
+ <comment>ContextActionCell More button</comment>
+ </data>
+</root> \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.iOS/Resources/StringResources.fi.resx b/Xamarin.Forms.Platform.iOS/Resources/StringResources.fi.resx
new file mode 100644
index 00000000..0244fc71
--- /dev/null
+++ b/Xamarin.Forms.Platform.iOS/Resources/StringResources.fi.resx
@@ -0,0 +1,128 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+ <!--
+ Microsoft ResX Schema
+
+ Version 2.0
+
+ The primary goals of this format is to allow a simple XML format
+ that is mostly human readable. The generation and parsing of the
+ various data types are done through the TypeConverter classes
+ associated with the data types.
+
+ Example:
+
+ ... ado.net/XML headers & schema ...
+ <resheader name="resmimetype">text/microsoft-resx</resheader>
+ <resheader name="version">2.0</resheader>
+ <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+ <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+ <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+ <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+ <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+ <value>[base64 mime encoded serialized .NET Framework object]</value>
+ </data>
+ <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+ <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+ <comment>This is a comment</comment>
+ </data>
+
+ There are any number of "resheader" rows that contain simple
+ name/value pairs.
+
+ Each data row contains a name, and value. The row also contains a
+ type or mimetype. Type corresponds to a .NET class that support
+ text/value conversion through the TypeConverter architecture.
+ Classes that don't support this are serialized and stored with the
+ mimetype set.
+
+ The mimetype is used for serialized objects, and tells the
+ ResXResourceReader how to depersist the object. This is currently not
+ extensible. For a given mimetype the value must be set accordingly:
+
+ Note - application/x-microsoft.net.object.binary.base64 is the format
+ that the ResXResourceWriter will generate, however the reader can
+ read any of the formats listed below.
+
+ mimetype: application/x-microsoft.net.object.binary.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.soap.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.bytearray.base64
+ value : The object must be serialized into a byte array
+ : using a System.ComponentModel.TypeConverter
+ : and then encoded with base64 encoding.
+ -->
+ <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+ <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+ <xsd:element name="root" msdata:IsDataSet="true">
+ <xsd:complexType>
+ <xsd:choice maxOccurs="unbounded">
+ <xsd:element name="metadata">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" />
+ </xsd:sequence>
+ <xsd:attribute name="name" use="required" type="xsd:string" />
+ <xsd:attribute name="type" type="xsd:string" />
+ <xsd:attribute name="mimetype" type="xsd:string" />
+ <xsd:attribute ref="xml:space" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="assembly">
+ <xsd:complexType>
+ <xsd:attribute name="alias" type="xsd:string" />
+ <xsd:attribute name="name" type="xsd:string" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="data">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+ <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+ <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+ <xsd:attribute ref="xml:space" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="resheader">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" />
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:choice>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:schema>
+ <resheader name="resmimetype">
+ <value>text/microsoft-resx</value>
+ </resheader>
+ <resheader name="version">
+ <value>2.0</value>
+ </resheader>
+ <resheader name="reader">
+ <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <resheader name="writer">
+ <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <data name="Cancel" xml:space="preserve">
+ <value>Kumoa</value>
+ <comment>ContextActionCell Cancel button</comment>
+ </data>
+ <data name="More" xml:space="preserve">
+ <value>Muut</value>
+ <comment>ContextActionCell More button</comment>
+ </data>
+</root> \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.iOS/Resources/StringResources.fr.resx b/Xamarin.Forms.Platform.iOS/Resources/StringResources.fr.resx
new file mode 100644
index 00000000..819845a0
--- /dev/null
+++ b/Xamarin.Forms.Platform.iOS/Resources/StringResources.fr.resx
@@ -0,0 +1,128 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+ <!--
+ Microsoft ResX Schema
+
+ Version 2.0
+
+ The primary goals of this format is to allow a simple XML format
+ that is mostly human readable. The generation and parsing of the
+ various data types are done through the TypeConverter classes
+ associated with the data types.
+
+ Example:
+
+ ... ado.net/XML headers & schema ...
+ <resheader name="resmimetype">text/microsoft-resx</resheader>
+ <resheader name="version">2.0</resheader>
+ <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+ <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+ <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+ <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+ <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+ <value>[base64 mime encoded serialized .NET Framework object]</value>
+ </data>
+ <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+ <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+ <comment>This is a comment</comment>
+ </data>
+
+ There are any number of "resheader" rows that contain simple
+ name/value pairs.
+
+ Each data row contains a name, and value. The row also contains a
+ type or mimetype. Type corresponds to a .NET class that support
+ text/value conversion through the TypeConverter architecture.
+ Classes that don't support this are serialized and stored with the
+ mimetype set.
+
+ The mimetype is used for serialized objects, and tells the
+ ResXResourceReader how to depersist the object. This is currently not
+ extensible. For a given mimetype the value must be set accordingly:
+
+ Note - application/x-microsoft.net.object.binary.base64 is the format
+ that the ResXResourceWriter will generate, however the reader can
+ read any of the formats listed below.
+
+ mimetype: application/x-microsoft.net.object.binary.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.soap.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.bytearray.base64
+ value : The object must be serialized into a byte array
+ : using a System.ComponentModel.TypeConverter
+ : and then encoded with base64 encoding.
+ -->
+ <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+ <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+ <xsd:element name="root" msdata:IsDataSet="true">
+ <xsd:complexType>
+ <xsd:choice maxOccurs="unbounded">
+ <xsd:element name="metadata">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" />
+ </xsd:sequence>
+ <xsd:attribute name="name" use="required" type="xsd:string" />
+ <xsd:attribute name="type" type="xsd:string" />
+ <xsd:attribute name="mimetype" type="xsd:string" />
+ <xsd:attribute ref="xml:space" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="assembly">
+ <xsd:complexType>
+ <xsd:attribute name="alias" type="xsd:string" />
+ <xsd:attribute name="name" type="xsd:string" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="data">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+ <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+ <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+ <xsd:attribute ref="xml:space" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="resheader">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" />
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:choice>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:schema>
+ <resheader name="resmimetype">
+ <value>text/microsoft-resx</value>
+ </resheader>
+ <resheader name="version">
+ <value>2.0</value>
+ </resheader>
+ <resheader name="reader">
+ <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <resheader name="writer">
+ <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <data name="Cancel" xml:space="preserve">
+ <value>Annuler</value>
+ <comment>ContextActionCell Cancel button</comment>
+ </data>
+ <data name="More" xml:space="preserve">
+ <value>Options</value>
+ <comment>ContextActionCell More button</comment>
+ </data>
+</root> \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.iOS/Resources/StringResources.he.resx b/Xamarin.Forms.Platform.iOS/Resources/StringResources.he.resx
new file mode 100644
index 00000000..fe8724a3
--- /dev/null
+++ b/Xamarin.Forms.Platform.iOS/Resources/StringResources.he.resx
@@ -0,0 +1,128 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+ <!--
+ Microsoft ResX Schema
+
+ Version 2.0
+
+ The primary goals of this format is to allow a simple XML format
+ that is mostly human readable. The generation and parsing of the
+ various data types are done through the TypeConverter classes
+ associated with the data types.
+
+ Example:
+
+ ... ado.net/XML headers & schema ...
+ <resheader name="resmimetype">text/microsoft-resx</resheader>
+ <resheader name="version">2.0</resheader>
+ <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+ <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+ <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+ <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+ <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+ <value>[base64 mime encoded serialized .NET Framework object]</value>
+ </data>
+ <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+ <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+ <comment>This is a comment</comment>
+ </data>
+
+ There are any number of "resheader" rows that contain simple
+ name/value pairs.
+
+ Each data row contains a name, and value. The row also contains a
+ type or mimetype. Type corresponds to a .NET class that support
+ text/value conversion through the TypeConverter architecture.
+ Classes that don't support this are serialized and stored with the
+ mimetype set.
+
+ The mimetype is used for serialized objects, and tells the
+ ResXResourceReader how to depersist the object. This is currently not
+ extensible. For a given mimetype the value must be set accordingly:
+
+ Note - application/x-microsoft.net.object.binary.base64 is the format
+ that the ResXResourceWriter will generate, however the reader can
+ read any of the formats listed below.
+
+ mimetype: application/x-microsoft.net.object.binary.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.soap.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.bytearray.base64
+ value : The object must be serialized into a byte array
+ : using a System.ComponentModel.TypeConverter
+ : and then encoded with base64 encoding.
+ -->
+ <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+ <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+ <xsd:element name="root" msdata:IsDataSet="true">
+ <xsd:complexType>
+ <xsd:choice maxOccurs="unbounded">
+ <xsd:element name="metadata">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" />
+ </xsd:sequence>
+ <xsd:attribute name="name" use="required" type="xsd:string" />
+ <xsd:attribute name="type" type="xsd:string" />
+ <xsd:attribute name="mimetype" type="xsd:string" />
+ <xsd:attribute ref="xml:space" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="assembly">
+ <xsd:complexType>
+ <xsd:attribute name="alias" type="xsd:string" />
+ <xsd:attribute name="name" type="xsd:string" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="data">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+ <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+ <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+ <xsd:attribute ref="xml:space" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="resheader">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" />
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:choice>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:schema>
+ <resheader name="resmimetype">
+ <value>text/microsoft-resx</value>
+ </resheader>
+ <resheader name="version">
+ <value>2.0</value>
+ </resheader>
+ <resheader name="reader">
+ <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <resheader name="writer">
+ <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <data name="Cancel" xml:space="preserve">
+ <value>ביטול</value>
+ <comment>ContextActionCell Cancel button</comment>
+ </data>
+ <data name="More" xml:space="preserve">
+ <value>עוד</value>
+ <comment>ContextActionCell More button</comment>
+ </data>
+</root> \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.iOS/Resources/StringResources.hi.resx b/Xamarin.Forms.Platform.iOS/Resources/StringResources.hi.resx
new file mode 100644
index 00000000..5ad57b4c
--- /dev/null
+++ b/Xamarin.Forms.Platform.iOS/Resources/StringResources.hi.resx
@@ -0,0 +1,128 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+ <!--
+ Microsoft ResX Schema
+
+ Version 2.0
+
+ The primary goals of this format is to allow a simple XML format
+ that is mostly human readable. The generation and parsing of the
+ various data types are done through the TypeConverter classes
+ associated with the data types.
+
+ Example:
+
+ ... ado.net/XML headers & schema ...
+ <resheader name="resmimetype">text/microsoft-resx</resheader>
+ <resheader name="version">2.0</resheader>
+ <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+ <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+ <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+ <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+ <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+ <value>[base64 mime encoded serialized .NET Framework object]</value>
+ </data>
+ <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+ <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+ <comment>This is a comment</comment>
+ </data>
+
+ There are any number of "resheader" rows that contain simple
+ name/value pairs.
+
+ Each data row contains a name, and value. The row also contains a
+ type or mimetype. Type corresponds to a .NET class that support
+ text/value conversion through the TypeConverter architecture.
+ Classes that don't support this are serialized and stored with the
+ mimetype set.
+
+ The mimetype is used for serialized objects, and tells the
+ ResXResourceReader how to depersist the object. This is currently not
+ extensible. For a given mimetype the value must be set accordingly:
+
+ Note - application/x-microsoft.net.object.binary.base64 is the format
+ that the ResXResourceWriter will generate, however the reader can
+ read any of the formats listed below.
+
+ mimetype: application/x-microsoft.net.object.binary.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.soap.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.bytearray.base64
+ value : The object must be serialized into a byte array
+ : using a System.ComponentModel.TypeConverter
+ : and then encoded with base64 encoding.
+ -->
+ <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+ <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+ <xsd:element name="root" msdata:IsDataSet="true">
+ <xsd:complexType>
+ <xsd:choice maxOccurs="unbounded">
+ <xsd:element name="metadata">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" />
+ </xsd:sequence>
+ <xsd:attribute name="name" use="required" type="xsd:string" />
+ <xsd:attribute name="type" type="xsd:string" />
+ <xsd:attribute name="mimetype" type="xsd:string" />
+ <xsd:attribute ref="xml:space" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="assembly">
+ <xsd:complexType>
+ <xsd:attribute name="alias" type="xsd:string" />
+ <xsd:attribute name="name" type="xsd:string" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="data">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+ <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+ <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+ <xsd:attribute ref="xml:space" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="resheader">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" />
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:choice>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:schema>
+ <resheader name="resmimetype">
+ <value>text/microsoft-resx</value>
+ </resheader>
+ <resheader name="version">
+ <value>2.0</value>
+ </resheader>
+ <resheader name="reader">
+ <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <resheader name="writer">
+ <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <data name="Cancel" xml:space="preserve">
+ <value>रद्द करें</value>
+ <comment>ContextActionCell Cancel button</comment>
+ </data>
+ <data name="More" xml:space="preserve">
+ <value>अधिक</value>
+ <comment>ContextActionCell More button</comment>
+ </data>
+</root> \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.iOS/Resources/StringResources.hr.resx b/Xamarin.Forms.Platform.iOS/Resources/StringResources.hr.resx
new file mode 100644
index 00000000..c7e01f21
--- /dev/null
+++ b/Xamarin.Forms.Platform.iOS/Resources/StringResources.hr.resx
@@ -0,0 +1,128 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+ <!--
+ Microsoft ResX Schema
+
+ Version 2.0
+
+ The primary goals of this format is to allow a simple XML format
+ that is mostly human readable. The generation and parsing of the
+ various data types are done through the TypeConverter classes
+ associated with the data types.
+
+ Example:
+
+ ... ado.net/XML headers & schema ...
+ <resheader name="resmimetype">text/microsoft-resx</resheader>
+ <resheader name="version">2.0</resheader>
+ <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+ <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+ <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+ <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+ <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+ <value>[base64 mime encoded serialized .NET Framework object]</value>
+ </data>
+ <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+ <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+ <comment>This is a comment</comment>
+ </data>
+
+ There are any number of "resheader" rows that contain simple
+ name/value pairs.
+
+ Each data row contains a name, and value. The row also contains a
+ type or mimetype. Type corresponds to a .NET class that support
+ text/value conversion through the TypeConverter architecture.
+ Classes that don't support this are serialized and stored with the
+ mimetype set.
+
+ The mimetype is used for serialized objects, and tells the
+ ResXResourceReader how to depersist the object. This is currently not
+ extensible. For a given mimetype the value must be set accordingly:
+
+ Note - application/x-microsoft.net.object.binary.base64 is the format
+ that the ResXResourceWriter will generate, however the reader can
+ read any of the formats listed below.
+
+ mimetype: application/x-microsoft.net.object.binary.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.soap.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.bytearray.base64
+ value : The object must be serialized into a byte array
+ : using a System.ComponentModel.TypeConverter
+ : and then encoded with base64 encoding.
+ -->
+ <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+ <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+ <xsd:element name="root" msdata:IsDataSet="true">
+ <xsd:complexType>
+ <xsd:choice maxOccurs="unbounded">
+ <xsd:element name="metadata">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" />
+ </xsd:sequence>
+ <xsd:attribute name="name" use="required" type="xsd:string" />
+ <xsd:attribute name="type" type="xsd:string" />
+ <xsd:attribute name="mimetype" type="xsd:string" />
+ <xsd:attribute ref="xml:space" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="assembly">
+ <xsd:complexType>
+ <xsd:attribute name="alias" type="xsd:string" />
+ <xsd:attribute name="name" type="xsd:string" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="data">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+ <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+ <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+ <xsd:attribute ref="xml:space" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="resheader">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" />
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:choice>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:schema>
+ <resheader name="resmimetype">
+ <value>text/microsoft-resx</value>
+ </resheader>
+ <resheader name="version">
+ <value>2.0</value>
+ </resheader>
+ <resheader name="reader">
+ <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <resheader name="writer">
+ <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <data name="Cancel" xml:space="preserve">
+ <value>Poništi</value>
+ <comment>ContextActionCell Cancel button</comment>
+ </data>
+ <data name="More" xml:space="preserve">
+ <value>Više</value>
+ <comment>ContextActionCell More button</comment>
+ </data>
+</root> \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.iOS/Resources/StringResources.hu.resx b/Xamarin.Forms.Platform.iOS/Resources/StringResources.hu.resx
new file mode 100644
index 00000000..1942899b
--- /dev/null
+++ b/Xamarin.Forms.Platform.iOS/Resources/StringResources.hu.resx
@@ -0,0 +1,128 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+ <!--
+ Microsoft ResX Schema
+
+ Version 2.0
+
+ The primary goals of this format is to allow a simple XML format
+ that is mostly human readable. The generation and parsing of the
+ various data types are done through the TypeConverter classes
+ associated with the data types.
+
+ Example:
+
+ ... ado.net/XML headers & schema ...
+ <resheader name="resmimetype">text/microsoft-resx</resheader>
+ <resheader name="version">2.0</resheader>
+ <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+ <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+ <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+ <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+ <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+ <value>[base64 mime encoded serialized .NET Framework object]</value>
+ </data>
+ <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+ <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+ <comment>This is a comment</comment>
+ </data>
+
+ There are any number of "resheader" rows that contain simple
+ name/value pairs.
+
+ Each data row contains a name, and value. The row also contains a
+ type or mimetype. Type corresponds to a .NET class that support
+ text/value conversion through the TypeConverter architecture.
+ Classes that don't support this are serialized and stored with the
+ mimetype set.
+
+ The mimetype is used for serialized objects, and tells the
+ ResXResourceReader how to depersist the object. This is currently not
+ extensible. For a given mimetype the value must be set accordingly:
+
+ Note - application/x-microsoft.net.object.binary.base64 is the format
+ that the ResXResourceWriter will generate, however the reader can
+ read any of the formats listed below.
+
+ mimetype: application/x-microsoft.net.object.binary.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.soap.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.bytearray.base64
+ value : The object must be serialized into a byte array
+ : using a System.ComponentModel.TypeConverter
+ : and then encoded with base64 encoding.
+ -->
+ <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+ <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+ <xsd:element name="root" msdata:IsDataSet="true">
+ <xsd:complexType>
+ <xsd:choice maxOccurs="unbounded">
+ <xsd:element name="metadata">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" />
+ </xsd:sequence>
+ <xsd:attribute name="name" use="required" type="xsd:string" />
+ <xsd:attribute name="type" type="xsd:string" />
+ <xsd:attribute name="mimetype" type="xsd:string" />
+ <xsd:attribute ref="xml:space" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="assembly">
+ <xsd:complexType>
+ <xsd:attribute name="alias" type="xsd:string" />
+ <xsd:attribute name="name" type="xsd:string" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="data">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+ <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+ <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+ <xsd:attribute ref="xml:space" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="resheader">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" />
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:choice>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:schema>
+ <resheader name="resmimetype">
+ <value>text/microsoft-resx</value>
+ </resheader>
+ <resheader name="version">
+ <value>2.0</value>
+ </resheader>
+ <resheader name="reader">
+ <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <resheader name="writer">
+ <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <data name="Cancel" xml:space="preserve">
+ <value>Mégsem</value>
+ <comment>ContextActionCell Cancel button</comment>
+ </data>
+ <data name="More" xml:space="preserve">
+ <value>Továbbiak</value>
+ <comment>ContextActionCell More button</comment>
+ </data>
+</root> \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.iOS/Resources/StringResources.id.resx b/Xamarin.Forms.Platform.iOS/Resources/StringResources.id.resx
new file mode 100644
index 00000000..182b8e94
--- /dev/null
+++ b/Xamarin.Forms.Platform.iOS/Resources/StringResources.id.resx
@@ -0,0 +1,128 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+ <!--
+ Microsoft ResX Schema
+
+ Version 2.0
+
+ The primary goals of this format is to allow a simple XML format
+ that is mostly human readable. The generation and parsing of the
+ various data types are done through the TypeConverter classes
+ associated with the data types.
+
+ Example:
+
+ ... ado.net/XML headers & schema ...
+ <resheader name="resmimetype">text/microsoft-resx</resheader>
+ <resheader name="version">2.0</resheader>
+ <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+ <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+ <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+ <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+ <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+ <value>[base64 mime encoded serialized .NET Framework object]</value>
+ </data>
+ <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+ <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+ <comment>This is a comment</comment>
+ </data>
+
+ There are any number of "resheader" rows that contain simple
+ name/value pairs.
+
+ Each data row contains a name, and value. The row also contains a
+ type or mimetype. Type corresponds to a .NET class that support
+ text/value conversion through the TypeConverter architecture.
+ Classes that don't support this are serialized and stored with the
+ mimetype set.
+
+ The mimetype is used for serialized objects, and tells the
+ ResXResourceReader how to depersist the object. This is currently not
+ extensible. For a given mimetype the value must be set accordingly:
+
+ Note - application/x-microsoft.net.object.binary.base64 is the format
+ that the ResXResourceWriter will generate, however the reader can
+ read any of the formats listed below.
+
+ mimetype: application/x-microsoft.net.object.binary.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.soap.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.bytearray.base64
+ value : The object must be serialized into a byte array
+ : using a System.ComponentModel.TypeConverter
+ : and then encoded with base64 encoding.
+ -->
+ <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+ <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+ <xsd:element name="root" msdata:IsDataSet="true">
+ <xsd:complexType>
+ <xsd:choice maxOccurs="unbounded">
+ <xsd:element name="metadata">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" />
+ </xsd:sequence>
+ <xsd:attribute name="name" use="required" type="xsd:string" />
+ <xsd:attribute name="type" type="xsd:string" />
+ <xsd:attribute name="mimetype" type="xsd:string" />
+ <xsd:attribute ref="xml:space" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="assembly">
+ <xsd:complexType>
+ <xsd:attribute name="alias" type="xsd:string" />
+ <xsd:attribute name="name" type="xsd:string" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="data">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+ <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+ <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+ <xsd:attribute ref="xml:space" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="resheader">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" />
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:choice>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:schema>
+ <resheader name="resmimetype">
+ <value>text/microsoft-resx</value>
+ </resheader>
+ <resheader name="version">
+ <value>2.0</value>
+ </resheader>
+ <resheader name="reader">
+ <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <resheader name="writer">
+ <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <data name="Cancel" xml:space="preserve">
+ <value>Batalkan</value>
+ <comment>ContextActionCell Cancel button</comment>
+ </data>
+ <data name="More" xml:space="preserve">
+ <value>Lainnya</value>
+ <comment>ContextActionCell More button</comment>
+ </data>
+</root> \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.iOS/Resources/StringResources.it.resx b/Xamarin.Forms.Platform.iOS/Resources/StringResources.it.resx
new file mode 100644
index 00000000..e7b49e09
--- /dev/null
+++ b/Xamarin.Forms.Platform.iOS/Resources/StringResources.it.resx
@@ -0,0 +1,128 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+ <!--
+ Microsoft ResX Schema
+
+ Version 2.0
+
+ The primary goals of this format is to allow a simple XML format
+ that is mostly human readable. The generation and parsing of the
+ various data types are done through the TypeConverter classes
+ associated with the data types.
+
+ Example:
+
+ ... ado.net/XML headers & schema ...
+ <resheader name="resmimetype">text/microsoft-resx</resheader>
+ <resheader name="version">2.0</resheader>
+ <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+ <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+ <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+ <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+ <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+ <value>[base64 mime encoded serialized .NET Framework object]</value>
+ </data>
+ <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+ <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+ <comment>This is a comment</comment>
+ </data>
+
+ There are any number of "resheader" rows that contain simple
+ name/value pairs.
+
+ Each data row contains a name, and value. The row also contains a
+ type or mimetype. Type corresponds to a .NET class that support
+ text/value conversion through the TypeConverter architecture.
+ Classes that don't support this are serialized and stored with the
+ mimetype set.
+
+ The mimetype is used for serialized objects, and tells the
+ ResXResourceReader how to depersist the object. This is currently not
+ extensible. For a given mimetype the value must be set accordingly:
+
+ Note - application/x-microsoft.net.object.binary.base64 is the format
+ that the ResXResourceWriter will generate, however the reader can
+ read any of the formats listed below.
+
+ mimetype: application/x-microsoft.net.object.binary.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.soap.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.bytearray.base64
+ value : The object must be serialized into a byte array
+ : using a System.ComponentModel.TypeConverter
+ : and then encoded with base64 encoding.
+ -->
+ <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+ <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+ <xsd:element name="root" msdata:IsDataSet="true">
+ <xsd:complexType>
+ <xsd:choice maxOccurs="unbounded">
+ <xsd:element name="metadata">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" />
+ </xsd:sequence>
+ <xsd:attribute name="name" use="required" type="xsd:string" />
+ <xsd:attribute name="type" type="xsd:string" />
+ <xsd:attribute name="mimetype" type="xsd:string" />
+ <xsd:attribute ref="xml:space" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="assembly">
+ <xsd:complexType>
+ <xsd:attribute name="alias" type="xsd:string" />
+ <xsd:attribute name="name" type="xsd:string" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="data">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+ <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+ <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+ <xsd:attribute ref="xml:space" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="resheader">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" />
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:choice>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:schema>
+ <resheader name="resmimetype">
+ <value>text/microsoft-resx</value>
+ </resheader>
+ <resheader name="version">
+ <value>2.0</value>
+ </resheader>
+ <resheader name="reader">
+ <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <resheader name="writer">
+ <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <data name="Cancel" xml:space="preserve">
+ <value>Annulla</value>
+ <comment>ContextActionCell Cancel button</comment>
+ </data>
+ <data name="More" xml:space="preserve">
+ <value>Altro</value>
+ <comment>ContextActionCell More button</comment>
+ </data>
+</root> \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.iOS/Resources/StringResources.ja.resx b/Xamarin.Forms.Platform.iOS/Resources/StringResources.ja.resx
new file mode 100644
index 00000000..aa6ed1a4
--- /dev/null
+++ b/Xamarin.Forms.Platform.iOS/Resources/StringResources.ja.resx
@@ -0,0 +1,128 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+ <!--
+ Microsoft ResX Schema
+
+ Version 2.0
+
+ The primary goals of this format is to allow a simple XML format
+ that is mostly human readable. The generation and parsing of the
+ various data types are done through the TypeConverter classes
+ associated with the data types.
+
+ Example:
+
+ ... ado.net/XML headers & schema ...
+ <resheader name="resmimetype">text/microsoft-resx</resheader>
+ <resheader name="version">2.0</resheader>
+ <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+ <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+ <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+ <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+ <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+ <value>[base64 mime encoded serialized .NET Framework object]</value>
+ </data>
+ <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+ <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+ <comment>This is a comment</comment>
+ </data>
+
+ There are any number of "resheader" rows that contain simple
+ name/value pairs.
+
+ Each data row contains a name, and value. The row also contains a
+ type or mimetype. Type corresponds to a .NET class that support
+ text/value conversion through the TypeConverter architecture.
+ Classes that don't support this are serialized and stored with the
+ mimetype set.
+
+ The mimetype is used for serialized objects, and tells the
+ ResXResourceReader how to depersist the object. This is currently not
+ extensible. For a given mimetype the value must be set accordingly:
+
+ Note - application/x-microsoft.net.object.binary.base64 is the format
+ that the ResXResourceWriter will generate, however the reader can
+ read any of the formats listed below.
+
+ mimetype: application/x-microsoft.net.object.binary.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.soap.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.bytearray.base64
+ value : The object must be serialized into a byte array
+ : using a System.ComponentModel.TypeConverter
+ : and then encoded with base64 encoding.
+ -->
+ <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+ <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+ <xsd:element name="root" msdata:IsDataSet="true">
+ <xsd:complexType>
+ <xsd:choice maxOccurs="unbounded">
+ <xsd:element name="metadata">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" />
+ </xsd:sequence>
+ <xsd:attribute name="name" use="required" type="xsd:string" />
+ <xsd:attribute name="type" type="xsd:string" />
+ <xsd:attribute name="mimetype" type="xsd:string" />
+ <xsd:attribute ref="xml:space" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="assembly">
+ <xsd:complexType>
+ <xsd:attribute name="alias" type="xsd:string" />
+ <xsd:attribute name="name" type="xsd:string" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="data">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+ <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+ <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+ <xsd:attribute ref="xml:space" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="resheader">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" />
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:choice>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:schema>
+ <resheader name="resmimetype">
+ <value>text/microsoft-resx</value>
+ </resheader>
+ <resheader name="version">
+ <value>2.0</value>
+ </resheader>
+ <resheader name="reader">
+ <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <resheader name="writer">
+ <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <data name="Cancel" xml:space="preserve">
+ <value>キャンセル</value>
+ <comment>ContextActionCell Cancel button</comment>
+ </data>
+ <data name="More" xml:space="preserve">
+ <value>その他</value>
+ <comment>ContextActionCell More button</comment>
+ </data>
+</root> \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.iOS/Resources/StringResources.ko.resx b/Xamarin.Forms.Platform.iOS/Resources/StringResources.ko.resx
new file mode 100644
index 00000000..2a2de740
--- /dev/null
+++ b/Xamarin.Forms.Platform.iOS/Resources/StringResources.ko.resx
@@ -0,0 +1,128 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+ <!--
+ Microsoft ResX Schema
+
+ Version 2.0
+
+ The primary goals of this format is to allow a simple XML format
+ that is mostly human readable. The generation and parsing of the
+ various data types are done through the TypeConverter classes
+ associated with the data types.
+
+ Example:
+
+ ... ado.net/XML headers & schema ...
+ <resheader name="resmimetype">text/microsoft-resx</resheader>
+ <resheader name="version">2.0</resheader>
+ <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+ <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+ <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+ <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+ <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+ <value>[base64 mime encoded serialized .NET Framework object]</value>
+ </data>
+ <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+ <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+ <comment>This is a comment</comment>
+ </data>
+
+ There are any number of "resheader" rows that contain simple
+ name/value pairs.
+
+ Each data row contains a name, and value. The row also contains a
+ type or mimetype. Type corresponds to a .NET class that support
+ text/value conversion through the TypeConverter architecture.
+ Classes that don't support this are serialized and stored with the
+ mimetype set.
+
+ The mimetype is used for serialized objects, and tells the
+ ResXResourceReader how to depersist the object. This is currently not
+ extensible. For a given mimetype the value must be set accordingly:
+
+ Note - application/x-microsoft.net.object.binary.base64 is the format
+ that the ResXResourceWriter will generate, however the reader can
+ read any of the formats listed below.
+
+ mimetype: application/x-microsoft.net.object.binary.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.soap.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.bytearray.base64
+ value : The object must be serialized into a byte array
+ : using a System.ComponentModel.TypeConverter
+ : and then encoded with base64 encoding.
+ -->
+ <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+ <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+ <xsd:element name="root" msdata:IsDataSet="true">
+ <xsd:complexType>
+ <xsd:choice maxOccurs="unbounded">
+ <xsd:element name="metadata">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" />
+ </xsd:sequence>
+ <xsd:attribute name="name" use="required" type="xsd:string" />
+ <xsd:attribute name="type" type="xsd:string" />
+ <xsd:attribute name="mimetype" type="xsd:string" />
+ <xsd:attribute ref="xml:space" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="assembly">
+ <xsd:complexType>
+ <xsd:attribute name="alias" type="xsd:string" />
+ <xsd:attribute name="name" type="xsd:string" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="data">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+ <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+ <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+ <xsd:attribute ref="xml:space" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="resheader">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" />
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:choice>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:schema>
+ <resheader name="resmimetype">
+ <value>text/microsoft-resx</value>
+ </resheader>
+ <resheader name="version">
+ <value>2.0</value>
+ </resheader>
+ <resheader name="reader">
+ <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <resheader name="writer">
+ <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <data name="Cancel" xml:space="preserve">
+ <value>취소</value>
+ <comment>ContextActionCell Cancel button</comment>
+ </data>
+ <data name="More" xml:space="preserve">
+ <value>기타</value>
+ <comment>ContextActionCell More button</comment>
+ </data>
+</root> \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.iOS/Resources/StringResources.ms.resx b/Xamarin.Forms.Platform.iOS/Resources/StringResources.ms.resx
new file mode 100644
index 00000000..fd679653
--- /dev/null
+++ b/Xamarin.Forms.Platform.iOS/Resources/StringResources.ms.resx
@@ -0,0 +1,128 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+ <!--
+ Microsoft ResX Schema
+
+ Version 2.0
+
+ The primary goals of this format is to allow a simple XML format
+ that is mostly human readable. The generation and parsing of the
+ various data types are done through the TypeConverter classes
+ associated with the data types.
+
+ Example:
+
+ ... ado.net/XML headers & schema ...
+ <resheader name="resmimetype">text/microsoft-resx</resheader>
+ <resheader name="version">2.0</resheader>
+ <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+ <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+ <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+ <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+ <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+ <value>[base64 mime encoded serialized .NET Framework object]</value>
+ </data>
+ <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+ <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+ <comment>This is a comment</comment>
+ </data>
+
+ There are any number of "resheader" rows that contain simple
+ name/value pairs.
+
+ Each data row contains a name, and value. The row also contains a
+ type or mimetype. Type corresponds to a .NET class that support
+ text/value conversion through the TypeConverter architecture.
+ Classes that don't support this are serialized and stored with the
+ mimetype set.
+
+ The mimetype is used for serialized objects, and tells the
+ ResXResourceReader how to depersist the object. This is currently not
+ extensible. For a given mimetype the value must be set accordingly:
+
+ Note - application/x-microsoft.net.object.binary.base64 is the format
+ that the ResXResourceWriter will generate, however the reader can
+ read any of the formats listed below.
+
+ mimetype: application/x-microsoft.net.object.binary.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.soap.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.bytearray.base64
+ value : The object must be serialized into a byte array
+ : using a System.ComponentModel.TypeConverter
+ : and then encoded with base64 encoding.
+ -->
+ <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+ <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+ <xsd:element name="root" msdata:IsDataSet="true">
+ <xsd:complexType>
+ <xsd:choice maxOccurs="unbounded">
+ <xsd:element name="metadata">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" />
+ </xsd:sequence>
+ <xsd:attribute name="name" use="required" type="xsd:string" />
+ <xsd:attribute name="type" type="xsd:string" />
+ <xsd:attribute name="mimetype" type="xsd:string" />
+ <xsd:attribute ref="xml:space" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="assembly">
+ <xsd:complexType>
+ <xsd:attribute name="alias" type="xsd:string" />
+ <xsd:attribute name="name" type="xsd:string" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="data">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+ <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+ <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+ <xsd:attribute ref="xml:space" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="resheader">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" />
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:choice>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:schema>
+ <resheader name="resmimetype">
+ <value>text/microsoft-resx</value>
+ </resheader>
+ <resheader name="version">
+ <value>2.0</value>
+ </resheader>
+ <resheader name="reader">
+ <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <resheader name="writer">
+ <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <data name="Cancel" xml:space="preserve">
+ <value>Batal</value>
+ <comment>ContextActionCell Cancel button</comment>
+ </data>
+ <data name="More" xml:space="preserve">
+ <value>Lagi</value>
+ <comment>ContextActionCell More button</comment>
+ </data>
+</root> \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.iOS/Resources/StringResources.nb.resx b/Xamarin.Forms.Platform.iOS/Resources/StringResources.nb.resx
new file mode 100644
index 00000000..671f06f6
--- /dev/null
+++ b/Xamarin.Forms.Platform.iOS/Resources/StringResources.nb.resx
@@ -0,0 +1,128 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+ <!--
+ Microsoft ResX Schema
+
+ Version 2.0
+
+ The primary goals of this format is to allow a simple XML format
+ that is mostly human readable. The generation and parsing of the
+ various data types are done through the TypeConverter classes
+ associated with the data types.
+
+ Example:
+
+ ... ado.net/XML headers & schema ...
+ <resheader name="resmimetype">text/microsoft-resx</resheader>
+ <resheader name="version">2.0</resheader>
+ <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+ <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+ <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+ <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+ <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+ <value>[base64 mime encoded serialized .NET Framework object]</value>
+ </data>
+ <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+ <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+ <comment>This is a comment</comment>
+ </data>
+
+ There are any number of "resheader" rows that contain simple
+ name/value pairs.
+
+ Each data row contains a name, and value. The row also contains a
+ type or mimetype. Type corresponds to a .NET class that support
+ text/value conversion through the TypeConverter architecture.
+ Classes that don't support this are serialized and stored with the
+ mimetype set.
+
+ The mimetype is used for serialized objects, and tells the
+ ResXResourceReader how to depersist the object. This is currently not
+ extensible. For a given mimetype the value must be set accordingly:
+
+ Note - application/x-microsoft.net.object.binary.base64 is the format
+ that the ResXResourceWriter will generate, however the reader can
+ read any of the formats listed below.
+
+ mimetype: application/x-microsoft.net.object.binary.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.soap.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.bytearray.base64
+ value : The object must be serialized into a byte array
+ : using a System.ComponentModel.TypeConverter
+ : and then encoded with base64 encoding.
+ -->
+ <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+ <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+ <xsd:element name="root" msdata:IsDataSet="true">
+ <xsd:complexType>
+ <xsd:choice maxOccurs="unbounded">
+ <xsd:element name="metadata">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" />
+ </xsd:sequence>
+ <xsd:attribute name="name" use="required" type="xsd:string" />
+ <xsd:attribute name="type" type="xsd:string" />
+ <xsd:attribute name="mimetype" type="xsd:string" />
+ <xsd:attribute ref="xml:space" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="assembly">
+ <xsd:complexType>
+ <xsd:attribute name="alias" type="xsd:string" />
+ <xsd:attribute name="name" type="xsd:string" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="data">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+ <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+ <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+ <xsd:attribute ref="xml:space" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="resheader">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" />
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:choice>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:schema>
+ <resheader name="resmimetype">
+ <value>text/microsoft-resx</value>
+ </resheader>
+ <resheader name="version">
+ <value>2.0</value>
+ </resheader>
+ <resheader name="reader">
+ <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <resheader name="writer">
+ <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <data name="Cancel" xml:space="preserve">
+ <value>Avbryt</value>
+ <comment>ContextActionCell Cancel button</comment>
+ </data>
+ <data name="More" xml:space="preserve">
+ <value>Mer</value>
+ <comment>ContextActionCell More button</comment>
+ </data>
+</root> \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.iOS/Resources/StringResources.nl.resx b/Xamarin.Forms.Platform.iOS/Resources/StringResources.nl.resx
new file mode 100644
index 00000000..dd592bac
--- /dev/null
+++ b/Xamarin.Forms.Platform.iOS/Resources/StringResources.nl.resx
@@ -0,0 +1,128 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+ <!--
+ Microsoft ResX Schema
+
+ Version 2.0
+
+ The primary goals of this format is to allow a simple XML format
+ that is mostly human readable. The generation and parsing of the
+ various data types are done through the TypeConverter classes
+ associated with the data types.
+
+ Example:
+
+ ... ado.net/XML headers & schema ...
+ <resheader name="resmimetype">text/microsoft-resx</resheader>
+ <resheader name="version">2.0</resheader>
+ <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+ <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+ <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+ <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+ <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+ <value>[base64 mime encoded serialized .NET Framework object]</value>
+ </data>
+ <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+ <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+ <comment>This is a comment</comment>
+ </data>
+
+ There are any number of "resheader" rows that contain simple
+ name/value pairs.
+
+ Each data row contains a name, and value. The row also contains a
+ type or mimetype. Type corresponds to a .NET class that support
+ text/value conversion through the TypeConverter architecture.
+ Classes that don't support this are serialized and stored with the
+ mimetype set.
+
+ The mimetype is used for serialized objects, and tells the
+ ResXResourceReader how to depersist the object. This is currently not
+ extensible. For a given mimetype the value must be set accordingly:
+
+ Note - application/x-microsoft.net.object.binary.base64 is the format
+ that the ResXResourceWriter will generate, however the reader can
+ read any of the formats listed below.
+
+ mimetype: application/x-microsoft.net.object.binary.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.soap.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.bytearray.base64
+ value : The object must be serialized into a byte array
+ : using a System.ComponentModel.TypeConverter
+ : and then encoded with base64 encoding.
+ -->
+ <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+ <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+ <xsd:element name="root" msdata:IsDataSet="true">
+ <xsd:complexType>
+ <xsd:choice maxOccurs="unbounded">
+ <xsd:element name="metadata">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" />
+ </xsd:sequence>
+ <xsd:attribute name="name" use="required" type="xsd:string" />
+ <xsd:attribute name="type" type="xsd:string" />
+ <xsd:attribute name="mimetype" type="xsd:string" />
+ <xsd:attribute ref="xml:space" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="assembly">
+ <xsd:complexType>
+ <xsd:attribute name="alias" type="xsd:string" />
+ <xsd:attribute name="name" type="xsd:string" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="data">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+ <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+ <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+ <xsd:attribute ref="xml:space" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="resheader">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" />
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:choice>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:schema>
+ <resheader name="resmimetype">
+ <value>text/microsoft-resx</value>
+ </resheader>
+ <resheader name="version">
+ <value>2.0</value>
+ </resheader>
+ <resheader name="reader">
+ <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <resheader name="writer">
+ <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <data name="Cancel" xml:space="preserve">
+ <value>Annuleer</value>
+ <comment>ContextActionCell Cancel button</comment>
+ </data>
+ <data name="More" xml:space="preserve">
+ <value>Meer</value>
+ <comment>ContextActionCell More button</comment>
+ </data>
+</root> \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.iOS/Resources/StringResources.pl.resx b/Xamarin.Forms.Platform.iOS/Resources/StringResources.pl.resx
new file mode 100644
index 00000000..5a99c8e1
--- /dev/null
+++ b/Xamarin.Forms.Platform.iOS/Resources/StringResources.pl.resx
@@ -0,0 +1,128 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+ <!--
+ Microsoft ResX Schema
+
+ Version 2.0
+
+ The primary goals of this format is to allow a simple XML format
+ that is mostly human readable. The generation and parsing of the
+ various data types are done through the TypeConverter classes
+ associated with the data types.
+
+ Example:
+
+ ... ado.net/XML headers & schema ...
+ <resheader name="resmimetype">text/microsoft-resx</resheader>
+ <resheader name="version">2.0</resheader>
+ <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+ <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+ <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+ <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+ <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+ <value>[base64 mime encoded serialized .NET Framework object]</value>
+ </data>
+ <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+ <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+ <comment>This is a comment</comment>
+ </data>
+
+ There are any number of "resheader" rows that contain simple
+ name/value pairs.
+
+ Each data row contains a name, and value. The row also contains a
+ type or mimetype. Type corresponds to a .NET class that support
+ text/value conversion through the TypeConverter architecture.
+ Classes that don't support this are serialized and stored with the
+ mimetype set.
+
+ The mimetype is used for serialized objects, and tells the
+ ResXResourceReader how to depersist the object. This is currently not
+ extensible. For a given mimetype the value must be set accordingly:
+
+ Note - application/x-microsoft.net.object.binary.base64 is the format
+ that the ResXResourceWriter will generate, however the reader can
+ read any of the formats listed below.
+
+ mimetype: application/x-microsoft.net.object.binary.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.soap.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.bytearray.base64
+ value : The object must be serialized into a byte array
+ : using a System.ComponentModel.TypeConverter
+ : and then encoded with base64 encoding.
+ -->
+ <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+ <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+ <xsd:element name="root" msdata:IsDataSet="true">
+ <xsd:complexType>
+ <xsd:choice maxOccurs="unbounded">
+ <xsd:element name="metadata">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" />
+ </xsd:sequence>
+ <xsd:attribute name="name" use="required" type="xsd:string" />
+ <xsd:attribute name="type" type="xsd:string" />
+ <xsd:attribute name="mimetype" type="xsd:string" />
+ <xsd:attribute ref="xml:space" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="assembly">
+ <xsd:complexType>
+ <xsd:attribute name="alias" type="xsd:string" />
+ <xsd:attribute name="name" type="xsd:string" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="data">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+ <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+ <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+ <xsd:attribute ref="xml:space" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="resheader">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" />
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:choice>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:schema>
+ <resheader name="resmimetype">
+ <value>text/microsoft-resx</value>
+ </resheader>
+ <resheader name="version">
+ <value>2.0</value>
+ </resheader>
+ <resheader name="reader">
+ <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <resheader name="writer">
+ <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <data name="Cancel" xml:space="preserve">
+ <value>Anuluj</value>
+ <comment>ContextActionCell Cancel button</comment>
+ </data>
+ <data name="More" xml:space="preserve">
+ <value>Więcej</value>
+ <comment>ContextActionCell More button</comment>
+ </data>
+</root> \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.iOS/Resources/StringResources.pt-BR.resx b/Xamarin.Forms.Platform.iOS/Resources/StringResources.pt-BR.resx
new file mode 100644
index 00000000..38182acf
--- /dev/null
+++ b/Xamarin.Forms.Platform.iOS/Resources/StringResources.pt-BR.resx
@@ -0,0 +1,128 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+ <!--
+ Microsoft ResX Schema
+
+ Version 2.0
+
+ The primary goals of this format is to allow a simple XML format
+ that is mostly human readable. The generation and parsing of the
+ various data types are done through the TypeConverter classes
+ associated with the data types.
+
+ Example:
+
+ ... ado.net/XML headers & schema ...
+ <resheader name="resmimetype">text/microsoft-resx</resheader>
+ <resheader name="version">2.0</resheader>
+ <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+ <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+ <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+ <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+ <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+ <value>[base64 mime encoded serialized .NET Framework object]</value>
+ </data>
+ <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+ <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+ <comment>This is a comment</comment>
+ </data>
+
+ There are any number of "resheader" rows that contain simple
+ name/value pairs.
+
+ Each data row contains a name, and value. The row also contains a
+ type or mimetype. Type corresponds to a .NET class that support
+ text/value conversion through the TypeConverter architecture.
+ Classes that don't support this are serialized and stored with the
+ mimetype set.
+
+ The mimetype is used for serialized objects, and tells the
+ ResXResourceReader how to depersist the object. This is currently not
+ extensible. For a given mimetype the value must be set accordingly:
+
+ Note - application/x-microsoft.net.object.binary.base64 is the format
+ that the ResXResourceWriter will generate, however the reader can
+ read any of the formats listed below.
+
+ mimetype: application/x-microsoft.net.object.binary.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.soap.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.bytearray.base64
+ value : The object must be serialized into a byte array
+ : using a System.ComponentModel.TypeConverter
+ : and then encoded with base64 encoding.
+ -->
+ <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+ <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+ <xsd:element name="root" msdata:IsDataSet="true">
+ <xsd:complexType>
+ <xsd:choice maxOccurs="unbounded">
+ <xsd:element name="metadata">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" />
+ </xsd:sequence>
+ <xsd:attribute name="name" use="required" type="xsd:string" />
+ <xsd:attribute name="type" type="xsd:string" />
+ <xsd:attribute name="mimetype" type="xsd:string" />
+ <xsd:attribute ref="xml:space" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="assembly">
+ <xsd:complexType>
+ <xsd:attribute name="alias" type="xsd:string" />
+ <xsd:attribute name="name" type="xsd:string" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="data">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+ <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+ <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+ <xsd:attribute ref="xml:space" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="resheader">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" />
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:choice>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:schema>
+ <resheader name="resmimetype">
+ <value>text/microsoft-resx</value>
+ </resheader>
+ <resheader name="version">
+ <value>2.0</value>
+ </resheader>
+ <resheader name="reader">
+ <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <resheader name="writer">
+ <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <data name="Cancel" xml:space="preserve">
+ <value>Cancelar</value>
+ <comment>ContextActionCell Cancel button</comment>
+ </data>
+ <data name="More" xml:space="preserve">
+ <value>Mais</value>
+ <comment>ContextActionCell More button</comment>
+ </data>
+</root> \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.iOS/Resources/StringResources.pt.resx b/Xamarin.Forms.Platform.iOS/Resources/StringResources.pt.resx
new file mode 100644
index 00000000..38182acf
--- /dev/null
+++ b/Xamarin.Forms.Platform.iOS/Resources/StringResources.pt.resx
@@ -0,0 +1,128 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+ <!--
+ Microsoft ResX Schema
+
+ Version 2.0
+
+ The primary goals of this format is to allow a simple XML format
+ that is mostly human readable. The generation and parsing of the
+ various data types are done through the TypeConverter classes
+ associated with the data types.
+
+ Example:
+
+ ... ado.net/XML headers & schema ...
+ <resheader name="resmimetype">text/microsoft-resx</resheader>
+ <resheader name="version">2.0</resheader>
+ <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+ <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+ <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+ <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+ <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+ <value>[base64 mime encoded serialized .NET Framework object]</value>
+ </data>
+ <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+ <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+ <comment>This is a comment</comment>
+ </data>
+
+ There are any number of "resheader" rows that contain simple
+ name/value pairs.
+
+ Each data row contains a name, and value. The row also contains a
+ type or mimetype. Type corresponds to a .NET class that support
+ text/value conversion through the TypeConverter architecture.
+ Classes that don't support this are serialized and stored with the
+ mimetype set.
+
+ The mimetype is used for serialized objects, and tells the
+ ResXResourceReader how to depersist the object. This is currently not
+ extensible. For a given mimetype the value must be set accordingly:
+
+ Note - application/x-microsoft.net.object.binary.base64 is the format
+ that the ResXResourceWriter will generate, however the reader can
+ read any of the formats listed below.
+
+ mimetype: application/x-microsoft.net.object.binary.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.soap.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.bytearray.base64
+ value : The object must be serialized into a byte array
+ : using a System.ComponentModel.TypeConverter
+ : and then encoded with base64 encoding.
+ -->
+ <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+ <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+ <xsd:element name="root" msdata:IsDataSet="true">
+ <xsd:complexType>
+ <xsd:choice maxOccurs="unbounded">
+ <xsd:element name="metadata">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" />
+ </xsd:sequence>
+ <xsd:attribute name="name" use="required" type="xsd:string" />
+ <xsd:attribute name="type" type="xsd:string" />
+ <xsd:attribute name="mimetype" type="xsd:string" />
+ <xsd:attribute ref="xml:space" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="assembly">
+ <xsd:complexType>
+ <xsd:attribute name="alias" type="xsd:string" />
+ <xsd:attribute name="name" type="xsd:string" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="data">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+ <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+ <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+ <xsd:attribute ref="xml:space" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="resheader">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" />
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:choice>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:schema>
+ <resheader name="resmimetype">
+ <value>text/microsoft-resx</value>
+ </resheader>
+ <resheader name="version">
+ <value>2.0</value>
+ </resheader>
+ <resheader name="reader">
+ <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <resheader name="writer">
+ <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <data name="Cancel" xml:space="preserve">
+ <value>Cancelar</value>
+ <comment>ContextActionCell Cancel button</comment>
+ </data>
+ <data name="More" xml:space="preserve">
+ <value>Mais</value>
+ <comment>ContextActionCell More button</comment>
+ </data>
+</root> \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.iOS/Resources/StringResources.resx b/Xamarin.Forms.Platform.iOS/Resources/StringResources.resx
new file mode 100644
index 00000000..e96cccd2
--- /dev/null
+++ b/Xamarin.Forms.Platform.iOS/Resources/StringResources.resx
@@ -0,0 +1,128 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+ <!--
+ Microsoft ResX Schema
+
+ Version 2.0
+
+ The primary goals of this format is to allow a simple XML format
+ that is mostly human readable. The generation and parsing of the
+ various data types are done through the TypeConverter classes
+ associated with the data types.
+
+ Example:
+
+ ... ado.net/XML headers & schema ...
+ <resheader name="resmimetype">text/microsoft-resx</resheader>
+ <resheader name="version">2.0</resheader>
+ <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+ <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+ <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+ <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+ <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+ <value>[base64 mime encoded serialized .NET Framework object]</value>
+ </data>
+ <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+ <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+ <comment>This is a comment</comment>
+ </data>
+
+ There are any number of "resheader" rows that contain simple
+ name/value pairs.
+
+ Each data row contains a name, and value. The row also contains a
+ type or mimetype. Type corresponds to a .NET class that support
+ text/value conversion through the TypeConverter architecture.
+ Classes that don't support this are serialized and stored with the
+ mimetype set.
+
+ The mimetype is used for serialized objects, and tells the
+ ResXResourceReader how to depersist the object. This is currently not
+ extensible. For a given mimetype the value must be set accordingly:
+
+ Note - application/x-microsoft.net.object.binary.base64 is the format
+ that the ResXResourceWriter will generate, however the reader can
+ read any of the formats listed below.
+
+ mimetype: application/x-microsoft.net.object.binary.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.soap.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.bytearray.base64
+ value : The object must be serialized into a byte array
+ : using a System.ComponentModel.TypeConverter
+ : and then encoded with base64 encoding.
+ -->
+ <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+ <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+ <xsd:element name="root" msdata:IsDataSet="true">
+ <xsd:complexType>
+ <xsd:choice maxOccurs="unbounded">
+ <xsd:element name="metadata">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" />
+ </xsd:sequence>
+ <xsd:attribute name="name" use="required" type="xsd:string" />
+ <xsd:attribute name="type" type="xsd:string" />
+ <xsd:attribute name="mimetype" type="xsd:string" />
+ <xsd:attribute ref="xml:space" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="assembly">
+ <xsd:complexType>
+ <xsd:attribute name="alias" type="xsd:string" />
+ <xsd:attribute name="name" type="xsd:string" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="data">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+ <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+ <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+ <xsd:attribute ref="xml:space" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="resheader">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" />
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:choice>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:schema>
+ <resheader name="resmimetype">
+ <value>text/microsoft-resx</value>
+ </resheader>
+ <resheader name="version">
+ <value>2.0</value>
+ </resheader>
+ <resheader name="reader">
+ <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <resheader name="writer">
+ <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <data name="Cancel" xml:space="preserve">
+ <value>Cancel</value>
+ <comment>ContextActionCell Cancel button</comment>
+ </data>
+ <data name="More" xml:space="preserve">
+ <value>More</value>
+ <comment>ContextActionCell More button</comment>
+ </data>
+</root> \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.iOS/Resources/StringResources.ro.resx b/Xamarin.Forms.Platform.iOS/Resources/StringResources.ro.resx
new file mode 100644
index 00000000..0135509d
--- /dev/null
+++ b/Xamarin.Forms.Platform.iOS/Resources/StringResources.ro.resx
@@ -0,0 +1,128 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+ <!--
+ Microsoft ResX Schema
+
+ Version 2.0
+
+ The primary goals of this format is to allow a simple XML format
+ that is mostly human readable. The generation and parsing of the
+ various data types are done through the TypeConverter classes
+ associated with the data types.
+
+ Example:
+
+ ... ado.net/XML headers & schema ...
+ <resheader name="resmimetype">text/microsoft-resx</resheader>
+ <resheader name="version">2.0</resheader>
+ <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+ <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+ <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+ <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+ <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+ <value>[base64 mime encoded serialized .NET Framework object]</value>
+ </data>
+ <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+ <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+ <comment>This is a comment</comment>
+ </data>
+
+ There are any number of "resheader" rows that contain simple
+ name/value pairs.
+
+ Each data row contains a name, and value. The row also contains a
+ type or mimetype. Type corresponds to a .NET class that support
+ text/value conversion through the TypeConverter architecture.
+ Classes that don't support this are serialized and stored with the
+ mimetype set.
+
+ The mimetype is used for serialized objects, and tells the
+ ResXResourceReader how to depersist the object. This is currently not
+ extensible. For a given mimetype the value must be set accordingly:
+
+ Note - application/x-microsoft.net.object.binary.base64 is the format
+ that the ResXResourceWriter will generate, however the reader can
+ read any of the formats listed below.
+
+ mimetype: application/x-microsoft.net.object.binary.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.soap.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.bytearray.base64
+ value : The object must be serialized into a byte array
+ : using a System.ComponentModel.TypeConverter
+ : and then encoded with base64 encoding.
+ -->
+ <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+ <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+ <xsd:element name="root" msdata:IsDataSet="true">
+ <xsd:complexType>
+ <xsd:choice maxOccurs="unbounded">
+ <xsd:element name="metadata">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" />
+ </xsd:sequence>
+ <xsd:attribute name="name" use="required" type="xsd:string" />
+ <xsd:attribute name="type" type="xsd:string" />
+ <xsd:attribute name="mimetype" type="xsd:string" />
+ <xsd:attribute ref="xml:space" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="assembly">
+ <xsd:complexType>
+ <xsd:attribute name="alias" type="xsd:string" />
+ <xsd:attribute name="name" type="xsd:string" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="data">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+ <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+ <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+ <xsd:attribute ref="xml:space" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="resheader">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" />
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:choice>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:schema>
+ <resheader name="resmimetype">
+ <value>text/microsoft-resx</value>
+ </resheader>
+ <resheader name="version">
+ <value>2.0</value>
+ </resheader>
+ <resheader name="reader">
+ <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <resheader name="writer">
+ <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <data name="Cancel" xml:space="preserve">
+ <value>Anulați</value>
+ <comment>ContextActionCell Cancel button</comment>
+ </data>
+ <data name="More" xml:space="preserve">
+ <value>Altele</value>
+ <comment>ContextActionCell More button</comment>
+ </data>
+</root> \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.iOS/Resources/StringResources.ru.resx b/Xamarin.Forms.Platform.iOS/Resources/StringResources.ru.resx
new file mode 100644
index 00000000..6789a97f
--- /dev/null
+++ b/Xamarin.Forms.Platform.iOS/Resources/StringResources.ru.resx
@@ -0,0 +1,128 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+ <!--
+ Microsoft ResX Schema
+
+ Version 2.0
+
+ The primary goals of this format is to allow a simple XML format
+ that is mostly human readable. The generation and parsing of the
+ various data types are done through the TypeConverter classes
+ associated with the data types.
+
+ Example:
+
+ ... ado.net/XML headers & schema ...
+ <resheader name="resmimetype">text/microsoft-resx</resheader>
+ <resheader name="version">2.0</resheader>
+ <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+ <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+ <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+ <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+ <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+ <value>[base64 mime encoded serialized .NET Framework object]</value>
+ </data>
+ <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+ <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+ <comment>This is a comment</comment>
+ </data>
+
+ There are any number of "resheader" rows that contain simple
+ name/value pairs.
+
+ Each data row contains a name, and value. The row also contains a
+ type or mimetype. Type corresponds to a .NET class that support
+ text/value conversion through the TypeConverter architecture.
+ Classes that don't support this are serialized and stored with the
+ mimetype set.
+
+ The mimetype is used for serialized objects, and tells the
+ ResXResourceReader how to depersist the object. This is currently not
+ extensible. For a given mimetype the value must be set accordingly:
+
+ Note - application/x-microsoft.net.object.binary.base64 is the format
+ that the ResXResourceWriter will generate, however the reader can
+ read any of the formats listed below.
+
+ mimetype: application/x-microsoft.net.object.binary.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.soap.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.bytearray.base64
+ value : The object must be serialized into a byte array
+ : using a System.ComponentModel.TypeConverter
+ : and then encoded with base64 encoding.
+ -->
+ <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+ <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+ <xsd:element name="root" msdata:IsDataSet="true">
+ <xsd:complexType>
+ <xsd:choice maxOccurs="unbounded">
+ <xsd:element name="metadata">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" />
+ </xsd:sequence>
+ <xsd:attribute name="name" use="required" type="xsd:string" />
+ <xsd:attribute name="type" type="xsd:string" />
+ <xsd:attribute name="mimetype" type="xsd:string" />
+ <xsd:attribute ref="xml:space" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="assembly">
+ <xsd:complexType>
+ <xsd:attribute name="alias" type="xsd:string" />
+ <xsd:attribute name="name" type="xsd:string" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="data">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+ <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+ <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+ <xsd:attribute ref="xml:space" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="resheader">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" />
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:choice>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:schema>
+ <resheader name="resmimetype">
+ <value>text/microsoft-resx</value>
+ </resheader>
+ <resheader name="version">
+ <value>2.0</value>
+ </resheader>
+ <resheader name="reader">
+ <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <resheader name="writer">
+ <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <data name="Cancel" xml:space="preserve">
+ <value>Oтменить</value>
+ <comment>ContextActionCell Cancel button</comment>
+ </data>
+ <data name="More" xml:space="preserve">
+ <value>Еще</value>
+ <comment>ContextActionCell More button</comment>
+ </data>
+</root> \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.iOS/Resources/StringResources.sk.resx b/Xamarin.Forms.Platform.iOS/Resources/StringResources.sk.resx
new file mode 100644
index 00000000..17dc74f4
--- /dev/null
+++ b/Xamarin.Forms.Platform.iOS/Resources/StringResources.sk.resx
@@ -0,0 +1,128 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+ <!--
+ Microsoft ResX Schema
+
+ Version 2.0
+
+ The primary goals of this format is to allow a simple XML format
+ that is mostly human readable. The generation and parsing of the
+ various data types are done through the TypeConverter classes
+ associated with the data types.
+
+ Example:
+
+ ... ado.net/XML headers & schema ...
+ <resheader name="resmimetype">text/microsoft-resx</resheader>
+ <resheader name="version">2.0</resheader>
+ <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+ <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+ <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+ <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+ <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+ <value>[base64 mime encoded serialized .NET Framework object]</value>
+ </data>
+ <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+ <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+ <comment>This is a comment</comment>
+ </data>
+
+ There are any number of "resheader" rows that contain simple
+ name/value pairs.
+
+ Each data row contains a name, and value. The row also contains a
+ type or mimetype. Type corresponds to a .NET class that support
+ text/value conversion through the TypeConverter architecture.
+ Classes that don't support this are serialized and stored with the
+ mimetype set.
+
+ The mimetype is used for serialized objects, and tells the
+ ResXResourceReader how to depersist the object. This is currently not
+ extensible. For a given mimetype the value must be set accordingly:
+
+ Note - application/x-microsoft.net.object.binary.base64 is the format
+ that the ResXResourceWriter will generate, however the reader can
+ read any of the formats listed below.
+
+ mimetype: application/x-microsoft.net.object.binary.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.soap.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.bytearray.base64
+ value : The object must be serialized into a byte array
+ : using a System.ComponentModel.TypeConverter
+ : and then encoded with base64 encoding.
+ -->
+ <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+ <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+ <xsd:element name="root" msdata:IsDataSet="true">
+ <xsd:complexType>
+ <xsd:choice maxOccurs="unbounded">
+ <xsd:element name="metadata">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" />
+ </xsd:sequence>
+ <xsd:attribute name="name" use="required" type="xsd:string" />
+ <xsd:attribute name="type" type="xsd:string" />
+ <xsd:attribute name="mimetype" type="xsd:string" />
+ <xsd:attribute ref="xml:space" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="assembly">
+ <xsd:complexType>
+ <xsd:attribute name="alias" type="xsd:string" />
+ <xsd:attribute name="name" type="xsd:string" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="data">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+ <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+ <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+ <xsd:attribute ref="xml:space" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="resheader">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" />
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:choice>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:schema>
+ <resheader name="resmimetype">
+ <value>text/microsoft-resx</value>
+ </resheader>
+ <resheader name="version">
+ <value>2.0</value>
+ </resheader>
+ <resheader name="reader">
+ <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <resheader name="writer">
+ <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <data name="Cancel" xml:space="preserve">
+ <value>Zrušiť</value>
+ <comment>ContextActionCell Cancel button</comment>
+ </data>
+ <data name="More" xml:space="preserve">
+ <value>Viac</value>
+ <comment>ContextActionCell More button</comment>
+ </data>
+</root> \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.iOS/Resources/StringResources.sv.resx b/Xamarin.Forms.Platform.iOS/Resources/StringResources.sv.resx
new file mode 100644
index 00000000..671f06f6
--- /dev/null
+++ b/Xamarin.Forms.Platform.iOS/Resources/StringResources.sv.resx
@@ -0,0 +1,128 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+ <!--
+ Microsoft ResX Schema
+
+ Version 2.0
+
+ The primary goals of this format is to allow a simple XML format
+ that is mostly human readable. The generation and parsing of the
+ various data types are done through the TypeConverter classes
+ associated with the data types.
+
+ Example:
+
+ ... ado.net/XML headers & schema ...
+ <resheader name="resmimetype">text/microsoft-resx</resheader>
+ <resheader name="version">2.0</resheader>
+ <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+ <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+ <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+ <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+ <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+ <value>[base64 mime encoded serialized .NET Framework object]</value>
+ </data>
+ <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+ <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+ <comment>This is a comment</comment>
+ </data>
+
+ There are any number of "resheader" rows that contain simple
+ name/value pairs.
+
+ Each data row contains a name, and value. The row also contains a
+ type or mimetype. Type corresponds to a .NET class that support
+ text/value conversion through the TypeConverter architecture.
+ Classes that don't support this are serialized and stored with the
+ mimetype set.
+
+ The mimetype is used for serialized objects, and tells the
+ ResXResourceReader how to depersist the object. This is currently not
+ extensible. For a given mimetype the value must be set accordingly:
+
+ Note - application/x-microsoft.net.object.binary.base64 is the format
+ that the ResXResourceWriter will generate, however the reader can
+ read any of the formats listed below.
+
+ mimetype: application/x-microsoft.net.object.binary.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.soap.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.bytearray.base64
+ value : The object must be serialized into a byte array
+ : using a System.ComponentModel.TypeConverter
+ : and then encoded with base64 encoding.
+ -->
+ <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+ <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+ <xsd:element name="root" msdata:IsDataSet="true">
+ <xsd:complexType>
+ <xsd:choice maxOccurs="unbounded">
+ <xsd:element name="metadata">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" />
+ </xsd:sequence>
+ <xsd:attribute name="name" use="required" type="xsd:string" />
+ <xsd:attribute name="type" type="xsd:string" />
+ <xsd:attribute name="mimetype" type="xsd:string" />
+ <xsd:attribute ref="xml:space" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="assembly">
+ <xsd:complexType>
+ <xsd:attribute name="alias" type="xsd:string" />
+ <xsd:attribute name="name" type="xsd:string" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="data">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+ <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+ <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+ <xsd:attribute ref="xml:space" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="resheader">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" />
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:choice>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:schema>
+ <resheader name="resmimetype">
+ <value>text/microsoft-resx</value>
+ </resheader>
+ <resheader name="version">
+ <value>2.0</value>
+ </resheader>
+ <resheader name="reader">
+ <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <resheader name="writer">
+ <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <data name="Cancel" xml:space="preserve">
+ <value>Avbryt</value>
+ <comment>ContextActionCell Cancel button</comment>
+ </data>
+ <data name="More" xml:space="preserve">
+ <value>Mer</value>
+ <comment>ContextActionCell More button</comment>
+ </data>
+</root> \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.iOS/Resources/StringResources.th.resx b/Xamarin.Forms.Platform.iOS/Resources/StringResources.th.resx
new file mode 100644
index 00000000..b9002921
--- /dev/null
+++ b/Xamarin.Forms.Platform.iOS/Resources/StringResources.th.resx
@@ -0,0 +1,128 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+ <!--
+ Microsoft ResX Schema
+
+ Version 2.0
+
+ The primary goals of this format is to allow a simple XML format
+ that is mostly human readable. The generation and parsing of the
+ various data types are done through the TypeConverter classes
+ associated with the data types.
+
+ Example:
+
+ ... ado.net/XML headers & schema ...
+ <resheader name="resmimetype">text/microsoft-resx</resheader>
+ <resheader name="version">2.0</resheader>
+ <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+ <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+ <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+ <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+ <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+ <value>[base64 mime encoded serialized .NET Framework object]</value>
+ </data>
+ <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+ <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+ <comment>This is a comment</comment>
+ </data>
+
+ There are any number of "resheader" rows that contain simple
+ name/value pairs.
+
+ Each data row contains a name, and value. The row also contains a
+ type or mimetype. Type corresponds to a .NET class that support
+ text/value conversion through the TypeConverter architecture.
+ Classes that don't support this are serialized and stored with the
+ mimetype set.
+
+ The mimetype is used for serialized objects, and tells the
+ ResXResourceReader how to depersist the object. This is currently not
+ extensible. For a given mimetype the value must be set accordingly:
+
+ Note - application/x-microsoft.net.object.binary.base64 is the format
+ that the ResXResourceWriter will generate, however the reader can
+ read any of the formats listed below.
+
+ mimetype: application/x-microsoft.net.object.binary.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.soap.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.bytearray.base64
+ value : The object must be serialized into a byte array
+ : using a System.ComponentModel.TypeConverter
+ : and then encoded with base64 encoding.
+ -->
+ <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+ <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+ <xsd:element name="root" msdata:IsDataSet="true">
+ <xsd:complexType>
+ <xsd:choice maxOccurs="unbounded">
+ <xsd:element name="metadata">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" />
+ </xsd:sequence>
+ <xsd:attribute name="name" use="required" type="xsd:string" />
+ <xsd:attribute name="type" type="xsd:string" />
+ <xsd:attribute name="mimetype" type="xsd:string" />
+ <xsd:attribute ref="xml:space" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="assembly">
+ <xsd:complexType>
+ <xsd:attribute name="alias" type="xsd:string" />
+ <xsd:attribute name="name" type="xsd:string" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="data">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+ <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+ <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+ <xsd:attribute ref="xml:space" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="resheader">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" />
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:choice>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:schema>
+ <resheader name="resmimetype">
+ <value>text/microsoft-resx</value>
+ </resheader>
+ <resheader name="version">
+ <value>2.0</value>
+ </resheader>
+ <resheader name="reader">
+ <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <resheader name="writer">
+ <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <data name="Cancel" xml:space="preserve">
+ <value>ยกเลิก</value>
+ <comment>ContextActionCell Cancel button</comment>
+ </data>
+ <data name="More" xml:space="preserve">
+ <value>เพิ่มเติม</value>
+ <comment>ContextActionCell More button</comment>
+ </data>
+</root> \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.iOS/Resources/StringResources.tr.resx b/Xamarin.Forms.Platform.iOS/Resources/StringResources.tr.resx
new file mode 100644
index 00000000..256919f4
--- /dev/null
+++ b/Xamarin.Forms.Platform.iOS/Resources/StringResources.tr.resx
@@ -0,0 +1,128 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+ <!--
+ Microsoft ResX Schema
+
+ Version 2.0
+
+ The primary goals of this format is to allow a simple XML format
+ that is mostly human readable. The generation and parsing of the
+ various data types are done through the TypeConverter classes
+ associated with the data types.
+
+ Example:
+
+ ... ado.net/XML headers & schema ...
+ <resheader name="resmimetype">text/microsoft-resx</resheader>
+ <resheader name="version">2.0</resheader>
+ <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+ <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+ <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+ <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+ <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+ <value>[base64 mime encoded serialized .NET Framework object]</value>
+ </data>
+ <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+ <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+ <comment>This is a comment</comment>
+ </data>
+
+ There are any number of "resheader" rows that contain simple
+ name/value pairs.
+
+ Each data row contains a name, and value. The row also contains a
+ type or mimetype. Type corresponds to a .NET class that support
+ text/value conversion through the TypeConverter architecture.
+ Classes that don't support this are serialized and stored with the
+ mimetype set.
+
+ The mimetype is used for serialized objects, and tells the
+ ResXResourceReader how to depersist the object. This is currently not
+ extensible. For a given mimetype the value must be set accordingly:
+
+ Note - application/x-microsoft.net.object.binary.base64 is the format
+ that the ResXResourceWriter will generate, however the reader can
+ read any of the formats listed below.
+
+ mimetype: application/x-microsoft.net.object.binary.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.soap.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.bytearray.base64
+ value : The object must be serialized into a byte array
+ : using a System.ComponentModel.TypeConverter
+ : and then encoded with base64 encoding.
+ -->
+ <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+ <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+ <xsd:element name="root" msdata:IsDataSet="true">
+ <xsd:complexType>
+ <xsd:choice maxOccurs="unbounded">
+ <xsd:element name="metadata">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" />
+ </xsd:sequence>
+ <xsd:attribute name="name" use="required" type="xsd:string" />
+ <xsd:attribute name="type" type="xsd:string" />
+ <xsd:attribute name="mimetype" type="xsd:string" />
+ <xsd:attribute ref="xml:space" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="assembly">
+ <xsd:complexType>
+ <xsd:attribute name="alias" type="xsd:string" />
+ <xsd:attribute name="name" type="xsd:string" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="data">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+ <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+ <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+ <xsd:attribute ref="xml:space" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="resheader">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" />
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:choice>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:schema>
+ <resheader name="resmimetype">
+ <value>text/microsoft-resx</value>
+ </resheader>
+ <resheader name="version">
+ <value>2.0</value>
+ </resheader>
+ <resheader name="reader">
+ <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <resheader name="writer">
+ <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <data name="Cancel" xml:space="preserve">
+ <value>Vazgeç</value>
+ <comment>ContextActionCell Cancel button</comment>
+ </data>
+ <data name="More" xml:space="preserve">
+ <value>Diğer</value>
+ <comment>ContextActionCell More button</comment>
+ </data>
+</root> \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.iOS/Resources/StringResources.uk.resx b/Xamarin.Forms.Platform.iOS/Resources/StringResources.uk.resx
new file mode 100644
index 00000000..c3a5aa44
--- /dev/null
+++ b/Xamarin.Forms.Platform.iOS/Resources/StringResources.uk.resx
@@ -0,0 +1,128 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+ <!--
+ Microsoft ResX Schema
+
+ Version 2.0
+
+ The primary goals of this format is to allow a simple XML format
+ that is mostly human readable. The generation and parsing of the
+ various data types are done through the TypeConverter classes
+ associated with the data types.
+
+ Example:
+
+ ... ado.net/XML headers & schema ...
+ <resheader name="resmimetype">text/microsoft-resx</resheader>
+ <resheader name="version">2.0</resheader>
+ <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+ <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+ <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+ <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+ <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+ <value>[base64 mime encoded serialized .NET Framework object]</value>
+ </data>
+ <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+ <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+ <comment>This is a comment</comment>
+ </data>
+
+ There are any number of "resheader" rows that contain simple
+ name/value pairs.
+
+ Each data row contains a name, and value. The row also contains a
+ type or mimetype. Type corresponds to a .NET class that support
+ text/value conversion through the Ty