Skip to content
This repository has been archived by the owner on May 1, 2024. It is now read-only.

Commit

Permalink
Restore ability to display header, footer, and empty view simultaneou…
Browse files Browse the repository at this point in the history
…sly (#13247) fixes #8326 fixes #13252

* Restore ability to display header, footer, and empty view simultaneously
Now passes test from issue 8326

* Handle null emptyview/emptyviewtemplate

* Fix height check when deciding to update footer based on emptyview presence

* Fix EmptyView layout direction when CollectionView is RTL and items go to zero

* Add missing null checks in show and hide methods
  • Loading branch information
hartez committed Dec 31, 2020
1 parent c27efa3 commit 324b426
Show file tree
Hide file tree
Showing 3 changed files with 118 additions and 71 deletions.
181 changes: 114 additions & 67 deletions Xamarin.Forms.Platform.iOS/CollectionView/ItemsViewController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -155,21 +155,13 @@ public override void ViewWillLayoutSubviews()
{
base.ViewWillLayoutSubviews();

if (!_initialized)
{
UpdateEmptyView();
}

// We can't set this constraint up on ViewDidLoad, because Forms does other stuff that resizes the view
// and we end up with massive layout errors. And View[Will/Did]Appear do not fire for this controller
// reliably. So until one of those options is cleared up, we set this flag so that the initial constraints
// are set up the first time this method is called.
EnsureLayoutInitialized();

if (_initialized)
{
LayoutEmptyView();
}
LayoutEmptyView();
}

void EnsureLayoutInitialized()
Expand All @@ -195,6 +187,8 @@ void EnsureLayoutInitialized()

ItemsViewLayout.SetInitialConstraints(CollectionView.Bounds.Size);
CollectionView.SetCollectionViewLayout(ItemsViewLayout, false);

UpdateEmptyView();
}

protected virtual UICollectionViewDelegateFlowLayout CreateDelegator()
Expand All @@ -220,9 +214,11 @@ public virtual void UpdateFlowDirection()
{
CollectionView.UpdateFlowDirection(ItemsView);

if (ItemsSource?.ItemCount == 0)
_emptyUIView?.UpdateFlowDirection(_emptyViewFormsElement);

if (_emptyViewDisplayed)
{
AlignEmptyView();
}

Layout.InvalidateLayout();
}

Expand Down Expand Up @@ -375,36 +371,12 @@ protected virtual void RegisterViewTypes()

protected abstract bool IsHorizontal { get; }

internal void UpdateEmptyView()
{
UpdateView(ItemsView?.EmptyView, ItemsView?.EmptyViewTemplate, ref _emptyUIView, ref _emptyViewFormsElement);

// If the empty view is being displayed, we might need to update it
UpdateEmptyViewVisibility(ItemsSource?.ItemCount == 0);
}

protected virtual CGRect DetermineEmptyViewFrame()
{
return new CGRect(CollectionView.Frame.X, CollectionView.Frame.Y,
CollectionView.Frame.Width, CollectionView.Frame.Height);
}

void LayoutEmptyView()
{
if (_emptyUIView == null)
{
UpdateEmptyView();
return;
}

var frame = DetermineEmptyViewFrame();

_emptyUIView.Frame = frame;

if (_emptyViewFormsElement != null && ItemsView.LogicalChildren.Contains(_emptyViewFormsElement))
_emptyViewFormsElement.Layout(frame.ToRectangle());
}

protected void RemeasureLayout(VisualElement formsElement)
{
if (IsHorizontal)
Expand Down Expand Up @@ -453,53 +425,128 @@ internal void UpdateView(object view, DataTemplate viewTemplate, ref UIView uiVi
}
}

void UpdateEmptyViewVisibility(bool isEmpty)
internal void UpdateEmptyView()
{
if (isEmpty && _emptyUIView != null)
if (!_initialized)
{
var emptyView = CollectionView.Superview.ViewWithTag(EmptyTag);
return;
}

if(emptyView != null)
{
emptyView.RemoveFromSuperview();
ItemsView.RemoveLogicalChild(_emptyViewFormsElement);
}
// Get rid of the old view
TearDownEmptyView();

_emptyUIView.Tag = EmptyTag;
// Set up the new empty view
UpdateView(ItemsView?.EmptyView, ItemsView?.EmptyViewTemplate, ref _emptyUIView, ref _emptyViewFormsElement);

var collectionViewContainer = CollectionView.Superview;
collectionViewContainer.AddSubview(_emptyUIView);

LayoutEmptyView();
// We may need to show the updated empty view
UpdateEmptyViewVisibility(ItemsSource?.ItemCount == 0);
}

if (_emptyViewFormsElement != null)
{
if (ItemsView.EmptyViewTemplate == null)
{
ItemsView.AddLogicalChild(_emptyViewFormsElement);
}
void UpdateEmptyViewVisibility(bool isEmpty)
{
if (!_initialized)
{
return;
}

// Now that the native empty view's frame is sized to the UICollectionView, we need to handle
// the Forms layout for its content
_emptyViewFormsElement.Layout(_emptyUIView.Frame.ToRectangle());
if (isEmpty)
{
ShowEmptyView();
}
else
{
HideEmptyView();
}
}

void AlignEmptyView()
{
if (_emptyUIView == null)
{
return;
}

if (CollectionView.EffectiveUserInterfaceLayoutDirection == UIUserInterfaceLayoutDirection.RightToLeft)
{
if (_emptyUIView.Transform.xx == -1)
{
return;
}

_emptyViewDisplayed = true;
FlipEmptyView();
}
else
{
// Is the empty view currently in the background? Swap back to the default.
if (_emptyViewDisplayed)
if (_emptyUIView.Transform.xx == -1)
{
_emptyUIView.RemoveFromSuperview();
_emptyUIView.Dispose();
_emptyUIView = null;

ItemsView.RemoveLogicalChild(_emptyViewFormsElement);
FlipEmptyView();
}
}
}

void FlipEmptyView()
{
// Flip the empty view 180 degrees around the X axis
_emptyUIView.Transform = CGAffineTransform.Scale(_emptyUIView.Transform, -1, 1);
}

void ShowEmptyView()
{
if (_emptyViewDisplayed || _emptyUIView == null)
{
return;
}

_emptyUIView.Tag = EmptyTag;
CollectionView.AddSubview(_emptyUIView);

_emptyViewDisplayed = false;
if (!ItemsView.LogicalChildren.Contains(_emptyViewFormsElement))
{
ItemsView.AddLogicalChild(_emptyViewFormsElement);
}

LayoutEmptyView();

AlignEmptyView();
_emptyViewDisplayed = true;
}

void HideEmptyView()
{
if (!_emptyViewDisplayed || _emptyUIView == null)
{
return;
}

_emptyUIView.RemoveFromSuperview();

_emptyViewDisplayed = false;
}

void TearDownEmptyView()
{
HideEmptyView();

// RemoveLogicalChild will trigger a disposal of the native view and its content
ItemsView.RemoveLogicalChild(_emptyViewFormsElement);

_emptyUIView = null;
_emptyViewFormsElement = null;
}

void LayoutEmptyView()
{
if (!_initialized || _emptyUIView == null || _emptyUIView.Superview == null)
{
return;
}

var frame = DetermineEmptyViewFrame();

_emptyUIView.Frame = frame;

if (_emptyViewFormsElement != null && ItemsView.LogicalChildren.Contains(_emptyViewFormsElement))
_emptyViewFormsElement.Layout(frame.ToRectangle());
}

TemplatedCell CreateAppropriateCellForLayout()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ public override void ViewWillLayoutSubviews()
else
{
if (_footerUIView.Frame.Y != ItemsViewLayout.CollectionViewContentSize.Height ||
_footerUIView.Frame.Y < emptyView?.Frame.Y)
_footerUIView.Frame.Y < (emptyView?.Frame.Y + emptyView?.Frame.Height))
UpdateHeaderFooterPosition();
}
}
Expand Down
6 changes: 3 additions & 3 deletions Xamarin.Forms.Platform.iOS/CollectionView/TemplateHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,9 @@ public static (UIView NativeView, VisualElement FormsElement) RealizeView(object
PropertyPropagationExtensions.PropagatePropertyChanged(null, templateElement, itemsView);

var renderer = CreateRenderer(templateElement);
// and set the EmptyView as its BindingContext
BindableObject.SetInheritedBindingContext(renderer.Element, view);

// and set the view as its BindingContext
renderer.Element.BindingContext = view;

return (renderer.NativeView, renderer.Element);
}
Expand Down

0 comments on commit 324b426

Please sign in to comment.