Proper way to reuse UITableViewCell containing UICollectionView - ios

I have a UITableView. Each cell contains horizontal collection view. When I scroll the table, collection view contains too much cells. I guess it is because the cells are not properly reused. In the illustration, the grey cells were not suppose to be there.
What is the proper code that I should put in the reuseCell? I tried the following but it made the app crash
over
ride func prepareForReuse()
{
super.prepareForReuse()
channelsCollectionView = UICollectionView()
}

It should be
//Reset the datasource
channelsCollectionView.dataSourceArray = []()
//Reload data of collectionView
channelsCollectionView.reloadData()
Again its depends, this is one way of doing

Please follow the below steps. That will work perfectly fine CollectionView inside UITableViewCell.
Make subclass of UICollectionView like this.
#interface MyCollectionView : UICollectionView <UICollectionViewDataSource, UICollectionViewDelegate>
/* Properties */
#property (nonatomic, strong) NSMutableArray *imagesArray;
#end
In your MyCollectionView.m file
#interface MyCollectionView ()
#property (nonatomic, strong) UICollectionViewFlowLayout *flowLayout;
#end
#implementation MyCollectionView
-(void)awakeFromNib {
[super awakeFromNib];
self.dataSource = self;
self.delegate = self;
//Set CollectionView Layout
[self setCollectionViewLayout:self.flowLayout];
}
#pragma mark - CollectionView DataSource methods
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView{
return 1;
}
-(NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section{
return _imagesArray.count;
}
-(UICollectionViewFlowLayout *)flowLayout{
// set your layout here
_flowLayout = [[UICollectionViewFlowLayout alloc] init];
[_flowLayout setSectionInset:UIEdgeInsetsMake(topSpacing, leftMargin, bottomSpacing, rightMargin)];
[_flowLayout setMinimumInteritemSpacing:cellSpacing];
[_flowLayout setMinimumLineSpacing:lineSpacing];
[_flowLayout setScrollDirection:UICollectionViewScrollDirectionHorizontal];
[_flowLayout setItemSize:CGSizeMake(cellWidth, cellHeight)];
return _flowLayout;
}
#pragma mark - setter method
-(void)setImagesArray:(NSMutableArray *)imagesArray {
_imagesArray = imagesArray;
[self reloadData];
}
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
// configure cell here
}
In your ViewController.m having UITableView
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
MyTableViewCell *cell = (MyTableViewCell *)[tableView dequeueReusableCellWithIdentifier:#"CellIdentifier" forIndexPath:indexPath];
cell.myCollectionView.imagesArray = //Array here
return cell;
}
You need to set fix height of your collectionView in TableViewCell. As you've horizontal collectionView.

Related

How to implement delegate and datasource methods for UICollectionView when it is inside a custom TableViewCell in iOS, objective C

Note : I don't want to use any third party libraries
I have tableview with with a custom table view cell (table view working fine).
Now inside the table view cell I want to implement a collection view.
Now my problem is , where should I implement the delegate methods and data source methods for the collection view, which is inside the custom table view cell . below is what I have tried.
Tabel View Controller implementation
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return 5;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
FirstTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"tvcell"];
return cell;
}
working properly
now inside this FirstTableViewCell there is a collection view.
This is my FirstTableViweCell.h File
#import <UIKit/UIKit.h>
#interface FirstTableViewCell : UITableViewCell<UICollectionViewDelegate,UICollectionViewDataSource>
#property (weak, nonatomic, nullable) IBOutlet UICollectionView *insideCollectionView;
#property (strong, nonnull,nonatomic) NSArray *mycollectionData;
- (void)setColletionData :(nonnull NSArray *)collectionData;
#end
and this is my FirstTableViewCell.m File
#import "FirstTableViewCell.h"
#implementation FirstTableViewCell
- (void)awakeFromNib {
[super awakeFromNib];
self.insideCollectionView.delegate = self;
// Initialization code
}
- (void)setSelected:(BOOL)selected animated:(BOOL)animated {
[super setSelected:selected animated:animated];
// Configure the view for the selected state
}
- (void)setColletionData:(NSArray *)collectionData
{
self.mycollectionData = collectionData;
[self.insideCollectionView reloadData];
}
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView
{
return 1;
}
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
return 10;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
UICollectionViewCell *ccell = [collectionView dequeueReusableCellWithReuseIdentifier:#"cvcell" forIndexPath:indexPath];
return ccell;
}
#end
following method
- (void)setColletionData:(NSArray *)collectionData
{
self.mycollectionData = collectionData;
[self.insideCollectionView reloadData];
}
I used to set the array for collectionView, in cellforrowatindexpath in tableview
So what is the correct way to implement DataSource and Delegate
methods for the CollectionView which is inside the Custom Table
View Cell.
Horraaaaaaaay ..... =D This is the completed answer for this.
Table View Implementation .m file
#import "ViewController.h"
#import "FirstTableViewCell.h"
#interface ViewController ()
#end
#implementation ViewController
{
NSArray *bbarray;
}
- (void)viewDidLoad {
[super viewDidLoad];
bbarray = [NSArray arrayWithObjects:#"33",#"44", nil];
// Do any additional setup after loading the view, typically from a nib.
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return 5;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
FirstTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"tvcell"];
[cell setColletionData:bbarray];
return cell;
}
#end
Implementation inside the Custom Table View cell
custom table view .h file
#import <UIKit/UIKit.h>
#interface FirstTableViewCell : UITableViewCell<UICollectionViewDelegate,UICollectionViewDataSource>
#property (weak, nonatomic, nullable) IBOutlet UICollectionView *insideCollectionView;
#property (strong, nonnull,nonatomic) NSArray *mycollectionData;
- (void)setColletionData :(nonnull NSArray *)collectionData;
#end
custom table view .m file
#import "FirstTableViewCell.h"
#implementation FirstTableViewCell
- (void)awakeFromNib {
[super awakeFromNib];
// These two are very important.
self.insideCollectionView.delegate = self;
self.insideCollectionView.dataSource = self;
// Initialization code
}
- (void)setSelected:(BOOL)selected animated:(BOOL)animated {
[super setSelected:selected animated:animated];
// Configure the view for the selected state
}
//this method is used to set the data , call it in cell for row at index path in table view implementation.
- (void)setColletionData:(NSArray *)collectionData
{
self.mycollectionData = collectionData;
[self.insideCollectionView reloadData];
}
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView
{
return 1;
}
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
NSLog(#"%lu", (unsigned long)self.mycollectionData.count);
return [self.mycollectionData count];
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
UICollectionViewCell *ccell = [collectionView dequeueReusableCellWithReuseIdentifier:#"cvcell" forIndexPath:indexPath];
return ccell;
}
#end
What you have is essentially correct. The only real change you need is to update the collection view data source and delegate methods in the custom cell class so they properly reference self.mycollectionData.
For example. Change numberOfItemsInSection to:
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
return self.mycollectionData.count;
}

UIView vs UITableCellView to show user posts

My app has a home screen where I show user posts loaded from the server. My problem is that I use UIView for each post, but it takes up a lot of space (also the user keeps on scrolling making it more memory consuming). Ex:
UIView* box=[[UIView alloc]initWithFrame:CGRectMake(0, postY, maxWidth, 500)];
[box setTag:(NSInteger)[post_id[i] integerValue]];
[box setBackgroundColor:[UIColor whiteColor]];
//Profile pic+++
UIImageView* profile_img=[[UIImageView alloc] initWithFrame:CGRectMake(10, 10, 30, 30)];
profile_img.layer.cornerRadius=profile_img.frame.size.width/2; //Make it round
profile_img.layer.masksToBounds=YES; //Make it round
profile_img.layer.borderWidth=0.5;
profile_img.layer.borderColor=[rgb(214, 222, 231) CGColor];
[box addSubview:profile_img];
And so on...
Does anyone know a better way of doing this? I tough of UITableCellView but it seems to be odd doing this task this way
I recommend you use a UITableViewController and then subclass UITableViewCell to create a custom cell for you to display the data. A UITableViewController instance contains a UITableView and you display the data by setting it in your custom UITableViewCell subclass. So basically, a UITableView contains various UITableViewCell and use the delegate methods to respond to events and actions on your table.
// YourCustomTableViewCell.h
#import <UIKit/UIKit.h>
#interface YourCustomTableViewCell : UITableViewCell
// Setup the properties for the cell, e.g
#property (strong, nonatomic) IBOutlet UIImageView *profileImageView; // Connect to outlet in storyboard file
#end
// YourCustomTableViewCell.m
#import "YourCustomTableViewCell.h"
#implementation YourCustomTableViewCell
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
// Initialization code
self.profileImageView = [[UIImageView alloc] initWithFrame:CGRectMake(10, 10, 30, 30)];
}
return self;
}
- (void)awakeFromNib
{
// Use this method to setup frames and sizes of your properties
self.profileImageView.layer.cornerRadius = profileImageView.frame.size.width/2; //Make it round
self.profileImageView.layer.masksToBounds = YES; //Make it round
self.profileImageView.layer.borderWidth=0.5;
self.profileImageView.layer.borderColor = [rgb(214, 222, 231) CGColor];
}
#end
// YourTableViewController.h
#import <UIKit/UIKit.h>
#interface YourTableViewController : UITableViewController
#end
// YourTableViewController.m
#import "YourTableViewController.h"
#import "YourCustomTableViewCell.h"
#interface YourTableViewController ()
#end
#implementation YourTableViewController
- (void)viewDidLoad
{
// Setup your data source for the table
self.tableView.dataSource = self;
// Setup other stuff after loading the view
}
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1; // Return the number of sections you want in the table view
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Return the number of rows for each section, in your case this would be something like this
return self.posts.count; // If your data is stored in an array
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *identifier = #"YourCustomCell";
YourCustomTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier forIndexPath:indexPath];
if (!cell){
cell = [[YourCustomTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier];
}
// Configure the cell...
// Here you add the code to display the data in the cell e.g.
[cell.profileImageView setImage:yourImageToDisplay];
return cell;
}
#end
As the comment said, you'd better choose UITableView as the posts' container and inherit from UITableViewCell as your box view.
Detailedly speaking much more, suppose the class inherited from UITableViewCell named MyTableViewCell, and you treat its contentView as your UIView *box. After that, you could tell the UITableView to use your MyTableViewCell as the Cell by sending him registerClass:forCellReuseIdentifier:.
Then, you can get MyTableViewCell instance by sending UITableView the
dequeueReusableCellWithIdentifier:forIndexPath: message. After being bound data (telling him display what posts), this instance can be returned to UITableView, which will arrange everything for you. Besides, these usually should be done in the UITableView's dataSource method - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath.

Unwanted Behaviour of UICollectionView

I am trying to display some text in UICollection view. But i am getting the error that
Property cell label not fount on object of type
UICOllectionViewCell
.h file
#import <UIKit/UIKit.h>
#interface ViewController : UIViewController<UICollectionViewDataSource,UICollectionViewDelegate>
#end
.m file
#import "ViewController.h"
#interface ViewController ()
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
-(NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView
{
return 1;
}
-(NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
return 10;
}
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
NSString *cellIdentifier =#"cell";
UICollectionViewCell* cell = [collectionView dequeueReusableCellWithReuseIdentifier:cellIdentifier forIndexPath:indexPath];
cell.cellLabel.text = #"Sample Text";
return cell;
}
#end
I have already set the delgate methods of UICollectionView.
Here is the Screenshot of Error
First note that even if you have a custom cell you are dequeing your collection view cell to a custom one :
UICollectionViewCell* cell = [collectionView dequeueReusableCellWithReuseIdentifier:cellIdentifier forIndexPath:indexPath];
In compile time the compiler doesn't know you are trying to use your custom cell, therefore that property doesn't exist.
Be sure that you:
Created a custom UICollectionViewCell that has your cellLabel property
That you registered your cell via code OR you used your cell identifier in the Interface Builder
That you imported your cell and you are using it in cellForItemAtIndexPath
Do you have your cellLabel outlet set in interface builder?
If NO then make one.
You should have something like this in your cell's .h file:
#property (weak, nonatomic) IBOutlet UILabel *cellLabel;
then later you really just need to do this:
cell.cellLabel.text = #"";
You can also make a label like this,
UILabel *cellLabel = [[UILabel alloc]initWithFrame:CGRectMake(0, 10, cell.bounds.size.width, 40)]; //custom size
[cell.contentView addSubview:cellLabel];
and use it like
cell.cellLabel.text = #"Sample Text";
There are tutorial on UICollectionView you can have a look on this link.

How can I return a TableView from a instance method?

I am trying to add a TableView as a subview in my RootViewController.The TableView will come from another ViewController(TableViewGeneratorController) instance method.
So,What is the best way to do this?
I have created a TableViewGeneratorController it works fine as a standalone app. Then from my RootViewController I have created one instance of the TableViewGeneratorController and trying to call the instance method prepareField,which will return the TableView. I got the TableView but
numberOfRowsInSection and cellForRowAtIndexPath is not getting called.
TableViewGeneratorController.h
#import <UIKit/UIKit.h>
#interface TableViewGeneratorController:UIViewController<UITableViewDataSource,UITableViewDelegate>
{
UITableView *tableView;
}
#property(strong,nonatomic)UITableView *generatedTbleView;
- (UITableView *)prepareField;
#end
TableViewGeneratorController.m
#import "TableViewGeneratorController.h"
#import "RootViewController.h"
#interface TableViewGeneratorController (){
}
#end
#implementation TableViewGeneratorController
- (void)viewDidLoad {
[super viewDidLoad];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (UITableView *)prepareField
{
tableView = [[UITableView alloc]initWithFrame:CGRectMake(10, 80, 300, 500)];
tableView.dataSource = self;
tableView.delegate = self;
tableView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
[tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:#"Cell"];
[tableView reloadData];
return tableView;
}
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return 10;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
static NSString *cellIndentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIndentifier];
if (cell == nil){
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIndentifier];
}
cell.textLabel.text = #"Yes";
return cell;
}
#end
RootViewController.m
Here I am trying to add the TableView as a subview.
TableViewGeneratorController *tableViewGeneratorController = [[TableViewGeneratorController alloc]initWithNibName:#"TableViewGeneratorController" bundle:nil];
UITableView *tv = [tableViewGeneratorController prepareField];
[self.view addSubview: tv];
What is the problem going on?
Thanks
What you are doing is not really the proper way.
The immediate issue is that the TableViewGeneratorController instance that you create goes out of scope and is deallocated. This leaves the table view with not existing data source or delegate. A simple workaround is to assign the TableViewGeneratorController instance to an instance variable instead of a local variable.
But the proper solution is to embed the TableViewGeneratorController as a child controller of the root view controller.
Change TableViewGeneratorController to be a UITableViewController and get rid of the prepareField method.
Then when you create the TableViewGeneratorController, you add it as a child controller. See the docs for UIViewController for details.

Self-sizing UITableViewCell with UITableView inside it - self-sizing doesn't work

I am having trouble with the new concept of self-sizing cells. They work great for simple custom cells, however, I am trying to have a UITableView inside one of my custom UITableViewCells. I thought I had set up everything correctly, the UITableView inside the cell has constraints and everything and the delegates and datasources are connected as well. What's happening is that 'numberOfRowsInSection' in ChecklistTableViewCell gets called and returns 5, but not the corresponding cellForRowAtIndexPath. Therefore, the cell that should include another UITableView is only shown as a smaller cell with no content.
My 'research' via Google has told me that cellForRowAtIndexPath might not get called because the space for the cells is too small. So, I set the rowHeight of all cells to some constant and the UITableView inside the cell is displayed - but I loose the self-sizing functionality.
Therefore, my question, do self-sizing cells not work with more complex components within custom cells or am I missing something basic or important?
First, the code of my UIViewController:
#interface MyViewController ()
#property (weak, nonatomic) IBOutlet UITableView *tableView;
#property (nonatomic, strong) NSArray *elements;
#end
#implementation MyViewController
- (void)viewDidLoad
{
[super viewDidLoad];
self.tableView.estimatedRowHeight = 500.0;
self.tableView.rowHeight = UITableViewAutomaticDimension;
}
#pragma mark - UITableView methods
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
Element *element = self.elements[indexPath.row];
if (something) {
...
} else if (something else) {
ChecklistTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"identifier"];
if (cell == nil) {
cell = [[ChecklistTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"identifier"];
}
cell.checklist = element.checklist;
return cell;
} else {
...
}
}
- (NSInteger) tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [self.elements count];
}
#end
Here is my code for the cell that has a UITableView inside of it:
#interface ChecklistTableViewCell : UITableViewCell <UITableViewDataSource, UITableViewDelegate>
#property (weak, nonatomic) IBOutlet UITableView *checklistTableView;
#property (strong, nonatomic) NSArray *checklist;
#end
#import "ChecklistTableViewCell.h"
#implementation ChecklistTableViewCell
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
// Initialization code
}
return self;
}
- (void)layoutSubviews
{
[super layoutSubviews];
// Setup table view (self-sizing cells!)
self.checklistTableView.estimatedRowHeight = 50.0;
self.checklistTableView.rowHeight = UITableViewAutomaticDimension;
}
#pragma mark - UITableView methods
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
ChecklistElementTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"someIdentifier"];
if (cell == nil) {
cell = [[ChecklistElementTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"someIdentifier"];
}
cell.checklistElementTitleLabel.text = self.checklist[indexPath.row];
return cell;
}
- (NSInteger) tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [self.checklist count];
}
#end
And the code for the UIChecklistElementTableViewCell (there's no 'special' code in the .m file):
#interface ChecklistElementTableViewCell : UITableViewCell
#property (weak, nonatomic) IBOutlet UILabel *checklistElementTitleLabel;
#property (strong, nonatomic) IBOutlet M13Checkbox *checkbox;
#end
In the end, I went with implementing
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
where I return the calculated height for the cell with the table view in it and for all other cells, I return UITableViewAutomaticDimension.
It's not what I had hoped for, but it works. If anyone has another solution, I'm still very interested.
Are you sure that you set correctly you Autolayout's constraints for the cell's elements?
Did you set all the UILabel's line numbers to 0?

Resources