File: iOS\Renderers\CarouselPageRenderer.cs
Web Access
Project: src\src\Compatibility\Core\src\Compatibility.csproj (Microsoft.Maui.Controls.Compatibility)
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Runtime.Versioning;
using Microsoft.Maui.Controls.Internals;
using Microsoft.Maui.Controls.Platform;
using Microsoft.Maui.Graphics;
using ObjCRuntime;
using UIKit;
using PointF = CoreGraphics.CGPoint;
using RectangleF = CoreGraphics.CGRect;
using SizeF = CoreGraphics.CGSize;
 
namespace Microsoft.Maui.Controls.Compatibility.Platform.iOS
{
	[System.Obsolete(Compatibility.Hosting.MauiAppBuilderExtensions.UseMapperInstead)]
	internal class CarouselPageRenderer : UIViewController, IVisualElementRenderer
	{
		bool _appeared;
		Dictionary<Page, UIView> _containerMap;
		bool _disposed;
		bool _ignoreNativeScrolling;
		UIScrollView _scrollView;
		VisualElementTracker _tracker;
		Page _previousPage;
 
		[Preserve(Conditional = true)]
		public CarouselPageRenderer()
		{
		}
 
		IElementController ElementController => Element as IElementController;
 
 
		protected CarouselPage Carousel
		{
			get { return (CarouselPage)Element; }
		}
 
		IPageController PageController => (IPageController)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);
 
			_previousPage = Carousel?.CurrentPage;
		}
 
		public void SetElementSize(Size size)
		{
			Element.Layout(new Rect(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;
			PageController.SendAppearing();
		}
 
		public override void ViewDidDisappear(bool animated)
		{
			base.ViewDidDisappear(animated);
 
			if (!_appeared || _disposed)
				return;
 
			_appeared = false;
			PageController.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);
 
			_scrollView = new UIScrollView { ShowsHorizontalScrollIndicator = false };
 
			_scrollView.DecelerationEnded += OnDecelerationEnded;
 
			UpdateBackground();
 
			View.Add(_scrollView);
 
			for (var i = 0; i < ElementController.LogicalChildren.Count; i++)
			{
				Element element = ElementController.LogicalChildren[i];
				var child = element as ContentPage;
				if (child != null)
					InsertPage(child, i);
			}
 
			PositionChildren();
 
			Carousel.PropertyChanged += OnPropertyChanged;
			Carousel.PagesChanged += OnPagesChanged;
		}
 
		[UnsupportedOSPlatform("tvos")]
		[UnsupportedOSPlatform("ios6.0")]
		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)
			{
				_previousPage = null;
 
				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;
					PageController?.SendDisappearing();
				}
 
				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 CarouselPageContainer(page);
 
			UIView view = renderer.NativeView;
 
			container.AddSubview(view);
			_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 >= ElementController.LogicalChildren.Count)
				return;
 
			var currentPage = (ContentPage)ElementController.LogicalChildren[SelectedIndex];
			if (_previousPage != currentPage)
			{
				_previousPage?.SendDisappearing();
				_previousPage = currentPage;
			}
			Carousel.CurrentPage = currentPage;
			currentPage.SendAppearing();
		}
 
		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 || e.PropertyName == VisualElement.BackgroundProperty.PropertyName)
				UpdateBackground();
			else if (e.PropertyName == Page.BackgroundImageSourceProperty.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 < ElementController.LogicalChildren.Count; i++)
			{
				Element element = ElementController.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()
		{
			this.ApplyNativeImageAsync(Page.BackgroundImageSourceProperty, bgImage =>
			{
				if (bgImage != null)
					View.BackgroundColor = UIColor.FromPatternImage(bgImage);
				else if (Element.BackgroundColor == null)
					View.BackgroundColor = ColorExtensions.BackgroundColor;
				else
				{
					if (Element.BackgroundColor == null)
						View.BackgroundColor = UIColor.White;
					else
						View.BackgroundColor = Element.BackgroundColor.ToPlatform();
 
					Brush background = Element.Background;
					View.UpdateBackground(background);
				}
			});
		}
 
		void UpdateCurrentPage(bool animated = true)
		{
			ContentPage current = Carousel.CurrentPage;
			if (current != null)
				ScrollToPage(CarouselPage.GetIndex(current), animated);
		}
 
		class CarouselPageContainer : UIView
		{
			public CarouselPageContainer(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);
			}
		}
 
		public void RegisterEffect(Effect effect)
		{
			VisualElementRenderer<VisualElement>.RegisterEffect(effect, View);
		}
	}
}