I have taken over an iOS project and have to refactor a list of views into a UITableView. I am using Storyboards and have subclassed UITableViewCell. One subclass is called MenuItemCell and has a headerLabel, detailLabel, and priceLabel which are properties set up in the Storyboard and configured in MenuItemCell. I am able to manipulate these via cellForAtIndexPath like this:
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
static NSString *MenuItemCellIdentifier=#"MenuItemCell";
id dic=self.tmpMenu.listItems[indexPath.row];
if([dic isKindOfClass:[MenuItem class]]){
MenuItemCell *cell = [self.menuTV dequeueReusableCellWithIdentifier:MenuItemCellIdentifier];
MenuItem *menuItem=(MenuItem *)dic;
cell.menuItem=menuItem;
cell.headerLabel.text=menuItem.header;
cell.headerLabel.numberOfLines=0;
cell.priceLabel.text=menuItem.price;
// how to handle this custom spotView
if([menuItem hasInstoreImage]){
UIView *instoreImageDot=[self circleWithColor:[UIColor redColor] radius:4];
[cell.spotView addSubview:instoreImageDot]; // ON SCROLLING, this populates to all the different table cells
}
return cell;
}
return nil;
}
The last piece is that there is a custom UIView called spotView. Currently, I am creating this circle in code in my controller via circleWithColor and trying to add to [cell.spotView] but scrolling causes this to populate on different table cells. How should I set this up? I have added a method to my custom view but this suffers from the same problem.
Cells get reused, you will need to tell the tableView to remove the custom View
if([menuItem hasInstoreImage]){
UIView *instoreImageDot=[self circleWithColor:[UIColor redColor] radius:4];
[cell.spotView addSubview:instoreImageDot];
}else{
//remove it if condition is not met
//or You can add a place holder view instead
}
What is happening is that iOS is reusing cells as you scroll and some of the reused cells already have the instoreImageDot view added as a subview.
You really shouldn't do layout stuff in the cellForRowAtIndexPath method. It should only ever be used to dequeue a reusable cell and then set the data for the cell. All the layout stuff should be handled by the cell itself.
Don't create the instoreImageDot in the controller. Add a method in your custom cell - something like (written in C#, but should be easy to translate):
UpdateCell(MenuItem item, bool hasInstoreIamge)
{
menuItem = item;
headerLabel.text = item.header;
priceLabel.text = item.price;
headerLabel.numberOfLines=0;
if (hasInstoreImage)
{
// code to add the instoreImageDot as a subview of the cell
}
}
Also in the Custom Cell, Implement the prepareForReuse method and inside this method, remove the instoreImageDot view from the cell - so that it can only ever be added once.
- (void)prepareForReuse {
if([self.subviews containsObject:instoreImageDot])
{
[instoreImageDot removeFromSuperview];
}
[super prepareForReuse];
}
Now your cellForRowAtIndexPath method can look like:
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
static NSString *MenuItemCellIdentifier=#"MenuItemCell";
id dic=self.tmpMenu.listItems[indexPath.row];
if([dic isKindOfClass:[MenuItem class]]){
MenuItemCell *cell = [self.menuTV dequeueReusableCellWithIdentifier:MenuItemCellIdentifier];
MenuItem *menuItem=(MenuItem *)dic;
cell.UpdateCell(menuItem, [menuItem hasInstoreImage]);
return cell;
}
return nil;
}
Related
I am creating a tableView which has collectionView inside.
what I did :
1. Created a table view inside a controller.
2. created a custom tableViewCell with collectionView inside of that and providing it with tag
3. Created a custom collectionViewcell.
Now i made controller the delegate and datasource for both collectionView and tableView.
Now tableView should have multiple tableViewcells and each tableViewCell has CollectionView
One tableViewCell has one section.
Problem :-
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
self.arrayModel = self.SlotsArray[indexPath.section];
}
But here is a catch : lets say my screen size shows two tableViewcell, then all methods of tableCells are called first and then all methods of collectionView are called instead i want that after 1st section of tableViewcell is created , it should call collectionView methods and create it. then go to tableView method make 2nd section and then create its collectionView.
Is there away to do this.
Somewhere i read on net thet i will have to subclass CollectionView and add a property index and then while setting viewcontroller as delegate and datasource , set index also.
But i have created CollectionView By nib and adding it to the tableCell class by "TAG"
self.imageCollectionView = [self.contentView viewWithTag:2511];
Can you tell how can i achieve this?
In your case, you should have one table view, having cells. Each cell will have a collection view inside it.
Number of table view cells will equal to the count of your data-source. As we all know, table view cell will be created one by one and assign each cell a data source which will be used to fill your collection view (which is inside each cell).
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
CategoryCell *cell = [tableView dequeueReusableCellWithIdentifier:kCellIdentifierCategory forIndexPath:indexPath];
NSArray *arrVideos = [parentArray objectAtIndex:indexPath.row];
[cell setCollectionData:arrVideos];
return cell;
}
Now, inside that table view cell class, implement setCollectionData method to fill its collection view:
- (void)setCollectionData:(NSArray *)collectionData
{
self.arrCollectionData = collectionData;
[self.yourCollectioView reloadData];
}
Now, each collection cell would be feed from this array:
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
VideoCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:kCellIdentifierVideo forIndexPath:indexPath];
NSDictionary *_video = [self.arrCollectionData objectAtIndex:[indexPath row]];
// Use this dictionary
[cell.lblVideoTitle setText:_video.title];
––––––––––
––––––––––
return cell;
}
Refer following image:
Hope it helps you !!!
You can take a look on this, surely it will help you to implement horizontal scrolling collection view inside uitableviewcell.
https://github.com/ThornTechPublic/HorizontalScrollingCollectionView/blob/master/GithubImages/videoGrid.gif
https://github.com/agusso/ASOXScrollTableViewCell
I have a layout in which I will have 2 UITableViews with custom cells. The second UITableView must be inside the first.
My question is: how to delegate second UITableView?
Can I delegate both to my ViewController? In that case it will use the same methods and I have to find out which UITableView is managed right now.
Or I have to delegate it inside custom UITableViewCell of the first UITableView?
Any recommendations are appreciated.
EDIT: I don't know how to implement solutions here, because I have Storyboard. Inside my current UIViewController I set delegate and dataSource of the first UITableView to my View Controller.
My problem is that I don't know how to set the same properties of the second Table View (which will be inside UITableViewCell). I can not set them to UITableViewCell (IB does not allow that).
Where and how to set then in the IB?
A far better solution would be to abstract the DataSource and Delegate implementations away from your view controller so that they can be personalised per tableview as required (please note that the code is taken from the objc.io article Lighter View Controllers.
E.g.
#implementation ArrayDataSource
- (id)itemAtIndexPath:(NSIndexPath*)indexPath {
return items[(NSUInteger)indexPath.row];
}
- (NSInteger)tableView:(UITableView*)tableView
numberOfRowsInSection:(NSInteger)section {
return items.count;
}
- (UITableViewCell*)tableView:(UITableView*)tableView
cellForRowAtIndexPath:(NSIndexPath*)indexPath {
id cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier
forIndexPath:indexPath];
id item = [self itemAtIndexPath:indexPath];
configureCellBlock(cell,item);
return cell;
}
#end
Then you could utilise it as follows:
void (^configureCell)(PhotoCell*, Photo*) = ^(PhotoCell* cell, Photo* photo) {
cell.label.text = photo.name;
};
photosArrayDataSource = [[ArrayDataSource alloc] initWithItems:photos
cellIdentifier:PhotoCellIdentifier
configureCellBlock:configureCell];
self.tableView.dataSource = photosArrayDataSource;
The same process could be followed with the UITableViewDelegate implementations to provide you with a very clean, separated and de-coupled code base. Your requirement for two tableviews will then be intrinsically easier to implement.
My answer is
For identifying two table view data source and delegate method is,better to set tag for the table views.
Set this below coding in your tableview delegates method.
if(tableView.tag==0)
{
}
else
{
}
Also you can vary this by assigning different name to these table view.
if(tableView==FirstTableView)
{
}
else
{
}
You just check table condition for every delegate method
Use this code to register custom cell.
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
if(tableView == self.yourFirstTable)
{
CustomCell *cell=[tableView dequeueReusableCellWithIdentifier:#"cellModifier"];
// your code
}
else
{
// second table cell code
}
return cell;
}
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
if(tableView == self.yourFirstTable)
{
// first tableView number of row return
}
else
{
// second table number of row return
}
}
And create prototype cell in TableView
And Set CellReusableId like this way
I want a tableview inside another tableviewCell like the following image.It shows one complete cell with a few details and a tableview. How can i do this?I was following this link Link.This is an old code .It is using xibs.I dont have any idea where to set the delegate for the inner tableview.Please help.Any suggestion will be realy helpfull.
My first idea would be:
Subclass UITableViewCell ("MainTableViewCell") and extend it with UITableViewDelegate and UITableViewDatasource.
Next to all the properties you need in "MainTableViewCell" add a TableView "tableViewFilms" and an array "films" for the Films. Also don't forget to add the datasource methods for a tableview to the implementation file.
To easily setup a cell I add a setup-method to the header-file. Which can be called once the cell is instantiated. You can modify it as you want, give it as many parameters as you want and in the implementation (see step 4) set datasource and delegate of your inner tableview.
- (void)setupCellWithDictionary:(NSDictionary *)dict AndArray:(NSArray *)filmsForInnerTable;
You can call this method in your datasource method, once a cell is instantiated:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
MainTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"MainTableViewCell" forIndexPath:indexPath];
NSDictionary *dict = (NSDictionary *) allDataDictionaries[indexPath.row];
[cell setupCellWithDictionary:dict AndArray:filmsForInnerTable];
return cell;
}
Subclass UITableViewCell another time: "FilmTableViewCell"
When setting up the a Cell of "MainTableViewCell", set the delegate and the datasource of "tableViewFilms" to self (object of "MainTableViewCell").
- (void)setupCellWithDictionary:(NSDictionary *)dict AndArray:(NSArray *)filmsForInnerTable{
self.films = filmsForInnerTable;
self.tableViewFilms.dataSource = self;
self.tableViewFilms.delegate = self;
[self.tableView reload];
//more instructions
}
Populate the tableview with the data from the array "films" using "FilmTableViewCells".
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
FilmTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"FilmTableViewCell" forIndexPath:indexPath];
Film *film = (Film*)films[indexPath.row];
[cell setupCellWithFilm:film];
return cell;
}
Hope this helps.
Don't forget to use Outlets, the method definitions and to set the reuse-identifiers for the cells!
Check my answer in this ios 8 Swift - TableView with embedded CollectionView. You have replace that UICollectionView with UITableView.
Everything else is pretty much the same. Its just a head start with UITableView and UICollectionView created programmatically.
I can change it accordingly if you don't understand.
I have custom cell called MyCustomCell, there is a label named pushLabel, in table view I get parameters of label like this:
cell.pushLabel.text = #"text";
I need to get in that label parameters in viewDidLoad (where table view is located), how this can be done?
In viewDidLoad method, UITableView still should have not been loaded so there no way you can get UITableViewCell objects.
One solution can be, maintain flag say you have
//Add in your .h file
BOOL hidePushLabel;
Now use these flag to hide intially in tableview like this:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *strCellIndentifier = #"cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:strCellIndentifier];
if (cell)
{
//By default hidePushLabel will be NO so hide pushLabel
if(!hidePushLabel)
cell.pushLabel.hidden = YES;
else
cell.pushLabel.hidden = NO;
}
return cell;
}
I made an UITableView and contained some custom UITableViewCells, in the fist cell (named cell0 for example) there are some UITextFields for input, when I scroll the tableView, cell0 will disappear from the top of screen, then How can I get the UITextField's text in cell0?
cellForRowAtIndexPath will return nil.
The only method I found is a tableview delegate
- (void)tableView:(UITableView *)tableView didEndDisplayingCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
[cell dealloc]
}
According to Apple Documentation about cellForRowAtIndexPath:, it returns "An object representing a cell of the table or nil if the cell is not visible or indexPath is out of range."
A UITableViewCell is a view, according to MVC Pattern. So I'd prefer maintaining a model object -- maybe it is as simple as a NSString instance -- to hold the text in the cell if I were you. You can observe UITextField's change by adding an observer of UITextFieldTextDidChangeNotification key to your controller.
- (void)textFieldDidChangeText:(NSNotification *)notification
{
// Assume your controller has a NSString (copy) property named "text".
self.text = [(UITextField *)[notification object] text]; // The notification's object property will return the UITextField instance who has posted the notification.
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// Dequeue cell...
// ...
if (!cell)
{
// Init cell...
// ...
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(textFieldDidChangeText:) name:UITextFieldTextDidChangeNotification object:yourTextField];
}
// Other code...
// ...
return cell;
}
Don't forget to remove the observer in your -dealloc.
As UITableViewCells leave the viewable area of the UITableView it is actually removed from the tableview and placed back into the reuse queue. If it is the selected for reuse it will be returned by dequeueReusableCellWithIdentifier:.
There is no callback for when a cell is removed from the view. However, prepareForReuse is called on the cell just before it is returned by dequeueReusableCellWithIdentifier:.
What are you ultimately trying to do?
You need to save the text somewhere (e.g. an NSArray) the moment it gets changed.
You can init textfield as instance variable.
look like:
.h
UITextField *textfiled;
.m
-(void)viewDidLoad
{
//init textfield
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
//init cell...
[cell addSubview:textfield];
return cell;
}