Has anyone implemented a decoration view for the iOS 6 UICollectionView? It's impossible
to find any tutorial on implementing a decoration view on the web. Basically in my app I have multiple sections, and I just wanted to display a decoration view behind each section. This should be simple to implement but I'm having no luck. This is driving me nuts... Thanks.
Here's a collection view layout decoration view tutorial in Swift (this is Swift 3, Xcode 8 seed 6).
Decoration views are not a UICollectionView feature; they essentially belong to the UICollectionViewLayout. No UICollectionView methods (or delegate or data source methods) mention decoration views. The UICollectionView knows nothing about them; it simply does what it is told.
To supply any decoration views, you will need a UICollectionViewLayout subclass; this subclass is free to define its own properties and delegate protocol methods that customize how its decoration views are configured, but that's entirely up to you.
To illustrate, I'll subclass UICollectionViewFlowLayout to impose a title label at the top of the collection view's content rectangle. This is probably a silly use of a decoration view, but it illustrates the basic principles perfectly. For simplicity, I'll start by hard-coding the whole thing, giving the client no ability to customize any aspect of this view.
There are four steps to implementing a decoration view in a layout subclass:
Define a UICollectionReusableView subclass.
Register the UICollectionReusableView subclass with the layout (not the collection view), by calling register(_:forDecorationViewOfKind:). The layout's initializer is a good place to do this.
Implement layoutAttributesForDecorationView(ofKind:at:) to return layout attributes that position the UICollectionReusableView. To construct the layout attributes, call init(forDecorationViewOfKind:with:) and configure the attributes.
Override layoutAttributesForElements(in:) so that the result of layoutAttributesForDecorationView(ofKind:at:) is included in the returned array.
The last step is what causes the decoration view to appear in the collection view. When the collection view calls layoutAttributesForElements(in:), it finds that the resulting array includes layout attributes for a decoration view of a specified kind. The collection view knows nothing about decoration views, so it comes back to the layout, asking for an actual instance of this kind of decoration view. You've registered this kind of decoration view to correspond to your UICollectionReusableView subclass, so your UICollectionReusableView subclass is instantiated and that instance is returned, and the collection view positions it in accordance with the layout attributes.
So let's follow the steps. Define the UICollectionReusableView subclass:
class MyTitleView : UICollectionReusableView {
weak var lab : UILabel!
override init(frame: CGRect) {
super.init(frame:frame)
let lab = UILabel(frame:self.bounds)
self.addSubview(lab)
lab.autoresizingMask = [.flexibleWidth, .flexibleHeight]
lab.font = UIFont(name: "GillSans-Bold", size: 40)
lab.text = "Testing"
self.lab = lab
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Now we turn to our UICollectionViewLayout subclass, which I'll call MyFlowLayout. We register MyTitleView in the layout's initializer; I've also defined some private properties that I'll need for the remaining steps:
private let titleKind = "title"
private let titleHeight : CGFloat = 50
private var titleRect : CGRect {
return CGRect(10,0,200,self.titleHeight)
}
override init() {
super.init()
self.register(MyTitleView.self, forDecorationViewOfKind:self.titleKind)
}
Implement layoutAttributesForDecorationView(ofKind:at:):
override func layoutAttributesForDecorationView(
ofKind elementKind: String, at indexPath: IndexPath)
-> UICollectionViewLayoutAttributes? {
if elementKind == self.titleKind {
let atts = UICollectionViewLayoutAttributes(
forDecorationViewOfKind:self.titleKind, with:indexPath)
atts.frame = self.titleRect
return atts
}
return nil
}
Override layoutAttributesForElements(in:); the index path here is arbitrary (I ignored it in the preceding code):
override func layoutAttributesForElements(in rect: CGRect)
-> [UICollectionViewLayoutAttributes]? {
var arr = super.layoutAttributesForElements(in: rect)!
if let decatts = self.layoutAttributesForDecorationView(
ofKind:self.titleKind, at: IndexPath(item: 0, section: 0)) {
if rect.intersects(decatts.frame) {
arr.append(decatts)
}
}
return arr
}
This works! A title label reading ``Testing'' appears at the top of the collection view.
Now I'll show how to make the label customizable. Instead of the title "Testing," we'll allow the client to set a property that determines the title. I'll give my layout subclass a public title property:
class MyFlowLayout : UICollectionViewFlowLayout {
var title = ""
// ...
}
Whoever uses this layout should set this property. For example, suppose this collection view is displaying the 50 U.S. states:
func setUpFlowLayout(_ flow:UICollectionViewFlowLayout) {
flow.headerReferenceSize = CGSize(50,50)
flow.sectionInset = UIEdgeInsetsMake(0, 10, 10, 10)
(flow as? MyFlowLayout)?.title = "States" // *
}
We now come to a curious puzzle. Our layout has a title property, the value of which needs to be communicated somehow to our MyTitleView instance. But when can that possibly happen? We are not in charge of instantiating MyTitleView; it happens automatically, when the collection view asks for the instance behind the scenes. There is no moment when the MyFlowLayout instance and the MyTitleView instance meet.
The solution is to use the layout attributes as a messenger. MyFlowLayout never meets MyTitleView, but it does create the layout attributes object that gets passed to the collection view to configure MyFlowLayout. So the layout attributes object is like an envelope. By subclassing UICollectionViewLayoutAttributes, we can include in that envelope any information we like — such as a title:
class MyTitleViewLayoutAttributes : UICollectionViewLayoutAttributes {
var title = ""
}
There's our envelope! Now we rewrite our implementation of layoutAttributesForDecorationView. When we instantiate the layout attributes object, we instantiate our subclass and set its title property:
override func layoutAttributesForDecorationView(
ofKind elementKind: String, at indexPath: IndexPath) ->
UICollectionViewLayoutAttributes? {
if elementKind == self.titleKind {
let atts = MyTitleViewLayoutAttributes( // *
forDecorationViewOfKind:self.titleKind, with:indexPath)
atts.title = self.title // *
atts.frame = self.titleRect
return atts
}
return nil
}
Finally, in MyTitleView, we implement the apply(_:) method. This will be called when the collection view configures the decoration view — with the layout attributes object as its parameter! So we pull out the title and use it as the text of our label:
class MyTitleView : UICollectionReusableView {
weak var lab : UILabel!
// ... the rest as before ...
override func apply(_ atts: UICollectionViewLayoutAttributes) {
if let atts = atts as? MyTitleViewLayoutAttributes {
self.lab.text = atts.title
}
}
}
It's easy to see how you might extend the example to make such label features as font and height customizable. Since we are subclassing UICollectionViewFlowLayout, some further modifications might also be needed to make room for the decoration view by pushing down the other elements. Also, technically, we should override isEqual(_:) in MyTitleView to differentiate between different titles. All of that is left as an exercise for the reader.
I got this working with a custom layout with the following:
Create a subclass of UICollectionReusableView and for example add an UIImageView to it:
#implementation AULYFloorPlanDecorationViewCell
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
UIImage *backgroundImage = [UIImage imageNamed:#"Layout.png"];
UIImageView *imageView = [[UIImageView alloc] initWithFrame:frame];
imageView.image = backgroundImage;
[self addSubview:imageView];
}
return self;
}
#end
Then in your controller in viewDidLoad register this subclass with the following code (replace code with your custom layout)
AULYAutomationObjectLayout *automationLayout = (AULYAutomationObjectLayout *)self.collectionView.collectionViewLayout;
[automationLayout registerClass:[AULYFloorPlanDecorationViewCell class] forDecorationViewOfKind:#"FloorPlan"];
In your custom layout then implement the following methods (or similar):
- (UICollectionViewLayoutAttributes *)layoutAttributesForDecorationViewOfKind:(NSString *)decorationViewKind atIndexPath:(NSIndexPath *)indexPath
{
UICollectionViewLayoutAttributes *layoutAttributes = [UICollectionViewLayoutAttributes layoutAttributesForDecorationViewOfKind:decorationViewKind withIndexPath:indexPath];
layoutAttributes.frame = CGRectMake(0.0, 0.0, self.collectionViewContentSize.width, self.collectionViewContentSize.height);
layoutAttributes.zIndex = -1;
return layoutAttributes;
}
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
NSMutableArray *allAttributes = [[NSMutableArray alloc] initWithCapacity:4];
[allAttributes addObject:[self layoutAttributesForDecorationViewOfKind:#"FloorPlan" atIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]]];
for (NSInteger i = 0; i < [self.collectionView numberOfItemsInSection:0]; i++)
{
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:0];
UICollectionViewLayoutAttributes *layoutAttributes = [self layoutAttributesForItemAtIndexPath:indexPath];
[allAttributes addObject:layoutAttributes];
}
return allAttributes;
}
There seems to be no documentation for it, but the following document got me on the right track: Collection View Programming Guide for iOS
UPDATE: It is probably better to subclass UICollectionReusableView for a decoration view instead of UICollectionViewCell
Here's how to do it in MonoTouch:
public class DecorationView : UICollectionReusableView
{
private static NSString classId = new NSString ("DecorationView");
public static NSString ClassId { get { return classId; } }
UIImageView blueMarble;
[Export("initWithFrame:")]
public DecorationView (RectangleF frame) : base(frame)
{
blueMarble = new UIImageView (UIImage.FromBundle ("bluemarble.png"));
AddSubview (blueMarble);
}
}
public class SimpleCollectionViewController : UICollectionViewController
{
public override void ViewDidLoad ()
{
base.ViewDidLoad ();
//Register the cell class (code for AnimalCell snipped)
CollectionView.RegisterClassForCell (typeof(AnimalCell), AnimalCell.ClassId);
//Register the supplementary view class (code for SideSupplement snipped)
CollectionView.RegisterClassForSupplementaryView (typeof(SideSupplement), UICollectionElementKindSection.Header, SideSupplement.ClassId);
//Register the decoration view
CollectionView.CollectionViewLayout.RegisterClassForDecorationView (typeof(DecorationView), DecorationView.ClassId);
}
//...snip...
}
public class LineLayout : UICollectionViewFlowLayout
{
public override UICollectionViewLayoutAttributes[] LayoutAttributesForElementsInRect (RectangleF rect)
{
var array = base.LayoutAttributesForElementsInRect (rect);
/*
...snip content relating to cell layout...
*/
//Add decoration view
var attributesWithDecoration = new List<UICollectionViewLayoutAttributes> (array.Length + 1);
attributesWithDecoration.AddRange (array);
var decorationIndexPath = NSIndexPath.FromIndex (0);
var decorationAttributes = LayoutAttributesForDecorationView (DecorationView.ClassId, decorationIndexPath);
attributesWithDecoration.Add (decorationAttributes);
var extended = attributesWithDecoration.ToArray<UICollectionViewLayoutAttributes> ();
return extended;
}
public override UICollectionViewLayoutAttributes LayoutAttributesForDecorationView (NSString kind, NSIndexPath indexPath)
{
var layoutAttributes = UICollectionViewLayoutAttributes.CreateForDecorationView (kind, indexPath);
layoutAttributes.Frame = new RectangleF (0, 0, CollectionView.ContentSize.Width, CollectionView.ContentSize.Height);
layoutAttributes.ZIndex = -1;
return layoutAttributes;
}
//...snip...
}
With an end result similar to:
In my case :
I wanted to upgrade from UITableView to UICollectionView.
uitableview sections >>> supplementary views
uitableview headerView >>> decoration view
In my case I felt subclassing layout and do other stuff, it's "too much"
for just simple "headerView" (decoration)
So my solution was just to create the headerview (not section) as first cell
and section 1 as the first section ( section 0 was size of zero)
Related
I am using UICollectionView to store images and I can reorder them by overriding CanMove And MoveItem.
But the items inside the UICollection only reorder when cell size is large like if cell size is around 106 height and width, then they can be reordered if they are smaller in size, they are cannot be reordered.
View:
public override void ViewDidLoad()
{
base.ViewDidLoad();
//ImageCv is the name of UiCollectionView
var collectionLayout = new PostImageFlowLayout(3, 0.85f);
var allCollectionSource = new PostImageColectionSource(ImageCv, (ViewModel as NewPostDetailViewModel));
ImageCv.RegisterNibForCell(PostImageCell.Nib, PostImageCell.Key);
ImageCv.RegisterClassForSupplementaryView(typeof(CollectionHeader), UICollectionElementKindSection.Header, new NSString("headerId"));
ImageCv.BackgroundColor = UIColor.Clear;
ImageCv.Hidden = false;
ImageCv.DataSource = allCollectionSource;
ImageCv.Delegate = collectionLayout;
var longPressGesture = new UILongPressGestureRecognizer(gesture =>
{
// Take action based on state
switch (gesture.State)
{
case UIGestureRecognizerState.Began:
var selectedIndexPath = ImageCv.IndexPathForItemAtPoint(gesture.LocationInView(View));
if (selectedIndexPath != null)
ImageCv.BeginInteractiveMovementForItem(selectedIndexPath);
Debug.WriteLine("Gesture Recognition: Activated");
break;
case UIGestureRecognizerState.Changed:
ImageCv.UpdateInteractiveMovement(gesture.LocationInView(View));
Debug.WriteLine("Gesture activated: Item location is changed");
break;
case UIGestureRecognizerState.Ended:
ImageCv.EndInteractiveMovement();
Debug.WriteLine("Gesture activation: complete");
break;
default:
ImageCv.CancelInteractiveMovement();
Debug.WriteLine("Gesture activation: Terminate");
break;
}
});
// Add the custom recognizer to the collection view
ImageCv.AddGestureRecognizer(longPressGesture);
}
UICollectionViewDelegateFlowLayout
using System;
using System.Windows.Input;
using CoreGraphics;
using UIKit;
namespace Sources.CollectionSources
{
public class PostImageFlowLayout : UICollectionViewDelegateFlowLayout
{
private float headerHeight;
private int noOfItems;
private bool isLoading;
public PostImageFlowLayout(int noOfItems, float headerHeight = 0f)
{
this.noOfItems = noOfItems;
this.headerHeight = headerHeight;
}
public override CGSize GetSizeForItem(UICollectionView collectionView, UICollectionViewLayout layout, Foundation.NSIndexPath indexPath)
{
return GetPostCellSize();
}
public override CGSize GetReferenceSizeForHeader(UICollectionView collectionView, UICollectionViewLayout layout, nint section)
{
return new CGSize(collectionView.Frame.Width, headerHeight);
}
public override UIEdgeInsets GetInsetForSection(UICollectionView collectionView, UICollectionViewLayout layout, nint section)
{
return new UIEdgeInsets(0, 0, 0, 0);
}
private CGSize GetPostCellSize()
{
var relativeWidth = (UIScreen.MainScreen.Bounds.Width - 2) / this.noOfItems;
return new CGSize(relativeWidth, relativeWidth);
//return new CGSize(55, 55);
}
}
}
Source
public class PostImageColectionSource : MvxCollectionViewSource
{
private NewPostDetailViewModel newPostDetailViewModel;
private string type;
static NSString animalCellId = new NSString("PostImageCell");
static NSString headerId = new NSString("Header");
List<IAnimal> animals;
protected override NSString DefaultCellIdentifier
{
get
{
return PostImageCell.Key;
}
}
public override System.Collections.IEnumerable ItemsSource
{
get
{
return base.ItemsSource;
}
set
{
base.ItemsSource = value;
CollectionView.ReloadData();
}
}
public PostImageColectionSource(UICollectionView collectionView, NewPostDetailViewModel newPostDetailViewModel) : base(collectionView)
{
this.newPostDetailViewModel = newPostDetailViewModel;
animals = new List<IAnimal>();
for (int i = 0; i < 20; i++)
{
animals.Add(new Monkey(i));
}
}
public override nint NumberOfSections(UICollectionView collectionView)
{
return 1;
}
public override nint GetItemsCount(UICollectionView collectionView, nint section)
{
return 5;// animals.Count;
}
public override UICollectionViewCell GetCell(UICollectionView collectionView, NSIndexPath indexPath)
{
var cell = (PostImageCell)collectionView.DequeueReusableCell(animalCellId, indexPath);
var animal = animals[indexPath.Row];
cell.Result(indexPath.Row);
return cell;
}
public override bool CanMoveItem(UICollectionView collectionView, NSIndexPath indexPath)
{
Debug.WriteLine("Ready to move images");
//System.Diagnostics.Debug.WriteLine("Checking if it can move the item");
return true;
}
public override void MoveItem(UICollectionView collectionView, NSIndexPath sourceIndexPath, NSIndexPath destinationIndexPath)
{
//base.MoveItem(collectionView, sourceIndexPath, destinationIndexPath);
Debug.WriteLine("Start moving images to reorder");
var item = animals[(int)sourceIndexPath.Item];
animals.RemoveAt((int)sourceIndexPath.Item);
animals.Insert((int)destinationIndexPath.Item, item);
}
}
When the GetPostCellSize in PostImageFlowLayout has width and height of around 100, the CanMove and MoveItem in PostImageColectionSource are being called and items are being reordered. But if the GetPostCellSize has width and height of around 50 or 70, even though the gestures are activated, CanMove and MoveItem in PostImageColectionSource are not being called hence cannot be moved.
Can anyone hope me with reordering the images in UICollectionView when the cell size is small like around width and height of 70.
Thank you.
I am tagging swift and objective-C as this issue is related to IOS in general and not xamarin specific
Main issue here is that you need to pass in the collection view to the gesture.LocationInView(View) call instead of the main View. In ViewDidLoad in the UILongPressGestureRecognizer change:
var selectedIndexPath = ImageCv.IndexPathForItemAtPoint(gesture.LocationInView(View));
and
ImageCv.UpdateInteractiveMovement(gesture.LocationInView(View));
to
var selectedIndexPath = ImageCv.IndexPathForItemAtPoint(gesture.LocationInView(ImageCv)); // <-- pass in ImageCV instead of View. (where ImageCV is the collection view)
and
ImageCv.UpdateInteractiveMovement(gesture.LocationInView(ImageCv)); // <-- pass in ImageCV instead of View.
Another thing to note, but not a huge deal, is that PostImageColectionSource is ultimately derived from UICollectionViewSource, which is a combo of UICollectionViewDelegate and UICollectionViewDataSource in one class, but is being assigned to the DataSource property of the collection view. All this means is that though you can implement methods for UICollectionViewDelegate in PostImageColectionSource the delegate methods will not be called on that class since the Delegate property of the collection view is set to the PostImageFlowLayout, which derives ultimately from UICollectionViewDelegate via UICollectionViewDelegateFlowLayout.
I am trying to setup a UICollectionView within my existing UIViewController. Everything is working except for getting a title to show for each section - I can't figure out what I'm doing wrong.
My code in the UIViewController to initiate the collection view:
public partial class ViewController : UIViewController
{
//...
public override void ViewDidLoad ()
{
base.ViewDidLoad ();
CollectionView_Outlet.RegisterClassForCell(typeof(ModifierCell), ModifierCell.CellID);
CollectionView_Outlet.RegisterClassForSupplementaryView (typeof(Header), UICollectionElementKindSection.Header, Header.HeaderId);
CollectionView_Outlet.ShowsHorizontalScrollIndicator = false;
CollectionView_Outlet.Source = new ModifiersSource(this);
CollectionView_Outlet.BackgroundColor = UIColor.White;
CollectionView_Outlet.ReloadData();
}
//...
}
Then I have created a subclass of UICollectionViewSource:
public class ModifiersSource : UICollectionViewSource
{
ViewController senderVC;
public ModifiersSource(ViewController sender)
{
senderVC = sender;
}
public override nint NumberOfSections(UICollectionView collectionView)
{
return 2;
}
public override nint GetItemsCount (UICollectionView collectionView, nint section)
{
return senderVC.modifiers.Count;
}
public override UICollectionViewCell GetCell(UICollectionView collectionView, NSIndexPath indexPath)
{
//...
}
public override UICollectionReusableView GetViewForSupplementaryElement(UICollectionView collectionView, NSString elementKind, NSIndexPath indexPath)
{
var headerView = (Header)collectionView.DequeueReusableSupplementaryView (elementKind, Header.HeaderId, indexPath);
headerView.Text = "Supplementary View";
return headerView;
}
}
And finally created:
public class Header : UICollectionReusableView
{
public static NSString HeaderId = new NSString("UserSource1");
UILabel label;
public string Text {
get {
return label.Text;
}
set {
label.Text = value;
SetNeedsDisplay ();
}
}
[Export ("initWithFrame:")]
public Header (RectangleF frame) : base (frame)
{
label = new UILabel (){
Frame = new RectangleF(0,0,300,50),
BackgroundColor = UIColor.Red};
AddSubview (label);
BackgroundColor = UIColor.White;
}
}
I've put a breakpoint on the GetViewForSupplementaryElement method but it never gets called. I've also set the following in my StoryBoard:
What am I missing?!
After many attempts of not being able to get the above to work, I manually set UICollectionViewFlowLayout whilst initiating the UIContainerView. Seems to have done the trick, but not sure why it didn't pick up the settings from my StoryBoard. Here is my working code:
public override void ViewDidLoad ()
{
base.ViewDidLoad ();
CollectionView_Outlet.RegisterClassForCell(typeof(ModifierCell), ModifierCell.CellID);
CollectionView_Outlet.RegisterClassForCell(typeof(ItemOptionCell), ItemOptionCell.CellID);
CollectionView_Outlet.RegisterClassForSupplementaryView (typeof(Header), UICollectionElementKindSection.Header, Header.HeaderId);
CollectionView_Outlet.ShowsHorizontalScrollIndicator = false;
//This is the new bit I added:
var layout = new UICollectionViewFlowLayout ();
layout.HeaderReferenceSize = new CGSize (300, 40);
CollectionView_Outlet.SetCollectionViewLayout (layout, false);
CollectionView_Outlet.Source = new ModifiersSource(this);
CollectionView_Outlet.ReloadData();
}
To get this to work in Xamarin, I had to enable Section Header under accessories (and it crashed in Xamarin, so from XCode). I had to do this even though I'm loading my header from a separate nib/xib into my collection view (but it will also show a reusable cell on the collection view that I don't think I need). Very specific but hopefully this saves someone in a similar situation some time!
Update
The issue seems to be resolved when using the designated collectionViewContentSize() method like so:
let contentSize = collectionViewContentSize()
Nevertheless, I would be very interested in an explanation behind this behaviour, so I've updated my question accordingly.
Original Question
I am trying to recreate the steps found in the first example of this article, but using Swift and Storyboards.
I have a custom UICollectionViewFlowLayout subclass with the following content:
import UIKit
class SpringyFlowLayout: UICollectionViewFlowLayout {
private lazy var animator: UIDynamicAnimator = {
return UIDynamicAnimator(collectionViewLayout: self)
}()
override func prepareLayout() {
super.prepareLayout()
let contentSize = collectionView!.contentSize
let items = super.layoutAttributesForElementsInRect(CGRect(origin: CGPointZero, size: contentSize)) as! [UICollectionViewLayoutAttributes]
if animator.behaviors.isEmpty {
for item in items {
let spring = UIAttachmentBehavior(item: item, attachedToAnchor: item.center)
spring.length = 0
spring.damping = 0.8
spring.frequency = 1.0
animator.addBehavior(spring)
}
}
}
override func layoutAttributesForElementsInRect(rect: CGRect) -> [AnyObject]? {
return animator.itemsInRect(rect)
}
override func layoutAttributesForItemAtIndexPath(indexPath: NSIndexPath) -> UICollectionViewLayoutAttributes! {
return animator.layoutAttributesForCellAtIndexPath(indexPath)
}
override func shouldInvalidateLayoutForBoundsChange(newBounds: CGRect) -> Bool {
let delta = newBounds.origin.y - collectionView!.bounds.origin.y
for spring in animator.behaviors {
let items = spring.items as! [UICollectionViewLayoutAttributes]
if let attributes = items.first {
attributes.center.y += delta
animator.updateItemUsingCurrentState(attributes)
}
}
return false
}
I have the correct Layout class set in Storyboards. When I run the app, my Collection View is empty.
I have determined that the issue is in the following snipped:
override func prepareLayout() {
super.prepareLayout()
let contentSize = collectionView!.contentSize
let items = super.layoutAttributesForElementsInRect(CGRect(origin: CGPointZero, size: contentSize)) as! [UICollectionViewLayoutAttributes]
//...
}
Since I subclass UICollectionViewFlowLayout, I thought I can rely on the super implementation to lay out the elements for me, and then modify their attributes. But when I check contentSize, it reports the width correctly, but the height is 0.
This leads to an empty items array -> no behaviors in animator -> animator.itemsInRect(_) returning an empty array -> the empty Collection View.
I just can't seem to find out what I'm missing. There should be no need to override the contentSize() method, since I'm using a flow layout.
The issue it that collectionView doesn't yet have a content size, because it just started to prepare it's layout. I don't believe that calling collectionView!.contentSize will actually compute the size. The reason collectionViewContentSize() works is because it will compute the size using your other layout code.
Here's something you might find interesting. I noticed in Ash Furrow's example in viewDidAppear a call to collectionViewLayout.invalidateLayout. Ash notes this isn't necessary when using storyboards due a difference in timing of the first invocation of prepareLayout versus when storyboards are not used.
I tried invalidating the collection view layout in viewDidAppear along with your code and a storyboard. Here is what I found:
Test Scenarios:
1) collectionView!.contentSize without invalidateLayout = cv is EMPTY
2) collectionViewContentSize() without invalidateLayout = cv WORKS
3) collectionView!.contentSize with invalidateLayout = cv WORKS
4) collectionViewContentSize() with invalidateLayout = cv WORKS
FYI
I've created a custom EntryElement for our MonoTouch.Dialog so that all the controls are aligned to the right. The code is as follows:
public class RightAlignEntryElement : EntryElement
{
private NSString _cellKey = new NSString("RightAlignedEntryElement");
public RightAlignEntryElement(string caption, string placeholder, string value) : base(caption, placeholder, value)
{
}
protected override UITextField CreateTextField(RectangleF frame)
{
var paddedFrame = frame;
paddedFrame.Width = frame.Width -= 10;
var textField = base.CreateTextField(paddedFrame);
textField.TextAlignment = UITextAlignment.Right;
return textField;
}
public override UITableViewCell GetCell(UITableView tv)
{
var cell = base.GetCell(tv);
return cell;
}
protected override NSString CellKey
{
get { return _cellKey; }
}
}
this works fine, until the Dialog gets refreshed, for example the table is scrolled, or subview is presented then dismissed.
Before there is padding on the right of the cell, and once scrolled the padding disappears. I've seen that this has been a problem is the past, and have seen various other suggestions on here and the Xamarin forums, but nothing seems to work for me.
See the attached screen grabs for before and after.
For the record, here is the response I got on another forum for this question:
The behaviour is due to not using the cell reuse in you RightAlignEntryElement
Your GetCell method should look something like:
public override UITableViewCell GetCell(UITableView tv)
{
var cell = tv.DequeueReusableCell (CellKey) as UITableViewCell;
if (cell == null) {
cell = base.GetCell(tv);
cell.TextLabel.Font = UIFont.FromName("HelveticaNeue-Light", 16);
}
return cell;
}
I am trying to use Xamarin.iOS. Everything makes sense until I get to customizing UI. How would you create a collection with customized look and feel for the collection control and it's cell controls?
UICollectionView is a bit similar to UITableView. To get the customization you want, you first need to define your custom cell class:
public class AnimalCell : UICollectionViewCell
{
UIImageView imageView;
[Export ("initWithFrame:")]
public AnimalCell (System.Drawing.RectangleF frame) : base (frame)
{
BackgroundView = new UIView{BackgroundColor = UIColor.Orange};
SelectedBackgroundView = new UIView{BackgroundColor = UIColor.Green};
ContentView.Layer.BorderColor = UIColor.LightGray.CGColor;
ContentView.Layer.BorderWidth = 2.0f;
ContentView.BackgroundColor = UIColor.White;
ContentView.Transform = CGAffineTransform.MakeScale (0.8f, 0.8f);
imageView = new UIImageView (UIImage.FromBundle ("placeholder.png"));
imageView.Center = ContentView.Center;
imageView.Transform = CGAffineTransform.MakeScale (0.7f, 0.7f);
ContentView.AddSubview (imageView);
}
public UIImage Image {
set {
imageView.Image = value;
}
}
}
Then you can reference this cell in your UICollectionViewDataSource class by overriding the GetCell method:
public override UICollectionViewCell GetCell (UICollectionView collectionView, MonoTouch.Foundation.NSIndexPath indexPath)
{
var animalCell = (AnimalCell)collectionView.DequeueReusableCell (animalCellId, indexPath);
var animal = animals [indexPath.Row];
animalCell.Image = animal.Image;
return animalCell;
}
For more info, you should checkout this tutorial on Xamarin's website that I pulled these examples from:
http://docs.xamarin.com/guides/ios/user_interface/introduction_to_collection_views
to add a background image, try this (in ViewDidLoad)
myview.BackgroundColor = UIColor.FromPatternImage(UIImage.FromFile("myimage.png"));