xplat
xplat

Reputation: 8636

UICollectionView vertical flow doesn't center first cell when going back

Check this video to understand exactly my issue.

I have a custom view composition which contains a UICollectionView where the user can scroll using two buttons Up/Down. The values should be centered while changing, everything works great until I return back to the first value which appears much lower than the center. Note that I don't have any problem using the same approach for horizontal scrolling views.

My approach of centering the value, copied from centering the X axis, just changed the relevant sections for Y axis.

Created a UICollectionViewFlowLayout derived class and set to the UICollectioViewFlowLayout of the UICollectionView. Note that the override method is not invoked when scrolling programmatically.

[Register("CenterVerticalCellCollectionViewFlowLayout")]
public class CenterVerticalCellCollectionViewFlowLayout : UICollectionViewFlowLayout
{
    public CenterVerticalCellCollectionViewFlowLayout(IntPtr ptr) : base(ptr)
    {

    }

    public override CGPoint TargetContentOffset(CGPoint proposedContentOffset, 
                                                CGPoint scrollingVelocity)
    {
        var cv = this.CollectionView;
        var cvBounds = cv.Bounds;
        var halfHeight = cvBounds.Height * 0.5;
        var proposedContentOffsetCenterY = proposedContentOffset.Y + halfHeight;
        var attributesForVisibleCells = LayoutAttributesForElementsInRect(cvBounds);
        UICollectionViewLayoutAttributes candidateAttributes = null;

        foreach (var attributes in attributesForVisibleCells)
        {
            // == Skip comparison with non-cell items (headers and footers) == //
            if (attributes.RepresentedElementCategory != UICollectionElementCategory.Cell)
            {
                continue;
            }

            if ((attributes.Center.Y == 0) || 
                (attributes.Center.Y > (cv.ContentOffset.Y + halfHeight) && 
                 scrollingVelocity.Y < 0))
            {
                continue;
            }

            // == First time in the loop == //
            if (candidateAttributes == null)
            {
                candidateAttributes = new UICollectionViewLayoutAttributes();
            }
            else
            {
                candidateAttributes = attributes;
                continue;
            }

            var a = attributes.Center.Y- proposedContentOffsetCenterY;
            var b = candidateAttributes.Center.Y - proposedContentOffsetCenterY;


            if (Math.Abs(a) < Math.Abs(b))
            {
                candidateAttributes = attributes;
            }
        }

        // Beautification step , I don't know why it works!
        if (proposedContentOffset.Y == -(cv.ContentInset.Top))
        {
            return proposedContentOffset;
        }

        if (candidateAttributes == null)
        {
            candidateAttributes = new UICollectionViewLayoutAttributes();
        }

        return new CGPoint(Math.Floor(candidateAttributes.Center.Y - 
            halfHeight), proposedContentOffset.Y);
    }
}

When loading the custom UIView I set the below.

CollectionView.ContentOffset = CGPoint.Empty;

In UIViewController.ViewWillLayoutSubviews I call the below method.

CollectionView.CollectionViewLayout.InvalidateLayout();

In UIViewController.ViewDidLayoutSubviews I call the below.

var insets = CollectionView.ContentInset;
var value = (View.Frame.Height - (CollectionView.CollectionViewLayout as UICollectionViewFlowLayout).ItemSize.Height) * 0.5;

insets.Top = (nfloat)value;
insets.Bottom = (nfloat)value;
CollectionView.ContentInset = insets;
CollectionView.DecelerationRate = UIScrollView.DecelerationRateFast;

And finally the code below for Up/Down programmatically scrolling.

partial void UpButtonTouchUpInside(UIButton sender)
{
    MoveSelectionCollectionViewCell();
}

partial void DownButtonTouchUpInside(UIButton sender)
{
    MoveSelectionCollectionViewCell(false);
}

void MoveSelectionCollectionViewCell(bool moveUp = true)
{
    var firstVisibleIndexPath = CollectionView.IndexPathsForVisibleItems.FirstOrDefault();

    if (firstVisibleIndexPath != null)
    {
        var row = moveUp ? firstVisibleIndexPath.Row + 1 : firstVisibleIndexPath.Row - 1;
        if (row > -1 &&
            row < _source.Collection.Count)
        {
            var scrollAtIndexPath = NSIndexPath.FromRowSection(row, 0);
            CollectionView.ScrollToItem(
                scrollAtIndexPath,
                UICollectionViewScrollPosition.CenteredVertically,
                true);
        }
    }
}

See below the UICollectionView IB properties.

IB Properties 1 IB Properties 2

Upvotes: 0

Views: 378

Answers (1)

xplat
xplat

Reputation: 8636

While reviewing my question made me realize the problem!

When in UIViewController.ViewDidLayoutSubviews calls the code it takes account of the View.Frame.Height which is wrong since the custom control View is not the same size of the UICollectionView hence demonstrating this issue.

Changing this:

var value = (View.Frame.Height - (CollectionView.CollectionViewLayout as UICollectionViewFlowLayout).ItemSize.Height) * 0.5;

To this:

var value = (CollectionView.Frame.Height - (CollectionView.CollectionViewLayout as UICollectionViewFlowLayout).ItemSize.Height) * 0.5;

Did the trick.

Upvotes: 1

Related Questions