Selecting different UITableViewCells from indexPath alone - ios

I have a scenario where upon selecting a UITableViewCell in didSelectRowAtIndexPath:, I need to load and get the information from a different UITableViewCell.
I'm registering and using two different xibs to be used as my tableViewCells to allow for some more customization.
- (void)viewDidLoad{
[super viewDidLoad];
self.TABLE_ROW_HEIGHT = 66;
self.tblView.delegate = self;
self.tblView.dataSource = self;
[self.tblView registerNib:[UINib nibWithNibName:#"BasicCell" bundle:nil] forCellReuseIdentifier:#"BasicCell"];
[self.tblView registerNib:[UINib nibWithNibName:#"DetailCell" bundle:nil] forCellReuseIdentifier:#"DetailCell"];
}
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
//Property of the view controller which is an IndexPath
self.selectedIndex = indexPath;
BasicModel *basicModel = [self.models objectAtIndex:indexPath.row];
[self.apiClient detailModelSearch:basicModel.id];
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
if([self.selectedIndex isEqual:indexPath]){
return 400.0f;
}
return 66.0f;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
BasicModel *basicModel = [self.models objectAtIndex:indexPath.row];
UITableViewCell *tableCell = nil;
if([self.selectedIndex isEqual:indexPath]){
DetailCell *detailCell = [tableView dequeueReusableCellWithIdentifier:#"DetailCell"];
tableCell = detailCell;
}
else{
BasicCell *basicCell = [tableView dequeueReusableCellWithIdentifier:#"BasicCell"];
tableCell = basicCell;
}
return tableCell;
}
-(APIClient *)apiClient{
if(!_apiClient){
_apiClient = [APIClient new];
__weak ViewController *_self = self;
_apiClient.detailModelSearchFinished = ^(DetailModel *detailModel){
_self.detailModel = detailModel;
//Problem is here
DetailCell *cell = [_self.tblView cellForRowAtIndexPath:_self.selectedIndexPath;
dispatch_async(dispatch_get_main_queue(), ^{
[_self.tblView beginUpdates];
[_self.tblView endUpdates];
[_self.tblView reloadData];
});
};
}
return _apiClient;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
return self.models.count;
}
The basic structure is as follows.
App load and loads all BasicModels into the the models array.
User selects a cell which prompts an API detail search request
When detail search request is finished, the callback returns a DetailModel
What should happen next is since I know the selected index path of the touched cell, I want to use the DetailCell instead of the BasicCell to present the detailedInformation that comes from the DetailModel. My problem is when I call
DetailCell *cell = [_self.tblView cellForRowAtIndexPath:_self.selectedIndexPath;
I always receive the BasicCell that does not have the detailed view components I need to bind the detailModel to.
BasicCell xib
Detail Cell Xib
Table View Normal:
Table View Expanded with detail Cel xib

Ok now is very clear.
I can think of two ways, one is if you don't care about fancy animations just remove the (basic) cell and insert a new (detail) cell at the same index path, only after tho you have updated the model as well and perform the eventual type checks.
Quick and dirty, if you want something more clean you may want to refactor the model objects using polimorfism or other suitable patterns.
Another way is to update directly the cell with the received data. You may apply some fancy animations but loosing possibly some performances advantages.

Pretty simple solution actually. Reload data must be called before I can grab the expanded cell. Simple as this:
[_self.tblView beginUpdates];
[_self.tblView endUpdates];
[_self.tblView reloadData];
DetailCell *expandedCell = (DetailCell *) [_self.tblView cellForRowAtIndexPath:_self.selectedIndex];
expandedCell.lblData.text = #"IT WORKS!";
});

Related

Obj-c - Return two custom cells at the same time

I have two different custom TableView Cells. That said, when the first cell type is returned, I want the other returned immediately after. Technically, the below code is what I want to occur:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
ChatTableViewCell *cell = (ChatTableViewCell *)[tableView dequeueReusableCellWithIdentifier:ChatTableIdentifier forIndexPath:indexPath];
UniversalAlertTableViewCell *cellUni = (UniversalAlertTableViewCell *)[tableView dequeueReusableCellWithIdentifier:ChatTableIdentifierUni forIndexPath:indexPath];
return cell;
return cellUni;
}
However, as we know, code stops executing after the first return cell. How can I accomplish this?
You can only return one specific type at a time. Use the indexPath parameter to decide which one to return.
The following code is a rough idea of what you need. It's up to you to adapt the if statement based on your actual needs.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
if (indexPath.section == 0) {
ChatTableViewCell *cell = (ChatTableViewCell *)[tableView dequeueReusableCellWithIdentifier:ChatTableIdentifier forIndexPath:indexPath];
// configure cell based on data from your data model for this indexPath
return cell;
} else {
UniversalAlertTableViewCell *cell = (UniversalAlertTableViewCell *)[tableView dequeueReusableCellWithIdentifier:ChatTableIdentifierUni forIndexPath:indexPath];
// configure cell based on data from your data model for this indexPath
return cell;
}
}
Your question is very interesting. First clarify your needs.
I guess your demand is probably like this, and once the layout is displayed, switch to another layout. Let me talk about my habit of using tables. I am used to using data to control display and animation behavior. For your needs, I can use two data sources, corresponding to two kinds of cells. After one data source is displayed, immediately switch to another data source, and finally refresh the table.
The code is probably like this, you need a data source array models, and then each cell corresponds to a data source.
#property (strong, nonatomic) NSArray *models;
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
ChatModel *model = ChatModel.new;
self.models = #[model];
[self.tableView reloadData];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
UniversalAlertModel *universalModel = UniversalAlertModel.new;
self.models = #[universalModel];
[self.tableView reloadData];
});
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
return self.models.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
id model = self.models[indexPath.row];
if ([model isMemberOfClass:ChatModel.class]) {
ChatTableViewCell *cell = (ChatTableViewCell *)[tableView dequeueReusableCellWithIdentifier:ChatTableIdentifier forIndexPath:indexPath];
// Set UI display according to model
return cell;
}else if ([model isMemberOfClass:UniversalAlertModel.class]){
UniversalAlertTableViewCell *cellUni = (UniversalAlertTableViewCell *)[tableView dequeueReusableCellWithIdentifier:ChatTableIdentifierUni forIndexPath:indexPath];
// Set UI display according to model
return cellUni;
}
}
You cannot return two custom cells at the same time. It is not possible, because the delegate only runs one time per row. You can return a different cell on a different row or section.

How to ensure fluency in a frequently refreshed TableView?

there has a tableView Who refreshed every 0.5 seconds;
i uesd tableView reloadData or reloadSections: withRowAnimation:
Both of them cause FPS decrease
what can i do for it?
the tableView has new data every 0.5 seconds
and need display new data immediately
code:
- (void)registerTableViewCells {
[self.tableView registerClass:[UITableViewCell1 class] forCellReuseIdentifier: UITableViewCell1Identifier];
[self.tableView registerClass:[UITableViewCell2 class] forCellReuseIdentifier: UITableViewCell2Identifier];
[self.tableView registerClass:[UITableViewCell3 class] forCellReuseIdentifier: UITableViewCell3Identifier];
}
- (void)makeUpDisplaySource {
NSMutableArray *arrM = [NSMutableArray array];
[arrM addObject:#[UITableViewCell1Identifier, #(60), #(1)]];
[arrM addObject:#[UITableViewCell2Identifier, #(55), #(10)]];
[arrM addObject:#[UITableViewCell3Identifier, #(30), #(10+_fortySeat*30)]];
// _fortySeat is boolValue
self.displaySource = arrM;
}
tableView delegate
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSArray *dataArr = self.displaySource[indexPath.section];
NSString *identifier = dataArr[0];
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier forIndexPath:indexPath];
return cell;
}
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {
if ([NSStringFromClass(cell.class) isEqualToString:#"UITableViewCell1"]) {
UITableViewCell1 *newcell = (UITableViewCell1 *)cell;
[newcell updatCellUI];
}
else if ([NSStringFromClass(cell.class) isEqualToString:#"UITableViewCell2"]) {
UITableViewCell2 *newcell = (UITableViewCell2 *)cell;
[newcell updatCellUIBuy: model1 sell: model2];
}
else if ([NSStringFromClass(cell.class) isEqualToString:#"UITableViewCell3"]){
cell.backgroundColor = GL_CELL_BACKGROUD_COLOR;
[cell addSubview:self.someView];// someView is a lazy property
}
}
If you are not using reusable cells, of course, reload the data every 0.5 seconds may cause FPS problems beacause you'll reload the data of all the cells present in your TableView.
Also even if you are reloading data with reloadSections:, you'll have problems if you are reloading all the modified cells. Even those who are not visible. -- And this may cause problems.
In that case I suggest you to :
Use : [self.tableView registerNib:[UINib nibWithNibName:#"TableViewCell" bundle:nil] forCellReuseIdentifier:#"CellID"];
This will register your tableView under a xib file and the cells under an ID.
In cellForRowAtIndexPath: load your cell using this ID :
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"CellID"];
Using reusable cells you will increase the speed of loading your TableView and it may help you while refreshing it.
If you are already using reusable cells I suggest you to let people know it in your question.
Even if your are using reusable cells you could simply decrease a bit the interval time between 2 reloads if you still have problems with FPS.
I might not have completly understood your question and I might be wrong on certains points. Of course do not hesitate to let me know/edit my answer, I will apreciate it.

UITableView reuse Questions

About UITableView reuse, when there are multiple different Cell, use a different identifier to distinguish good or use an identifier and the Cell subViews remove, add content again good, if the Cell is very many cases, these reusable, what kind of specific access rules, when an identifier in the queue on the position is how to remove the master answer, thank you
_testTableView = [[UITableView alloc] initWithFrame:self.view.bounds style:UITableViewStylePlain];
_testTableView.dataSource = self;
_testTableView.delegate = self;
[_testTableView setRowHeight:80.];
[self.view addSubview:_testTableView];
[_testTableView registerClass:[TestTableViewCell class] forCellReuseIdentifier:testKeyOne];
[_testTableView registerClass:[TestTwoTableViewCell class] forCellReuseIdentifier:testKeyTwo];
//one way
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
//one
if(indexPath.row < 15){
TestTableViewCell * oneCell = [tableView dequeueReusableCellWithIdentifier:testKeyOne forIndexPath:indexPath];
return oneCell;
}else{
TestTwoTableViewCell * oneCell = [tableView dequeueReusableCellWithIdentifier:testKeyTwo forIndexPath:indexPath];
return oneCell;
}
return nil;
}
two way:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell * cell = [tableView dequeueReusableCellWithIdentifier:testKeyOne forIndexPath:indexPath];
for(UIView * view in cell.subviews){
[view removeFromSuperview];
}
//[cell addSubview:];
return cell;
}
one way or two,or Other better way,and Reuse of specific originally, enter the reuse and take out the order of the queue order
You want to go with option one. The table view data source should not be adding or removing views from table view cells. That just gets too messy.
Another option is to have just one cell subclass, but code the subclass to hide and show views as needed. I wouldn't have it add and remove views. That's way more complex code and way more expensive time-wise, which isn't great when you're trying to get a high frame rate when scrolling.

TableViewCell subtitle won't change unless the cell is tapped

The cells in my table have a subtitle set that will show some extra information loaded from a web server. When the app loads the subtitle will just say "Loading..." and then when the response is received, and parsed the cell is updated.
The problem is, unless I tap on the cell the subtitle will stay at "Loading...". As soon as I tap on it it updates to the correct subtitle.
Here I initialize the cell, and set the temporary subtitle while the http request is performed
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
// Setting the tableviewcell titles
UITableViewCell * cell = [tableView dequeueReusableCellWithIdentifier:#"Cell" forIndexPath:indexPath];
cell.detailTextLabel.text = #"Loading...";
cell.detailTextLabel.enabled = NO;
return cell;
}
I've tried making calling the request method in different places:
willDisplayCell and in cellForRowAtIndexPath.
The method that gets the data from the web server uses an asynchronous NSURLConnection which when a successful response is received I update the cell subtitle text using:
// Map Reduce the array used by the TableView
for (int i = 0; i < [self.routes count]; i++) {
if(cellMatches){
UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:i inSection:0]];
cell.detailTextLabel.text = #"Data received!";
cell.userInteractionEnabled = YES;
cell.textLabel.enabled = YES;
cell.detailTextLabel.enabled = YES;
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
}
I know that you can reload a specific cell using tableView reloadRowsAtIndexPaths but that doesn't seem to work when I implement this code:
[self.tableView beginUpdates];
// Change cell subtitle
[self.tableView reloadRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationNone];
[self.tableView endUpdates];
I have a timer set up to call the request method every 30 seconds, when that is called it works no problem and updates the subtitle right away without me having to tap it. So I think the problem is that the cell isn't initialized or maybe it's being reinitialized after the web request is made. But I don't call reloadAllData during this method.
What you need to do is update your cellForRowAtIndexPath so it checks for the data. If the data is available, set the subtitle to the data, otherwise show "Loading". This requires that you have some sort of data model that stores the data when it is received.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
UITableViewCell * cell = [tableView dequeueReusableCellWithIdentifier:#"Cell" forIndexPath:indexPath];
BOOL enabled = YES;
NSString *subtitle = ... // get the value from the data model
if (subtitle) {
cell.textLabel.text = ... // whatever value goes here
cell.detailTextLabel.text = subtitle;
cell.detailTextLabel.enabled = YES;
} else {
// There's no value for this row yet - show "Loading"
cell.textLabel.text = ... // whatever value goes here when loading
cell.detailTextLabel.text = #"Loading";
cell.detailTextLabel.enabled = NO;
}
return cell;
}
Be sure you set the same set of cell properties in both halves of the if/else statement as needed.
When you get new data and update your data model, simply call reloadRowsAtIndexPaths: for the proper cell path. The above code will then properly update the cell.
The code you have now to update a cell should be removed since it is not the proper way.
Try this:
[self.tableView reloadSections:[NSIndexSet indexSetWithIndex:0] withRowAnimation:UITableViewRowAnimationAutomatic];
Instead of:
[self.tableView reloadRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationNone];
Alongside with rmaddy's solution, I also needed to add one important thing that I found in a similar question:
dispatch_async(dispatch_get_main_queue(), ^{
[self.tableView reloadData];
});
Fixed the issue completely.

Add new UITableView row with custom text

Using this code
- (IBAction)testAdd:(id)sender
{
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:self.numberOfRows inSection:0];
[self.tableView beginUpdates];
self.numberOfRows++;
[self.tableView insertRowsAtIndexPaths:#[indexPath]withRowAnimation:UITableViewRowAnimationBottom];
[self.tableView endUpdates];
}
I'm able to add a new item to a tableView via an 'add' button on the app. This basically adds an item identical to the item already on the table that preceded it.
For example, I have a tableview with the first row displaying a string "TEST", hitting add adds another row that displays "TEST".
I would like to be able to pass in a custom value for the new row, so hitting add outputs a row with say "NEWTHING".
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"UITableViewCell" forIndexPath:indexPath];
// Configure the cell...
cell.textLabel.text = self.val2;
return cell;
}
My data source is actually another view controller that takes user inputs and sends it to my tabelViewController, with the text for the item as "val2".
What I actually want to achieve is the ability to hit add, go back to the user input view controller, get the new data and send it back to my tableViewController to be displayed
What you're asking, is the kinda stuff that is to be done in -cellForRowAtIndexPath: (most of the times, it depends on the way you have designed your datasource) but if it doesn't matter to you, then you can do:
- (IBAction)testAdd:(id)sender
{
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:self.numberOfRows
inSection:0];
self.numberOfRows++;
[self.tableView beginUpdates];
[self.tableView insertRowsAtIndexPaths:#[indexPath]
withRowAnimation:UITableViewRowAnimationBottom];
[self.tableView endUpdates];
UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
[cell.textLabel setText:#"NEWTHING"];
}
But note that when you scroll far up/down and return to this cell, it will most probably show "TEST" (that's where -cellForRowAtIndexPath: will show it's true purpose)
PS: Include your -cellForRowAtIndexPath: method implementation in the question if you want to proceed further
EDIT:
Your -cellForRowAtIndexPath is too static... in the sense that it simply sets self.val2 to cell.textLabel.
Lets say you start with 10 rows, -cellForRowAtIndexPath will be called 10 times and every time, it will set self.val2 onto the current cell's textLabel.
Now... when you add one row (on a button tap), the -cellForRowAtIndexPath will be called for the 11th cell and the same* text will be set to it.
*this technically happened but we quickly changed the cell's text
Basically, the tableView doesn't know how to differentiate between an existing cell and a new added cell because the datasource itself is not dynamic.
To direct the tableView on how to handle different cells, we need to create a more dynamic datasource.
There are different approaches use but I'd generally do it this way:
- (void)viewDidLoad
{
[super viewDidLoad];
self.val2 = #"TEST";
//declare "NSMutableArray *arrDatasource;" globally
//this will be the soul of the tableView
arrDatasource = [[NSMutableArray alloc] init];
int i_numberOfCells = 10;
//populate beginning cells with default text
for (int i = 0; i < i_numberOfCells; i++) {
NSMutableDictionary *dictionary = [[NSMutableDictionary alloc] init];
[dictionary setObject:self.val2 forKey:#"displayText"];
[arrDatasource addObject:dictionary];
}
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
//return number of objects in arrDatasource
return arrDatasource.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"UITableViewCell" forIndexPath:indexPath];
//pick up value for key "displayText" and set it onto the cell's label
[cell.textLabel setText:arrDatasource[indexPath.row][#"displayText"]];
//this will be dynamic in nature because you can modify the contents
//of arrDatasource and simply tell tableView to update appropriately
return cell;
}
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
//make indexPath of new cell to be created
NSIndexPath *indexPathNEXT = [NSIndexPath indexPathForItem:arrDatasource.count inSection:0];
//add the appropriate contents to a dictionary
NSMutableDictionary *dictionary = [[NSMutableDictionary alloc] init];
[dictionary setObject:#"NEWTHING" forKey:#"displayText"];
//add the dictionary object to the main array which is the datasource
[arrDatasource addObject:dictionary];
//add it to tableView
[self.tableView beginUpdates];
[self.tableView insertRowsAtIndexPaths:#[indexPathNEXT]
withRowAnimation:UITableViewRowAnimationAutomatic];
[self.tableView endUpdates];
//this ends up calling -cellForRowAtIndexPath for the newly created cell
//-cellForRowAtIndexPath shows the text (you put in the dictionary in this method above)
}
PS: -cellForRowAtIndexPath: is called whenever cell updates or refreshes or needs to be displayed and so this method needs to be implemented properly

Resources