Last active
October 8, 2021 00:31
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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