I have been doing much reading and looking at app samples and am still unsure how to implement a collection view that pushes to a detail view with paging?
I am able to get a collection view to push to a static detail view (unfortunately without titles) and I can get paging working without a collection view but not both together!? I also need to take across a title as well which is an important part.
Please can someone help me with this as I may go insane soon :)
Many thanks in advance!
Just push another collection view controller with flow layout, horizontal scrolling direction, enabled paging property and set itemSize property of your layout to be full screen. Before pushing set content offset of this controller to be on a selected image. Try the following code. You should call initWithCollectionViewLayout to initialize your collection view.
-(id)initWithCollectionViewLayout:(UICollectionViewLayout *)layout {
if (self = [super initWithCollectionViewLayout:layout]) {
[self.collectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:#"Cell"];
UICollectionViewFlowLayout *layout = (UICollectionViewFlowLayout *)self.collectionView.collectionViewLayout;
layout.itemSize = self.collectionView.bounds.size;
layout.minimumLineSpacing = 0;
layout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
self.collectionView.pagingEnabled = YES;
// In order to see cells you can declare something like colors array in .h
self.colors = #[[UIColor redColor], [UIColor greenColor], [UIColor blueColor]];
}
return self;
}
-(NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView {
return 1;
}
-(NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
return self.shopNames.count;
}
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
UICollectionViewCell *cell = [self.collectionView dequeueReusableCellWithReuseIdentifier:#"Cell" forIndexPath:indexPath];
[cell.contentView setBackgroundColor:self.colors[indexPath.item]];
return cell;
}
P.S. To set content offset use [self.collectionView setContentOffset:CGPointMake(40, 0)];
Related
I have a ViewController that contain a CollectionView :
- (void)viewDidLoad
{
UICollectionViewFlowLayout *layout = [UICollectionViewFlowLayout new];
collectionview = [[UICollectionView alloc]initWithFrame:CGRectZero collectionViewLayout:layout];
collectionview.backgroundColor = [UIColor colorWithRed:0.86 green:0.86 blue:0.86 alpha:1.0];
collectionview.translatesAutoresizingMaskIntoConstraints = false;
collectionview.delegate = self;
collectionview.dataSource = self;
collectionview.bounces = true;
[collectionview registerClass:[CollectionViewCell1 class] forCellWithReuseIdentifier:#"cell1"];
[self.view addSubview:collectionview];
[collectionview sdc_alignEdgesWithSuperview:UIRectEdgeAll];
[collectionview registerClass:[RecipeCollectionReusableView class] forSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:#"HeaderView"];
}
-(UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath
{
UICollectionReusableView *reusableview = nil;
RecipeCollectionReusableView *headerView = [collectionView dequeueReusableSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:#"HeaderView" forIndexPath:indexPath];
headerView.backgroundColor = [UIColor greenColor];
headerView.title.text = #"ABC";
reusableview = headerView;
return reusableview;
}
and RecipeCollectionReusableView is :
- (void)initialize
{
_title = [UILabel new];
_title.translatesAutoresizingMaskIntoConstraints = false;
[self addSubview:_title];
[_title sdc_centerInSuperview];
}
but there isn't any header on the screen after run.
Did you do the required implementation for the header?
https://developer.apple.com/documentation/uikit/uicollectionviewdelegateflowlayout/1617702-collectionview?language=objc
collectionView:layout:referenceSizeForHeaderInSection:
Return Value
The size of the header. If you return a value of size (0, 0), no header is added.
Discussion
If you do not implement this method, the flow layout uses the value in its
headerReferenceSize
property to set the size of the header.
During layout, only the size that corresponds to the appropriate scrolling direction is used. For example, for the vertical scrolling direction, the layout object uses the height value returned by your method. (In that instance, the width of the header would be set to the width of the collection view.) If the size in the appropriate scrolling dimension is 0, no header is added.
This article completely helped me :
How to add Header wit Supplementary view to UiCollectionView in Objective-C
I'm having a weird issue, when using a collection view with dynamic sizes, this issue doesn't happens while using fixed sizes.
After a reload the first cell of each section disappears, but only if they are out of the screen. After a few tests I realize that the cell didn't disappear, but its hidden bellow the section header.
Do you have any idea what is causing this?
Collection without reloading:
Collection after reloading with cell visible:
Collection after reloading with cell out of screen:
3D view of the cell after reloading:
The code:
#pragma mark - UICollectionViewDataSource
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView {
return self.sections.count;
}
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)sectionIndex {
Section *section = [self.sections objectAtIndex:sectionIndex];
return section.items.count;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
Section *section = [self.sections objectAtIndex:indexPath.section];
Item *item = [section.items objectAtIndex:indexPath.row];
if (self.editing) {
EditingCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"cell-editing" forIndexPath:indexPath];
cell.item = item;
return cell;
} else {
BasicCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"cell" forIndexPath:indexPath];
cell.item = item;
return cell;
}
}
- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath {
if ([kind isEqualToString:UICollectionElementKindSectionHeader]) {
HeaderCollectionReusableView *header = [collectionView dequeueReusableSupplementaryViewOfKind:kind withReuseIdentifier:#"header" forIndexPath:indexPath];
Section *section = [self.sections objectAtIndex:indexPath.section];
header.title = section.title;
return header;
} else {
UICollectionReusableView *footer = [collectionView dequeueReusableSupplementaryViewOfKind:kind withReuseIdentifier:#"footer" forIndexPath:indexPath];
return footer;
}
}
#implementation DetailCollectionViewLayout
- (instancetype)init {
if (self = [super init]) {
[self initialize];
}
return self;
}
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
if (self = [super initWithCoder:aDecoder]) {
[self initialize];
}
return self;
}
- (void)prepareLayout {
CGFloat cellWidth = (isIPAD) ? 288 : CGRectGetWidth(self.collectionView.bounds);
CGFloat headerWidth = CGRectGetWidth(self.collectionView.bounds);
// CGFloat ratio = (isIPAD) ? 0.33 : 0.66;
self.estimatedItemSize = CGSizeMake(cellWidth, 53);
self.headerReferenceSize = CGSizeMake(headerWidth, 50);
self.footerReferenceSize = CGSizeMake(headerWidth, 1 + self.minimumInteritemSpacing);
[super prepareLayout];
}
- (void)initialize {
self.minimumLineSpacing = 0;
self.minimumInteritemSpacing = (isIPAD) ? 5 : 10;
self.estimatedItemSize = CGSizeZero;
self.scrollDirection = UICollectionViewScrollDirectionVertical;
self.sectionInset = UIEdgeInsetsZero;
}
-(BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds {
return YES;
}
#end
I made a simple sample project and record a video: http://sendvid.com/330uo5jm
It looks like the issue is the position from the first cell.
UICollectionViewCell autosizing can be a little, uh... interesting even at the best of times. I've had this exact issue in the past, and similar issues too.
Use a different value for .estimatedItemSize, the closer to the actual item size the better. I noticed you're using a size of CGRectZero at first. I wouldn't recommend doing that. Just set it once, at the start, with a value close to your size. Try a few values, see what works for you. For me, it took a bit of fine tuning.
For anyone developing for iOS 10 (at the time of writing this hasn't been released) there is a new collection view property that lets the collection view determine the estimated size itself. Set the itemSize to UICollectionViewFlowLayoutAutomaticSize, you shouldn't need to set .estimatedItemSize explicitly.
make sure your estimatedItemSize in your code same as size of cell in your xib or storyboard.Don't changes it's size runtime.
can you check with use of identifier like...
NSString *CellIdentifier = [NSString stringWithFormat:#"%d_%d",indexPath.section,indexPath.row];
Sometimes it happens if you have a big difference between estimated size and real size of cell.
Check you have a clear consequence of top to bottom constraints (if you are using autolayout).
Is there something that can break the autolayout to work properly ? E.g compresion resistance settings ?
Are you sure there are data for the cell after reload ? (Will be weird, but to be sure, just double check that.)
Also as Apple denotes here Apple - self sizing guide try to set the estimation of size as close as possible to real dimensions.
You can also try to refer to invalidation of collection layout as you are using your own. Refer to Possible flow-layout help
Try to set the estimation as close as possible and you will see if it solve your problem.
This is a duplicate of this question. I'm asking again because the accepted answer is not working and no one's providing more explanation on how the supposed correct answer works.
So here's the situation: I want to display the collection view into one single row. To do this, I applied a custom UICollectionViewFlowLayout to the collection view and set the scrolling to horizontal. Everything is working fine, except for section header disappeared.
To remedy this, I implemented this function:
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout referenceSizeForHeaderInSection:(NSInteger)section
{
return CGSizeMake(350, 35);
}
Now the header is shown, but the problem is it is displayed to the left of the cells, instead of the usual top.
I stumbled upon the link above while searching for a solution, but like I've said, the accepted answer is not working at all and I could not find other solutions about this situation. So can anyone help me here?
we can do that by using the delegate method -
(UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout insetForSectionAtIndex:(NSInteger)section
and keeping the left inset to minus value of width of supplementary view and managing the top inset
Have you tried using the header with something like this?
First: set it up in viewDidLoad...
UICollectionViewFlowLayout *flowLayout = [[UICollectionViewFlowLayout alloc] init];
flowLayout.headerReferenceSize = CGSizeMake(self.collectionView.bounds.size.width, 30);
// add any other setup you need
[self.collectionView setCollectionViewLayout:flowLayout];
Second: add header view ...
#define LABEL_TAG 128
- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath {
UICollectionReusableView *headerView = [collectionView dequeueReusableSupplementaryViewOfKind:
UICollectionElementKindSectionHeader withReuseIdentifier:#"SectionHeader" forIndexPath:indexPath];
UILabel *label = (UILabel *)[headerView viewWithTag:LABEL_TAG];
if (!label) {
label = [[UILabel alloc] initWithFrame:CGRectInset(headerView.bounds, 5, 5)];
label.tag = MY_HEADER_LABEL_TAG;
label.font = [UIFont boldSystemFontOfSize:12];
label.textColor = [UIColor redColor];
[headerView addSubview:label];
}
label.text = [NSString stringWithFormat:#"Section %d", indexPath.section];
return headerView;
}
I am trying to simply add a UIView to a UICollectionViewCell that takes up the entire cell's frame. The code I have now only displays a single cell in the top left corner (I suspect each cell is getting layer out on top of each other). How exactly can I do this?
I plan on subclassing UIView later so as to customize the view that I am adding to the cell. I don't want to subclass UICollectionViewCell however based on the way I will be using it.
#pragma mark - Collection view data source
-(NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView {
return 1;
}
-(NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
return 6;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"Cell" forIndexPath:indexPath];
UIView *menuItem = [[UIView alloc] init];
menuItem.frame = cell.frame;
menuItem.backgroundColor = [UIColor greenColor];
[cell addSubview:menuItem];
return cell;
}
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath; {
return CGSizeMake(60, 60);
}
You'll need to use bounds, not frame. If you don't know the difference between the two or why this matters, you should read up on this and get comfortable with it before going further:
menuItem.frame = cell.bounds;
You'll also want to set an autoresizing mask, since the cell won't initially be at the correct size:
menuItem.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
And, really, you should be adding this to the cell's contentView property:
[cell.contentView addSubview:menuItem];
menuItem.frame = cell.contentView.bounds;
That said, if you plan on adding lots of subviews to menuItem, I recommend subclassing UICollectionViewCell instead of trying to build it in your cellForItemAtIndexPath: method. It will be much easier to control if the layout and setup is encapsulated in a different class, and you can respond to height / width changes by overriding layoutSubviews.
You should only interact with the cell's contentView in this case.
UIView *menuItem = [UIView new];
menuItem.frame = cell.contentView.bounds;
menuItem.backgroundColor = [UIColor greenColor];
[cell.contentView addSubview:menuItem];
It's Swift version.
let menuItem = UIView.init()
menuItem.frame = cell.bounds
menuItem.backgroundColor = .green
cell.contentView.addSubview(menuItem)
menuItem.frame = cell.contentView.bounds
Swift 5 Apple doc:
var contentView: UIView
The main view to which you add your cell’s custom content.
When configuring a cell, you add any custom views representing your cell’s content to this view. The cell object places the content in
this view in front of any background views.
var backgroundView: UIView?
The view that is displayed behind the cell’s other content.
Use this property to assign a custom background view to the cell. The background view is placed behind the content view and its frame
is automatically adjusted so that it fills the bounds of the cell.
Example:
let template = UIView()
self.backgroundView = template
template.layer.cornerRadius = 10
template.backgroundColor = .white
I have an UICollectionView and Custom UICollectionViewCell, where i'm loading images, when i scroll the UICollectionView, i'm seeing all the cells are refreshing, here is the code for UICollectionView delegates,
In ViewDidLoad adding this first for adding CustomCell
-(void)ViewdidLoad{
UINib *nib = [UINib nibWithNibName:#"NMCFAIPadWishListCell" bundle:nil];
[self.accountDetailsCollectionView registerNib:nib forCellWithReuseIdentifier:#"Cell"];
}
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
return [[self wishListData] count];
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
static NSString *identifier = #"Cell";
NMCFAIPadWishListCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:identifier forIndexPath:indexPath];
[cell setWishList:[[self wishListData] objectAtIndex:[indexPath row]] delegate:self];
return cell;
}
In setWishList method just assigning the values from the array to label and i have a button in Xib for each cell in my custom UICollectionViewCell, when user taps on that button i'm just changing the label BG color
- (void)setWishList:(NSString*)product delegate:(id)delegate
{
self.label.text = product;
}
Below is the button action
- (IBAction)editProduct:(id)sender
{
self.label.backgroundColor = [UIColor redColor];
}
Here my problem is when i scroll the Custom Cell and tap on Button in any Cell the label BG is not only changing in current cell but also in MANY CELLS.
You should not attempt to store any state in/on your cells as the cell objects themselves are reused at the discretion of the UICollectionView.
One solution to your problem could be:
In your editProduct: method (assuming your editProduct: method is in your custom UICollectionViewCell implementation), inform the collection view’s controller that the user has “selected” that product via a protocol method (or block or some other messaging mechanism).
In your view controller, when receiving the above message, identify the index of the cell for which the button has been tapped (indexPathForCell: might be useful here) and store the fact that the item at index n has been selected. An NSArray might be useful here.
In the same method, force a reload of the cell that has been tapped with reloadItemsAtIndexPaths: or a similar method. This will force the collectionView:cellForRowAtIndexPath: method to be called.
Implement something like the following in your collectionView:cellForRowAtIndexPath: method:
BOOL itemSelected = ((NSNumber *)isProductSelectedArray[indexPath.row]).boolValue; // You can't store `BOOL`s directly into NSArrays. So I've assumed an NSNumber here.
cell.backgroundColor = itemSelected ? [UIColor redColor] : [UIColor clearColor] // Or some other color to indicate non-selection.
As an aside, if you declare “ViewdidLoad” instead of “viewDidLoad”, you might find your code doesn’t behave the way you intend. Don’t forget to call [super viewDidLoad] somewhere in your implementation too.
Finally, I recommend getting a better handle on the concept of cell reuse by reading the “Collection View Basics” chapter of Apple’s “Collection View Programming Guide for iOS” - specifically the section titled “Reusable Views Improve Performance”.
Cells do not maintain a state. An array of objects that correspond to the cells should main the state since cells are recycled very often. For instance, inside you cellForItemAtIndexPath:
....
BOOL isWishListSet = self.isWishListSetArray[indexPath.row];
UIColor *cellColor = [UIColor redColor];
if (isWishListSet) {
cellColor = [UIColor blackColor];
}
cell.backgroundColor = cellColor;
....
EDIT 1:
As gavdotnet mentions in his answer, cell states should be held in a parallel array, not in the cell itself. So you would have one array that holds the data you want to show and another that holds the state of whether the cell has been selected to be on the wishlist:
#interface WishListViewController ()
#property (nonatomic, strong) NSArray *wishListData;
#property (nonatomic, strong) NSMutableArray *wishListStatus;
#end
#implementation WishListViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Initialize arrays
self.wishListData = [NSArray array];
self.wishListStatus = [NSMutableArray array];
}
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView {
return 1;
}
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
return self.wishListData.count;
}
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"Cell" forIndexPath:indexPath];
NSNumber *isWishListSet = self.wishListStatus[indexPath.row];
UIColor *cellColor = [UIColor redColor];
if (isWishListSet.boolValue) {
cellColor = [UIColor blackColor];
}
cell.backgroundColor = cellColor;
return cell;
}
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
UICollectionViewCell *cell = [collectionView cellForItemAtIndexPath:indexPath];
NSNumber *isWishListSet = self.wishListStatus[indexPath.row];
if (isWishListSet.boolValue) {
isWishListSet = [NSNumber numberWithBool:NO];
} else {
isWishListSet = [NSNumber numberWithBool:YES];
}
[self.wishListStatus replaceObjectAtIndex:indexPath.row withObject:isWishListSet];
UIColor *cellColor = [UIColor redColor];
if (isWishListSet.boolValue) {
cellColor = [UIColor blackColor];
}
cell.backgroundColor = cellColor;
}
The section
UIColor *cellColor = [UIColor redColor];
if (isWishListSet.boolValue) {
cellColor = [UIColor blackColor];
}
cell.backgroundColor = cellColor;
is repeated, so it should be in its own method, but that is up to you decide really. The example shows your data array, which populates the cells, and your wishListStatus array which holds the status of the cell. If we were not going to dequeue cells, this would not be an issue. But since we are in this case, the status must be maintained outside of the cell.
The line you are using:
[cell setWishList:[[self wishListData] objectAtIndex:[indexPath row]] delegate:self];
should be changed to something like:
[cell setDelegate:self];
since the delegate is never toggled and is always set to 'self'.
Cells are being reused because of that they are refreshing.
CollectionView reuses cells so the multiple change of background color is the correct behavior.
To fix your problem, customize your UICollectionViewCell(NMCFAIPadWishListCell) instance as follows:
UIView *backgroundView = [[UIView alloc] initWithFrame:self.bounds];
backgroundView.backgroundColor = [UIColor clearColor];
self.backgroundView = backgroundView;
UIView *selectedBGView = [[UIView alloc] initWithFrame:self.bounds];
selectedBGView.backgroundColor = [UIColor redColor];
self.selectedBackgroundView = selectedBGView;
Use the delegate method for extra selection behavior:
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
{
// Instead of button actions, use this delegate method
}
Check out UICollectionViewCell Reference for more details. UICollectionViewCell has three properties backgroundView, selectedBackgroundView and selected which are sufficient for your needs.