iOS - Pagination using a Dictionary based UITableView - ios

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.)

Related

UITableView animating deletion of all rows

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.

How to Replace Values Between View Controllers?

I'm building an iOS app that parses JSON data when the first ViewController loads. In another ViewController, I have a UIPicker, which includes an array objects that pass their own corresponding urls, and a UIButton which calls the segue back to the first ViewController. However, when I call the segue, the View Controller still parses the same data from the initial nsurl, instead of the data called to replace it. How do I fix this so that when I select an object from the UIPicker and pass the data to the next controller, the first view controller parses that data accordingly?
Here is my code:
MainVC.m
-(void)viewDidLoad{
[super viewDidLoad];
_bburl = [NSURL URLWithString:#"http://www.example.com/api/sites?count=75"];
NSData *jsonData = [NSData dataWithContentsOfURL:_bburl];
NSURLSession *session = [NSURLSession sharedSession];
NSURLRequest *request = [[NSURLRequest alloc]initWithURL:_bburl];
NSURLSessionDownloadTask *task = [session downloadTaskWithURL:_bburl completionHandler:^(NSURL *location, NSURLResponse *response, NSError *error){
NSLog(#"%#", response);
NSData *data = [[NSData alloc]initWithContentsOfURL:location];
NSDictionary *dataDictionary = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&error];
. . . . .
}
MenuVC.m
-(void)viewDidLoad{
[super viewDidLoad];
self->catArray = [[NSArray alloc] initWithObjects:#"All",#"4-Letter", #"Animal", #"Auction", #"Blog", #"Business", #"Celluar", #"Children",#"Community", #"Consulting", #"Dating", #"Design", #"Download", nil];
self->URLArray = [[NSMutableArray alloc]init];
[URLArray addObject: #"https://www.example.com/api/sites/?count=75"];
[URLArray addObject: #"https://www.example.com/api/sites/?count=75&category=4-letter"];
[URLArray addObject: #"https://www.example.com/api/sites/?count=75&category=animal"];
[URLArray addObject: #"https://www.example.com/api/sites/?count=75&category=auction"];
[URLArray addObject: #"https://www.example.com/api/sites/?count=75&category=blog"];
[URLArray addObject: #"https://www.example.com/api/sites/?count=75&category=business"];
[URLArray addObject: #"https://www.example.com/api/sites/?count=75&category=cellular"];
[URLArray addObject: #"https://www.example.com/api/sites/?count=75&category=children"];
[URLArray addObject: #"https://www.example.com/api/sites/?count=75&category=community"];
[URLArray addObject: #"https://www.example.com/api/sites/?count=75&category=consulting"];
[URLArray addObject: #"https://www.example.com/api/sites/?count=75&category=dating"];
[URLArray addObject: #"https://www.exapmle.com/api/sites/?count=75&category=design"];
[URLArray addObject: #"https://www.example.com/api/sites/?count=75&category=download"];
self->picker = catArray;
}
-(NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView {
// This method returns the number of components we want in our Picker.
// The components are the colums.
return 1;
}
-(NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component {
// This returns the number of rows in each component. We use the count of our array to determine the number of rows.
return [self->catArray count];
return 0;
}
#pragma mark Picker Delegate Methods
- (NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component{
return [self->catArray objectAtIndex:row];
return 0;
}
-(void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component
{
selectedRow = [pickerView selectedRowInComponent:0];
NSString *catURLString = [URLArray objectAtIndex:selectedRow];
catURL = [NSURL URLWithString:catURLString];
MainViewController *mainVC = [[MainViewController alloc]init];
mainVC.bburl = catURL;
NSLog(#"%d, %#", selectedRow, catURL);
}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.identifier isEqualToString:#"backFromMenuSegue"]) {
NSString *catURLString = [URLArray objectAtIndex:[self->picker selectedRowInComponent:0]];
catURL = [NSURL URLWithString:catURLString];
MainViewController *mainVC = segue.destinationViewController;
mainVC.bburl = catURL;
NSLog(#"Selected url: %#",catURL);
}
}
When you call the segue back to the first ViewController the first viewController has already loaded. So method viewDidLoad will not be called. The quickest and dirty way to solve this is move all the code in viewDidLoad to viewWillAppear

AMSlideMenu Cell click takes time to open another view?

I’m building an article reading app.I’m using AMSliderMenu(https://github.com/SocialObjects-Software/AMSlideMenu)
library for menu list.When i click on any cell in AMSlideMenu it load into another table view which contain list of articles.
I’m fetching article data in uitableview with JSON.
The issue is when i click on list of menu in AMSlideMenu it takes time to open another view.How can i resolve this problem.
Here is my code:
- (void)viewDidLoad
{
[super viewDidLoad];
BOOL myBool = [self isNetworkAvailable];
if (myBool)
{
#try {
_Title1 = [[NSMutableArray alloc] init];
_Author1 = [[NSMutableArray alloc] init];
_Images1 = [[NSMutableArray alloc] init];
NSData* data = [NSData dataWithContentsOfURL:ysURL];
NSArray *ys_avatars = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
if(ys_avatars){
for (int j=0;j<ys_avatars.count;j++)
{
[_Title1 addObject:ys_avatars[j][#"title"]];
[_Author1 addObject: ys_avatars[j][#"author"]];
[_Images1 addObject: ys_avatars[j][#"featured_img"]];
}
}
else
{
NSLog(#"asd");
}
}
#catch (NSException *exception) {
}
[self LoadMore];
[self LoadMore];
});
});
}
}
-(void)LoadMore
{ BOOL myBool = [self isNetworkAvailable];
if (myBool)
{
#try {
// for table cell seperator line color
self.tableView.separatorColor = [UIColor colorWithRed:190/255.0 green:190/255.0 blue:190/255.0 alpha:1.0];
// for displaying the previous screen lable with back button in details view controller
UIBarButtonItem *backbutton1 = [[UIBarButtonItem alloc] initWithTitle:#"" style:UIBarButtonItemStyleBordered target:nil action:nil];
[[self navigationItem] setBackBarButtonItem:backbutton1];
NSString *urlString=[NSString stringWithFormat:#"www.example.com&page=%d",x]; NSURL *newUrl=[NSURL URLWithString:urlString];
NSData* data = [NSData dataWithContentsOfURL:newUrl];
NSArray *ys_avatars = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
x++;
if(ys_avatars){
for (int j=0;j<_Title1.count ;j++)
{
[_Title1 addObject:ys_avatars[j][#"title"]];
[_Author1 addObject: ys_avatars[j][#"author"]];
[_Images1 addObject: ys_avatars[j][#"featured_img"]];
} }
else
{ NSLog(#"asd"); }
[self.tableView reloadData];}
#catch (NSException *exception) {
}} }
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return _Title1.count;
}
Its because you have done many many works in viewDidLoad method that's executing synchronously, and that view controller will not be shown until the code in viewDidLoad will not executed completely.
So, load your data asynchronously or do it in viewDidAppear method, but I strongly suggest you to do it asynchronously.

How to load array from web service for searching in uitextfield?

I wish to send an array of say stationNames in a SOAP call .
Please help.
This code for manual input to array
- (void)autoCompleteTextField:(MLPAutoCompleteTextField *)textField
possibleCompletionsForString:(NSString *)string
completionHandler:(void (^)(NSArray *))handler
{
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
dispatch_async(queue, ^{
if(self.simulateLatency){
CGFloat seconds = arc4random_uniform(4)+arc4random_uniform(4); //normal distribution
NSLog(#"sleeping fetch of completions for %f", seconds);
sleep(seconds);
}
NSArray *completions;
if(self.testWithAutoCompleteObjectsInsteadOfStrings){
completions = [self allCountryObjects];
} else {
completions = [self allCountries];
}
handler(completions);
});
}
- (NSArray *)allCountryObjects
{
if(!self.countryObjects){
NSArray *countryNames = [self allCountries];
NSMutableArray *mutableCountries = [NSMutableArray new];
for(NSString *countryName in countryNames){
DEMOCustomAutoCompleteObject *country = [[DEMOCustomAutoCompleteObject alloc] initWithCountry:countryName];
[mutableCountries addObject:country];
}
[self setCountryObjects:[NSArray arrayWithArray:mutableCountries]];
}
return self.countryObjects;
}
- (NSArray *)allCountries
{
NSArray *countries =
#[
#"Item1",
#"Item2",
#"Item3",
#"Item4",
#"Item5",
#"Item6",
#"Item7",
#"Item8",
#"Item9",
];
return countries;
}
- (BOOL)autoCompleteTextField:(MLPAutoCompleteTextField *)textField
shouldConfigureCell:(UITableViewCell *)cell
withAutoCompleteString:(NSString *)autocompleteString
withAttributedString:(NSAttributedString *)boldedString
forAutoCompleteObject:(id<MLPAutoCompletionObject>)autocompleteObject
forRowAtIndexPath:(NSIndexPath *)indexPath;
//This is your chance to customize an autocomplete tableview cell before it appears
in the autocomplete tableview
NSString *filename = [autocompleteString stringByAppendingString:#".png"];
filename = [filename stringByReplacingOccurrencesOfString:#" " withString:#"-"];
filename = [filename stringByReplacingOccurrencesOfString:#"&" withString:#"and"];
[cell.imageView setImage:[UIImage imageNamed:filename]];
return YES;
}
- (void)autoCompleteTextField:(MLPAutoCompleteTextField *)textField
didSelectAutoCompleteString:(NSString *)selectedString
withAutoCompleteObject:(id<MLPAutoCompletionObject>)selectedObject
forRowAtIndexPath:(NSIndexPath *)indexPath
{
if(selectedObject){
NSLog(#"selected object from autocomplete menu %# with string %#", selectedObject,
[selectedObject autocompleteString]);
} else {
NSLog(#"selected string '%#' from autocomplete menu", selectedString);
}
}

Program crashing after trying to read a NSString

My program:
I have view parentView and childView. childView has a UITextField. This UITextField is completed by the user. Then UITextField.text is passed as a NSString to parentView via a NSNotificationCenter. The NSLog in the Notification method shows that the value has been passes successfully to parentView.
Now the issue.... when I try to access _guideDescription (that contains the value being passed) in the saveIntoExisting my app crashes. I dont understand why is crashing when it was able to retrieve the value in the notification method.
There is no error just (llbs).
Anyone has an idea why is this happening?
- (void)viewWillAppear:(BOOL)animated {
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:#selector(notificationSelectedDescription:)
name:#"selectedDescription"
object:nil];
}
- (void) notificationSelectedDescription:(NSNotification *)notification {
_guideDescription = [[notification userInfo] valueForKey:#"selected"];
NSLog(#"Show me: %#",[_guideDescription description]);
}
- (IBAction)createExercise:(UIButton *)sender {
if (![self fetch]) {
NSLog(#"It doesnt exist already");
[self save];
} else {
NSLog(#"It does exist already");
/*
* ADD CODE ON HOW TO ACT WHEN EXISTS
*/
[self saveIntoExisting];
}
}
- (void) saveIntoExisting {
NSLog(#"saveintoExisting 1");
NSLog(#"saveintoExisting show me: %#",[_guideDescription description]);
NSFetchRequest *fetchExercise = [[NSFetchRequest alloc] init];
NSEntityDescription *entityItem = [NSEntityDescription entityForName:#"Exercise" inManagedObjectContext:context];
[fetchExercise setEntity:entityItem];
[fetchExercise setPredicate:[NSPredicate predicateWithFormat:#"exerciseName == %#",_originalExerciseName]];
[fetchExercise setRelationshipKeyPathsForPrefetching:[NSArray arrayWithObjects:#"User", nil]];
fetchExercise.predicate = [NSPredicate predicateWithFormat:#"user.email LIKE %#",_appDelegate.currentUser];
NSError *error = nil;
NSArray* oldExercise = [[context executeFetchRequest:fetchExercise error:&error] mutableCopy];
Exercise *newExercise = (Exercise*) oldExercise[0];
newExercise.exerciseName = _exerciseName.text;
newExercise.exercisePic = _selectedImage;
if (![_guideDescription isEqualToString:#""]) {
newExercise.exerciseDescription =_guideDescription;
}
if (_youTubeLink){
newExercise.youtube =_youTubeLink;
}
if (![_selectRoutine.titleLabel.text isEqualToString:#"add to routine"]) {
newExercise.routine = [[self getCurrentRoutine] exercise];
[[self getCurrentUser] addRoutines:[[self getCurrentRoutine] exercise]];
}
[[self getCurrentUser] addExercisesObject:newExercise];
error = nil;
[context save:&error ];
}
The problem is probably that that _guideDescription string already is deallocated when you try to access it in -saveIntoExisted. You are now doing
_guideDescription = [[NSString alloc] initWithString:[[notification userInfo] valueForKey:#"selected"]];
and that works, but I would recommend:
[_guideDescription retain];
That makes sure the system doesn't deallocate the string.

Resources