Я добавил интерфейс IDisposable
к одному из моих DatatTemplate
s и реализовал соответствующие методы. В исходном коде плагина Dispose()
вызывается для свойства View
подкласса UIViewController
. Но метод Dispose()
моего DataTemplate
никогда не вызывается. Я не могу войти в вызов dispose, отладчик перемещается только на следующую строку.
Если я распечатываю тип с помощью View.GetType()
, я получаю следующий результат
Xamarin.Forms.Platform.iOS.Platform + DefaultRenderer
Если я посмотрю на свойство View
(наведите курсор мыши), появится дочернее свойство с именем Element
, которое соответствует моему типу DataTemplate
. Разве Dispose()
не вызывается для всех элементов child ? Почему это так и как мне добиться, чтобы мой пользовательский Dispose()
метод моего DataTemplate
вызывался?
Пример кода:
MainPage.xaml
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:TestCarouselView"
xmlns:controls="clr-namespace:CarouselView.FormsPlugin.Abstractions;assembly=CarouselView.FormsPlugin.Abstractions"
x:Class="TestCarouselView.MainPage">
<controls:CarouselViewControl x:Name="carouselView" Orientation="Horizontal" InterPageSpacing="10" VerticalOptions="FillAndExpand" HorizontalOptions="FillAndExpand">
</controls:CarouselViewControl>
</ContentPage>
MainPage.xaml.cs
public partial class MainPage : ContentPage
{
private int maxValue = 3;
private CustomViewModel model;
private DataTemplate template;
public MainPage()
{
InitializeComponent();
this.model = new CustomViewModel(this.maxValue);
this.template = new DataTemplate(CreateDataTemplateView);
this.carouselView.ItemsSource = this.model.ObjectList;
this.carouselView.ItemTemplate = this.template;
this.carouselView.Position = this.maxValue;
this.carouselView.PositionSelected += CarouselView_PositionSelected;
}
private void CarouselView_PositionSelected(object sender, CarouselView.FormsPlugin.Abstractions.PositionSelectedEventArgs e)
{
var currentObject = this.model.ObjectList.ElementAt(e.NewValue);
var nextToLastObject = this.model.ObjectList.ElementAt(this.model.ObjectList.Count - 2);
var secondObject = this.model.ObjectList.ElementAt(1);
if (currentObject == nextToLastObject)
{
this.SwipeAppend();
}
else if (currentObject == secondObject)
{
this.SwipePrepend();
}
}
private View CreateDataTemplateView()
{
CustomTemplateView template = new CustomTemplateView();
template.BindingContext = this.model.ObjectList;
// here I'm subscribing to events from the DataTemplate
template.ElementSelected += OnElementSelected;
return (View)template;
}
public void SwipeAppend()
{
int newId = Int32.Parse(this.model.ObjectList[this.model.ObjectList.Count - 1].Id) + 1;
this.model.ObjectList.Add(new CustomObject(newId.ToString()));
this.model.ObjectList.RemoveAt(0);
}
public void SwipePrepend()
{
int oldPosition = this.carouselView.Position;
int oldId = Int32.Parse(this.model.ObjectList[0].Id);
this.model.ObjectList.Insert(0, new CustomObject((oldId - 1).ToString()));
this.model.ObjectList.RemoveAt(this.model.ObjectList.Count - 1);
this.carouselView.Position = oldPosition + 1;
}
}
CustomViewModel.cs
public class CustomViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public ObservableCollection<CustomObject> ObjectList { get; set; }
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public CustomViewModel(int amount)
{
ObjectList = new ObservableCollection<CustomObject>();
for (int i = -amount; i <= amount; i++)
{
ObjectList.Add(new CustomObject(i.ToString()));
}
}
}
CustomObject.cs
public class CustomObject
{
private string id;
public string Id
{
get { return this.id; }
set { this.id = value; }
}
public CustomObject(string id)
{
this.id = id;
}
}
CustomTemplateView.xaml
<?xml version="1.0" encoding="UTF-8"?>
<ContentView xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:skia="clr-namespace:SkiaSharp.Views.Forms;assembly=SkiaSharp.Views.Forms"
x:Class="TestCarouselView.CustomTemplateView">
<ContentView.Content>
<skia:SKCanvasView
AbsoluteLayout.LayoutFlags="All"
AbsoluteLayout.LayoutBounds="0,0,1,1"
x:Name="drawingArea"
IgnorePixelScaling="True" />
</ContentView.Content>
</ContentView>
CustomTemplateView.xaml.cs
public partial class CustomTemplateView : ContentView, IDisposable
{
private CustomObject myData;
public CustomTemplateView()
{
InitializeComponent();
// here I'm subscribing to MessagingCenter
MessagingCenter.Subscribe<SelectElement>(this, Msg_SelectElement, message =>
{
this.SelectElement(message.Element);
});
}
protected override void OnBindingContextChanged()
{
base.OnBindingContextChanged();
this.myData = this.BindingContext as CustomObject;
if (this.myData != null)
{
this.SetupContent();
}
}
private void SetupContent()
{
this.drawingArea.PaintSurface += drawingArea_PaintSurface;
}
private void drawingArea_PaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
args.Surface.Canvas.Clear();
SKPaint textPaint = new SKPaint
{
IsAntialias = true,
Style = SKPaintStyle.Fill,
Color = SKColors.Black,
TextSize = 92,
StrokeWidth = 1,
};
args.Surface.Canvas.DrawText(this.myData.Id, args.Info.Width/2, args.Info.Height/2, textPaint);
}
// Implement IDisposable.
// Do not make this method virtual.
// A derived class should not be able to override this method.
public void Dispose()
{
Dispose(true);
// This object will be cleaned up by the Dispose method.
// Therefore, you should call GC.SupressFinalize to
// take this object off the finalization queue
// and prevent finalization code for this object
// from executing a second time.
GC.SuppressFinalize(this);
}
// Dispose(bool disposing) executes in two distinct scenarios.
// If disposing equals true, the method has been called directly
// or indirectly by a user's code. Managed and unmanaged resources
// can be disposed.
// If disposing equals false, the method has been called by the
// runtime from inside the finalizer and you should not reference
// other objects. Only unmanaged resources can be disposed.
protected virtual void Dispose(bool disposing)
{
System.Diagnostics.Debug.WriteLine("Dispose is called");
// Check to see if Dispose has already been called.
if (!this.disposed)
{
// If disposing equals true, dispose all managed
// and unmanaged resources.
if (disposing)
{
// Dispose managed resources.
this.drawingArea.PaintSurface -= drawingArea_PaintSurface;
MessagingCenter.Unsubscribe<SelectElement>(this, Msg_SelectElement);
}
// Call the appropriate methods to clean up
// unmanaged resources here.
// If disposing is false,
// only the following code is executed.
// ...
// Note disposing has been done.
disposed = true;
}
}
// Use C# destructor syntax for finalization code.
// This destructor will run only if the Dispose method
// does not get called.
// It gives your base class the opportunity to finalize.
// Do not provide destructors in types derived from this class.
~CustomTemplateView()
{
System.Diagnostics.Debug.WriteLine("Finalizer is called");
// Do not re-create Dispose clean-up code here.
// Calling Dispose(false) is optimal in terms of
// readability and maintainability.
Dispose(false);
}
}
Изменения в плагине CarouselView :
CarouselViewImplementation.cs
async Task RemovePage(int position)
{
if (Element == null || pageController == null || Source == null) return;
if (Source?.Count > 0)
{
// To remove latest page, rebuild pageController or the page wont disappear
if (Source.Count == 1)
{
Source.RemoveAt(position);
SetNativeView();
}
else
{
// ### Beginn of modification ###
// Remove controller from ChildViewControllers
if (ChildViewControllers != null)
{
ViewContainer child = ChildViewControllers.FirstOrDefault(c => c.Tag == Source[position]);
if (child != null)
{
ChildViewControllers.Remove(child);
child.Dispose();
}
}
object elementToRemove = Source.ElementAt(position);
//Source.RemoveAt(position);
Source.Remove(elementToRemove);
//elementToRemove.Dispose();
// ### End of modification ###
// To remove current page
if (position == Element.Position)
{
var newPos = position - 1;
if (newPos == -1)
newPos = 0;
// With a swipe transition
if (Element.AnimateTransition)
await Task.Delay(100);
var direction = position == 0 ? UIPageViewControllerNavigationDirection.Forward : UIPageViewControllerNavigationDirection.Reverse;
var firstViewController = CreateViewController(newPos);
pageController.SetViewControllers(new[] { firstViewController }, direction, Element.AnimateTransition, s =>
{
isChangingPosition = true;
Element.Position = newPos;
isChangingPosition = false;
SetArrowsVisibility();
SetIndicatorsCurrentPage();
// Invoke PositionSelected as DidFinishAnimating is only called when touch to swipe
Element.SendPositionSelected();
Element.PositionSelectedCommand?.Execute(null);
});
}
else
{
var firstViewController = pageController.ViewControllers[0];
pageController.SetViewControllers(new[] { firstViewController }, UIPageViewControllerNavigationDirection.Forward, false, s =>
{
SetArrowsVisibility();
SetIndicatorsCurrentPage();
// Invoke PositionSelected as DidFinishAnimating is only called when touch to swipe
Element.SendPositionSelected();
Element.PositionSelectedCommand?.Execute(null);
});
}
}
prevPosition = Element.Position;
}
}
UIViewController CreateViewController(int index)
{
// ### Beginn of modification ###
if (ChildViewControllers == null)
ChildViewControllers = new List<ViewContainer>();
// ### End of modification ###
// Significant Memory Leak for iOS when using custom layout for page content #125
var newTag = Source[index];
foreach (ViewContainer child in pageController.ChildViewControllers)
{
if (child.Tag == newTag)
return child;
}
View formsView = null;
object bindingContext = null;
if (Source != null && Source?.Count > 0)
bindingContext = Source.Cast<object>().ElementAt(index);
var dt = bindingContext as DataTemplate;
// Support for List<View> as ItemsSource
var view = bindingContext as View;
// Support for List<DataTemplate> as ItemsSource
if (dt != null)
{
formsView = (View)dt.CreateContent();
}
else
{
if (view != null)
{
if (ChildViewControllers == null)
ChildViewControllers = new List<ViewContainer>();
// Return from the local copy of controllers
foreach(ViewContainer controller in ChildViewControllers)
{
if (controller.Tag == view)
{
return controller;
}
}
formsView = view;
}
else
{
var selector = Element.ItemTemplate as DataTemplateSelector;
if (selector != null)
formsView = (View)selector.SelectTemplate(bindingContext, Element).CreateContent();
else
formsView = (View)Element.ItemTemplate.CreateContent();
formsView.BindingContext = bindingContext;
}
}
// HeightRequest fix
formsView.Parent = this.Element;
// UIScreen.MainScreen.Bounds.Width, UIScreen.MainScreen.Bounds.Height
var rect = new CGRect(Element.X, Element.Y, ElementWidth, ElementHeight);
var nativeConverted = formsView.ToiOS(rect);
//if (dt == null && view == null)
//formsView.Parent = null;
var viewController = new ViewContainer();
viewController.Tag = bindingContext;
viewController.View = nativeConverted;
// Only happens when ItemsSource is List<View>
if (ChildViewControllers != null)
{
ChildViewControllers.Add(viewController);
Console.WriteLine("ChildViewControllers count = " + ChildViewControllers.Count());
}
return viewController;
}