I was wondering how to combine two array
\'s into one array
.
I want the combined tableView
to show the most recent
The cause of the complexity in this question is that the data is coming from two different sources asynchronously. Get our arms around that and we've got the problem under control. My suggestion differs from the others in that it aims to take care of the multiple asynch sources right away, leaving simpler code in all the other areas.
The way to handle two asynch sources is to serialize them with a nested completion. Here, I just took your posted code and factored it into two methods, one for each api. Each takes a success and failure block matching the object manager's interface...
- (void)loadOneWithSuccess:(void (^)(RKObjectRequestOperation *operation, RKMappingResult *mappingResult))success
failure:(void (^)(RKObjectRequestOperation *operation, NSError *error))failure {
NSString *apikey = @kCLIENTKEY;
NSDictionary *queryParams = @{@"apikey" : apikey};
NSString *path = [NSString stringWithFormat:@"v1/n/?limit=4&leafs=%@&themes=%@", leafAbbreviation, themeID];
[self.eObjectManager getObjectsAtPath:path parameters:queryParams success:success failure:failure];
}
- (void)loadTwoWithSuccess:(void (^)(RKObjectRequestOperation *operation, RKMappingResult *mappingResult))success
failure:(void (^)(RKObjectRequestOperation *operation, NSError *error))failure {
NSString *path = @"v1/u/2/m/recent/?client_id=e999";
[self.iObjectManager getObjectsAtPath:path parameters:nil success:success failure:failure];
}
Now we can make loadMedia do what we need, which is to load from each api, combine and sort as a single model. Declare an NSMutableArray
property called combinedModel
. Other answers have suggested this as tableDataList or contentArray. The key difference in my suggestion is to take care of the combination as part of a combined fetch.
- (void)loadMedia {
self.combinedModel = [NSMutableArray array];
[self loadOneWithSuccess:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
[self.combinedModel addObjectsFromArray:mappingResult];
// here's the trick. call API2 here. Doing so will serialize these two requests
[self loadTwoWithSuccess:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
[self.combinedModel addObjectsFromArray:mappingResult];
[self sortCombinedModel];
[self.tableView reloadData];
} failure:^(RKObjectRequestOperation *operation, NSError *error) {
NSLog(@"No?: %@", error);
}];
} failure:^(RKObjectRequestOperation *operation, NSError *error) {
NSLog(@"No?: %@", error);
}];
}
Now only two problems remain (1) sorting an array of heterogenous objects, (2) rendering heterogenous objects in a table view. First sort:
- (void)sortCombinedModel {
[self.combinedModel sortUsingComparator:^NSComparisonResult(id a, id b) {
NSDate *dateA, *dateB;
dateA = ([a isKindOfClass:[Feed self]])? ((Feed *)a).published : ((Data *)a).created_time;
dateB = ([b isKindOfClass:[Feed self]])? ((Feed *)b).published : ((Data *)b).created_time;
return [dateA compare:dateB];
}];
}
Now for the table, self.combinedModel
is the new model for the table view. All the datasource methods should refer to it. cellForRowAtIndexPath: should behave just like the sort:. In a nutshell...
id model = self.combinedModel[indexPath.row];
if ([model isKindOfClass:[Feed self]) {
Feed *feed = (Feed *)model;
// configure cell with feed
} else {
Data *data = (Data *)model;
// configure cell with data
}
I would keep a single property for the table data:
@property (strong, nonatomic) NSMutableArray *tableDataList;
and have a method that is called in each success block:
- (void)addTableData:(NSArray *)items
{
[self.tableDataList addObjectsFromArray:items];
[self.tableDataList sortUsingDescriptors:...];
[self.tableView reloadData];
}
to which you pass mappingResult.array
and you fill in the sort descriptor information to get your timeline ordering.
Then your table view delegate / data source methods are really simple and just refer to self.tableDataList
.
This is relatively similar to the answer from @Larme. From here I would get the item and check the class to decide how to configure the cell:
id item = [self.tableDataList objectAtIndex:indexPath.row];
if ([item isKindOfClass:[Feed class]]) {
Feed *feed = (Feed *)item;
... // configure the feed cell
} else {
Data *data = (Data *)item;
... // configure the data cell
}
I suppose that your actual code works. These are steps you could do. I didn't try (don't know if its compiles), but you'll get the whole idea.
• I'll go with:
@property (nonatomic, strong) NSMutableArray *contentArray;
Don't forget to initialize it: contentsArray = [[NSMutableArray alloc] init];
before doing the WebService requests.
• In the blocks, I'll do:
[contentsArray addObjectsFromArray:mappingResult.array];
• You need to sort them by dates, so since we just need to add a method to sort them, but I don't know how are Feed
& Data
(objects in the [mappingResult array]
), I'll let you do it. But I give you the main idea, since in the comments of the answer of @Wain, you said that the dates were NSString
.
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
//Do the dateFormatter settings, you may have to use 2 NSDateFormatters if the format is different for Data & Feed
//The initialization of the dateFormatter is done before the block, because its alloc/init take some time, and you may have to declare it with "__block"
//Since in your edit you do that and it seems it's the same format, just do @property (nonatomic, strong) NSDateFormatter dateFormatter;
NSArray *sortedArray = [contentsArray sortedArrayUsingComparator:^NSComparisonResult(id a, id b)
{
NSDate *aDate, bDate;
if ([a isKindOfClass:[Feed class]])
aDate = [dateFormatter dateFromString:(Feed *)a.created_time];
else //if ([a isKindOfClass:[Data class]])
aDate = [dateFormatter dateFromString:(Data *)a.published];
if ([b isKindOfClass:[Feed class]])
bDate = [dateFormatter dateFromString:(Feed *)b.created_time];
else //if ([b isKindOfClass:[Data class]])
bDate = [dateFormatter dateFromString:(Data *)b.published];
return [aDate compare:bDate];
}];
• In the datasource method tableView:numberOfRowsInSection
, do return [contentsArray count];
• In the datasource method tableView:cellForRowAtIndexPath:
if ([[[contentsArray objectAtIndexPath:[indexPath row]] isKindOfClass:[Feed class]])
{
Feed *feed = [contentsArray objectAtIndexPath:[indexPath row]];
CellClassFeed *cell = [tableView dequeueReusableCellWithIdentifier:@"APICell1"];
//Do your thing
}
else //if ([[[contentsArray objectAtIndexPath:[indexPath row]] isKindOfClass:[Data class]])
{
Data *data = [contentsArray objectAtIndexPath:[indexPath row]];
CellClassData *cell = [tableView dequeueReusableCellWithIdentifier:@"APICell2"];
//Do your thing
}
In your UITableViewDataSource methods, combine both arrays and use one or another accordingly:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// API1 + API2
return hArray.count + iArray.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell;
if(indexPath.row < hArray.count) {
// API 1
YourAPI1Cell *api1Cell = [tableView dequeueReusableCellWithIdentifier:@"YourAPI1Cell"];
// Do everything you need to do with the api1Cell
// Use the index in 'indexPath.row' to get the object from you array
cell = api1Cell;
} else {
// API 2
YourAPI2Cell *api2Cell = [tableView dequeueReusableCellWithIdentifier:@"YourAPI2Cell"];
// Do everything you need to do with the api2Cell
// Remember to use 'indexPath.row - hArray.count' as the index for getting an object for your second array
cell = api2Cell;
}
return cell;
}