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 _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 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(); 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 changed = ElementChanged; if (changed != null) changed(this, e); } void Clear() { foreach (KeyValuePair 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); } } } }