File: iOS\Renderers\SwipeViewRenderer.cs
Web Access
Project: src\src\Compatibility\Core\src\Compatibility.csproj (Microsoft.Maui.Controls.Compatibility)
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using CoreGraphics;
using Foundation;
using Microsoft.Extensions.Logging;
using Microsoft.Maui.Controls.Internals;
using Microsoft.Maui.Controls.Platform;
using Microsoft.Maui.Controls.PlatformConfiguration.iOSSpecific;
using Microsoft.Maui.Graphics;
using ObjCRuntime;
using UIKit;
using Specifics = Microsoft.Maui.Controls.PlatformConfiguration.iOSSpecific.SwipeView;
 
namespace Microsoft.Maui.Controls.Compatibility.Platform.iOS
{
	[System.Obsolete(Compatibility.Hosting.MauiAppBuilderExtensions.UseMapperInstead)]
	public class SwipeViewRenderer : ViewRenderer<SwipeView, UIView>
	{
		const float MinimumOpenSwipeThresholdPercentage = 0.15f; // 15%
		const float OpenSwipeThresholdPercentage = 0.6f; // 60%
		const double SwipeThreshold = 250;
		const double SwipeItemWidth = 100;
		const double SwipeAnimationDuration = 0.2;
		const double SwipeMinimumDelta = 10;
 
		readonly Dictionary<ISwipeItem, object> _swipeItems;
		UITapGestureRecognizer _tapGestureRecognizer;
		UIPanGestureRecognizer _panGestureRecognizer;
		View _scrollParent;
		UIView _contentView;
		UIStackView _actionView;
		SwipeTransitionMode _swipeTransitionMode;
		SwipeDirection? _swipeDirection;
		CGPoint _initialPoint;
		bool _isTouchDown;
		bool _isSwiping;
		double _swipeOffset;
		double _swipeThreshold;
		CGRect _originalBounds;
		List<CGRect> _swipeItemsRect;
		double _previousScrollX;
		double _previousScrollY;
		int _previousFirstVisibleIndex;
		bool _isSwipeEnabled;
		bool _isScrollEnabled;
		bool _isResettingSwipe;
		bool _isOpen;
		OpenSwipeItem _previousOpenSwipeItem;
		bool _isDisposed;
 
		[Microsoft.Maui.Controls.Internals.Preserve(Conditional = true)]
		public SwipeViewRenderer()
		{
			_swipeItems = new Dictionary<ISwipeItem, object>();
			_isScrollEnabled = true;
 
			_tapGestureRecognizer = new UITapGestureRecognizer(HandleTap)
			{
				CancelsTouchesInView = false,
				DelaysTouchesBegan = false,
				DelaysTouchesEnded = false
			};
 
			_tapGestureRecognizer.ShouldReceiveTouch = OnShouldReceiveTouch;
 
			_panGestureRecognizer = new UIPanGestureRecognizer(HandlePan)
			{
				CancelsTouchesInView = false,
				DelaysTouchesBegan = false,
				DelaysTouchesEnded = false
			};
 
			_panGestureRecognizer.ShouldRecognizeSimultaneously = (recognizer, gestureRecognizer) => true;
 
			AddGestureRecognizer(_tapGestureRecognizer);
			AddGestureRecognizer(_panGestureRecognizer);
		}
 
		protected override void OnElementChanged(ElementChangedEventArgs<SwipeView> e)
		{
			if (e.NewElement != null)
			{
				e.NewElement.OpenRequested += OnOpenRequested;
				e.NewElement.CloseRequested += OnCloseRequested;
 
				if (Control == null)
				{
					SetNativeControl(CreateNativeControl());
				}
 
				UpdateContent();
				UpdateIsSwipeEnabled();
				UpdateSwipeTransitionMode();
			}
 
			if (e.OldElement != null)
			{
				e.OldElement.OpenRequested -= OnOpenRequested;
				e.OldElement.CloseRequested -= OnCloseRequested;
			}
 
			base.OnElementChanged(e);
		}
 
		protected override UIView CreateNativeControl()
		{
			return new UIView();
		}
 
		public override void WillMoveToWindow(UIWindow window)
		{
			base.WillMoveToWindow(window);
 
			if (window != null && _scrollParent == null)
			{
				_scrollParent = Element.FindParentOfType<ScrollView>();
 
				if (_scrollParent is ScrollView scrollView)
				{
					scrollView.Scrolled += OnParentScrolled;
					return;
				}
 
				_scrollParent = Element.FindParentOfType<ListView>();
 
				if (_scrollParent is ListView listView)
				{
					listView.Scrolled += OnParentScrolled;
					return;
				}
 
				_scrollParent = Element.FindParentOfType<CollectionView>();
 
				if (_scrollParent is CollectionView collectionView)
				{
					collectionView.Scrolled += OnParentScrolled;
				}
			}
		}
 
		public override void LayoutSubviews()
		{
			base.LayoutSubviews();
 
			if (Bounds.X < 0 || Bounds.Y < 0)
				Bounds = new CGRect(0, 0, Bounds.Width, Bounds.Height);
 
			if (_contentView != null && _contentView.Frame.IsEmpty)
				_contentView.Frame = Bounds;
		}
 
		protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
		{
			base.OnElementPropertyChanged(sender, e);
 
			if (e.PropertyName == ContentView.ContentProperty.PropertyName)
				UpdateContent();
			else if (e.PropertyName == VisualElement.IsEnabledProperty.PropertyName)
				UpdateIsSwipeEnabled();
			else if (e.PropertyName == Specifics.SwipeTransitionModeProperty.PropertyName)
				UpdateSwipeTransitionMode();
		}
 
		protected override void SetBackgroundColor(Color color)
		{
			if (Element.BackgroundColor != null)
				BackgroundColor = Element.BackgroundColor.ToPlatform();
			else
				BackgroundColor = ColorExtensions.BackgroundColor;
		}
 
		protected override void SetBackground(Brush brush)
		{
			Brush background = Element.Background;
 
			if (Brush.IsNullOrEmpty(background))
				return;
 
			if (Control != null)
				Control.UpdateBackground(background);
		}
 
		public override void TouchesEnded(NSSet touches, UIEvent evt)
		{
			if (_swipeOffset != 0)
			{
				TouchesCancelled(touches, evt);
				return;
			}
 
			base.TouchesEnded(touches, evt);
		}
 
		public override void TouchesCancelled(NSSet touches, UIEvent evt)
		{
			var navigationController = GetUINavigationController(GetViewController());
 
			if (navigationController != null)
				navigationController.InteractivePopGestureRecognizer.Enabled = true;
 
			if (touches.AnyObject is UITouch anyObject)
			{
				CGPoint point = anyObject.LocationInView(this);
				HandleTouchInteractions(GestureStatus.Canceled, point);
			}
 
			base.TouchesCancelled(touches, evt);
		}
 
		protected override void Dispose(bool disposing)
		{
			if (_isDisposed)
				return;
 
			if (disposing)
			{
				if (Element != null)
				{
					Element.OpenRequested -= OnOpenRequested;
					Element.CloseRequested -= OnCloseRequested;
 
					if (Element.Content != null)
						Element.Content.PropertyChanged -= OnContentPropertyChanged;
				}
 
				if (_scrollParent != null)
				{
					if (_scrollParent is ScrollView scrollView)
						scrollView.Scrolled -= OnParentScrolled;
 
					if (_scrollParent is ListView listView)
						listView.Scrolled -= OnParentScrolled;
 
					if (_scrollParent is CollectionView collectionView)
						collectionView.Scrolled -= OnParentScrolled;
				}
 
				if (_tapGestureRecognizer != null)
				{
					Control.RemoveGestureRecognizer(_tapGestureRecognizer);
					_tapGestureRecognizer.Dispose();
					_tapGestureRecognizer = null;
				}
 
				if (_panGestureRecognizer != null)
				{
					Control.RemoveGestureRecognizer(_panGestureRecognizer);
					_panGestureRecognizer.Dispose();
					_panGestureRecognizer = null;
				}
 
				if (_contentView != null)
				{
					_contentView.Dispose();
					_contentView = null;
				}
 
				if (_actionView != null)
				{
					_actionView.Dispose();
					_actionView = null;
				}
 
				if (_swipeItemsRect != null)
				{
					_swipeItemsRect.Clear();
					_swipeItemsRect = null;
				}
			}
 
			_isDisposed = true;
 
			base.Dispose(disposing);
		}
 
		public override UIView HitTest(CGPoint point, UIEvent uievent)
		{
			if (!UserInteractionEnabled || Hidden)
				return null;
 
			foreach (var subview in Subviews)
			{
				if (subview.UserInteractionEnabled)
				{
					var view = HitTest(subview, point, uievent);
 
					if (view != null)
						return view;
				}
			}
 
			return base.HitTest(point, uievent);
		}
 
		UIView HitTest(UIView view, CGPoint point, UIEvent uievent)
		{
			if (view.Subviews == null)
				return null;
 
			foreach (var subview in view.Subviews)
			{
				if (subview.UserInteractionEnabled)
				{
					CGPoint subPoint = subview.ConvertPointFromView(point, this);
					UIView result = subview.HitTest(subPoint, uievent);
 
					if (result != null)
					{
						return result;
					}
				}
			}
 
			return null;
		}
 
		bool OnShouldReceiveTouch(UIGestureRecognizer recognizer, UITouch touch)
		{
			return _swipeOffset != 0;
		}
 
		void UpdateContent()
		{
			ClipsToBounds = true;
 
			if (Element.Content == null)
			{
				_contentView = CreateEmptyContent();
				AddSubview(_contentView);
			}
			else
			{
				Element.Content.PropertyChanged += OnContentPropertyChanged;
 
				if (Subviews.Length > 0)
					_contentView = Subviews[0];
			}
 
			if (_contentView != null)
				BringSubviewToFront(_contentView);
		}
 
		void OnContentPropertyChanged(object sender, PropertyChangedEventArgs e)
		{
			if (e.PropertyName == VisualElement.IsEnabledProperty.PropertyName)
				UpdateIsSwipeEnabled();
		}
 
		void HandleTap()
		{
			if (_tapGestureRecognizer == null)
				return;
 
			if (_isSwiping)
				return;
 
			var state = _tapGestureRecognizer.State;
 
			if (state != UIGestureRecognizerState.Cancelled)
			{
				if (_contentView == null)
					return;
 
				var point = _tapGestureRecognizer.LocationInView(this);
 
				if (_isOpen)
				{
					if (!TouchInsideContent(point))
						ProcessTouchSwipeItems(point);
					else
						ResetSwipe();
				}
			}
		}
 
		void HandlePan(UIPanGestureRecognizer panGestureRecognizer)
		{
			if (_isSwipeEnabled && panGestureRecognizer != null)
			{
				CGPoint point = panGestureRecognizer.LocationInView(this);
				var navigationController = GetUINavigationController(GetViewController());
 
				switch (panGestureRecognizer.State)
				{
					case UIGestureRecognizerState.Began:
						if (navigationController != null)
							navigationController.InteractivePopGestureRecognizer.Enabled = false;
 
						HandleTouchInteractions(GestureStatus.Started, point);
						break;
					case UIGestureRecognizerState.Changed:
						HandleTouchInteractions(GestureStatus.Running, point);
						break;
					case UIGestureRecognizerState.Ended:
						if (navigationController != null)
							navigationController.InteractivePopGestureRecognizer.Enabled = true;
 
						HandleTouchInteractions(GestureStatus.Completed, point);
						break;
					case UIGestureRecognizerState.Cancelled:
						if (navigationController != null)
							navigationController.InteractivePopGestureRecognizer.Enabled = true;
 
						HandleTouchInteractions(GestureStatus.Canceled, point);
						break;
				}
			}
		}
 
		UIView CreateEmptyContent()
		{
			var emptyContentView = new UIView
			{
				BackgroundColor = Colors.Transparent.ToPlatform()
			};
 
			return emptyContentView;
		}
 
		void UpdateIsSwipeEnabled()
		{
			UserInteractionEnabled = true;
			_isSwipeEnabled = Element.IsEnabled;
 
			var isContentEnabled = Element.Content.IsEnabled;
			_contentView.UserInteractionEnabled = isContentEnabled;
		}
 
		bool IsHorizontalSwipe()
		{
			return _swipeDirection == SwipeDirection.Left || _swipeDirection == SwipeDirection.Right;
		}
 
		bool IsValidSwipeItems(SwipeItems swipeItems)
		{
			return swipeItems != null && swipeItems.Where(s => s.IsVisible).Count() > 0;
		}
 
		void UpdateSwipeItems()
		{
			if (_contentView == null || _actionView != null)
				return;
 
			SwipeItems items = GetSwipeItemsByDirection();
 
			if (items == null || items.Count == 0)
				return;
 
			_swipeItemsRect = new List<CGRect>();
 
			double swipeItemsWidth;
 
			if (_swipeDirection == SwipeDirection.Left || _swipeDirection == SwipeDirection.Right)
				swipeItemsWidth = (items != null ? items.Count : 0) * SwipeItemWidth;
			else
				swipeItemsWidth = _contentView.Frame.Width;
 
			_actionView = new UIStackView
			{
				Axis = UILayoutConstraintAxis.Horizontal,
				Frame = new CGRect(0, 0, swipeItemsWidth, _contentView.Frame.Height)
			};
 
			foreach (var item in items)
			{
				UIView swipeItem = null;
 
				if (item is SwipeItem formsSwipeItem)
				{
					formsSwipeItem.PropertyChanged += OnSwipeItemPropertyChanged;
					swipeItem = CreateSwipeItem(formsSwipeItem);
					_actionView.AddSubview(swipeItem);
					_swipeItems.Add(formsSwipeItem, swipeItem);
				}
 
				if (item is SwipeItemView formsSwipeItemView)
				{
					formsSwipeItemView.PropertyChanged += OnSwipeItemPropertyChanged;
					swipeItem = CreateSwipeItemView(formsSwipeItemView);
					_actionView.AddSubview(swipeItem);
					_swipeItems.Add(formsSwipeItemView, swipeItem);
				}
			}
 
			AddSubview(_actionView);
			BringSubviewToFront(_contentView);
 
			LayoutSwipeItems(GetNativeSwipeItems());
		}
 
		void LayoutSwipeItems(List<UIView> childs)
		{
			if (_actionView == null || childs == null)
				return;
 
			_swipeItemsRect.Clear();
 
			var items = GetSwipeItemsByDirection();
 
			if (items == null || items.Count == 0)
				return;
 
			if (_originalBounds == CGRect.Empty)
				_originalBounds = _contentView.Frame;
 
			int i = 0;
			float previousWidth = 0;
 
			foreach (var child in childs)
			{
				if (!child.Hidden)
				{
					var item = items[i];
					var swipeItemSize = GetSwipeItemSize(item);
 
					float swipeItemHeight = (float)swipeItemSize.Height;
					float swipeItemWidth = (float)swipeItemSize.Width;
 
					switch (_swipeDirection)
					{
						case SwipeDirection.Left:
							child.Frame = new CGRect(_originalBounds.X + _contentView.Frame.Width - (swipeItemWidth + previousWidth), _originalBounds.Y, swipeItemWidth, swipeItemHeight);
							break;
						case SwipeDirection.Right:
							child.Frame = new CGRect(_originalBounds.X + previousWidth, _originalBounds.Y, swipeItemWidth, swipeItemHeight);
							break;
						case SwipeDirection.Down:
							child.Frame = new CGRect(_originalBounds.X + previousWidth, _originalBounds.Y, swipeItemWidth, swipeItemHeight);
							break;
						case SwipeDirection.Up:
							child.Frame = new CGRect(_originalBounds.X + previousWidth, _contentView.Frame.Height - swipeItemHeight + _originalBounds.Y, swipeItemWidth, swipeItemHeight);
							break;
					}
 
					if (child is UIButton button)
					{
						UpdateSwipeItemIconImage(button, (SwipeItem)item);
						UpdateSwipeItemInsets(button);
					}
 
					i++;
					previousWidth += swipeItemWidth;
				}
 
				_swipeItemsRect.Add(child.Frame);
			}
		}
 
		List<UIView> GetNativeSwipeItems()
		{
			var swipeItems = new List<UIView>();
 
			foreach (var view in _actionView.Subviews)
				swipeItems.Add(view);
 
			return swipeItems;
		}
 
		void OnSwipeItemPropertyChanged(object sender, PropertyChangedEventArgs e)
		{
			var swipeItem = (ISwipeItem)sender;
 
			if (e.PropertyName == SwipeItem.IsVisibleProperty.PropertyName)
			{
				UpdateIsVisibleSwipeItem(swipeItem);
			}
		}
 
		void UpdateIsVisibleSwipeItem(ISwipeItem item)
		{
			if (!_isOpen)
				return;
 
			_swipeItems.TryGetValue(item, out object view);
 
			if (view != null && view is UIView nativeView)
			{
				bool hidden = false;
 
				if (item is SwipeItem swipeItem)
					hidden = !swipeItem.IsVisible;
 
				if (item is SwipeItemView swipeItemView)
					hidden = !swipeItemView.IsVisible;
 
				_swipeThreshold = 0;
				nativeView.Hidden = hidden;
				LayoutSwipeItems(GetNativeSwipeItems());
				SwipeToThreshold(false);
			}
		}
 
		UIButton CreateSwipeItem(SwipeItem formsSwipeItem)
		{
			var swipeItem = new UIButton(UIButtonType.Custom)
			{
				RestorationIdentifier = formsSwipeItem.Text,
				BackgroundColor = formsSwipeItem.BackgroundColor.ToPlatform()
			};
 
			if (!string.IsNullOrEmpty(formsSwipeItem.Text))
				swipeItem.RestorationIdentifier = formsSwipeItem.Text;
 
			if (!string.IsNullOrEmpty(formsSwipeItem.AutomationId))
				swipeItem.AccessibilityIdentifier = formsSwipeItem.AutomationId;
 
			swipeItem.SetTitle(formsSwipeItem.Text, UIControlState.Normal);
 
			var textColor = GetSwipeItemColor(formsSwipeItem.BackgroundColor);
			swipeItem.SetTitleColor(textColor.ToPlatform(), UIControlState.Normal);
			swipeItem.UserInteractionEnabled = false;
			swipeItem.Hidden = !formsSwipeItem.IsVisible;
 
			if (!string.IsNullOrEmpty(formsSwipeItem.AutomationId))
				swipeItem.AccessibilityIdentifier = formsSwipeItem.AutomationId;
 
			return swipeItem;
		}
 
		UIView CreateSwipeItemView(SwipeItemView formsSwipeItemView)
		{
			var renderer = Platform.CreateRenderer(formsSwipeItemView);
			Platform.SetRenderer(formsSwipeItemView, renderer);
			UpdateSwipeItemViewLayout(formsSwipeItemView);
 
			var swipeItemView = renderer?.NativeView;
 
			if (swipeItemView != null)
				swipeItemView.Hidden = !formsSwipeItemView.IsVisible;
 
			return swipeItemView;
		}
 
		void UpdateSwipeItemViewLayout(SwipeItemView swipeItemView)
		{
			var swipeItemSize = GetSwipeItemSize(swipeItemView);
 
			swipeItemView.Layout(new Rect(0, 0, swipeItemSize.Width, swipeItemSize.Height));
		}
 
		void UpdateSwipeTransitionMode()
		{
			if (Element.IsSet(Specifics.SwipeTransitionModeProperty))
				_swipeTransitionMode = Element.OnThisPlatform().GetSwipeTransitionMode();
			else
				_swipeTransitionMode = SwipeTransitionMode.Reveal;
		}
 
		void UpdateSwipeItemInsets(UIButton button, float spacing = 0.0f)
		{
			if (button.ImageView?.Image == null)
				return;
 
			button.ContentMode = UIViewContentMode.Center;
			button.ImageView.ContentMode = UIViewContentMode.ScaleAspectFit;
 
			var imageSize = button.ImageView.Image.Size;
 
			var titleEdgeInsets = new UIEdgeInsets(spacing, -imageSize.Width, -imageSize.Height, 0.0f);
#pragma warning disable CA1416, CA1422 // TODO: TitleEdgeInsets, StringSize(...), ImageEdgeInsets unsupported on: 'ios' 15.0 and later
			button.TitleEdgeInsets = titleEdgeInsets;
 
			var labelString = button.TitleLabel.Text ?? string.Empty;
 
#pragma warning disable BI1234 // Type or member is obsolete
			var titleSize = !string.IsNullOrEmpty(labelString) ? labelString.StringSize(button.TitleLabel.Font) : CGSize.Empty;
#pragma warning restore BI1234 // Type or member is obsolete
			var imageEdgeInsets = new UIEdgeInsets(-(titleSize.Height + spacing), 0.0f, 0.0f, -titleSize.Width);
			button.ImageEdgeInsets = imageEdgeInsets;
#pragma warning restore CA1416, CA1422
		}
 
		Color GetSwipeItemColor(Color backgroundColor)
		{
			var luminosity = 0.2126 * backgroundColor.Red + 0.7152 * backgroundColor.Green + 0.0722 * backgroundColor.Blue;
 
			return luminosity < 0.75 ? Colors.White : Colors.Black;
		}
 
		async void UpdateSwipeItemIconImage(UIButton swipeButton, SwipeItem swipeItem)
		{
			if (swipeButton == null)
				return;
 
			if (swipeItem.IconImageSource == null || swipeItem.IconImageSource.IsEmpty)
			{
				swipeButton.SetImage(null, UIControlState.Normal);
			}
			else
			{
				var image = await swipeItem.IconImageSource.GetNativeImageAsync();
 
				var maxWidth = swipeButton.Frame.Width * 0.5f;
				var maxHeight = swipeButton.Frame.Height * 0.5f;
 
				var resizedImage = MaxResizeSwipeItemIconImage(image, maxWidth, maxHeight);
 
				try
				{
					swipeButton.SetImage(resizedImage.ImageWithRenderingMode(UIImageRenderingMode.AlwaysTemplate), UIControlState.Normal);
					var tintColor = GetSwipeItemColor(swipeItem.BackgroundColor);
					swipeButton.TintColor = tintColor.ToPlatform();
				}
				catch (Exception)
				{
					// UIImage ctor throws on file not found if MonoTouch.ObjCRuntime.Class.ThrowOnInitFailure is true;
					Forms.MauiContext?.CreateLogger<SwipeViewRenderer>()?.LogWarning("Cannot load SwipeItem Icon");
				}
			}
		}
 
		UIImage MaxResizeSwipeItemIconImage(UIImage sourceImage, nfloat maxWidth, nfloat maxHeight)
		{
			if (sourceImage == null)
				return null;
 
			var sourceSize = sourceImage.Size;
			var maxResizeFactor = Math.Min(maxWidth / sourceSize.Width, maxHeight / sourceSize.Height);
 
			if (maxResizeFactor > 1)
				return sourceImage;
 
			var width = maxResizeFactor * sourceSize.Width;
			var height = maxResizeFactor * sourceSize.Height;
			UIGraphics.BeginImageContextWithOptions(new CGSize((nfloat)width, (nfloat)height), false, 0);
			sourceImage.Draw(new CGRect(0, 0, (nfloat)width, (nfloat)height));
			var resultImage = UIGraphics.GetImageFromCurrentImageContext();
			UIGraphics.EndImageContext();
 
			return resultImage;
		}
 
		void HandleTouchInteractions(GestureStatus status, CGPoint point)
		{
			switch (status)
			{
				case GestureStatus.Started:
					ProcessTouchDown(point);
					break;
				case GestureStatus.Running:
					ProcessTouchMove(point);
					break;
				case GestureStatus.Canceled:
				case GestureStatus.Completed:
					ProcessTouchUp();
					break;
			}
 
			_isTouchDown = false;
		}
 
		void ProcessTouchDown(CGPoint point)
		{
			if (_isSwiping || _isTouchDown || _contentView == null)
				return;
 
			if (TouchInsideContent(point) && _isOpen)
				ResetSwipe();
 
			_initialPoint = point;
			_isTouchDown = true;
		}
 
		void ProcessTouchMove(CGPoint point)
		{
			if (_contentView == null || !TouchInsideContent(point))
				return;
 
			if (!_isOpen)
			{
				ResetSwipeToInitialPosition();
 
				_swipeDirection = SwipeDirectionHelper.GetSwipeDirection(new Point(_initialPoint.X, _initialPoint.Y), new Point(point.X, point.Y));
 
				UpdateSwipeItems();
			}
 
			if (!_isSwiping)
			{
				RaiseSwipeStarted();
				_isSwiping = true;
			}
 
			if (!ValidateSwipeDirection() || _isResettingSwipe)
				return;
 
			_swipeOffset = GetSwipeOffset(_initialPoint, point);
			UpdateIsOpen(_swipeOffset != 0);
 
			if (Math.Abs(_swipeOffset) > double.Epsilon)
			{
				IsParentScrollEnabled(false);
				Swipe();
			}
 
			RaiseSwipeChanging();
		}
 
		void ProcessTouchUp()
		{
			_isTouchDown = false;
 
			if (!_isSwiping)
				return;
 
			_isSwiping = false;
			IsParentScrollEnabled(true);
 
			RaiseSwipeEnded();
 
			if (_isResettingSwipe || !ValidateSwipeDirection())
				return;
 
			ValidateSwipeThreshold();
		}
 
		void IsParentScrollEnabled(bool scrollEnabled)
		{
			var swipeThresholdPercent = MinimumOpenSwipeThresholdPercentage * GetSwipeThreshold();
 
			if (Math.Abs(_swipeOffset) < swipeThresholdPercent)
				return;
 
			if (scrollEnabled == _isScrollEnabled)
				return;
 
			_isScrollEnabled = scrollEnabled;
 
			var parent = this.GetParentOfType<UIScrollView>();
 
			if (parent != null)
				parent.ScrollEnabled = _isScrollEnabled;
		}
 
		bool TouchInsideContent(CGPoint point)
		{
			if (_contentView == null)
				return false;
 
			bool touchContent = TouchInsideContent(_contentView.Frame.Left, _contentView.Frame.Top, _contentView.Frame.Width, _contentView.Frame.Height, point.X, point.Y);
 
			return touchContent;
		}
 
		bool TouchInsideContent(double x1, double y1, double x2, double y2, double x, double y)
		{
			if (x > x1 && x < (x1 + x2) && y > y1 && y < (y1 + y2))
				return true;
 
			return false;
		}
 
		SwipeItems GetSwipeItemsByDirection()
		{
			if (_swipeDirection.HasValue)
				return GetSwipeItemsByDirection(_swipeDirection.Value);
 
			return null;
		}
 
		SwipeItems GetSwipeItemsByDirection(SwipeDirection swipeDirection)
		{
			SwipeItems swipeItems = null;
 
			switch (swipeDirection)
			{
				case SwipeDirection.Left:
					swipeItems = Element.RightItems;
					break;
				case SwipeDirection.Right:
					swipeItems = Element.LeftItems;
					break;
				case SwipeDirection.Up:
					swipeItems = Element.BottomItems;
					break;
				case SwipeDirection.Down:
					swipeItems = Element.TopItems;
					break;
			}
 
			return swipeItems;
		}
 
		void Swipe(bool animated = false)
		{
			if (_contentView == null)
				return;
 
			var offset = ValidateSwipeOffset(_swipeOffset);
			_isOpen = offset != 0;
			var swipeAnimationDuration = animated ? SwipeAnimationDuration : 0;
 
			if (_swipeTransitionMode == SwipeTransitionMode.Reveal)
			{
				Animate(swipeAnimationDuration, 0.0, UIViewAnimationOptions.CurveEaseOut, () =>
				{
					switch (_swipeDirection)
					{
						case SwipeDirection.Left:
							_contentView.Frame = new CGRect(_originalBounds.X + offset, _originalBounds.Y, _originalBounds.Width, _originalBounds.Height);
							break;
						case SwipeDirection.Right:
							_contentView.Frame = new CGRect(_originalBounds.X + offset, _originalBounds.Y, _originalBounds.Width, _originalBounds.Height);
							break;
						case SwipeDirection.Up:
							_contentView.Frame = new CGRect(_originalBounds.X, _originalBounds.Y + offset, _originalBounds.Width, _originalBounds.Height);
							break;
						case SwipeDirection.Down:
							_contentView.Frame = new CGRect(_originalBounds.X, _originalBounds.Y + offset, _originalBounds.Width, _originalBounds.Height);
							break;
					}
				}, null);
			}
 
			if (_swipeTransitionMode == SwipeTransitionMode.Drag)
			{
				var actionBounds = _actionView.Bounds;
				double actionSize;
 
				Animate(swipeAnimationDuration, 0.0, UIViewAnimationOptions.CurveEaseOut, () =>
				{
					switch (_swipeDirection)
					{
						case SwipeDirection.Left:
							_contentView.Frame = new CGRect(_originalBounds.X + offset, _originalBounds.Y, _originalBounds.Width, _originalBounds.Height);
							actionSize = Element.RightItems.Count * SwipeItemWidth;
							_actionView.Frame = new CGRect(actionSize + offset, actionBounds.Y, actionBounds.Width, actionBounds.Height);
							break;
						case SwipeDirection.Right:
							_contentView.Frame = new CGRect(_originalBounds.X + offset, _originalBounds.Y, _originalBounds.Width, _originalBounds.Height);
							actionSize = Element.LeftItems.Count * SwipeItemWidth;
							_actionView.Frame = new CGRect(-actionSize + offset, actionBounds.Y, actionBounds.Width, actionBounds.Height);
							break;
						case SwipeDirection.Up:
							_contentView.Frame = new CGRect(_originalBounds.X, _originalBounds.Y + offset, _originalBounds.Width, _originalBounds.Height);
							actionSize = _contentView.Frame.Height;
							_actionView.Frame = new CGRect(actionBounds.X, actionSize - Math.Abs(offset), actionBounds.Width, actionBounds.Height);
							break;
						case SwipeDirection.Down:
							_contentView.Frame = new CGRect(_originalBounds.X, _originalBounds.Y + offset, _originalBounds.Width, _originalBounds.Height);
							actionSize = _contentView.Frame.Height;
							_actionView.Frame = new CGRect(actionBounds.X, -actionSize + Math.Abs(offset), actionBounds.Width, actionBounds.Height);
							break;
					}
				}, null);
			}
		}
 
		double ValidateSwipeOffset(double offset)
		{
			var swipeThreshold = GetSwipeThreshold();
 
			switch (_swipeDirection)
			{
				case SwipeDirection.Left:
					if (offset > 0)
						offset = 0;
 
					if (_isResettingSwipe && offset < 0)
						offset = 0;
 
					if (Math.Abs(offset) > swipeThreshold)
						return -swipeThreshold;
					break;
				case SwipeDirection.Right:
					if (offset < 0)
						offset = 0;
 
					if (_isResettingSwipe && offset > 0)
						offset = 0;
 
					if (Math.Abs(offset) > swipeThreshold)
						return swipeThreshold;
					break;
				case SwipeDirection.Up:
					if (offset > 0)
						offset = 0;
 
					if (_isResettingSwipe && offset < 0)
						offset = 0;
 
					if (Math.Abs(offset) > swipeThreshold)
						return -swipeThreshold;
					break;
				case SwipeDirection.Down:
					if (offset < 0)
						offset = 0;
 
					if (_isResettingSwipe && offset > 0)
						offset = 0;
 
					if (Math.Abs(offset) > swipeThreshold)
						return swipeThreshold;
					break;
			}
 
			return offset;
		}
 
		void UnsubscribeSwipeItemEvents()
		{
			var items = GetSwipeItemsByDirection();
 
			if (items == null)
				return;
 
			foreach (var item in items)
			{
				if (item is SwipeItem formsSwipeItem)
					formsSwipeItem.PropertyChanged -= OnSwipeItemPropertyChanged;
 
				if (item is SwipeItemView formsSwipeItemView)
					formsSwipeItemView.PropertyChanged -= OnSwipeItemPropertyChanged;
			}
		}
 
		void DisposeSwipeItems()
		{
			_isOpen = false;
			UnsubscribeSwipeItemEvents();
			_swipeItems.Clear();
			_swipeThreshold = 0;
			_swipeOffset = 0;
			_originalBounds = CGRect.Empty;
 
			if (_actionView != null)
			{
				_actionView.RemoveFromSuperview();
				_actionView.Dispose();
				_actionView = null;
			}
 
			if (_swipeItemsRect != null)
			{
				_swipeItemsRect.Clear();
				_swipeItemsRect = null;
			}
 
			UpdateIsOpen(false);
		}
 
		void ResetSwipeToInitialPosition()
		{
			_isResettingSwipe = false;
			_isSwiping = false;
			_swipeThreshold = 0;
			_swipeDirection = null;
			DisposeSwipeItems();
		}
 
		void ResetSwipe(bool animated = true)
		{
			if (_swipeItemsRect == null || _contentView == null)
				return;
 
			_isResettingSwipe = true;
			_isSwiping = false;
			_swipeThreshold = 0;
			_swipeDirection = null;
 
			if (animated)
			{
				Animate(SwipeAnimationDuration, 0.0, UIViewAnimationOptions.CurveEaseOut, () =>
				{
					if (_originalBounds != CGRect.Empty)
						_contentView.Frame = new CGRect(_originalBounds.X, _originalBounds.Y, _originalBounds.Width, _originalBounds.Height);
				},
				() =>
				{
					DisposeSwipeItems();
					_isResettingSwipe = false;
				});
			}
			else
			{
				if (_originalBounds != CGRect.Empty)
					_contentView.Frame = new CGRect(_originalBounds.X, _originalBounds.Y, _originalBounds.Width, _originalBounds.Height);
 
				DisposeSwipeItems();
				_isResettingSwipe = false;
			}
		}
 
		void ValidateSwipeThreshold()
		{
			if (_swipeDirection == null)
				return;
 
			var swipeThresholdPercent = OpenSwipeThresholdPercentage * GetSwipeThreshold();
 
			if (Math.Abs(_swipeOffset) >= swipeThresholdPercent)
			{
				var swipeItems = GetSwipeItemsByDirection();
 
				if (swipeItems == null)
					return;
 
				if (swipeItems.Mode == SwipeMode.Execute)
				{
					foreach (var swipeItem in swipeItems)
					{
						if (swipeItem.IsVisible)
							ExecuteSwipeItem(swipeItem);
					}
 
					if (swipeItems.SwipeBehaviorOnInvoked != SwipeBehaviorOnInvoked.RemainOpen)
						ResetSwipe();
				}
				else
					SwipeToThreshold();
			}
			else
				ResetSwipe();
		}
 
		void SwipeToThreshold(bool animated = true)
		{
			var completeAnimationDuration = animated ? SwipeAnimationDuration : 0;
 
			if (_swipeTransitionMode == SwipeTransitionMode.Reveal)
			{
				Animate(completeAnimationDuration, 0.0, UIViewAnimationOptions.CurveEaseIn,
					() =>
					{
						_swipeOffset = GetSwipeThreshold();
						double swipeThreshold = _swipeOffset;
 
						switch (_swipeDirection)
						{
							case SwipeDirection.Left:
								_contentView.Frame = new CGRect(_originalBounds.X - swipeThreshold, _originalBounds.Y, _originalBounds.Width, _originalBounds.Height);
								break;
							case SwipeDirection.Right:
								_contentView.Frame = new CGRect(_originalBounds.X + swipeThreshold, _originalBounds.Y, _originalBounds.Width, _originalBounds.Height);
								break;
							case SwipeDirection.Up:
								_contentView.Frame = new CGRect(_originalBounds.X, _originalBounds.Y - swipeThreshold, _originalBounds.Width, _originalBounds.Height);
								break;
							case SwipeDirection.Down:
								_contentView.Frame = new CGRect(_originalBounds.X, _originalBounds.Y + swipeThreshold, _originalBounds.Width, _originalBounds.Height);
								break;
						}
					},
				   () =>
				   {
					   _isSwiping = false;
				   });
			}
 
			if (_swipeTransitionMode == SwipeTransitionMode.Drag)
			{
				Animate(completeAnimationDuration, 0.0, UIViewAnimationOptions.CurveEaseIn,
					() =>
					{
						_swipeOffset = GetSwipeThreshold();
						double swipeThreshold = _swipeOffset;
						var actionBounds = _actionView.Bounds;
						double actionSize;
 
						switch (_swipeDirection)
						{
							case SwipeDirection.Left:
								_contentView.Frame = new CGRect(_originalBounds.X - swipeThreshold, _originalBounds.Y, _originalBounds.Width, _originalBounds.Height);
								actionSize = Element.RightItems.Count * SwipeItemWidth;
								_actionView.Frame = new CGRect(actionSize - swipeThreshold, actionBounds.Y, actionBounds.Width, actionBounds.Height);
								break;
							case SwipeDirection.Right:
								_contentView.Frame = new CGRect(_originalBounds.X + swipeThreshold, _originalBounds.Y, _originalBounds.Width, _originalBounds.Height);
								actionSize = Element.LeftItems.Count * SwipeItemWidth;
								_actionView.Frame = new CGRect(-actionSize + swipeThreshold, actionBounds.Y, actionBounds.Width, actionBounds.Height);
								break;
							case SwipeDirection.Up:
								_contentView.Frame = new CGRect(_originalBounds.X, _originalBounds.Y - swipeThreshold, _originalBounds.Width, _originalBounds.Height);
								actionSize = _contentView.Frame.Height;
								_actionView.Frame = new CGRect(actionBounds.X, actionSize - Math.Abs(swipeThreshold), actionBounds.Width, actionBounds.Height);
								break;
							case SwipeDirection.Down:
								_contentView.Frame = new CGRect(_originalBounds.X, _originalBounds.Y + swipeThreshold, _originalBounds.Width, _originalBounds.Height);
								actionSize = _contentView.Frame.Height;
								_actionView.Frame = new CGRect(actionBounds.X, -actionSize + Math.Abs(swipeThreshold), actionBounds.Width, actionBounds.Height);
								break;
						}
					},
				   () =>
				   {
					   _isSwiping = false;
				   });
			}
 
		}
 
		double GetSwipeThreshold()
		{
			if (Math.Abs(_swipeThreshold) > double.Epsilon)
				return _swipeThreshold;
 
			var swipeItems = GetSwipeItemsByDirection();
 
			if (swipeItems == null)
				return 0;
 
			_swipeThreshold = GetSwipeThreshold(swipeItems);
 
			return _swipeThreshold;
		}
 
		double GetSwipeThreshold(SwipeItems swipeItems)
		{
			var threshold = Element?.Threshold;
 
			if (threshold.HasValue && threshold.Value > 0)
				return threshold.Value;
 
			double swipeThreshold = 0;
			bool isHorizontal = IsHorizontalSwipe();
 
			if (swipeItems.Mode == SwipeMode.Reveal)
			{
				if (isHorizontal)
				{
					foreach (var swipeItem in swipeItems)
					{
						if (swipeItem.IsVisible)
						{
							var swipeItemSize = GetSwipeItemSize(swipeItem);
							swipeThreshold += swipeItemSize.Width;
						}
					}
				}
				else
					swipeThreshold = GetSwipeItemHeight();
			}
			else
				swipeThreshold = CalculateSwipeThreshold();
 
			return ValidateSwipeThreshold(swipeThreshold);
		}
 
		double CalculateSwipeThreshold()
		{
			var swipeItems = GetSwipeItemsByDirection();
 
			float swipeItemsHeight = 0;
			float swipeItemsWidth = 0;
			bool useSwipeItemsSize = false;
 
			foreach (var swipeItem in swipeItems)
			{
				if (swipeItem is SwipeItemView)
					useSwipeItemsSize = true;
 
				if (swipeItem.IsVisible)
				{
					var swipeItemSize = GetSwipeItemSize(swipeItem);
					swipeItemsHeight += (float)swipeItemSize.Height;
					swipeItemsWidth += (float)swipeItemSize.Width;
				}
			}
 
			if (useSwipeItemsSize)
			{
				var isHorizontalSwipe = IsHorizontalSwipe();
 
				return isHorizontalSwipe ? swipeItemsWidth : swipeItemsHeight;
			}
			else
			{
				if (_contentView != null)
				{
					var contentWidth = _contentView.Frame.Width;
					var contentWidthSwipeThreshold = contentWidth * 0.8f;
 
					return contentWidthSwipeThreshold;
				}
			}
 
			return SwipeThreshold;
		}
 
		double ValidateSwipeThreshold(double swipeThreshold)
		{
			var swipeFrame = _contentView != null ? _contentView.Frame : Frame;
 
			if (IsHorizontalSwipe())
			{
				if (swipeThreshold > swipeFrame.Width)
					swipeThreshold = swipeFrame.Width;
 
				return swipeThreshold;
			}
 
			if (swipeThreshold > swipeFrame.Height)
				swipeThreshold = swipeFrame.Height;
 
			return swipeThreshold;
		}
 
		Size GetSwipeItemSize(ISwipeItem swipeItem)
		{
			var items = GetSwipeItemsByDirection();
 
			double threshold = Element.Threshold;
			var contentHeight = _contentView.Frame.Height;
			var contentWidth = _contentView.Frame.Width;
 
			if (IsHorizontalSwipe())
			{
				if (swipeItem is SwipeItem)
				{
					return new Size(items.Mode == SwipeMode.Execute ? (threshold > 0 ? threshold : contentWidth) / items.Count : (threshold < SwipeItemWidth ? SwipeItemWidth : threshold), contentHeight);
				}
 
				if (swipeItem is SwipeItemView horizontalSwipeItemView)
				{
					var swipeItemViewSizeRequest = horizontalSwipeItemView.Measure(double.PositiveInfinity, double.PositiveInfinity, MeasureFlags.IncludeMargins);
 
					double swipeItemWidth;
 
					if (swipeItemViewSizeRequest.Request.Width > 0)
						swipeItemWidth = threshold > swipeItemViewSizeRequest.Request.Width ? threshold : swipeItemViewSizeRequest.Request.Width;
					else
						swipeItemWidth = threshold > SwipeItemWidth ? threshold : SwipeItemWidth;
 
					return new Size(swipeItemWidth, contentHeight);
				}
			}
			else
			{
				if (swipeItem is SwipeItem)
				{
					var swipeItemHeight = GetSwipeItemHeight();
					return new Size(contentWidth / items.Count, (threshold > 0 && threshold < swipeItemHeight) ? threshold : swipeItemHeight);
				}
 
				if (swipeItem is SwipeItemView verticalSwipeItemView)
				{
					var swipeItemViewSizeRequest = verticalSwipeItemView.Measure(double.PositiveInfinity, double.PositiveInfinity, MeasureFlags.IncludeMargins);
 
					double swipeItemHeight;
 
					if (swipeItemViewSizeRequest.Request.Width > 0)
						swipeItemHeight = threshold > swipeItemViewSizeRequest.Request.Height ? threshold : (float)swipeItemViewSizeRequest.Request.Height;
					else
						swipeItemHeight = threshold > contentHeight ? threshold : contentHeight;
 
					return new Size(contentWidth / items.Count, swipeItemHeight);
				}
			}
 
			return Size.Zero;
		}
 
		double GetSwipeItemHeight()
		{
			var items = GetSwipeItemsByDirection();
 
			if (items.Any(s => s is SwipeItemView))
			{
				var itemsHeight = new List<double>();
 
				foreach (var swipeItem in items)
				{
					if (swipeItem is SwipeItemView swipeItemView)
					{
						var swipeItemViewSizeRequest = swipeItemView.Measure(double.PositiveInfinity, double.PositiveInfinity, MeasureFlags.IncludeMargins);
						itemsHeight.Add(swipeItemViewSizeRequest.Request.Height);
					}
				}
 
				return itemsHeight.Max();
			}
 
			return _contentView.Frame.Height;
		}
 
		bool ValidateSwipeDirection()
		{
			if (_swipeDirection == null)
				return false;
 
			var swipeItems = GetSwipeItemsByDirection();
			return IsValidSwipeItems(swipeItems);
		}
 
		double GetSwipeOffset(CGPoint initialPoint, CGPoint endPoint)
		{
			double swipeOffset = 0;
 
			switch (_swipeDirection)
			{
				case SwipeDirection.Left:
				case SwipeDirection.Right:
					swipeOffset = endPoint.X - initialPoint.X;
					break;
				case SwipeDirection.Up:
				case SwipeDirection.Down:
					swipeOffset = endPoint.Y - initialPoint.Y;
					break;
			}
 
			return swipeOffset;
		}
 
		void ProcessTouchSwipeItems(CGPoint point)
		{
			if (_isResettingSwipe)
				return;
 
			var swipeItems = GetSwipeItemsByDirection();
 
			if (swipeItems == null || _swipeItemsRect == null)
				return;
 
			int i = 0;
 
			foreach (var swipeItemRect in _swipeItemsRect)
			{
				var swipeItem = swipeItems[i];
 
				if (swipeItem.IsVisible)
				{
					var swipeItemX = swipeItemRect.Left;
					var swipeItemY = swipeItemRect.Top;
 
					if (TouchInsideContent(swipeItemX, swipeItemY, swipeItemRect.Width, swipeItemRect.Height, point.X, point.Y))
					{
						ExecuteSwipeItem(swipeItem);
 
						if (swipeItems.SwipeBehaviorOnInvoked != SwipeBehaviorOnInvoked.RemainOpen)
							ResetSwipe();
 
						break;
					}
				}
 
				i++;
			}
		}
 
		UIViewController GetViewController()
		{
			var window = UIApplication.SharedApplication.GetKeyWindow();
			var viewController = window.RootViewController;
 
			while (viewController.PresentedViewController != null)
				viewController = viewController.PresentedViewController;
 
			return viewController;
		}
 
		UINavigationController GetUINavigationController(UIViewController controller)
		{
			if (controller != null)
			{
				if (controller is UINavigationController)
				{
					return (controller as UINavigationController);
				}
 
				if (controller.ChildViewControllers.Any())
				{
					var childs = controller.ChildViewControllers.Count();
 
					for (int i = 0; i < childs; i++)
					{
						var child = GetUINavigationController(controller.ChildViewControllers[i]);
 
						if (child is UINavigationController)
						{
							return (child as UINavigationController);
						}
					}
				}
			}
 
			return null;
		}
 
		void ExecuteSwipeItem(ISwipeItem item)
		{
			if (item == null)
				return;
 
			bool isEnabled = true;
 
			if (item is SwipeItem swipeItem)
				isEnabled = swipeItem.IsEnabled;
 
			if (item is SwipeItemView swipeItemView)
				isEnabled = swipeItemView.IsEnabled;
 
			if (isEnabled)
				item.OnInvoked();
		}
 
		void UpdateIsOpen(bool isOpen)
		{
			if (Element == null)
				return;
 
			((ISwipeViewController)Element).IsOpen = isOpen;
		}
 
		void OnParentScrolled(object sender, ScrolledEventArgs e)
		{
			var horizontalDelta = e.ScrollX - _previousScrollX;
			var verticalDelta = e.ScrollY - _previousScrollY;
 
			if (horizontalDelta >= SwipeMinimumDelta || verticalDelta >= SwipeMinimumDelta)
				ResetSwipe();
 
			_previousScrollX = e.ScrollX;
			_previousScrollY = e.ScrollY;
		}
 
		void OnParentScrolled(object sender, ItemsViewScrolledEventArgs e)
		{
			var firstVisibleIndexDelta = e.FirstVisibleItemIndex - _previousFirstVisibleIndex;
 
			if (firstVisibleIndexDelta != 0)
				ResetSwipe();
 
			_previousFirstVisibleIndex = e.FirstVisibleItemIndex;
		}
 
		void OnOpenRequested(object sender, OpenRequestedEventArgs e)
		{
			if (_contentView == null)
				return;
 
			var openSwipeItem = e.OpenSwipeItem;
			var animated = e.Animated;
 
			ProgrammaticallyOpenSwipeItem(openSwipeItem, animated);
		}
 
		void ProgrammaticallyOpenSwipeItem(OpenSwipeItem openSwipeItem, bool animated)
		{
			if (_isOpen)
			{
				if (_previousOpenSwipeItem == openSwipeItem)
					return;
 
				ResetSwipe(false);
			}
 
			_previousOpenSwipeItem = openSwipeItem;
 
			switch (openSwipeItem)
			{
				case OpenSwipeItem.BottomItems:
					_swipeDirection = SwipeDirection.Up;
					break;
				case OpenSwipeItem.LeftItems:
					_swipeDirection = SwipeDirection.Right;
					break;
				case OpenSwipeItem.RightItems:
					_swipeDirection = SwipeDirection.Left;
					break;
				case OpenSwipeItem.TopItems:
					_swipeDirection = SwipeDirection.Down;
					break;
			}
 
			var swipeItems = GetSwipeItemsByDirection();
 
			if (swipeItems.Where(s => s.IsVisible).Count() == 0)
				return;
 
			var swipeThreshold = GetSwipeThreshold();
			UpdateOffset(swipeThreshold);
 
			UpdateSwipeItems();
			Swipe(animated);
 
			_swipeOffset = Math.Abs(_swipeOffset);
		}
 
		void UpdateOffset(double swipeOffset)
		{
			switch (_swipeDirection)
			{
				case SwipeDirection.Right:
				case SwipeDirection.Down:
					_swipeOffset = swipeOffset;
					break;
				case SwipeDirection.Left:
				case SwipeDirection.Up:
					_swipeOffset = -swipeOffset;
					break;
			}
		}
 
		void OnCloseRequested(object sender, CloseRequestedEventArgs e)
		{
			var animated = e.Animated;
 
			ResetSwipe(animated);
		}
 
		void RaiseSwipeStarted()
		{
			if (_swipeDirection == null || !ValidateSwipeDirection())
				return;
 
			var swipeStartedEventArgs = new SwipeStartedEventArgs(_swipeDirection.Value);
			((ISwipeViewController)Element).SendSwipeStarted(swipeStartedEventArgs);
		}
 
		void RaiseSwipeChanging()
		{
			if (_swipeDirection == null)
				return;
 
			var swipeChangingEventArgs = new SwipeChangingEventArgs(_swipeDirection.Value, _swipeOffset);
			((ISwipeViewController)Element).SendSwipeChanging(swipeChangingEventArgs);
		}
 
		void RaiseSwipeEnded()
		{
			if (_swipeDirection == null || !ValidateSwipeDirection())
				return;
 
			bool isOpen = false;
 
			var swipeThresholdPercent = OpenSwipeThresholdPercentage * GetSwipeThreshold();
 
			if (Math.Abs(_swipeOffset) >= swipeThresholdPercent)
				isOpen = true;
 
			var swipeEndedEventArgs = new SwipeEndedEventArgs(_swipeDirection.Value, isOpen);
			((ISwipeViewController)Element).SendSwipeEnded(swipeEndedEventArgs);
		}
	}
}