Skip to content

Instantly share code, notes, and snippets.

@matthewrdev
Last active October 8, 2021 00:31
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save matthewrdev/ced89a8fe99362f78ce50123f75517e3 to your computer and use it in GitHub Desktop.
Save matthewrdev/ced89a8fe99362f78ce50123f75517e3 to your computer and use it in GitHub Desktop.
For cells in Xamarin.Forms apps that perform a lot of resize requests, batches and defers the resize operation to improve list view performance.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Threading;
using UIKit;
public class ResizableViewCell : ViewCell
{
public event EventHandler Resize;
public void ResizeSelf()
{
if (Device.RuntimePlatform == Device.iOS)
{
Resize?.Invoke(this, null);
}
else
{
ForceUpdateSize();
}
}
public void BindResizeEvents()
{
if (this.View is IRequestCellResize requestResize)
{
requestResize.RequestResize -= OnRequestResize;
requestResize.RequestResize += OnRequestResize;
}
}
protected override void OnAppearing()
{
base.OnAppearing();
BindResizeEvents();
}
private void OnRequestResize(object sender, EventArgs e)
{
ResizeSelf();
}
public object Tag { get; set; }
public T GetTag<T>() where T : class
{
return Tag as T;
}
}
using System;
using UIKit;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
[assembly: ExportRenderer(typeof(ResizableViewCell), typeof(ResizableViewCellRenderer))]
namespace MyApp.iOS.Renderers
{
public class ResizableViewCellRenderer : ViewCellRenderer
{
private readonly Lazy<ITableViewResizeManager> _tableViewResizeEventsQueue = new Lazy<ITableViewResizeManager>(() => FreshTinyIoC.FreshTinyIoCContainer.Current.Resolve< ITableViewResizeManager>());
ITableViewResizeManager TableViewResizeManager => _tableViewResizeEventsQueue.Value;
public override UITableViewCell GetCell(Cell cell, UITableViewCell nativeCell, UITableView tableView)
{
if (!(cell is ResizableViewCell resizableCell))
{
return base.GetCell(cell, nativeCell, tableView);
}
resizableCell.Tag = new WeakReference<UITableView>(tableView);
resizableCell.Resize -= ResizableCell_Resize;
resizableCell.Resize += ResizableCell_Resize;
nativeCell = base.GetCell(cell, nativeCell, tableView);
/* On IOS Once a cell is selected in a list view it changes the background color of the list view cell
* This results in an issue where the borders of buttons, input and any button colors are removed from the view
* This ensures that the selection style doesnt change so that we dont see any border or color missing issues for list view cells.
*/
if (nativeCell != null)
{
nativeCell.SelectionStyle = UITableViewCellSelectionStyle.None;
}
return nativeCell;
}
private void ResizableCell_Resize(object sender, EventArgs e)
{
var tableViewReference = (sender as ResizableViewCell)?.GetTag<WeakReference<UITableView>>();
if (tableViewReference is null
|| !tableViewReference.TryGetTarget(out var tableView))
{
return;
}
TableViewResizeManager.RequestResize(tableView);
}
}
}
public interface IRequestCellResize
{
event EventHandler RequestResize;
void RaiseRequestResize();
}
namespace MyApp.iOS.Services
{
public class TableViewResizeManager : ITableViewResizeManager
{
/// <summary>
/// A heuristic that defers the execution of the resize request to allow for batching.
/// </summary>
private const int tableResizeDelayMilliseconds= 50;
class ScheduledResizeRequest : IDisposable
{
public ScheduledResizeRequest(UITableView tableView)
: this(new WeakReference<UITableView>(tableView), tableView.GetHashCode())
{
}
public ScheduledResizeRequest(WeakReference<UITableView> tableViewReference, int key)
{
TableViewReference = tableViewReference;
Key = key;
_dispatchTimer = new System.Timers.Timer(tableResizeDelayMilliseconds);
_dispatchTimer.Start();
}
private void DispatchTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
OnScheduledResizeRequested?.Invoke(this, EventArgs.Empty);
}
public WeakReference<UITableView> TableViewReference { get; }
public int Key { get; }
readonly System.Timers.Timer _dispatchTimer;
public event EventHandler OnScheduledResizeRequested;
public void Reset()
{
Debug.WriteLine("Deferred resize request reset for " + Key);
try
{
_dispatchTimer.Elapsed -= DispatchTimer_Elapsed;
_dispatchTimer.Stop();
_dispatchTimer.Start();
}
finally
{
_dispatchTimer.Elapsed += DispatchTimer_Elapsed;
}
}
public void Dispose()
{
_dispatchTimer.Elapsed -= DispatchTimer_Elapsed;
_dispatchTimer.Dispose();
}
}
readonly object _pendingResizeRequestsLock = new object();
readonly List<ScheduledResizeRequest> _pendingResizeRequests = new List<ScheduledResizeRequest>();
public void RequestResize(UITableView tableView)
{
ScheduledResize(tableView);
}
private void ScheduledResize(UITableView tableView)
{
if (tableView is null)
{
return;
}
var key = tableView.GetHashCode();
lock (_pendingResizeRequestsLock)
{
var existingRequest = _pendingResizeRequests.FirstOrDefault(r => r.Key == key);
if (existingRequest != null)
{
existingRequest.Reset();
return;
}
}
var request = new ScheduledResizeRequest(tableView);
lock (_pendingResizeRequestsLock)
{
_pendingResizeRequests.Add(request);
}
request.OnScheduledResizeRequested += Request_OnScheduledResizeRequested;
request.Reset();
Debug.WriteLine("Resize request enqueued for: " + tableView.GetHashCode());
}
private void Request_OnScheduledResizeRequested(object sender, EventArgs e)
{
if (sender is ScheduledResizeRequest request)
{
Xamarin.Essentials.MainThread.BeginInvokeOnMainThread(() =>
{
RunTableViewUpdates(request);
});
}
}
private void RunTableViewUpdates(ScheduledResizeRequest resizeRequest)
{
if (resizeRequest.TableViewReference.TryGetTarget(out var tableView)
&& tableView != null)
{
tableView.BeginUpdates();
tableView.EndUpdates();
RemoveResizeRequest(resizeRequest.Key);
}
}
private void RemoveResizeRequest(int key)
{
lock (_pendingResizeRequestsLock)
{
var items = _pendingResizeRequests.Where(request => request.Key == key).ToList();
foreach (var item in items)
{
item.Dispose();
_pendingResizeRequests.Remove(item);
}
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment