I'm trying to use the new UICollectionViewCompositionalLayout
on iOS13. I'd like to show two different cell types, but I want the layout system to auto size the cells. I wanted to leverage NSCollectionLayoutDimension.estimated
thinking that auto size would be taken care of for me.
I'm trying to do this with Xamarin, but Swift coders should be able to read the below code fine. I'm not very strong with auto layout and dynamic cell sizes, but I was able to get thus far looking at online resources. Pretty much every UICollectionViewCompositionalLayout
example out there seems to be using a single cell template.
public class MyViewController : UIViewController
{
private UICollectionView _collectionView;
public static List<BaseModel> Models = new List<BaseModel>();
public override void ViewDidLoad()
{
base.ViewDidLoad();
PopulateModels();
_collectionView = new UICollectionView(View.Frame, GetUiCollectionViewLayout())
{
BackgroundColor = UIColor.Brown,
DataSource = new MyViewUICollectionViewDataSource(),
ContentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentBehavior.Never,
TranslatesAutoresizingMaskIntoConstraints = false,
ShowsVerticalScrollIndicator = false,
ShowsHorizontalScrollIndicator = false
};
_collectionView.RegisterClassForCell(typeof(MyViewUICollectionViewCell), MyViewUICollectionViewCell.ReuseIdentifier);
_collectionView.RegisterClassForCell(typeof(MyViewUICollectionViewCell2), MyViewUICollectionViewCell2.ReuseIdentifier);
View.AddSubview(_collectionView);
NSLayoutConstraint.ActivateConstraints(new[]
{
_collectionView.TopAnchor.ConstraintEqualTo(View.SafeAreaLayoutGuide.TopAnchor),
_collectionView.BottomAnchor.ConstraintEqualTo(View.SafeAreaLayoutGuide.BottomAnchor),
_collectionView.LeftAnchor.ConstraintEqualTo(View.SafeAreaLayoutGuide.LeftAnchor),
_collectionView.RightAnchor.ConstraintEqualTo(View.SafeAreaLayoutGuide.RightAnchor)
});
}
private static void PopulateModels()
{
for (var i = 0; i < 100; i++)
{
BaseModel baseModel;
if (i % 2 == 0)
{
baseModel = new Model1
{
UIColor = UIColor.Red
};
}
else
{
baseModel = new Model2
{
Text = "Item " + i
};
}
Models.Add(baseModel);
}
}
private UICollectionViewLayout GetUiCollectionViewLayout()
{
var layoutSize = NSCollectionLayoutSize.Create(NSCollectionLayoutDimension.CreateFractionalWidth(1), NSCollectionLayoutDimension.CreateEstimated(200));
var item = NSCollectionLayoutItem.Create(layoutSize);
var group = NSCollectionLayoutGroup.CreateHorizontal(layoutSize: layoutSize, subitem: item, count: 1);
var section = NSCollectionLayoutSection.Create(group);
// this is what you need for content inset
section.ContentInsets = new NSDirectionalEdgeInsets(top: 5, leading: 5, bottom: 5, trailing: 5);
// this is spacing between items
section.InterGroupSpacing = 5;
var layout = new UICollectionViewCompositionalLayout(section);
return layout;
}
}
public class BaseModel
{
}
public class Model1 : BaseModel
{
public UIColor UIColor { get; set; }
}
public class Model2 : BaseModel
{
public string Text { get; set; }
}
public class MyViewUICollectionViewDataSource : UICollectionViewDataSource
{
public override UICollectionViewCell GetCell(UICollectionView collectionView, NSIndexPath indexPath)
{
var model = MyViewController.Models[(int) indexPath.Item];
switch (model)
{
case Model1 model1:
{
var cell = collectionView.DequeueReusableCell(MyViewUICollectionViewCell.ReuseIdentifier, indexPath) as MyViewUICollectionViewCell;
cell.ColorView.BackgroundColor = model1.UIColor;
return cell;
}
case Model2 model2:
{
var cell2 = collectionView.DequeueReusableCell(MyViewUICollectionViewCell2.ReuseIdentifier, indexPath) as MyViewUICollectionViewCell2;
cell2.Label.Text = model2.Text;
return cell2;
}
default:
throw new Exception();
}
}
public override nint GetItemsCount(UICollectionView collectionView, nint section)
{
return MyViewController.Models.Count;
}
}
public class MyViewUICollectionViewCell : UICollectionViewCell
{
public const string ReuseIdentifier = "MyViewUICollectionViewCell";
public UIView ColorView { get; }
[Export("initWithFrame:")]
public MyViewUICollectionViewCell(CGRect frame) : base(frame)
{
var container = new UIView
{
BackgroundColor = UIColor.White,
TranslatesAutoresizingMaskIntoConstraints = false
};
container.Layer.CornerRadius = 5;
ContentView.AddSubview(container);
container.TopAnchor.ConstraintEqualTo(ContentView.TopAnchor).Active = true;
container.LeftAnchor.ConstraintEqualTo(ContentView.LeftAnchor).Active = true;
container.BottomAnchor.ConstraintEqualTo(ContentView.BottomAnchor).Active = true;
container.RightAnchor.ConstraintEqualTo(ContentView.RightAnchor).Active = true;
ColorView = new UIView
{
TranslatesAutoresizingMaskIntoConstraints = false
};
ColorView.Layer.CornerRadius = 10;
container.AddSubview(ColorView);
ColorView.CenterXAnchor.ConstraintEqualTo(container.CenterXAnchor).Active = true;
ColorView.CenterYAnchor.ConstraintEqualTo(container.CenterYAnchor).Active = true;
ColorView.WidthAnchor.ConstraintEqualTo(20).Active = true;
ColorView.HeightAnchor.ConstraintEqualTo(50).Active = true;
}
}
public class MyViewUICollectionViewCell2 : UICollectionViewCell
{
public const string ReuseIdentifier = "MyViewUICollectionViewCell2";
public UILabel Label { get; }
[Export("initWithFrame:")]
public MyViewUICollectionViewCell2(CGRect frame) : base(frame)
{
var container = new UIView
{
BackgroundColor = UIColor.White,
TranslatesAutoresizingMaskIntoConstraints = false
};
container.Layer.CornerRadius = 5;
ContentView.AddSubview(container);
container.TopAnchor.ConstraintEqualTo(ContentView.TopAnchor).Active = true;
container.LeftAnchor.ConstraintEqualTo(ContentView.LeftAnchor).Active = true;
container.BottomAnchor.ConstraintEqualTo(ContentView.BottomAnchor).Active = true;
container.RightAnchor.ConstraintEqualTo(ContentView.RightAnchor).Active = true;
Label = new UILabel
{
TranslatesAutoresizingMaskIntoConstraints = false
};
container.AddSubview(Label);
Label.CenterXAnchor.ConstraintEqualTo(container.CenterXAnchor).Active = true;
Label.CenterYAnchor.ConstraintEqualTo(container.CenterYAnchor).Active = true;
//Label.WidthAnchor.ConstraintEqualTo(20).Active = true;
//Label.HeightAnchor.ConstraintEqualTo(20).Active = true;
}
}
As you can see, the problem is both cell types use the same estimated height of 200. I'm not sure what I'm doing wrong here. I want both cell types to be resized automatically to fit their content instead of having extra space to hit 200. I tried using a small estimated size like 10, but that didn't change anything. How should the code be changed to have a working auto size feature?
That's depending on the constraints you set in the CollectionViewCell
, don't give a fixed size to label otherwise it won't auto-size, in the MyViewUICollectionViewCell2, change the label's constraints to:
public class MyViewUICollectionViewCell2 : UICollectionViewCell
{
public const string ReuseIdentifier = "MyViewUICollectionViewCell2";
public UILabel Label { get; }
[Export("initWithFrame:")]
public MyViewUICollectionViewCell2(CGRect frame) : base(frame)
{
var container = new UIView
{
BackgroundColor = UIColor.White,
TranslatesAutoresizingMaskIntoConstraints = false
};
container.Layer.CornerRadius = 5;
ContentView.AddSubview(container);
container.TopAnchor.ConstraintEqualTo(ContentView.TopAnchor).Active = true;
container.LeftAnchor.ConstraintEqualTo(ContentView.LeftAnchor).Active = true;
container.BottomAnchor.ConstraintEqualTo(ContentView.BottomAnchor).Active = true;
container.RightAnchor.ConstraintEqualTo(ContentView.RightAnchor).Active = true;
Label = new UILabel
{
Lines = 0;
TranslatesAutoresizingMaskIntoConstraints = false
};
container.AddSubview(Label);
Label.TopAnchor.ConstraintEqualTo(container.TopAnchor).Active = true;
Label.LeftAnchor.ConstraintEqualTo(container.LeftAnchor).Active = true;
Label.BottomAnchor.ConstraintEqualTo(container.BottomAnchor).Active = true;
Label.RightAnchor.ConstraintEqualTo(container.RightAnchor).Active = true;
//Label.CenterXAnchor.ConstraintEqualTo(container.CenterXAnchor).Active = true;
//CenterYAnchor.ConstraintEqualTo(container.CenterYAnchor).Active = true;
//Label.WidthAnchor.ConstraintEqualTo(20).Active = true;
//Label.HeightAnchor.ConstraintEqualTo(20).Active = true;
}
}
To the colorView
in MyViewUICollectionViewCell1
, if your know the size of colorView
, you should specify the size of the container
as the same size of colorView
, it won't grow like label with different texts.