Uicollectionview Adding Uicollectioncell

Add UICollectionView in UICollectionViewCell

If you really want to insert an collectionView inside a collectionViewCell then there is a pretty simple step. Create an instance of UICollectionView and add it the collectionViewCell. You can use this example if you like.

//
// ViewController.swift
// StackOverFlowAnswer
//
// Created by BIKRAM BHANDARI on 18/6/17.
// Copyright © 2017 BIKRAM BHANDARI. All rights reserved.
//

import UIKit

class ViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {

let cellId = "CellId"; //Unique cell id

override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .red; //just to test

collectionView.register(Cell.self, forCellWithReuseIdentifier: cellId) //register collection view cell class
setupViews(); //setup all views
}

func setupViews() {

view.addSubview(collectionView); // add collection view to view controller
collectionView.delegate = self; // set delegate
collectionView.dataSource = self; //set data source

collectionView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true; //set the location of collection view
collectionView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true; // top anchor of collection view
collectionView.heightAnchor.constraint(equalTo: view.heightAnchor).isActive = true; // height
collectionView.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true; // width

}

let collectionView: UICollectionView = { // collection view to be added to view controller
let cv = UICollectionView(frame: .zero, collectionViewLayout: UICollectionViewFlowLayout()); //zero size with flow layout
cv.translatesAutoresizingMaskIntoConstraints = false; //set it to false so that we can suppy constraints
cv.backgroundColor = .yellow; // test
return cv;
}();

//deque cell
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath);
// cell.backgroundColor = .blue;
return cell;
}

// number of rows
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 5;
}

//size of each CollecionViewCell
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: view.frame.width, height: 200);
}
}

// first UICollectionViewCell
class Cell: UICollectionViewCell, UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout {

let cellId = "CellId"; // same as above unique id

override init(frame: CGRect) {
super.init(frame: frame);

setupViews();
collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: cellId); //register custom UICollectionViewCell class.
// Here I am not using any custom class

}

func setupViews(){
addSubview(collectionView);

collectionView.delegate = self;
collectionView.dataSource = self;

collectionView.leftAnchor.constraint(equalTo: leftAnchor).isActive = true;
collectionView.rightAnchor.constraint(equalTo: rightAnchor).isActive = true;
collectionView.topAnchor.constraint(equalTo: topAnchor).isActive = true;
collectionView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true;
}

let collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout();
layout.scrollDirection = .horizontal; //set scroll direction to horizontal
let cv = UICollectionView(frame: .zero, collectionViewLayout: layout);
cv.backgroundColor = .blue; //testing
cv.translatesAutoresizingMaskIntoConstraints = false;
return cv;
}();

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath);
cell.backgroundColor = .red;
return cell;
}

func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 5;
}

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: self.frame.width, height: self.frame.height - 10);
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}

Sample screenshot

How do I add a UICollectionView inside a UICollectionViewCell?

You don't need tableview for that just in your collectionview cell xib drag another collection and set its scrolling property to vertical,then create xib from inner collectionview.

In outer collectionview cell use that method to set delegate and datasource

// code
override func awakeFromNib() {
super.awakeFromNib()
serInnerCollectionView()
}

func serInnerCollectionView() {

collectionViewInner.delegate = self
collectionViewInner.dataSource = self
collectionViewInner.register(UINib(nibName: " FooterCollectionViewCell", bundle: nil), forCellWithReuseIdentifier: "FooterCollectionViewCellIdentifier")

}

Inaccuracy addSubview in UICollectionViewCell

First of all, instead of:

addSubview(shortContentPost)

use:

contentView.addSubview(shortContentPost)

Do this for all the subviews that you add. contentView is supposed to hold your content.

Implement this prepareForReuse in your cell:

override func prepareForReuse() {
super.prepareForReuse()

let subviews = contentView.subviews
for subview in subviews {
subview.removeFromSuperview()
}
}

This will clean your cell's content before reusing the cell.

Although I would STRONGLY recommend NOT to add/remove those subviews dynamically, because then you lose much of the reuse mechanism. I would add all the views (labels/imageViews) to the cell by default, and then use their isHidden property to hide/unhide them based on the content (e.g., if an image should be show, set the isHidden to true on all the others). This would be performance-wise better for reusing, because then the UI objects will not have to be recreated everytime a cell is reused, you would just reconfigure the content of the labels/imageViews.

UICollectionView adding UICollectionCell

Only UICollectionView inside StoryBoard have UICollectionViewCell inside.
If use XIB, create a new XIB with CellName.xib, add CollectionViewCell to it, specify name of UICollectionView custom class. After that use registerNib.

Sample code: https://github.com/lequysang/TestCollectionViewWithXIB

How to add views between UICollectionViewCells in a UICollectionView?

I studied more of how UICollectionViewLayouts work and figured out how to solve it. I have an UICollectionReusableView subclass called OrangeView that will be positioned between my views, than I wrote an UICollectionViewFlowLayout subclass called CategoriesLayout that will deal with my layout.

Sorry for the big block of code, but here is how it looks like:

@implementation CategoriesLayout

- (void)prepareLayout {
// Registers my decoration views.
[self registerClass:[OrangeView class] forDecorationViewOfKind:@"Vertical"];
[self registerClass:[OrangeView class] forDecorationViewOfKind:@"Horizontal"];
}

- (UICollectionViewLayoutAttributes *)layoutAttributesForDecorationViewOfKind:(NSString *)decorationViewKind atIndexPath:(NSIndexPath *)indexPath {
// Prepare some variables.
NSIndexPath *nextIndexPath = [NSIndexPath indexPathForItem:indexPath.row+1 inSection:indexPath.section];

UICollectionViewLayoutAttributes *cellAttributes = [self layoutAttributesForItemAtIndexPath:indexPath];
UICollectionViewLayoutAttributes *nextCellAttributes = [self layoutAttributesForItemAtIndexPath:nextIndexPath];

UICollectionViewLayoutAttributes *layoutAttributes = [UICollectionViewLayoutAttributes layoutAttributesForDecorationViewOfKind:decorationViewKind withIndexPath:indexPath];

CGRect baseFrame = cellAttributes.frame;
CGRect nextFrame = nextCellAttributes.frame;

CGFloat strokeWidth = 4;
CGFloat spaceToNextItem = 0;
if (nextFrame.origin.y == baseFrame.origin.y)
spaceToNextItem = (nextFrame.origin.x - baseFrame.origin.x - baseFrame.size.width);

if ([decorationViewKind isEqualToString:@"Vertical"]) {
CGFloat padding = 10;

// Positions the vertical line for this item.
CGFloat x = baseFrame.origin.x + baseFrame.size.width + (spaceToNextItem - strokeWidth)/2;
layoutAttributes.frame = CGRectMake(x,
baseFrame.origin.y + padding,
strokeWidth,
baseFrame.size.height - padding*2);
} else {
// Positions the horizontal line for this item.
layoutAttributes.frame = CGRectMake(baseFrame.origin.x,
baseFrame.origin.y + baseFrame.size.height,
baseFrame.size.width + spaceToNextItem,
strokeWidth);
}

layoutAttributes.zIndex = -1;
return layoutAttributes;
}

- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect {
NSArray *baseLayoutAttributes = [super layoutAttributesForElementsInRect:rect];
NSMutableArray * layoutAttributes = [baseLayoutAttributes mutableCopy];

for (UICollectionViewLayoutAttributes *thisLayoutItem in baseLayoutAttributes) {
if (thisLayoutItem.representedElementCategory == UICollectionElementCategoryCell) {
// Adds vertical lines when the item isn't the last in a section or in line.
if (!([self indexPathLastInSection:thisLayoutItem.indexPath] ||
[self indexPathLastInLine:thisLayoutItem.indexPath])) {
UICollectionViewLayoutAttributes *newLayoutItem = [self layoutAttributesForDecorationViewOfKind:@"Vertical" atIndexPath:thisLayoutItem.indexPath];
[layoutAttributes addObject:newLayoutItem];
}

// Adds horizontal lines when the item isn't in the last line.
if (![self indexPathInLastLine:thisLayoutItem.indexPath]) {
UICollectionViewLayoutAttributes *newHorizontalLayoutItem = [self layoutAttributesForDecorationViewOfKind:@"Horizontal" atIndexPath:thisLayoutItem.indexPath];
[layoutAttributes addObject:newHorizontalLayoutItem];
}
}
}

return layoutAttributes;
}

@end

I also wrote a category with some methods to check if an index path is the last in a line, in the last line or the last in a section:

@implementation UICollectionViewFlowLayout (Helpers)

- (BOOL)indexPathLastInSection:(NSIndexPath *)indexPath {
NSInteger lastItem = [self.collectionView.dataSource collectionView:self.collectionView numberOfItemsInSection:indexPath.section] -1;
return lastItem == indexPath.row;
}

- (BOOL)indexPathInLastLine:(NSIndexPath *)indexPath {
NSInteger lastItemRow = [self.collectionView.dataSource collectionView:self.collectionView numberOfItemsInSection:indexPath.section] -1;
NSIndexPath *lastItem = [NSIndexPath indexPathForItem:lastItemRow inSection:indexPath.section];
UICollectionViewLayoutAttributes *lastItemAttributes = [self layoutAttributesForItemAtIndexPath:lastItem];
UICollectionViewLayoutAttributes *thisItemAttributes = [self layoutAttributesForItemAtIndexPath:indexPath];

return lastItemAttributes.frame.origin.y == thisItemAttributes.frame.origin.y;
}

- (BOOL)indexPathLastInLine:(NSIndexPath *)indexPath {
NSIndexPath *nextIndexPath = [NSIndexPath indexPathForItem:indexPath.row+1 inSection:indexPath.section];

UICollectionViewLayoutAttributes *cellAttributes = [self layoutAttributesForItemAtIndexPath:indexPath];
UICollectionViewLayoutAttributes *nextCellAttributes = [self layoutAttributesForItemAtIndexPath:nextIndexPath];

return !(cellAttributes.frame.origin.y == nextCellAttributes.frame.origin.y);
}

@end

And this is the final result:

Final Result

Call function from UICollectionViewCell to use in UICollectionView

The flow is inversed but yes, the easiest way is via notifications
in you collection view

Put this in your collectionView button pressed method

NotificationCenter.default.post(name: Notification.Name("handleNewJob"), object: nil)

Add observer in you collection view cell (init or awakeFromNib depends)

NotificationCenter.default.addObserver(self, selector: #selector(handleNewJob, name: NSNotification.Name(rawValue: "handleNewJob"), object: nil)

How to set UICollectionViewCell Width and Height programmatically

Use this method to set custom cell height width.

Make sure to add this protocols

UICollectionViewDelegate

UICollectionViewDataSource

UICollectionViewDelegateFlowLayout

If you are using swift 5 or xcode 11 and later you need to set Estimate Size to none using storyboard in order to make it work properly. If you will not set that than below code will not work as expected.

Sample Image

Swift 4 or Later

extension YourViewController: UICollectionViewDelegate {
//Write Delegate Code Here
}

extension YourViewController: UICollectionViewDataSource {
//Write DataSource Code Here
}

extension YourViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: screenWidth, height: screenWidth)
}
}

Objective-C

@interface YourViewController : UIViewController<UICollectionViewDelegate,UICollectionViewDataSource,UICollectionViewDelegateFlowLayout>

- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath
{
return CGSizeMake(CGRectGetWidth(collectionView.frame), (CGRectGetHeight(collectionView.frame)));
}

Swift 5 How UICollectionViewCell changes it's content

First, the .text property of a UILabel can be nil, so it is optional.

If you add this to a view controller's viewDidLoad():

    let label = UILabel()
print(label.text)
label.text = "abc"
print(label.text)

the output in the debug console will be:

nil
Optional("abc")

Next, if you set the text of a label and then set it again, it replaces any existing text:

    let label = UILabel()
print(label.text)
label.text = "abc"
print(label.text)
label.text = "1"
print(label.text)

now the output is:

nil
Optional("abc")
Optional("1")

If you want to get rid of the "Optional" part, you need to unwrap the text:

    let label = UILabel()
if let s = label.text {
print(s)
}
label.text = "abc"
if let s = label.text {
print(s)
}
label.text = "1"
if let s = label.text {
print(s)
}

output:

abc
1

Note there is no "nil" output... because the first if let... statement fails, so the print statement does not get executed.

So, if you want your label to show "CUSTOM 1" / "CUSTOM 2" / "CUSTOM 3" / etc, you can either set it that way in cellForRowAt:

cell.myLabel.text = "CUSTOM \(indexPath.row)"

or, you can append the existing text:

// unwrap the optional text
if let s = cell.myLabel.text {
cell.myLabel.text = s + " \(indexPath.row)"
}

Note, however, that cells are reused, so you could end up with:

CUSTOM 1 7 15 8 23

Much better to set the full text to what you want it to be, than to try and append something each time the cell is used.

Manipulate uicollectionview inside uicollectionviewcell from uiviewcontroller

In your case, you didn't set the delegate you've created, you should set it inside:

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cellNumQuestion = collectionView.dequeueReusableCell(withReuseIdentifier: "celdaExterior", for: indexPath) as! ExteriorCollectionViewCell
// Setting the delegate
delegateExterior = cellNumQuestion
return cellNumQuestion
}

Also, in order to avoid reference-cycle set your delegateExterior pointer to be weak:

weak var delegateExterior: ExteriorCollectionViewCellDelegate?

Another solution will be to add an observer inside ExteriorCollectionViewCell and when the button is tapped and the IBAction method gets called inside your UIViewController, use post notification to pass that event into your observer.

i.e:

Add the observer that will listen to your events inside your ExteriorCollectionViewCell:

 NotificationCenter.default.addObserver(forName: NSNotification.Name(rawValue: "EventForCollectionView"),
object: nil,
queue: nil) { [weak self] (_) in
// This method will be invoked when you post notification from your IBAction method inside your UIVC
self?.YourCustomMethod()
}

Send your notification event from your IBAction inside the IBAction method:

NotificationCenter.default.post(name: NSNotification.Name(rawValue: "EventForCollectionView"), object: nil)

But if the button is inside your UICollectionViewCell you can just use IBAction method inside your UICollectionViewCell and do your logic there.



Related Topics



Leave a reply



Submit