Saving, retaining, & retrieving selected cells in array - ios

I've read every post and tried every solution multiple times, but cannot get correct functionality.
I have a tableview with data from a local JSON file. I need the user to be able to:
select multiple cells
show check marks on selected cells
write those selections to an array
delete the selections when unchecked
5. save/retain the selections with the check marks when user switches view or leaves and comes back to tableview, closes and reopens app, etc.
I've managed to get 1-4 working, but I'm stuck on #5 and can't figure it out for the life of me. I've tried NSUserDefaults every way I could. Any help is appreciated. Below is the current code.
Also, why am I having to double click a cell to uncheck it?
#interface FilterViewController () <UISearchResultsUpdating>
#property (nonatomic, strong) NSArray *IngredientsArray;
#property (nonatomic, strong) UISearchController *searchController;
#property (nonatomic, strong) NSMutableArray *searchResults;
#property (nonatomic, strong) NSMutableArray *selectedCell;
#property (nonatomic, strong) NSMutableArray *selectedIngredients;
//I added this property to keep track of the selected row
#property (strong,nonatomic) NSIndexPath *selectedPath;
#end
#implementation FilterViewController {
NSArray *_locations;
}
- (void)viewDidLoad {
[super viewDidLoad];
self.selectedIngredients = [NSMutableArray array];
self.selectedCell = [NSMutableArray array];
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
self.lastIndexPath = [defaults objectForKey:#"lastIndexPathUsed"];
// Create a new JSONLoader with a local file URL
JSONLoaderIngreds *jsonLoader = [[JSONLoaderIngreds alloc] init];
NSURL *url = [[NSBundle mainBundle] URLForResource:#"locations" withExtension:#"json"];
// There's no transition in our storyboard to our search results tableview or navigation controller
// so we'll have to grab it using the instantiateViewControllerWithIdentifier: method
UINavigationController *searchResultsController = [[self storyboard] instantiateViewControllerWithIdentifier:#"FilterViewSearchResultsNavController"];
// Our instance of UISearchController will use searchResults
self.searchController = [[UISearchController alloc] initWithSearchResultsController:searchResultsController];
// The searchcontroller's searchResultsUpdater property will contain our tableView.
self.searchController.searchResultsUpdater = self;
// create the searchBar programatically.
self.searchController.searchBar.frame = CGRectMake(self.searchController.searchBar.frame.origin.x,
self.searchController.searchBar.frame.origin.y,
self.searchController.searchBar.frame.size.width, 44.0);
self.tableView.tableHeaderView = self.searchController.searchBar;
//Sets LocationsViewController as presenter for LocationDetailViewController after searxh results dsiplayed
//and selected.. Required so searchbar doesn't show in detailsview after segue, and instead, default nav
//controller back button displays.
self.definesPresentationContext = true;
// Load the data on a background queue...
// As we are using a local file it's not really necessary, but if we were connecting to an online URL then we'd need it
//NSString *ingreds = [dict objectForKey:#"ingredients"]
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
_locations = [jsonLoader ingredientsFromJSONFile:url];
// Now that we have the data, reload the table data on the main UI thread
[self.tableView performSelectorOnMainThread:#selector(reloadData) withObject:nil waitUntilDone:YES];
});
}
// Just before showing the LocationViewController, set the selected Location object
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
LocationsViewController *vc = segue.destinationViewController;
NSIndexPath *indexPath = [self.tableView indexPathForCell:sender];
vc.location = [_locations objectAtIndex:indexPath.row];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
}
- (void)viewWillAppear:(BOOL)animated
{
//I added this if clause to select the row that was last selected
if (self.selectedPath != nil) {
[self.tableView selectRowAtIndexPath:self.selectedPath animated:NO scrollPosition:UITableViewScrollPositionNone];
}
}
#pragma mark - Table View Controller Methods
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
self.selectedPath = indexPath;
NSString *Ingredient = [_locations objectAtIndex:indexPath.row];
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
if([self isRowSelectedOnTableView:tableView atIndexPath:indexPath]){
[self.selectedCell removeObject:indexPath];
[self.selectedIngredients removeObject:Ingredient];
cell.accessoryType = UITableViewCellAccessoryNone;
} else {
[self.selectedCell addObject:indexPath];
[self.selectedIngredients addObject:Ingredient];
cell.accessoryType = UITableViewCellAccessoryCheckmark;
}
NSLog(#"***************Selected Ingredients**************** %#", self.selectedIngredients);
NSUserDefaults *userdefaults = [NSUserDefaults standardUserDefaults];
[userdefaults setObject:[NSString stringWithFormat:#"%ld",(long)indexPath.section] forKey:#"lastIndexPathUsed"];
[userdefaults synchronize];
}
-(BOOL)isRowSelectedOnTableView:(UITableView *)tableView atIndexPath:(NSIndexPath *)indexPath
{
return ([self.selectedCell containsObject:indexPath]) ? YES : NO;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *unifiedID = #"FilterCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:unifiedID];
if (!cell) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:unifiedID];
}
cell.textLabel.text = [_locations objectAtIndex:indexPath.row];
cell.imageView.image = [UIImage imageNamed:#"ingredientsicon3232.png"];
//if the indexPath was found among the selected ones, set the checkmark on the cell
cell.accessoryType = ([self isRowSelectedOnTableView:tableView atIndexPath:indexPath]) ? UITableViewCellAccessoryCheckmark : UITableViewCellAccessoryNone;
return cell;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [_locations count];
}
UPDATE:
I managed to changed code as suggested to save selections to array in NSUserDefaults using the updated code below, but I still can't figure out the cellForRowAtIndexPath code needed to save/recall checkmarks.
How would I code cellForRowAtIndexPath to recall checkmarks?
Saving selections to array with this code:
ViewDidLoad code:
_selections = [NSMutableArray arrayWithArray:(NSArray *)[[NSUserDefaults standardUserDefaults] objectForKey:#"selections"]];
if(_selections == nil){
_selections = [[NSMutableArray alloc] init];
}
didSelectRowAtIndexPath code:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
{
if ([_selections containsObject: cell.textLabel.text] == NO){
[_selections addObject:cell.textLabel.text];
cell.accessoryType = UITableViewCellAccessoryCheckmark;
} else {
[_selections removeObject:cell.textLabel.text];
cell.accessoryType = UITableViewCellAccessoryNone;
}
NSLog(#"***************Selected Ingredients**************** %#", _selections);
NSUserDefaults *userdefaults = [NSUserDefaults standardUserDefaults];
[userdefaults setObject:_selections forKey:#"selections"];
[userdefaults synchronize];
NSLog(#"-------------NSUserDefaults------------%#", [[NSUserDefaults standardUserDefaults] dictionaryRepresentation])
}

Your solution is pretty straight forward.
First thing is you need to save an array of selected index paths to your user default object instead of just last selected path.
Whenever a user selects or deselects a row. Add and remove objects from the same array and save it back to your user defaults.
In your cellForRowAtIndexPath, check whether your index path exists in the array saved in the user defaults. If it exists, select the row with checkmark otherwise leave it as it is.
Hope this helps.

I think instead of working with those indexPath, i recommended you to work straight with the data itself, add a Bool property to indicate the selection in your Ingredient class, then save the whole array in CoreData/Realm/NSUserDefault, that way is more correctly since your data can be change, lead to the selection indexPath that you save can be not correct anymore

Related

stuck at 3rd level of hierarchal tableview data

I am developing app which is using storyboard and using two UITableView and one Label UIViewController. This app take data from the .plist file. I am able to retrieve data from .plsit show in first table and the according to the selected row data shown on second table but when I click to view the detail second table only one array strings I can view and can't view others. here is the sample for understating. A > AA > AAA and B > BB > BBB.
I have three topics in the first table i.e. Lumber Puncture, Urinary catheterization and veni puncture. Now when I click on each cell it displays me data in second UITableView according to the cell. Lets suppose showing me following data:
introduction
indication
equipments
etc
Now when I click the introduction of my Lumber puncture data it shows me detail of lumber puncture from the .plist array data. But if I click on the Urinary catheterization introduction on the second data cell, it also shows me the first Lumber puncture detail. So how can I solve this problem? kindly help me with your detail answers as I am new to develop iOS apps. Thanks
Here is some code for the first table and passing data to second table.
#implementation ViewController
{
NSArray *tableData;
NSArray *Components_Procedure;
}
#synthesize tableView1;
- (void)viewDidLoad
{
[super viewDidLoad];
// Find out the path of recipes.plist
NSString *path = [[NSBundle mainBundle] pathForResource:#"recipes" ofType:#"plist"];
// Load the file content and read the data into arrays
NSDictionary *dict = [[NSDictionary alloc] initWithContentsOfFile:path];
tableData = [dict objectForKey:#"Procedure"];
Components_Procedure = [dict objectForKey:#"Component"];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [tableData count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *simpleTableIdentifier = #"ProcedureCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:simpleTableIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:simpleTableIdentifier];
}
cell.textLabel.text = [tableData objectAtIndex:indexPath.row];
return cell;
}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.identifier isEqualToString:#"Procedure"]) {
NSIndexPath *indexPath = [self.tableView1 indexPathForSelectedRow];
Components *destViewController = segue.destinationViewController;
destViewController. componentArray= [Components_Procedure objectAtIndex:indexPath.row];
}
}
#end
Second table code and passing detail data to UIViewcontrollers UILables
#implementation Components
{
NSArray *detail;
}
#synthesize tableView2;
#synthesize componentArray;
- (void)viewDidLoad
{
[super viewDidLoad];
// Find out the path of recipes.plist
NSString *path = [[NSBundle mainBundle] pathForResource:#"recipes" ofType:#"plist"];
// Load the file content and read the data into arrays
NSDictionary *dict = [[NSDictionary alloc] initWithContentsOfFile:path];
detail = [dict objectForKey:#"LumberPuncture"];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [componentArray count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *simpleTableIdentifier = #"ComponentCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:simpleTableIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:simpleTableIdentifier];
}
cell.textLabel.text = [componentArray objectAtIndex:indexPath.row];
return cell;
}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.identifier isEqualToString:#"Detail"]) {
NSIndexPath *indexPath = [self.tableView2 indexPathForSelectedRow];
DetailView *destViewController = segue.destinationViewController;
destViewController.headerName = [componentArray objectAtIndex:indexPath.row];
destViewController.contentName = [detail objectAtIndex:indexPath.row];
}
}
#end
and detail UIViewController class
#import "DetailView.h"
#implementation DetailView
#synthesize headerLabel;
#synthesize headerName;
#synthesize contentLabel;
#synthesize contentName;
- (void)viewDidLoad
{
[super viewDidLoad];
// Set the Label text with the selected recipe
headerLabel.text = headerName;
contentLabel.text = contentName;
}
#end
You need to use both methods, in didSelectRowAtIndexPath you should call [self performSegueWithIdentifier:#"identifier" sender:self];
In the same View Controller you should have the prepareForSegue method grab the destinationViewController
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath (NSIndexPath *)indexPath
{
self.someProperty = [self.someArray objectAtIndex:indexPath.row];
[self performSegueWithIdentifier:#"segueID" sender:self];
}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
UIViewController *vcToPushTo = segue.destinationViewController;
vcToPushTo.propertyToSet = self.someProperty;
}
Just add this method:
– tableView:didSelectRowAtIndexPath:
Typically, you use “didSelectRowAtIndexPath” method, which is invoked after user selects a row, to handle the row selection and this is where you add code to specify the action when the row is selected.
This and the "willSelectRowAtIndexPath" methods are used for row selection. The only difference is that “willSelectRowAtIndexPath” is called when a specified row is about to be selected. Usually you make use of this method to prevent selection of a particular cell from taking place.

NSArray is not overwritting on viewDidAppear

I have a menu with two items, Login and Settings, displayed in a tableView. Login and Settings are stored in an NSArray. On ViewDidLoad I am checking if user exist in NSUserDefaults. If user exist then change the Login to Logout and vice versa. The issue is that on ViewDidLoad it only changes the name once when the application is opened after being cleared from cache. Therefore, in order to see the change after login in or login out you need to clear the application from cache and re-open it. I want the changes to apply immediately. Every time the menu button click before the menu open and viewDidAppear is accessed. I know this because I placed a break point and traced it. It does what is supposed to. But the it never changes the value of login to logout or vice versa.
MenuController.
-(void)ChangeLoginLabel {
NSUserDefaults *data = [NSUserDefaults standardUserDefaults];
NSString *User = [data objectForKey:#"User"];
if(User==nil) {
_extraMenuItems = [[NSMutableArray alloc] initWithObjects:#"Settings", #"Login", nil];
}
else {
_extraMenuItems = [[NSMutableArray alloc] initWithObjects:#"Settings", #"Logout", nil];
}
}
- (void)viewWillAppear:(BOOL)animated {
[self ChangeLoginLabel];
}
- (void)viewDidLoad
{
[super viewDidLoad];
[self ChangeLoginLabel];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)sectionIndex
{
return self.extraMenuItems.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *CellIdentifier = #"Formal";
UITableViewCell *cell = [self.extraTableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
}
NSString *cellValue = [self.extraMenuItems objectAtIndex:indexPath.row];
cell.textLabel.lineBreakMode = NSLineBreakByWordWrapping;
cell.textLabel.numberOfLines = 0;
cell.textLabel.text = cellValue;
cell.textLabel.textColor = [UIColor blackColor];
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if (indexPath.row == 0)
{
NSLog(#"Settings cell tapped!");
}
else if (indexPath.row == 1)
{
//closes when login view appears
[self.slidingViewController resetTopView];
NSString *LoginTextLabel = [self.extraMenuItems objectAtIndex:indexPath.row];
if([LoginTextLabel isEqualToString:#"Logout"]) {
}
else {
UIViewController * vc = [self.storyboard instantiateViewControllerWithIdentifier:#"Login"];
[self presentViewController:vc animated:YES completion:nil];
}
}
// Deselect the row
[tableView deselectRowAtIndexPath:indexPath animated:YES];
}
Logs
2014-12-12 13:06:53.290 xxx[1695:270056] _extraMenuItems: (
Settings,
Login
)
2014-12-12 13:07:04.290 xxx[1695:270056] _extraMenuItems: (
Settings,
Logout
)
How can I force the array to change the value immediately when the user logged in or logged out?
If I'm understanding correctly, you're trying to update the label in your table based on the change in NSUserDefaults as triggered by a login/logout from another view controller; so when you return to the MenuViewController, ChangeLoginLabel is triggered by the viewDidAppear, the extraMenuItems array is in fact set to contain the appropriate values, and you expect the labels in the table to change accordingly. But the problem is, that you're not reloading the table upon your return to the view.
So first off, I'd recommend removing [self ChangeLoginLabel]; from your viewDidLoad since it's redundant. And secondly, I'd recommend changing your ChangeLoginLabel method to include a table reload, like so:
-(void)ChangeLoginLabel {
NSUserDefaults *data = [NSUserDefaults standardUserDefaults];
NSString *User = [data objectForKey:#"User"];
if(User==nil) {
self.extraMenuItems = [[NSMutableArray alloc] initWithObjects:#"Settings", #"Login", nil];
}
else {
self.extraMenuItems = [[NSMutableArray alloc] initWithObjects:#"Settings", #"Logout", nil];
}
[self.tableView reloadData];
}
- (void)viewWillAppear:(BOOL)animated {
[self ChangeLoginLabel];
}
Or if you'd prefer just to reload the relevant row you can replace [self.tableView reloadData]; with:
NSIndexPath* indexPath = [NSIndexPath indexPathForRow:1 inSection:0];
[self.tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationNone];
Do you want viewWillAppear instead? viewDidAppear happens after the view already is rendered I believe.
call
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[self ChangeLoginLabel];
}
or
- (void)viewDidAppear:(BOOL)animated {
[super viewDifAppear:animated];
[self ChangeLoginLabel];
}

How to populate an array from detailViewController and return to masterViewController in iOS?

I have a tableView from which I navigate to a 'viewController' ie PassengerInfoViewController in which I have a form.I fill up the form , make a dictionary out of it and add it to a NSMutableArray.
So when I am going back to the tableView' I would like to pass this array and then reload thetableView` with the filled array.
So here's what I am doing : -
//navigate to the form
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
PassengerInfoViewController *pivc = [self.storyboard instantiateViewControllerWithIdentifier:#"PassengerInfoViewController"];
[self.navigationController pushViewController:pivc animated:YES];
}
//After filling up the form
-(void)goBackToPassengerDetails:(UIButton*)sender{
NSString *title = self.titleTextField.text;
NSString *fname = self.firstNameTextField.text;
NSString *lname =self.lastNameTextField.text;
NSString *ageStr = self.ageTextField.text;
NSMutableDictionary *dict = [[NSMutableDictionary alloc]init];
[dict setValue:title forKey:#"title"];
[dict setValue:fname forKey:#"firstName"];
[dict setValue:lname forKey:#"lastName"];
[dict setValue:ageStr forKey:#"age"];
Passenger *passengerObj = [Passenger sharedInstance]; //Singleton
[passengerObj.passengerDetailArray addObject:dict];
PassengerDetailsViewController *pdc = [self.storyboard instantiateViewControllerWithIdentifier:#"PassengerDetailsViewController"];
[pdc getPassengerinfo:passengerObj.passengerDetailArray];
[self.navigationController popViewControllerAnimated:YES];
Once I navigate back I reload the table view.However celForRow method doesn't populate new values.
-(void)getPassengerinfo:(NSMutableArray*)arr{
passenger_infoArray =[[NSMutableArray alloc]init];
passenger_infoArray = arr;
NSLog(#"Passenger Info Array : %#", passenger_infoArray);//Shows array of dictionaries
[passengerInfoTableView reloadData];
}
My cellForRow method looks like this:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
//Not called when I am doing reload data
static NSString *CellIdentifier = #"Cell";
cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil)
{
cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:CellIdentifier ];
}
NSLog(#"Table view array : %#",passenger_infoArray);
if (passenger_infoArray.count>0) {
NSString *temp= [[passenger_infoArray objectAtIndex:indexPath.row]objectForKey:#"firstName"];
cell.textLabel.text = temp;
}else{
cell.textLabel.text = [NSString stringWithFormat:#"Passenger %ld", (long)indexPath.row+1];
cell.imageView.image = [UIImage imageNamed:#"user.png"];
}
return cell;
}
You can pass data using
Delegates
Notifications
Properties
Global variables
Using Delegates is good practice, and last two are not preferred.
You can pass data to previous view controller by using delegate method
In PassengerInfoViewController.h add below code just above of your import statement
#protocol passengerInfoViewControllerdelegate <NSObject>
-(void)sendData:(NSMutableArray *)arrData;
#end
After this create one property, as shown below
#property(nonatomic,assign)id delegate;
Now, in PassengerInfoViewController.m synthesize the property and after that
-(void)viewWillDisappear:(BOOL)animated
{
[delegate sendData:yourArray];
}
Now in tableview
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
PassengerInfoViewController *pivc = [self.storyboard instantiateViewControllerWithIdentifier:#"PassengerInfoViewController"];
pivc.delegate = self; //Add this line to your code
[self.navigationController pushViewController:pivc animated:YES];
}
Now, it's time to implement delegate method which we created in viewcontroller
-(void)sendData:(NSMutableArray *)arrData
{
passenger_infoArray = arrData;
[passengerInfoTableView reloadData];
}
Old way to do this was using delegates. Better way to do this is via an unwind segue because there's much less code and you don't have to create a protocol.
Once your form completes, your PDC should call [self performSegue:#"theNameOfTheUnwindSegueeYouCreateInTheStoryboard"]. Then, in your PDC's prepareForSegue method, get the destinationViewController property from the storyboardSegue parameter - this is your VC containing the Table View. Set whatever model property you're using to back the table view - in your case it looks to be the passenger_infoArray object, which you'll have to make mutable if isn't already and also expose publicly if it isn't already.

Show if tableview cell have been clicked before?

I'm developing an app with a news feed.
This is a PFQueryTableView loading content from parse.com.
I also have a detail ViewController attached with a segue.
My question is:
How can I show that there is a cell that hasn't been clicked before?
Like in the Mail application in IOS.
Here's a picture:
http://i.stack.imgur.com/vO2N3.png
- (id)initWithCoder:(NSCoder *)aCoder
{
self = [super initWithCoder:aCoder];
if (self) {
// Custom the table
// The className to query on
self.parseClassName = #"Nyhet";
// The key of the PFObject to display in the label of the default cell style
self.textKey = #"Rubrik";
// Whether the built-in pull-to-refresh is enabled
self.pullToRefreshEnabled = YES;
// Whether the built-in pagination is enabled
self.paginationEnabled = YES;
// The number of objects to show per page
self.objectsPerPage = 10;
}
return self;
}
- (UIStatusBarStyle)preferredStatusBarStyle
{
return UIStatusBarStyleDefault;
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Change button color
sidebarButton.tintColor = [UIColor whiteColor];
// Set the side bar button action. When it's tapped, it'll show up the sidebar.
sidebarButton.target = self.revealViewController;
sidebarButton.action = #selector(revealToggle:);
// Set the gesture
[self.view addGestureRecognizer:self.revealViewController.panGestureRecognizer];
}
- (void)viewDidUnload
{
[super viewDidUnload];
// Release any retained subviews of the main view.
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return (interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown);
}
- (PFQuery *)queryForTable
{
PFQuery *query = [PFQuery queryWithClassName:self.parseClassName];
// If no objects are loaded in memory, we look to the cache first to fill the table
// and then subsequently do a query against the network.
/* if ([self.objects count] == 0) {
query.cachePolicy = kPFCachePolicyCacheThenNetwork;
}*/
[query orderByDescending:#"Prioritering"];
return query;
}
// Override to customize the look of a cell representing an object. The default is to display
// a UITableViewCellStyleDefault style cell with the label being the first key in the object.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath object:(PFObject *)object
{
static NSString *simpleTableIdentifier = #"RecipeCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:simpleTableIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:simpleTableIdentifier];
}
UILabel *nameLabel = (UILabel*) [cell viewWithTag:101];
nameLabel.text = [object objectForKey:#"Rubrik"];
UILabel *prepTimeLabel = (UILabel*) [cell viewWithTag:102];
prepTimeLabel.text = [object objectForKey:#"Detaljer"];
return cell;
}
- (void) objectsDidLoad:(NSError *)error
{
[super objectsDidLoad:error];
NSLog(#"error: %#", [error localizedDescription]);
}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.identifier isEqualToString:#"showRecipeDetail"]) {
NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow];
RecipeDetailViewController *destViewController = segue.destinationViewController;
PFObject *object = [self.objects objectAtIndex:indexPath.row];
Recipe *recipe = [[Recipe alloc] init];
recipe.name = [object objectForKey:#"Rubrik"];
recipe.prepTime = [object objectForKey:#"Text"];
recipe.detaljer = [object objectForKey:#"Detaljer"];
recipe.rubriker = [object objectForKey:#"Rubrik"];
destViewController.recipe = recipe;
}
}
#end
I think it's better use custom UITableViewCell example:
CustomTableViewCell.h
#interface CustomTableViewCell : UITableViewCell
#property(assign) bool beforeClicked
#end
CustomTableViewCell.m
#import CustomTableViewCell.h
#implementation CustomTableViewCell
#synthesize beforeClicked;
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
// Initialization code
}
return self;
}
#end
And, example:
CustomTableViewCell.h //add this header
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath object:(PFObject *)object
{
static NSString *simpleTableIdentifier = #"RecipeCell";
CustomTableViewCell *cell = (CustomTableViewCell *)[tableView dequeueReusableCellWithIdentifier:simpleTableIdentifier];
if (cell == nil) {
cell = (CustomTableViewCell *)[[CustomTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:simpleTableIdentifier];
}
UILabel *nameLabel = (UILabel*) [cell viewWithTag:101];
nameLabel.text = [object objectForKey:#"Rubrik"];
UILabel *prepTimeLabel = (UILabel*) [cell viewWithTag:102];
prepTimeLabel.text = [object objectForKey:#"Detaljer"];
cell.beforeClicked = NO;
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath;
{
UITableViewCell *myCell = [tableView cellForRowAtIndexPath:indexPath];
myCell.beforeClicked = YES;
}
Two solutions come to mind.
The simpler solution would be using an NSMutableArray to hold all visited cells. In didSelectRowAtIndexPath: do this :
if (![visitedCellsArray containsObject:[NSNumber numberWithInt:indexPath.row]])
[visitedCellsArray addObject:[NSNumber numberWithInt:indexPath.row]];
Then set a backgroundColor to your cell (just a suggestion, you may use any UI indicator - dot, color, etc) in your cellForRowAtIndexPath:
Second, might not be conducive to your purpose. I don't really know if the dictionary you are using is mutable or not, and whether adding keys will have any effect on your flow of control. But If adding a key is possible, add a key like #"seen" and set it with [NSNumber numberWithBool:YES] in didSelectRowAtIndexPath:, and in cellForRowAtIndexPath: add appropriate UI indication based on the value of this key. This method is better memory wise.
I'd give your PFReceipt objects a property BOOL new.
Then just get and set that property and in cellForRow you set the font based on the vale of the property.
clean and even persistent.
...
so to reiterate:
modify PFReceipt object to have a property new
modify - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath object:(PFObject *)object
...
cell.clickedBefore = !receipe.new;
mark receipes as no longer new when shown
...
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.identifier isEqualToString:#"showRecipeDetail"]) {
...
PFObject *object = [self.objects objectAtIndex:indexPath.row];
selectedCell.clickedBefore = !object.new;
None of the solutions provided take into account that there may be more than one user of the app. If you store a "read" or "new" property in the object, this will only show if ANYBODY has read it. So if Jane reads it, Mark will see it as read.
To handle this separately for all users, you need another parse class to handle reading status. This gets a little complex to implement, but it is the only way to solve it all on the server.
Another option would be to store a read status list on every client. This means some kind of local persistent store that knows what recipes are read, and compare this to the data fetched from Parse.

How do I drill down through a plist using UITableViews?

I'm wanting to drill down through a plist using uitableviews to get the a specific school. The drill down goes state->district->school. I've created a plist, but am not 100% sure that the structure is the best. Also, I can get the first set of information active on the first tableview, but am not sure how to proceed from there. Will I need to create a tableview for each drill down(stateview, districtview, schoolview) or can I reuse a generic tableview since they will simple be lists? Below is what I have so far. Thanks for your help.
PLIST
<plist version="1.0">
<array>
<dict>
<key>districts</key>
<dict>
<key>District 1</key>
<array>
<string>School 2</string>
<string>School 1</string>
</array>
<key>District 2</key>
<array>
<string>School 3</string>
<string>School 4</string>
</array>
</dict>
<key>state</key>
<string>South Dakota</string>
</dict>
<dict>
<key>districts</key>
<array>
<string>District 1</string>
<string>District 2</string>
</array>
<key>state</key>
<string>Arkansas</string>
</dict>
<dict>
<key>districts</key>
<array>
<string>District 3</string>
<string>District 4</string>
</array>
<key>state</key>
<string>New York</string>
</dict>
</array>
</plist>
And here is my viewcontroller
#import "plistViewController.h"
#interface plistViewController ()
#end
#implementation plistViewController
- (id)initWithStyle:(UITableViewStyle)style
{
self = [super initWithStyle:style];
if (self) {
}
return self;
}
#synthesize content = _content;
-(NSArray *)content
{
if (!_content) {
_content = [[NSArray alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:#"Data" ofType:#"plist"]];
}
return _content;
}
- (void)viewDidLoad
{
[super viewDidLoad];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
}
#pragma mark - Table view data source
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [self.content count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
cell.textLabel.text = [[self.content objectAtIndex:indexPath.row] valueForKey:#"state"];
return cell;
}
#pragma mark - Table view delegate
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
// Navigation logic may go here. Create and push another view controller.
}
#end
The best thing about UITableView is that it doesn't care what data it displays. It just asks its delegate a few different questions:
How many sections should I have?
How many rows will be in each section?
May I have a UITableViewCell for this _ Index Path?
So, you have to focus on making your delegate responses provide the correct data.
So, first split your plist up into manageable chunks. The UITableView prima-donna dataSource is an NSArray. Neatly maps to tableViews because of indexing logic.
That said, your first tableViewController plistViewController has good logic for displaying info. Specifically, you are querying the NSDictionary at array position x and asking for it to return its state object. 3 Dictionary objects, 3 strings returned. Nice.
So how do you go to the next level? Your tableView will help you here. It asks a specific question of its delegate:
What do I do when user touches Section Y Row X?
You're going to need to set up another UITableViewController subclass called DistrictViewController. In the header .h file, you are going to need to make a strong property to an NSDictionary object. like so:
//DistrictViewController.h
#interface DistrictViewController : UITableViewController
#property (nonatomic, strong) NSDictionary *districtDictionary;
#end
//DistrictViewController.m
#implementation DistrictViewController
#synthesize districtDictionary;
And there we have it. This class is now set up to keep track of 1 NSDictionary object. Now you just have to configure your table delegate methods to show you the data you want.
The first example, what would be in the top Row (index:0) of the NSArray, you have a dictionary that has 2 keys: District 1 and District 2. But this is a problem. NSDictionary doesn't map to TableViews quite as easily, because NSDictionary objects don't use indexes to work. Don't fret. NSDictionary has a method called allKeys, which will give you an array of each key in the dictionary. This is useful for when you will be receiving an NSDictionary from somewhere but not know what keys it contains beforehand.
So, the questions your tableView asks, let's answer them:
//How many sections will be in me: Let's just say 1 for now.
//How many rows will be in this section:
//Ask the NSDictionary how many keys it has:
NSArray *keyArray = [self.districtDictionary allKeys];
return [keyArray count];
//Give me a tableCell for index path X,Y
//First, get your array of keys back:
NSArray *keyArray = [self.districtDictionary allKeys];
//Next, find the key for the given table index:
NSString *myKey = [keyArray objectAtIndex:indexPath.row];
//Finally, display this string in your cell:
cell.textLabel.text = myKey;
After this, you'd do the same thing for the final view. Set up a viewController for schools and call it SchoolViewController and make set it up to be in charge of an NSArray. Just like before:
#interface SchoolViewController : UITableViewController
#property (nonatomic, strong) NSArray *schoolArray;
#end
#implementation SchoolViewController
#synthesize schoolArray;
In this view, it will be a lot like the first. You just have this viewController answer the table's questions like before:
How many sections? We need 1
How many rows? We need as many as in the array return [schoolArray count];
Give me a cell: cell.textLabel.text = [schoolArray objectAtIndex:indexPath.row];
The final piece that puts this all together is in the final question the table asks.
What do I do when a user touches a row?
In each view, look at this method signature:
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
This is where you add your logic to hook things up. In the first view plistViewController, do this:
NSDictionary *topLevelDictionary = [self.content objectAtIndex:indexPath.row];
NSDictionary *allDistricts = [topLevelDictionary objectForKey:#"districts"];
DistrictViewController *dView = [[DistrictViewController alloc] initWithStyle:UITableViewStylePlain];
dView.districtDictionary = allDistricts;
[self.navigationController pushViewController:dView animated:YES];
In the second view, DistrictViewController do this:
NSArray *keyArray = [self.districtDictionary allKeys];
NSString *myKey = [keyArray objectAtIndex:indexPath.row];
NSArray *schoolArray = [self.districtDictionary objectForKey:myKey];
SchoolViewController *sView = [[SchoolViewController alloc]initWithStyle:UITableViewStylePlain];
sView.schoolArray = schoolArray;
[self.navigationController pushViewController:sView animated:YES];
I hope this helps you. I typed this all in a plain text editor. Hopefully there's no misspellings. You'll need to #import the associated viewControllers in each one! Good luck.
To create drill down tables:
you can do:
- (void)viewDidLoad
{
[super viewDidLoad];
NSArray *districts = [NSArray arrayWithObjects:#"district1", #"district2", #"district3", nil];
NSArray *states = [NSArray arrayWithObjects:#"NY", #"NJ", #"NO", #"StateOther1", #"StateOther2", nil];
NSArray *schools = [NSArray arrayWithObjects:#"", #"school1", #"school2", #"school3", #"school4", nil];
NSMutableDictionary *schoolSection = [NSMutableDictionary dictionary];
[schoolSection schools forKey:#"items"];
[schoolSection setObject:#"Shools" forKey:#"title"];
NSMutableDictionary *districtSection = [NSMutableDictionary dictionary];
[districtSection setObject:districts forKey:#"items"];
[districtSection setObject:#"Section" forKey:#"title"];
NSMutableDictionary *stateSection = [NSMutableDictionary dictionary];
[districtSection setObject:states forKey:#"items"];
[districtSection setObject:#"State" forKey:#"title"];
self.adresses = #[schoolSection, districtSection,stateSection];
}
Next:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return self.adresses.count;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
NSDictionary *currentSection = [self.adresses objectAtIndex:section];
if ([[currentSection objectForKey:#"isOpen"] boolValue]) {
NSArray *items = [currentSection objectForKey:#"items"];
return items.count;
}
return 0;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:CellIdentifier];
}
NSDictionary *currentSection = [self.adresses objectAtIndex:indexPath.section];
NSArray *items = [currentSection objectForKey:#"items"];
NSString *currentItem = [items objectAtIndex:indexPath.row];
cell.textLabel.text = currentItem;
return cell;
}
Next:
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
NSDictionary *currentSection = [self.adresses objectAtIndex:section];
NSString *sectionTitle = [currentSection objectForKey:#"title"];
BOOL isOpen = [[currentSection objectForKey:#"isOpen"] boolValue];
NSString *arrowNmae = isOpen? #"arrowUp":#"arrowDown";
UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
button.frame = CGRectMake(0.0f, 0.0f, 320.0f, 50.0f);
button.tag = section;
button.backgroundColor = [UIColor brownColor];
[button setTitle:sectionTitle forState:UIControlStateNormal];
[button addTarget:self action:#selector(didSelectSection:)
forControlEvents:UIControlEventTouchUpInside];
[button setImage:[UIImage imageNamed:arrowNmae] forState:UIControlStateNormal];
return button;
}
Next:
- (void)didSelectSection:(UIButton*)sender {
//get current section
NSMutableDictionary *currentSection = [self.adresses objectAtIndex:sender.tag];
//get elements of section
NSArray *items = [currentSection objectForKey:#"items"];
//create array of indexes
NSMutableArray *indexPaths = [NSMutableArray array];
for (int i=0; i<items.count; i++) {
[indexPaths addObject:[NSIndexPath indexPathForRow:i inSection:sender.tag]];
}
//get current state of section is opened
BOOL isOpen = [[currentSection objectForKey:#"isOpen"] boolValue];
//set new state
[currentSection setObject:[NSNumber numberWithBool:!isOpen] forKey:#"isOpen"];
//animate of adding and deleting of cells
if (isOpen) {
[self.tableView deleteRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationTop];
} else {
[self.tableView insertRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationTop];
}
//reload button image
NSString *arrowNmae = isOpen? #"arrowDown.png":#"arrowUp.png";
[sender setImage:[UIImage imageNamed:arrowNmae] forState:UIControlStateNormal];
}
And you can customize this table, like you need.
Example of drill down tables you can download here (click "Скачать" button)
You should pass the array of districts to a new view controller that can display them. The new view controller should have a property called districts, I would also recommend creating an initializer that accepts an array of districts which sets this property.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSArray *districts = [[self.content objectAtIndex:indexPath.row] valueForKey:#"districts"];
DistrictsViewController *districtsvc =
[[DistrictsViewController alloc] initWithNibName:nil
bundle:nil
districts:districts];
[self.navigationController pushViewController:districtsvc];
}
From your example I'm not sure where the school info would come from so if it is difficult to say if you would be able to easily create a single generic view controller to drill down from state to school.

Resources