When animating the deletion of all rows and hence the deletion of all sections that included all rows of a UITableView I am running into this error:
CRASH: attempt to delete row 2 from section 0, but there are only 0 sections before the update
In particular I have a singleton manager class that serves as the table view data source and delegate. I post an NSNotification to tell the table view to delete rows that should be deleted, and that NSNotification triggers the following method:
dispatch_async(dispatch_get_main_queue(), ^{
if ([[[Manager sharedManager] justDeletedIndices] count] > 0) {
[mainTableView beginUpdates];
NSMutableArray <NSIndexPath *> *pathsToDelete = [[Manager sharedManager] justDeletedIndices];
[mainTableView deleteRowsAtIndexPaths:pathsToDelete withRowAnimation:UITableViewRowAnimationFade];
[[Manager sharedManager] setJustDeletedIndices:[NSMutableArray new]];
[mainTableView endUpdates];
} else {
[mainTableView reloadData];
}
});
The code for the method is in turn triggered by a method in Manager like so:
- (void) deleteMessagesForNotificationObjects: (NSArray <Object *> *) objects {
// this is where the property that includes the NSIndexPath
[self p_updatePathsToDeleteForDeletedObjects:objects];
// this is the notification that should trigger the code above
[[NSNotificationCenter defaultCenter] postNotificationName:#"RefreshTableView" object:self];
// this is where I modify the underlying data structures that power
// the table view's data source
NSMutableArray *localObjects = [objects mutableCopy];
for (Object *obj in localObjects) {
[self deleteMessageWithToken:obj.token andUniqueID:nil andFireDate:obj.displayDate];
}
NSArray *allKeys = [self.displayDict allKeys];
NSMutableArray <NSString *> *keysToDelete = [NSMutableArray new];
for (NSString *key in allKeys) {
NSMutableArray <Object *> *currentArr = self.displayDict[key];
NSMutableArray <Object *> *objsToDelete = [NSMutableArray new];
for (int i = 0; i < [localObjects count]; i ++) {
if ([currentArr containsObject:localObjects[i]]) {
[objsToDelete addObject:localObjects[i]];
}
}
[currentArr removeObjectsInArray:objsToDelete];
[localObjects removeObjectsInArray:objsToDelete];
if ([currentArr count] < 1) {
[keysToDelete addObject:key];
}
}
[self.displayDict removeObjectsForKeys:keysToDelete];
self.keyOrder = [[[self class] orderedKeysFromDict:self.displayDict] mutableCopy];
}
I am unclear as to what has to happen in what order. How do the commands indicating to a table view that it has to delete certain rows in an animated fashion (discussed here: Add/Delete UITableViewCell with animation?) relate to the ordering of actually modifying the underlying data source? In what order do I (1) animate row deletion and section deletion and (2) actually delete those rows and sections?
The answer is that the data source has to be modified inside beginUpdates and endUpdates, not in another method or elsewhere in the code.
Related
What is the correct way of using NSDiffableDataSourceSnapshot and - (void)tableView:(nonnull UITableView *)tableView prefetchRowsAtIndexPaths:(nonnull NSArray<NSIndexPath *> *)indexPaths.
It seems that every time prefetch reloads table view, table view asks for more prefetching, after calling apply snapshot, creating infinite loop.
- (void)reloadViews {
//[self.tableView reloadData];
NSMutableArray *items = [NSMutableArray new];
for (TCHChannel* channel in self.channels) {
[items addObject:channel.sid];
}
if ([items count] == 0) {
return;
}
NSDiffableDataSourceSnapshot<ConversationSectionType*, NSString*> *snapshot =
[[NSDiffableDataSourceSnapshot<ConversationSectionType*, NSString*> alloc] init];
ConversationSectionType *main = [ConversationSectionType new];
main.section = kMain;
[snapshot appendSectionsWithIdentifiers:#[main]];
[snapshot appendItemsWithIdentifiers:items intoSectionWithIdentifier:main];
[self.diffDataSource applySnapshot:snapshot animatingDifferences:NO];
}
And here is prefetch method:
- (void)tableView:(nonnull UITableView *)tableView prefetchRowsAtIndexPaths:(nonnull NSArray<NSIndexPath *> *)indexPaths {
for (NSIndexPath *indexPath in indexPaths) {
TCHChannel *channel = [self channelForIndexPath:indexPath];
NSMutableSet *currentChannelIds = [NSMutableSet new];
for (ConversationListViewModelUpdateOperation *op in self.modelQueue.operations) {
[currentChannelIds addObject:[op channelId]];
}
if ([currentChannelIds containsObject:channel.sid]) {
continue;
}
NSParameterAssert(channel != nil);
ConversationListViewModelUpdateOperation *op = [[ConversationListViewModelUpdateOperation alloc] initWithChannel:channel cache:self.channelViewModelsCache];
op.completionBlock = ^{
dispatch_async(dispatch_get_main_queue(), ^(void){
[self reloadViews];
});
};
[self.modelQueue addOperation:op];
}
}
Model queue is just operation queue:
- (NSOperationQueue*)modelQueue {
if (_modelQueue == nil) {
_modelQueue = [[NSOperationQueue alloc] init];
_modelQueue.maxConcurrentOperationCount = 4;
}
return _modelQueue;
}
Is there a way to use prefetching with diffable data sources without apply asking for more indexes?
EDIT:
So calling reloadData in prefetch methods makes infinite loop.. According to https://andreygordeev.com/2017/02/20/uitableview-prefetching/
WARNING: do not call tableView.reloadData() or
tableView.reloadRows(...) from tableView(_ tableView: UITableView,
prefetchRowsAt indexPaths: [IndexPath]) method! These methods provoke
UITableView to call prefetchRowsAt... and thus lead to infinity loop.
Soo.. how has Apple intended for prefetching to be used with Diffable Data Sources? ... -.-
I'm building an iOS application which has a tableview with multiple sections.
The sections depends on the number of keys on an NSMutableDictionary.
I'm trying to make pagination by adding data to that dictionary every time I reach the last cell of the row.
My problem is that the pagination event is fired multiple times and makes the sections look in different order. I guess the last problem is because of the method I'm using to add new data is addEntriesFromDictionary.
So basically my questions are:
- Is the Dictionary data source approach a proper one? Should I change it for an Array data source one?
- Causes of the multiple pagination calls issue
Here's my code so far:
#implementation FooViewController
- (void)viewDidLoad {
self.FooStore = [[FooStore alloc]init];
[self.FooStore getFooDTO: #1];
[self setNavigationBar:nil];
[self setNeedsStatusBarAppearanceUpdate];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(handleFooDTOChange:)
name:#"FooDTOChanged"
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(handleLoadDataFoo:)
name:#"loadDataFoo"
object:nil];
self.activityView = [[UIActivityIndicatorView alloc]
initWithActivityIndicatorStyle: UIActivityIndicatorViewStyleGray];
self.activityView.center = self.view.center;
[self.activityView startAnimating];
[self.view addSubview:self.activityView];
}
- (void)handleFooDTOChange:(NSNotification *)note {
NSDictionary *theData = [note userInfo];
if (theData != nil) {
FooDTO *FooDTO = theData[#"FooDTO"];
if(FooDTO.date != nil){
[self setNavigationBar:FooDTO.date];
[self.activityView stopAnimating];
}
}
}
- (void)handleLoadDataFoo:(NSNotification *)note {
NSDictionary *theData = [note userInfo];
if (theData != nil) {
[self.FooStore getFooDTO: theData[#"paginaActual"]];
}
}
… some code ..
#end
Table View Controller:
#implementation FooTableViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.currentPage = 1;
self.data = [[NSMutableDictionary alloc] init];
[self.tableView setHidden:YES];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(handleFooDTOChange:)
name:#"FooDTOChanged"
object:nil];
}
- (void)handleFooDTOChange:(NSNotification *)note {
NSDictionary *theData = [note userInfo];
if (theData != nil) {
FooDTO *FooDTO = theData[#"FooDTO"];
if ([[FooDTO.dataFoo allKeys] count] > 0){
[self.data addEntriesFromDictionary: FooDTO.dataFoo];
[self.tableView setHidden:NO];
[self.tableView reloadData];
}
}
}
-(void) dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
// Return the number of sections.
return self.data.allKeys.count;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
// Return the number of rows in the section.
NSString *arrayKey = [self.data.allKeys objectAtIndex:section];
NSArray *a = [self.data objectForKey:arrayKey];
return a.count;
}
-(void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *arrayKey = [self.data.allKeys objectAtIndex:indexPath.section];
NSArray *dataKey = [self.data objectForKey:arrayKey];
if(indexPath.row == [dataKey count] -1 && indexPath.section == [self.data.allKeys count] - 1){
self.currentPage++;
NSDictionary *dataDictionary = #{
#"currentPage": [NSNumber numberWithInt: self.currentPage]
};
[[NSNotificationCenter defaultCenter] postNotificationName:#"loaddataFoo" object:self userInfo:dataDictionary];
}
}
Service:
#implementation FooStore
- (FooDTO *) getFooDTO: (NSNumber *) page
{
FooDTO *FooDTO =[[FooDTO alloc]init];
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
NSString *dateStr = [dateFormat stringFromDate:date];
NSDictionary *parameters = #{
//urlparameters
};
[manager POST:#"http://my.service/foos.json" parameters:parameters
success:^(AFHTTPRequestOperation *operation, id responseObject) {
//handle data
}
NSDictionary *dataDictionary = #{
#"FooDTO": FooDTO
};
[[NSNotificationCenter defaultCenter] postNotificationName:#"FooDTOChanged" object:self userInfo:dataDictionary];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", error);
NSDictionary *dataDictionary = #{
#"error": error
};
[[NSNotificationCenter defaultCenter] postNotificationName:#"FooError" object:self userInfo:dataDictionary];
}
];
return nil;
};
#end
Dictionaries are unordered collections. The sections and rows in a table view must be served up in the same order every time the table view asks for information, so dictionaries are not suitable.
It's pretty common to use a single NSArray or NSMutableArray for a single-section table view, or an array of arrays for a multi-sectioned table view. (The outer array contains your sections and the inner arrays contain the rows for each section.
You can make the data for each cell a dictionary if that's helpful (So you would have an array of arrays of dictionaries.) That way each key/value pair in the inner dictionary contains different information that you need to display in your cells.
I would suggest NOT using a dictionary to represent the cell in your table view. (But as noted you CAN use a dictionary to save the different settings for a cell.)
I have dropped my code into a situation where I need to call UITableView data source methods written in some UIViewController class before a particular view is presented so that the cells get prepopulated and I can set a BOOL that the data in the not present viewController class is valid or not. I may explain it in more detail if required, but I wanted to know if its possible to do that. If yes, then how to do it? .. as a particular set of my code written after [tableView reloadData] is dependent on running the dataSource methods of UITableView. Please throw some light on this, if needs to be handled in a specific thread?
Following is the case where I call reloadData. Note: This is happening in another class when basicFactsViewController's viewWillAppear method has not been called yet:
- (BOOL) isComplete {
dispatch_async(dispatch_get_main_queue(), ^{
[basicFactsViewController.tableView reloadData];
});
return basicFactsViewController.isComplete && selectedVehicleId && selectedMakeId && selectedModelId && selectedYearId && selectedTrimId;
}
Now basicFactsViewController.isComplete is checked in this method:
- (BOOL) isComplete {
[self collectKeyHighlights];
return _isComplete;
}
Now the dictionary "tableCells" in the method below uses the cells population to check whether all features have been completed or not:
- (NSDictionary *) collectKeyHighlights {
NSMutableDictionary *key_highlights_update = [NSMutableDictionary new];
NSMutableDictionary *cell_highlight_update = [NSMutableDictionary new];
if(visible_key_highlights.count == 0) _isComplete = YES;
_isComplete = YES;
__block NSMutableArray *reloadCellAtIndexPathSet = [[NSMutableArray alloc] init];
[visible_key_highlights enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
NSDictionary *feature = (NSDictionary *)obj;
UITableViewCell *cell = [self.tableCells objectForKey:[NSIndexPath indexPathForRow:idx inSection:0]];
if(cell) {
if([cell isKindOfClass:[DRColorSelectionTableViewCell class]]) {
NSInteger selectedIndex = ((DRColorSelectionTableViewCell *)cell).selectedIndex;
NSInteger numberOfSegments = ((DRColorSelectionTableViewCell *)cell).numberOfSegments;
if(selectedIndex > -1 ) {
NSArray *dataValues = [[visible_key_highlights objectAtIndex:idx] objectForKey:#"data_values"];
NSDictionary *colorData;
BOOL reloadCellForIndexPath = NO;
if (numberOfSegments == selectedIndex) {
colorData = #{ #"normalized" : #"user_defined", #"isother" : #YES, #"hexcode":#"#FFFFFF", #"actual":((DRColorSelectionTableViewCell *)cell).otherColorTextField.text};
reloadCellForIndexPath = YES;
}
else{
colorData = [dataValues objectAtIndex:selectedIndex];
}
[key_highlights_update setObject:colorData forKey:[feature objectForKey:#"name"]];
[cell_highlight_update setObject:colorData forKey:[feature objectForKey:#"name"]];
if (![colorData isEqual:[prevSelections objectForKey:[feature objectForKey:#"name"]]]) {
[reloadCellAtIndexPathSet addObject:((DRColorSelectionTableViewCell *)cell).indexPath];
}
//if (reloadCellForIndexPath) {
//}
} else {
_isComplete = NO;
}
} else if([cell isKindOfClass:[DRInputTableViewCell class]]) {
NSString *textInput = ((DRInputTableViewCell *)cell).inputTextField.text;
if([textInput length]) {
[key_highlights_update setObject:[NSString toSnakeCase:textInput] forKey:[feature objectForKey:#"name"]];
[cell_highlight_update setObject:textInput forKey:[feature objectForKey:#"name"]];
}else {
_isComplete = NO;
}
} else if([cell isKindOfClass:[DRPickerTableViewCell class]]) {
NSString *textInput = ((DRPickerTableViewCell *)cell).inputField.text;
if([textInput length]) {
[key_highlights_update setObject:[NSString toSnakeCase:textInput] forKey:[feature objectForKey:#"name"]];
[cell_highlight_update setObject:textInput forKey:[feature objectForKey:#"name"]];
} else {
_isComplete = NO;
}
} else if([cell isKindOfClass:[DRSwitchTableViewCell class]]) {
// send this everytime for now
BOOL isSelected = ((DRSwitchTableViewCell *)cell).toggleButton.selected;
[key_highlights_update setObject:[NSNumber numberWithBool:isSelected] forKey:[feature objectForKey:#"name"]];
[cell_highlight_update setObject:[NSNumber numberWithBool:isSelected] forKey:[feature objectForKey:#"name"]];
}
}
else{
_isComplete = NO;
}
}];
prevSelections = cell_highlight_update;
if ([reloadCellAtIndexPathSet count]) {
[self.tableView beginUpdates];
[self.tableView reloadRowsAtIndexPaths:reloadCellAtIndexPathSet withRowAnimation:UITableViewRowAnimationNone];
[self.tableView endUpdates];
}
return key_highlights_update;
}
Now here since
[tableView reloadData]
is not calling cellForRowAtIndePath:, hence, tableCells is not getting populated, hence, I am always getting _isComplete = NO.
If I understand correctly, there is processing being done when the tableview loads (calls it's dataSource methods) and you want to trigger that early to use its results. Calling [basicFactsViewController.tableView reloadData]; early won't work if the basicFactsViewController hasn't been displayed yet. If basicFactsViewController is a UIViewController and has the default view and the tableView property is a subview of that standard view, then (if I remember correctly) the tableView property will be nil until the basicFactsViewController has been displayed. A shortcut around that is to access the viewController's view property and cause it to initialize (viewDidLoad and all that). You can do that by simply messaging the viewController: [basicFactsViewController view].
If I've been right so far I'm fairly confident that will initialize the tableView property. But I'm not sure if it will cause the table view to load its data. And even if it does work, it's definitely not the best solution to the piece of code you're trying to architect. Apple's design for UIKit has been focused on the model/view/controller pattern and it's easier to go with the flow and do the same. I imagine that you could move the processing that is in the data source methods for the tableView out into another class (or maybe even the same class), and call that method to get everything ready for both the tableView and any other checks that you have, storing the data in dictionaries and arrays in such a way that you can easily load them by index into the tableView when cellForIndex is called.
I have a UITableView that uses paging. All the delegates, and datasource are set.
My table view fetches a list of ten cars over the network and displays them by sending a page number (currentPage). During this fetch request I also get the pageCount which is the number of pages that contains cars on the server. Each page contains 10 cars.
I create a loading cell on the row that equals self.allCars.count which is my car array. This cell then fetches the next ten, and adds them to the self.allCars.count array. A loading cell is then created again for self.allCars.count + 1 etc. (I hope you get the picture, if not please ask).
On first launch the list contains All Cars which is the default request. However, the user can change it from a drop down. For example, they can select Blue Cars. This is passed into the fetchCars methods as the params parameter.
There is an unwanted behaviour in my code however: When I scroll down through the list, with the default paramter selected, and I scroll down three pages (three network calls to fetchCars...) and the array now contains 30 cars displayed in the tableView. However I now want to start a different search from scratch, so I go to the drop down, and select to filter by only blue cars (donePickerBlue). This method removes all the car objects, sets the currentPage back to 1, calls the network for the blue cars, and reloads the data. The unwanted behaviour occurs here. Because there had been 30 cells/indexPath.rows, the network call is called 3 times. This is because the indexPath.row < self.allCars.count is not true. This is where I am stuck, I can't seem to figure out how to fix it, so that if the search parameter is change (blue in this case) that it should treat it as new, I thought the [tableView reloadData] would handle this, but unfortunately it remembers how many index paths there are.
Its something i've been stuck on for a while. I've a feeling im missing something very simple to fix it.
Header file
#property (nonatomic) NSInteger currentPage;
#property (nonatomic) NSInteger pageCount;
Implementation
-(void)viewDidLoad{
...
self.currentPage = 1;
...
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
if (self.allCars.count ==0) {
return 0;
}
else{
if (self.currentPage<self.pageCount)
return self.allCars.count+1;
}
return 0;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
UITableViewCell * cell = nil;
if (self.allCars.count!=0) {
if(indexPath.row <self.allCars.count){//here is where the problem occurs
cell=[self customCellForIndexPath:indexPath tableView:tableView];
}
else {
cell=[self loadingCell];
}
}
else{
// Disable user interaction for this cell.
cell = [[UITableViewCell alloc] init];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
}
return cell;
}
-(UITableViewCell *)loadingCell{
UITableViewCell * cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil];
UIActivityIndicatorView * activityIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
activityIndicator.center = cell.center;
cell.backgroundColor = [UIColor lightGrayColor];
[cell addSubview:activityIndicator];
cell.tag=kLoadingCellTag;
[activityIndicator startAnimating];
return cell;
}
-(void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath{
if (cell.tag==kLoadingCellTag) {
self.currentPage++;
[self performSelector:#selector(getCars:withParams) withObject:nil afterDelay:1.5f];
}
}
-(void)getCars{
[self getCars:url withParams:params];
}
-(void)getCars: (NSURL *)url withParams: (NSString *)params{
NSMutableURLRequest * request = [[NSMutableURLRequest alloc] initWithURL:url cachePolicy:0 timeoutInterval:80];
[request setHTTPBody:[params dataUsingEncoding:NSUTF8StringEncoding]];
[request setHTTPMethod:#"POST"];
NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
sessionConfig.timeoutIntervalForResource=1;
NSURLSession * session = [NSURLSession sessionWithConfiguration:sessionConfig];
NSURLSessionDataTask * task = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSHTTPURLResponse * httpResp = (NSHTTPURLResponse *)response;
NSDictionary * dataDict = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:nil];
if (data) {
switch (httpResp.statusCode) {
case 200:{
dispatch_async(dispatch_get_main_queue(), ^{
self.pageCount = [dataDict[#"message"][#"total_pages"] intValue];
NSArray * carsArray = dataDict[#"message"][#"results"];
for (NSDictionary *cDict in carsArray) {
Car *car = [Car carWithID:[cDict[#"car_id"] stringValue] ];
car.car_name=cDict[#"car_name"];
car.car_description = cDict[#"car_description"];
[self.allCars addObject:car];
}
[self.tableView reloadData];
});
break;
}
default:
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(#"Error");
});
break;
}
}
else{
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(#"Error");
});
}
}];
[task resume];
}
//reset list to start new search
-(void)donePickingBlue{
[self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0] atScrollPosition:UITableViewScrollPositionTop animated:YES];
self.currentPage=1;
[self.allCars removeAllObjects];
[self getCars:url withParams:blue];
}
Edit
I seem to have resolved the the problem by doing the following;
//reset list to start new search
-(void)donePickingBlue{
self.currentPage=1;
[self.allCars removeAllObjects];
[self.tableView reloadData];//after removing all the cars, now we call reload, as there are no cars. I was calling reload in `[self getCars:....]` just below, and thought this was enough.
[self getCars:url withParams:blue];
}
I was able to answer my own problem. The answer can be seen in the Edit above incase anybody else has the same problem.
It should have been;
//reset list to start new search
-(void)donePickingBlue{
self.currentPage=1;
[self.allCars removeAllObjects];
[self.tableView reloadData];//after removing all the cars, now we call reload, as there are no cars. I was calling reload in `[self getCars:....]` just below, and thought this was enough.
[self getCars:url withParams:blue];
}
If you want to download cars page by page, willDisplayCell: is pretty good choice. But you must change the condition a little, to prevent downloading the same data multiple times. Also, I recommend you to change data model and provide ability to determine a page for particular cars. That's what I mean:
-(void)tableView:(UITableView *)tableView
willDisplayCell:(UITableViewCell *)cell
forRowAtIndexPath:(NSIndexPath *)indexPath{
// 10 cells on page
NSUInteger currentPage = indexPath.row / 10;
// Check, if cars for the current page are downloaded
if (carsOnPagesDict[#(currentPage)] != nil) {
// Add a stub to indicate that downloading started
// You can use this later to display correct cell
// Also it prevents getCars: from calling multiple times for the current page
carsOnPagesDict[#(currentPage)] = #"downloading";
// I removed delay for simplicity
[self getCars:url withParams:params forPage:currentPage];
}
}
Also, change getCars method:
-(void)getCars:(NSURL *)url withParams:(NSString *)params forPage:(NSUInteger)page{
// Creating request...
// ...
// Processing response...
// ...
// Array obtained:
NSArray *carsArray = dataDict[#"message"][#"results"];
// Storing required data to the array
NSMutableArray *cars = [NSMutableArray arrayWithCapacity:carsArray.count];
for (NSDictionary *cDict in carsArray) {
Car *car = [Car carWithID:[cDict[#"car_id"] stringValue] ];
car.car_name=cDict[#"car_name"];
car.car_description = cDict[#"car_description"];
[cars addObject:car];
}
// Save cars to the dictionary for the page given
carsOnPagesDict[#(page)] = cars;
// ...
// Resuming tasks...
}
You may consider using CoreData to store that cars.
Its complicated to put in words but lemme give it a try. I have a MenuViewController that has an array with category names, tapping on the category rows in tableview instantiate a different view controller using the Storyboard ID.
Now if i use different classes for each view controller, that would be a lot of redundant code and classes. What i want to do is to use one class for all these view controllers lets call it PrimaryViewController and upon selecting different categories in the MenuViewController, it calls different methods or blocks in the PrimaryViewController.
Here is the method in the PrimaryViewController:
- (void) fetchData:(NSInteger )pageNumber
{
channel = [[TheFeedStore sharedStore] fetchWebService:pageNumber withCompletion:^(RSSChannel *obj, NSError *err){
if (!err) {
int currentItemCount = [[channel items] count];
channel = obj;
int newItemCount = [[channel items] count];
int itemDelta = newItemCount - currentItemCount;
if (itemDelta > 0) {
NSMutableArray *rows = [NSMutableArray array];
for (int i = 0; i < itemDelta; i++) {
NSIndexPath *ip = [NSIndexPath indexPathForRow:i inSection:0];
[rows addObject:ip];
}
[[self tableView] insertRowsAtIndexPaths:rows withRowAnimation:UITableViewRowAnimationBottom];
}
}
}];
}
The above code has the ability to load one category. Notice the first line "channel = [[TheFeedStore sharedStore] fetchWebService", the other categories are named "fetchWebServiceCat2", "fetchWebServiceCat3" and "fetchWebServiceCat4" in another class named TheFeedStore.
What i want is when a different view controller is instantiated from the MenuViewController, it should use PrimaryViewController's fetchData method to call a different category method of TheFeedStore.
Thanks!
[store fetchWebService:webService withCompletion:completion];
is equivalent to:
[store performSelector:#selector(fetchWebService:withCompletion:)
withObject:webService
withObject:completion];
So you can do this:
SEL sel = nil;
if (...) sel = #selector(fetchWebService:withCompletion:);
if (...) sel = #selector(fetchWebServiceCat2:withCompletion:);
...
[store performSelector:sel withObject:webService withObject:^{}];
Or even this:
SEL sel = NSSelectorFromString([NSString stringWithFormat:#"fetchWebService%#:withCompletion:", #"Cat2"]);
...