I created a table view that has collection view inside its cells with this tutorial Creating a Scrolling Filmstrip Within a UITableView but I want to achieve a banner kind of feature that has centred paging and scrolls automatically to each cell item. I enabled page scrolling but didn't work. How do I do this? I currently have a UIView in the table view cell that serves as the data source for the collection view. Here is the code for the UIView subclass.
#interface ContainerCellView () <UICollectionViewDataSource, UICollectionViewDelegate>
#property (weak, nonatomic) IBOutlet UICollectionView *collectionView;
#property (strong, nonatomic) NSArray *collectionData;
#end
#implementation ContainerCellView
#pragma mark - Getter/Setter ovverides
- (void)setCollectionData:(NSArray *)collectionData {
_collectionData = collectionData;
[_collectionView setContentOffset:CGPointZero animated:YES];
[_collectionView reloadData];
}
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView {
return 1;
}
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
return [self.collectionData count];
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"ArticleCollectionViewCell";
BannersCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:CellIdentifier forIndexPath:indexPath];
NSDictionary *cellData = [self.collectionData objectAtIndex:indexPath.row];
cell.articleTitle.text = [cellData objectForKey:#"title"];
return cell;
}
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
NSDictionary *cellData = [self.collectionData objectAtIndex:indexPath.row];
[[NSNotificationCenter defaultCenter] postNotificationName:#"didSelectItemFromCollectionView" object:cellData];
}
#end
You are possibly looking for property for collectionView called isPagingEnabled which comes from UIScrollView, otherwise, you should create your cell big enough to achieve this pretty nice effect. However, you can relate here, if you are looking for paging by cells click here Hope this may help you
Related
I have attached my sample project with github and provided the link belowI have a table view and in that tableview the cell consists of a UICollectionView. This means that every cell can scroll horizontally upto a certain limit,lets say 5.Both the tableView cell and collectionview cell have custom cell classesThe tableview cell has also a UIPageControl which will change when we will scroll the UICollectionViewCells horizontally
I managed to do this part of work but suppose I have scrolled the 2nd tableview cell's collectionview to 3rd Position and this repeats somewhat around on 9th tableview cell. The 9th tableview cell also has the same data. But I havent set anything yet on 9th. So this means the problem is of reusability and data in getting incorrect while scrolling.
My Code so far is
//ViewController.h
#import <UIKit/UIKit.h>
#interface ViewController : UIViewController<UITableViewDataSource,UITableViewDelegate,UICollectionViewDataSource,UICollectionViewDelegate,UICollectionViewDelegateFlowLayout>
#property (weak, nonatomic) IBOutlet UITableView *tableView;
#end
Implementation Class
//ViewController.m
#import "ViewController.h"
#import "MyCollectionViewCell.h"
#import "MyTableViewCell.h"
#interface ViewController ()
{
MyTableViewCell* tableViewCell;
}
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
}
#pragma mark TableView Delegates
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection: (NSInteger)section{
return 10;
}
-(UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
tableViewCell=(MyTableViewCell*)[tableView dequeueReusableCellWithIdentifier:#"MyTableViewCell"];
if(tableViewCell==nil)
{
tableViewCell=[[MyTableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"MyTableViewCell"];
}
UICollectionViewFlowLayout *flowLayout =
[[UICollectionViewFlowLayout alloc] init];
[flowLayout setScrollDirection:UICollectionViewScrollDirectionHorizontal];
[flowLayout setMinimumInteritemSpacing:0.0f];
[flowLayout setMinimumLineSpacing:0.0f];
[tableViewCell.collectionView setPagingEnabled:YES];
[tableViewCell.collectionView setCollectionViewLayout:flowLayout];
[tableViewCell.collectionView setBackgroundColor:[UIColor whiteColor]];
tableViewCell.pageControl.tag=indexPath.row;
tableViewCell.collectionView.tag=indexPath.row;
NSLog(#"Index path row %ld",indexPath.row);
[tableViewCell setSelectionStyle:UITableViewCellSelectionStyleNone];
tableViewCell.path=indexPath;
return tableViewCell;
}
#pragma mark CollectionView Delegates
-(NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section{
return 5;
}
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
MyCollectionViewCell* cell=(MyCollectionViewCell*)[collectionView dequeueReusableCellWithReuseIdentifier:#"MyCollectionViewCell" forIndexPath:indexPath];
if(cell==nil)
{
cell=[[MyCollectionViewCell alloc]initWithFrame:CGRectMake(0, 0, collectionView.frame.size.width, collectionView.frame.size.height)];
}
[cell.myLabel setText:[NSString stringWithFormat:#"%ld",(long)indexPath.row]];
return cell;
}
-(CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
return collectionView.frame.size;
}
-(UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout: (UICollectionViewLayout *)collectionViewLayout insetForSectionAtIndex:(NSInteger)section{
return UIEdgeInsetsMake(0, 0, 0, 0);
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView{
if ([scrollView isKindOfClass:[UICollectionView class]])
{
UICollectionView *mainCollection = (UICollectionView *) scrollView;
NSIndexPath *myIP = [NSIndexPath indexPathForRow:mainCollection.tag inSection:0];
MyTableViewCell *cell = (MyTableViewCell *)[self.tableView cellForRowAtIndexPath:myIP];
CGRect visibleRect = (CGRect){.origin = mainCollection.contentOffset, .size = mainCollection.bounds.size};
CGPoint visiblePoint = CGPointMake(CGRectGetMidX(visibleRect), CGRectGetMidY(visibleRect));
NSIndexPath *visibleIndexPath = [mainCollection indexPathForItemAtPoint:visiblePoint];
NSLog(#"visibleIndexPath %ld",(long)visibleIndexPath.row);
[cell.pageControl setCurrentPage:visibleIndexPath.row];
}
}
#end
Cells classes
//MyTableViewCell.h
#import <UIKit/UIKit.h>
#interface MyTableViewCell : UITableViewCell
#property (weak, nonatomic) IBOutlet UICollectionView *collectionView;
#property (weak, nonatomic) IBOutlet UIPageControl *pageControl;
#property (strong, nonatomic) NSIndexPath * path;
#end
MyCollectionViewCell.h
//MyCollectionViewCell.h
#import <UIKit/UIKit.h>
#interface MyCollectionViewCell : UICollectionViewCell
#property (weak, nonatomic) IBOutlet UILabel *myLabel;
#end
View is Something like
My concern is that whenever I make any change lets say on cell 1 on table viewcell and scroll the collection in it to lets say 2nd index, then this is re repeated whenever i scroll the complete table
NOTE: I have kept number of cells of table to 10 to see the repeated impact.
The Github link of my sample project is https://github.com/RajanMaheshwari/TableAndCollection
First of all:
[collectionView dequeueReusableCellWithReuseIdentifier:#"MyCollectionViewCell" forIndexPath:indexPath]
always returns a view, so you don't need that:
if(cell==nil)
{
cell=[[MyCollectionViewCell alloc]initWithFrame:CGRectMake(0, 0, collectionView.frame.size.width, collectionView.frame.size.height)];
}
since it's not gonna happen.
And the real reason for you is that you have only one cell for all UITableView...
Where the idea of keeping tableCell as property comes from ?
MyTableViewCell* tableViewCell;
You need to create a cell instance for each indexPath separately, right now you have one instance which is displayed several times, that's why changing collection view page on one of them is affecting other rows - since it's the same object (view).
After you remove that problem, you need to remember that elements will be reused - which means your first row instance, you be reused again on 5th row, after it dissapears from the screen, so after you dequeue the elements, you need to set all variables to this row.
You cannot keep your variables inside this row, because it will be reused, so even though you have 100 rows, you will around 5-6 instances (depends on row height) that will be reused across whole table.
You need to keep important variables, necessary to render every row, outside of the views and configure views based on those.
In your case you should keep page index of pageControl for every row - use NSDictionary for that and keep a NSNumber for every indexPath in your UITableView.
Whenever page is changing, update the value in this NSDictionary (defaults to zero).
When you create a cell with collection view, set proper value to pageControl based on what you have in NSDictionary for this indexPath
I'm just beginning IOS development and am wondering if anybody could help me figure out how to get the cell details that are being clicked.
I have a cell like this:
#import <UIKit/UIKit.h>
#interface ICICell : UICollectionViewCell
#property (weak, nonatomic) IBOutlet UILabel *myLabel;
#property (weak, nonatomic)IBOutlet ICICell *myCell;
#property (weak, nonatomic) IBOutlet UIImageView *myGallery;
#end
in the view controller I am populating the cells like this:
-(UICollectionViewCell * ) collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
ICICell * aCell = [collectionView dequeueReusableCellWithReuseIdentifier:#"myCell" forIndexPath:indexPath];
aCell.myLabel.text = self.dataArray[indexPath.row];
UIImage *img;
long row = [indexPath row];
img = [UIImage imageNamed:self.iciImages[row]];
aCell.myGallery.image = img;
return aCell;
}
I have found the didDeselectItemAtIndexPath method, but is there a method didSelectItemAtIndexPath?
I am trying to get the label text for a selected cell. Bit confused why there is a didDeselect and no didSelect? Thanks in advance.
Linda Keating,
There are both methods available as you are expecting..have a reference from below
-(void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
{
/* Here you can do any code for Selected item at indexpath.*/
}
-(void)collectionView:(UICollectionView *)collectionView didDeselectItemAtIndexPath:(NSIndexPath *)indexPath
{
}
Thanks.
Of course You can navigate into Detail Screen of Collection View cell.
Just use didSelectItemAtIndexPath method.
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
{
VIPRoomDetailController *enterIntoRoomDetail = [[VIPRoomDetailController alloc]initWithNibName:#"VIPRoomDetailController" bundle:nil];
[self.navigationController pushViewController:enterIntoRoomDetail animated:YES];
}
Also Import Detail controller in your collection View class In my case it is VIPRoomDetailController
I implemented a UICollectionView inside a UIViewController. The header cell is created in a separate.xib file and it is implemented in a separate .h and .m files with the IBOutlets and IBActions. The rest of the cells are implemented in the same UIVIewController (The reason is because I added this Parallax effect).
I would like to modify the info of the IBoutlets (labels and buttons) that are in the header cell (CSCellUser.h) from the viewcontroller (RankingViewController) which containts the collectionview, how can I do this?
Cell: CSCellUser.h
#interface CSCellUser : UICollectionViewCell
#property IBOutlet UILabel *myScoreValueLabel;
#property IBOutlet UILabel *myRankingValueLabel;
-(IBAction) sendButtonTouchHandler:(id) sender;
#end
UIViewController: RankingViewController.h
#interface RankingViewController : CommonViewController <UICollectionViewDelegate, UICollectionViewDataSource> {
}
#property IBOutlet UICollectionView *collectionView1;
#end
UIViewController:RankingViewController.m
- (void)viewDidLoad
{
[super viewDidLoad];
//Parallax Effect, UICollectionView
// Locate the layout
CSStickyHeaderFlowLayout *layout = (id)self.collectionView1.collectionViewLayout;
if ([layout isKindOfClass:[CSStickyHeaderFlowLayout class]]) {
layout.parallaxHeaderReferenceSize = CGSizeMake(320, 220);
layout.parallaxHeaderAlwaysOnTop = YES;
}
// Locate the nib and register it to your collection view
UINib *headerNib = [UINib nibWithNibName:#"CSHeaderRanking" bundle:nil];
[self.collectionView1 registerNib:headerNib
forSupplementaryViewOfKind:CSStickyHeaderParallaxHeader
withReuseIdentifier:#"TopViewCell"];
//get the position of the user and the ranking (this should update the IBOutlets in the CSCellUser.h)
[self getUserRanking];
//get the ranking of users (this updates each cell of the ranking in cellForItemAtIndexPath)
[self getRanking];
}
- (NSInteger) numberOfSectionsInCollectionView:
(UICollectionView *)collectionView
{
return 1;
}
- (NSInteger) collectionView:(UICollectionView *)collectionView
numberOfItemsInSection:(NSInteger)section
{
return [ranking count];
}
- (UICollectionViewCell *) collectionView:(UICollectionView *)collectionView
cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier: #"UsersCell" forIndexPath:indexPath];
if ([ranking count] > 0)
{
UserRanking *user = [ranking objectAtIndex:indexPath.row]; //Fill the cell
UILabel *usernameLabel = (UILabel *)[cell viewWithTag:101];
usernameLabel.text = user.username;
UILabel *scoreLabel = (UILabel *)[cell viewWithTag:102];
scoreLabel.text = [NSString stringWithFormat:#"%d", user.score];
UILabel *gamesLabel = (UILabel *)[cell viewWithTag:103];
gamesLabel.text =[NSString stringWithFormat:#"%d", user.tries];
}
return cell;
}
//Header
- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath
{
UICollectionReusableView *reusableview = nil;
if ([kind isEqualToString:CSStickyHeaderParallaxHeader]) {
reusableview = [collectionView dequeueReusableSupplementaryViewOfKind:kind withReuseIdentifier:#"TopViewCell" forIndexPath:indexPath];
}
return reusableview;
}
EDIT:
This is how I am trying to change one of the labels from cellForRowAtIndexPath but it does not change.
CSHeaderRanking *topcell = [collectionView dequeueReusableCellWithReuseIdentifier: #"TopCell" forIndexPath:indexPath];
topcell.myScoreValueLabel.text = #"32";
If TopCell is mapped in its xib, all you have to do is refer to its properties. If you want to customize, you'll need to over-ride the CCSticky... and refer to it in your xib and this class.
I am configuring a UICollectionViewCell in a subclass, it adds 2 subviews to the contentView property, both are UIImageView and both have the hidden property set to YES. These subviews are "checked" and "unchecked" images that overlay the primary UIImageView in the cell to indicate whether or not the current cell is selected using UICollectionView's "multiple select" feature.
When the cell is tapped, collectionView:didSelectItemAtIndexPath: is called on the delegate, and I'd like to setHidden:NO on the "checked" UIImageView. Calling this on the cell does nothing at all -- the cell is seemingly locked in its originally drawn state.
Is it possible to make changes to a cell outside collectionView:cellForItemAtIndexPath:? I have tried manually adding subviews within collectionView:didSelectItemAtIndexPath:, but it just makes absolutely no change to the UI. I have verified that the delegate method is getting called, it's just not making my cell changes.
- (void) collectionView(UICollectionView *)cv didSelectItemAtIndexPath(NSIndexPath *)indexPath {
ShotCell *cell = [self collectionView:cv cellForItemAtIndexPath:indexPath];
UILabel *testLabel = UILabel.alloc.init;
testLabel.text = #"FooBar";
testLabel.sizeToFit;
[cell.contentView.addSubview testLabel];
}
The way you're trying to do this is incorrect. You need to keep a reference to the selected cell or cells in a property. In this example, I use an array to hold index paths of the selected cells, then check whether the index path passed in to cellForItemAtIndexPath is contained in that array. I unselect the cell if you click on one that's already selected:
#interface ViewController ()
#property (strong,nonatomic) NSArray *theData;
#property (strong,nonatomic) NSMutableArray *paths;
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.paths = [NSMutableArray new];
self.theData = #[#"One",#"Two",#"Three",#"Four",#"Five",#"Six",#"Seven",#"Eight"];
[self.collectionView registerNib:[UINib nibWithNibName:#"CVCell" bundle:nil] forCellWithReuseIdentifier:#"cvCell"];
UICollectionViewFlowLayout *flowLayout = [[UICollectionViewFlowLayout alloc] init];
[flowLayout setScrollDirection:UICollectionViewScrollDirectionVertical];
[self.collectionView setCollectionViewLayout:flowLayout];
}
-(NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
return self.theData.count;
}
-(CVCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
static NSString *cellIdentifier = #"cvCell";
CVCell *cell = (CVCell *) [collectionView dequeueReusableCellWithReuseIdentifier:cellIdentifier forIndexPath:indexPath];
cell.backgroundColor = [UIColor whiteColor];
cell.label.text = self.theData[indexPath.row];
if ([self.paths containsObject:indexPath]) {
[cell.iv setHidden:NO]; // iv is an IBOutlet to an image view in the custom cell
}else{
[cell.iv setHidden:YES];
}
return cell;
}
-(void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
if ([self.paths containsObject:indexPath]) {
[self.paths removeObject:indexPath];
}else{
[self.paths addObject:indexPath];
}
[self.collectionView reloadItemsAtIndexPaths:#[indexPath]];
}
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
return CGSizeMake(150, 150);
}
And I have to ask this as well. Been looking through the code for hours now and tried everything, but why does my old picture not get deselected? The user should be able to select one icon only. The selected icon is saved in Core Database. So this icon is also preselected when opening this view. However this item doesn't get deselected when he selects a new icon..why?
#import "IconSelectionCollectionViewController.h"
#import "IconSelectionCell.h"
#interface IconSelectionCollectionViewController ()
#property (nonatomic, strong) NSArray *icons;
#end
#implementation IconSelectionCollectionViewController
#synthesize mainCategory = _mainCategory;
#pragma mark Initialize model
- (void)setMainCategory:(MainCategory *)mainCategory
{
//TODO
_mainCategory = mainCategory;
self.title = mainCategory.name;
}
#pragma mark View setup
- (void)viewDidLoad
{
[super viewDidLoad];
[self.collectionView registerClass:IconSelectionCell.class forCellWithReuseIdentifier:#"IconCell"];
// Do any additional setup after loading the view.
self.icons = [NSArray arrayWithObjects:#"DefaultIcon", #"Car", #"Diploma", #"Earth", #"Flight", #"Home", #"Pen", #"Scooter", #"Ship", #"Train", nil];
self.collectionView.allowsSelection = YES;
self.collectionView.allowsMultipleSelection = NO;
}
#pragma mark Data source delegate methods
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
return self.icons.count;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *identifier = #"IconCell";
IconSelectionCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:identifier forIndexPath:indexPath];
cell.iconImageView.image = [UIImage imageNamed:[self.icons objectAtIndex:indexPath.row]];
if([self.mainCategory.icon isEqualToString:[self.icons objectAtIndex:indexPath.row]]){
cell.selected = YES;
}
return cell;
}
-(NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView
{
return 1;
}
#pragma mark Collection View Delegate methods
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
{
self.mainCategory.icon = [self.icons objectAtIndex:indexPath.row];
}
#pragma mark – UICollectionViewDelegateFlowLayout
- (CGSize)collectionView:(UICollectionView *)collectionView layout:
(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
CGSize itemSize;
itemSize.height = 62;
itemSize.width = 62;
return itemSize;
}
- (UIEdgeInsets)collectionView:
(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout insetForSectionAtIndex:(NSInteger)section {
return UIEdgeInsetsMake(10, 10, 10, 10);
}
#end
I am not incredibly familiar with collection views, but they are very similar to table views and this is a problem that sometimes occurs with UITableviews, so I will work through a couple possible solutions.
One way to do this would be to call [self.collectionView reloadData] after the selection code. This should cause the collectionView to redraw the images, setting only one of them to be the selected image.
However, I'm not confident this would solve the problem, since dequeue reusable cells could cause the "selection" value to be set for the reused cell. So alternatively, I'd imagine you could grab both cells using UICollectionView's method cellForItemAtIndexPath: and then set their selected value in the way you set them in the main method. That may just work, but if it does not, try doing that and calling reload data again.