I have a table view that lists athletes. when an athlete is selected, I wish to have the detail view controller (the controller that is pushed onto the stack) to know all the attributes about the athlete. his/her name, birthday, phone number, etc. But im unsure on how to pass this information.
allathletes.h
-(void)viewWillAppear:(BOOL)animated{
AppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
_managedObjectContext = [appDelegate managedObjectContext];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *athlete = [NSEntityDescription entityForName:#"Athlete" inManagedObjectContext:_managedObjectContext];
[request setEntity:athlete];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"last" ascending:YES];
NSArray *sortDescriptors = [[NSArray alloc]initWithObjects:sortDescriptor, nil];
[request setSortDescriptors:sortDescriptors];
NSError *error = nil;
NSMutableArray *mutableFetchResults = [[_managedObjectContext executeFetchRequest:request error:&error] mutableCopy];
if (mutableFetchResults == nil){
//handle error
}
[self setAthleteArray:mutableFetchResults];
[self.tableView reloadData];
}
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
NSString *segueIdentifier = [segue identifier];
if ([segueIdentifier isEqualToString:#"setAthlete"])
{
UINavigationController *navController = (UINavigationController *)[segue destinationViewController];
AllAthletes *athleteList = (AllAthletes *)[[navController viewControllers] lastObject];
//the line below gets an error :(
AthleteDetail.managedObjectContext = self.managedObjectContext;
}
}
Before pushing a detail view controller, set a property on it with the data to be displayed, like:
myDetailViewController.myModel = selectedModel;
In the detail view, you can set up the view using this data in viewWillAppear.
I think you're going to want to use delegates. Here is a great tutorial on how to do that: Link
Related
I have simply TODO list in which there are:
protocol's method reloadTableViewWhenItChanges;
fetch data (i.e. core data);
Table view reloads after stop/run compiler.
But I need to reload table view in viewDidLoad (not in viewWillAppear/viewDidAppear), using delegation table reloads only after something has changed in managedObjectContext.
How can I do that?
Here is my code (what is wrong in it? Where do I have to put [self.tableView reloadData] in my code, for example?):
- (void)viewDidLoad {
[super viewDidLoad];
self.title = #"Note List";
self.barButtonAddNote = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd
target:self
action:#selector(addNewNote:)];
[self.navigationItem setRightBarButtonItem:self.barButtonAddNote animated:YES];
[self reloadTableViewWhenItChanges]; }
- (void)reloadTableViewWhenItChanges // protocol's method {
self.addNoteViewController.delegate = self;
if ([self.managedObjectContext hasChanges])
{
NSError *error;
[self.managedObjectContext save:&error];
}
[self fetchData];
[self.tableView reloadData]; }
- (void)fetchData {
NSFetchRequest *request = [[NSFetchRequest alloc]init];
NSEntityDescription *note = [NSEntityDescription entityForName:#"Note"
inManagedObjectContext:self.managedObjectContext];
[request setEntity:note];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"task" ascending:YES];
NSArray *sortDescriptors = [NSArray arrayWithObjects:sortDescriptor, nil];
[request setSortDescriptors:sortDescriptors];
NSError *error = nil;
NSArray *mutableFetchResults = [self.managedObjectContext executeFetchRequest:request error:&error];
if (mutableFetchResults != nil){
self.notesArray = [[NSMutableArray alloc] initWithArray:mutableFetchResults];
} }
As mentioned by #eik_cloner, you don't need to reload table view in viewDidLoad because it's done automatically.
If you want to do it manually then you have to call reloadData method of UITableView wherever you want as:
[yourTableView reloadData];
In Objective C :-
[tableViewName reloadData];
In Swift :-
tableViewName.reloadData()
Make sure you have Configure the delegates with UIViewController through UIStoryboard or By code.
I am having the following issue when presenting the UITableViewController.
List[12426:444700] Warning: Attempt to present <UserTableViewController: 0x7fe4ba5d8e50> on <UserViewController: 0x7fe4ba5b6db0> whose view is not in the window hierarchy!
Here's the part of my code
#implementation UserTableViewController
#synthesize users;
- (void)viewDidLoad {
// UserTableViewController
// Create Fetch Request in viewDidLoad
AppDelegate *appDelegate = [[UIApplication sharedApplication]delegate];
//Create NSManagedObjectContext instance
NSManagedObjectContext *context = [appDelegate managedObjectContext];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"User" inManagedObjectContext:context];
NSFetchRequest *request = [[NSFetchRequest alloc]init];
[request setEntity:entity];
[request setReturnsObjectsAsFaults:NO];
NSError *error;
NSArray *array = [[context executeFetchRequest:request error:&error]mutableCopy];
//setting users with array of fetched objects
[self setUsers:array];
NSLog(#"firstName is %#",array);
//[self presentViewController:UserTableViewController animated:YES completion:nil];
}
Really appreciate help.
You are attempting to present ([self presentViewController:animated:completion:]) UserTableViewController from within your UserViewController when your UserViewController instance is not part of the window hierarchy, e.g. on the navigation stack (has been pushed or presented itself).
I have read a lot of content and watched several tutorials on how to search Core Data by using a search bar however I have not yet seen anything for how to change a the sort descriptor by passing a setting from a Settings view to the Table View.
I have a search bar button item that when pressed goes to a SearchSettingsVC. The views communicate by passing boolean's from one VC to the other and vise-versa. The problem is that the table is not corresponding accordingly by changing the TableVC order - (I have tried calling self.tableview beginUpdates, self.tableview reload, self fetchedResultsController among other things).
The point is to reorder the TableVC results, not to present only specific results like a predicate does
I have created a delegate for the SettingsVC so that I can pass boolean value to the SettingsVC which is then capable of returning a different changed value if there are any changes.
The problem is that I cannot manage to get the table view (or prehaps even the fetched results) to update.
Here is my code for my -(NSFetchedResultsController*) fetchedResultsController method:
// return if already initialized
if (self.fetchedResultsController != nil) {
return self.fetchedResultsController;
}
if (dateSearch == true){
NSManagedObjectContext *managedObjectContext = [self managedObjectContext];
// the entity to fetch
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Details" inManagedObjectContext:managedObjectContext];
// how to sort the data
NSSortDescriptor *sort = [NSSortDescriptor sortDescriptorWithKey:#"addDate" ascending:YES];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:entity];
[request setSortDescriptors:[NSArray arrayWithObject:sort]];
self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:request
managedObjectContext:managedObjectContext
sectionNameKeyPath:nil
cacheName:nil];
// fetch the data
NSError *e = nil;
if (![self.fetchedResultsController performFetch:&e]) {
NSLog(#"fetch error(Date): %#", e );
abort();
}
NSLog(#"Dates loaded");
}
if (mostAmount == true){
NSManagedObjectContext *managedObjectContext = [self managedObjectContext];
// the entity to fetch
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Details" inManagedObjectContext:managedObjectContext];
// how to sort the data
NSSortDescriptor *sort = [NSSortDescriptor sortDescriptorWithKey:#"amount" ascending:NO];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:entity];
[request setSortDescriptors:[NSArray arrayWithObject:sort]];
self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:request
managedObjectContext:managedObjectContext
sectionNameKeyPath:nil
cacheName:nil];
// fetch the data
NSError *e = nil;
if (![self.fetchedResultsController performFetch:&e]) {
NSLog(#"fetch error (Most Fuel): %#", e);
abort();
}
NSLog(#"Amount loaded");
}
else{
NSManagedObjectContext *managedObjectContext = [self managedObjectContext];
// the entity to fetch
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Details" inManagedObjectContext:managedObjectContext];
// how to sort the data
NSSortDescriptor *sort = [NSSortDescriptor sortDescriptorWithKey:#"addDate" ascending:YES];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:entity];
[request setSortDescriptors:[NSArray arrayWithObject:sort]];
self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:request
managedObjectContext:managedObjectContext
sectionNameKeyPath:nil
cacheName:nil];
// fetch the data
NSError *e = nil;
if (![self.fetchedResultsController performFetch:&e]) {
NSLog(#"fetch error(Date): %#", e );
abort();
}
NSLog(#"Defualt loaded");
}
return self.fetchedResultsController;
I required a default because at the beginning when I initialise the booleans in the TableVC to false they can be updated when I go to my settings VC.
My SearchSettingsVC has UISwitches that change the values from true to false (and vice-versa) which successfully update the equivalent booleans in the TableVC as represented when I return to the SearchSettingsVC.
My prepareForSegue code in my TableVC
if ([[segue identifier] isEqualToString:#"searchSettings"]){
//pass new search settings in here
SearchSelectionSettings * settingsVC = (SearchSelectionSettings *)segue.destinationViewController;
settingsVC.delegate = self;
settingsVC.dateSearch = dateSearch;
settingsVC.mostAmount = mostAmount;
This is my closeSettings method which is located in my TableVC
#pragma mark - SettingsViewControllerDelegate methods
//record the settings changed in the settings view
//dismissViewController changes the screen
- (void)closeSettings:(id)sender {
NSLog(#"Working?");
dateSearch = ((SearchSelectionSettings *)sender).dateSearch;
mostAmount = ((SearchSelectionSettings *)sender).mostAmount;
[self dismissViewControllerAnimated:YES completion:nil];
[self FetchedResultsController];
NSIndexPath * indexPath;
[self tableView:self.tableView cellForRowAtIndexPath:indexPath];
}
Passing boolean values from one VC to other works perfectly. Getting the TableView to update according to the fetchedResults if statements does not, my switches in the SettingsVC are updated every time without any issues. Can anyone help or recommend a tutorial?
The opening lines of the fetchedResultscontroller getter, i.e.:
// return if already initialized
if (self.fetchedResultsController != nil) {
return self.fetchedResultsController;
}
mean that, once your fetchedResultsController has been created, the remaining code will not be executed when you access it. A quick way to resolve your problem would therefore be to set self.fetchedResultsController to nil in your closeSettings method, and then reload your table view. When the tableview reloads, it will access the fetchedResultsController again, and since it is now nil, the above code will be bypassed and your code will be used.
#pragma mark - SettingsViewControllerDelegate methods
//record the settings changed in the settings view
//dismissViewController changes the screen
- (void)closeSettings:(id)sender {
NSLog(#"Working?");
dateSearch = ((SearchSelectionSettings *)sender).dateSearch;
mostAmount = ((SearchSelectionSettings *)sender).mostAmount;
[self dismissViewControllerAnimated:YES completion:nil];
self.fetchedResultsController = nil;
[self.tableView reloadData];
}
Alternatively, you can modify the fetchedResultsController's fetch and then get it to reperform the fetch:
#pragma mark - SettingsViewControllerDelegate methods
//record the settings changed in the settings view
//dismissViewController changes the screen
- (void)closeSettings:(id)sender {
NSLog(#"Working?");
dateSearch = ((SearchSelectionSettings *)sender).dateSearch;
mostAmount = ((SearchSelectionSettings *)sender).mostAmount;
[self dismissViewControllerAnimated:YES completion:nil];
NSFetchRequest *request = [[NSFetchRequest fetchRequestWithEntityName:#"Details"];
NSSortDescriptor *sort;
if (dateSearch == true){
sort = [NSSortDescriptor sortDescriptorWithKey:#"addDate" ascending:YES];
NSLog(#"Dates loaded");
} else if (mostAmount == true) {
sort = [NSSortDescriptor sortDescriptorWithKey:#"amount" ascending:NO];
NSLog(#"Amount loaded");
} else {
sort = [NSSortDescriptor sortDescriptorWithKey:#"addDate" ascending:YES];
NSLog(#"Default loaded");
}
[request setSortDescriptors:[NSArray arrayWithObject:sort]];
self.fetchedResultsController.fetchRequest = request;
// fetch the data
NSError *e = nil;
if (![self.fetchedResultsController performFetch:&e]) {
NSLog(#"fetch error (Most Fuel): %#", e);
abort();
}
[self.tableView reloadData];
}
That way, you can simplify your fetchedResultsController code so it just loads the default.
I build my TabBar programmatically and based on if the User is loggedIn, I set
[self.window setRootViewController:home];
Here is the code I call in:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
if (![Persistence loggedIn])
{
[self showLoginScreen];
}
else
{
SignupBase *login = [STLoginSignupBase new];
[login loginUserwithUsername:[Persistence username] andPassword:[Persistence authPass] requestByNewUser:NO completionBlock:^(NSError *error)
{
if (!error)
{
[login loginSuccess];
[self showTabBarScreen];
}
else
{
[STAlertViewUtils showAlert:#"" :error.localizedDescription :kButtonTitleDismiss];
[self showLoginScreen];
}
}];
}
-(void)showTabBarScreen
{
dispatch_async(dispatch_get_main_queue(), ^{
TabBarVC *tabBarVC = [[TabBarVC alloc]init];
[self.window setRootViewController:tabBarVC];
});
}
-(void)showLoginScreen
{
dispatch_async(dispatch_get_main_queue(), ^{
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"STLoginSignup" bundle:nil];
HomeVC *homeVC = (HomeVC *)[storyboard instantiateViewControllerWithIdentifier:[HomeVC storyboardID]];
UINavigationController *home = [[UINavigationController alloc]initWithRootViewController:homeVC];
[self.window setRootViewController:home];
});
}
In the TabBar, the first tab "Inbox" is a tableViewController managed with NSFetchedResultsController. When I launch the app for the first time, all the objects are fetched and displayed in the tableView beautifully; however, when I logout and login back in, and "Inbox" is reloaded, I get a blank tableView. Zero objects are fetched locally and even if RESTkit fetches objects, they don't appear in the tableView. When I stop the app in the simulator and relaunch it, all the objects are fetched locally and remotely, and appear in the tableView as they should!
Here is how I logout from the Profile tab (different tab):
- (void)logoutWithCompletionBlock:(void(^)(void))completionBlock
{
[Persistence setLoggedInStatus:NO];
RKObjectManager *objectManager = [self getObjectManager];
[objectManager.HTTPClient clearAuthorizationHeader];
AppDelegate *delegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
NSManagedObjectContext *managedObjectContext = delegate.managedObjectStore.mainQueueManagedObjectContext;
[managedObjectContext reset];
[delegate deregisterWithUrbanAirship];
if (completionBlock)
{
completionBlock();
}
}
After I log back in the App, "Inbox" tab viewController is loaded again.
In my "Inbox" loadView which gets called, I have the following code:
- (void)loadView
{
[self getManagedObjectFromAppDelegate]
}
- (void)getManagedObjectFromAppDelegate
{
AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
[appDelegate setupCoreDataWithRESTKit];
self.objectManager = [self getObjectManager];
self.objectManager.managedObjectStore = appDelegate.managedObjectStore;
self.objectManager.managedObjectStore.managedObjectCache = appDelegate.managedObjectStore.managedObjectCache;
self.managedObjectContext = self.objectManager.managedObjectStore.mainQueueManagedObjectContext;
}
This is the code in [AppDelegate setupCoreDataWithRESTKit];
- (RKManagedObjectStore *)setupCoreDataWithRESTKit
{
NSError * error;
NSURL * modelURL = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:#"App" ofType:#"momd"]];
NSManagedObjectModel * managedObjectModel = [[[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL] mutableCopy];
self.managedObjectStore = [[RKManagedObjectStore alloc] initWithManagedObjectModel:managedObjectModel];
[self.managedObjectStore createPersistentStoreCoordinator];
NSArray * searchPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString * documentPath = [searchPaths objectAtIndex:0];
NSPersistentStore * persistentStore = [self.managedObjectStore addSQLitePersistentStoreAtPath:[NSString stringWithFormat:#"%#/App%#.sqlite", documentPath, [Persistence username]] fromSeedDatabaseAtPath:nil withConfiguration:nil options:[self optionsForSqliteStore] error:&error];
NSAssert(persistentStore, #"Failed to add persistent store with error: %#", error);
NSLog(#"Path: %#", [NSString stringWithFormat:#"%#/App%#.sqlite", documentPath, [Persistence username]]);
if(!persistentStore){
NSLog(#"Failed to add persistent store: %#", error);
}
[self.managedObjectStore createManagedObjectContexts];
return self.managedObjectStore;
}
Please note that each user has a different .sqlite file loaded based on their username: i.e. AppUserName. So when I logout and log back in if it's a same user, then the same file is created/loaded. If it's a different user, then a different name file is created/loaded.
Question: Why does NSFetchedResultsController displays an empty tableView after I logout and log back in, but it works fine when I launch the app the first time?
*EDIT *
I changed and tried the code below but the problem persists:
- (void)logoutWithCompletionBlock:(void(^)(void))completionBlock
{
[Persistence setLoggedInStatus:NO];
RKObjectManager *objectManager = [self getObjectManager];
[objectManager.HTTPClient clearAuthorizationHeader];
AppDelegate *delegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
NSManagedObjectContext *managedObjectContext = delegate.managedObjectStore.mainQueueManagedObjectContext;
[self clearManagedObjectContext:managedObjectContext];
[delegate deregisterWithUrbanAirship];
if (completionBlock)
{
completionBlock();
}
}
- (void)clearManagedObjectContext:(NSManagedObjectContext*)managedObjectContext
{
NSFetchRequest * fetch = [[NSFetchRequest alloc] init];
[fetch setEntity:[NSEntityDescription entityForName:#"EntityA" inManagedObjectContext:managedObjectContext]];
NSMutableArray *result = [NSMutableArray arrayWithArray:[managedObjectContext executeFetchRequest:fetch error:nil]];
for (id entityA in result)
{
[managedObjectContext deleteObject:entityA];
}
[result removeAllObjects];
[fetch setEntity:[NSEntityDescription entityForName:#"EntityB" inManagedObjectContext:managedObjectContext]];
result = [NSMutableArray arrayWithArray:[managedObjectContext executeFetchRequest:fetch error:nil]];
for (id entityB in result)
{
[managedObjectContext deleteObject:entityB];
}
[result removeAllObjects];
[fetch setEntity:[NSEntityDescription entityForName:#"EntityC" inManagedObjectContext:managedObjectContext]];
result = [NSMutableArray arrayWithArray:[managedObjectContext executeFetchRequest:fetch error:nil]];
for (id entityC in result)
{
[managedObjectContext deleteObject:entityC];
}
[result removeAllObjects];
[managedObjectContext saveToPersistentStore:nil];
}
You shouldn't be doing [managedObjectContext reset]; unless you tear down the persistent store that is backing the main thread context (so, tear down the whole Core Data stack and destroy the SQLite file).
Either correct this or just loop over the things you want to delete in the context and save the changes up to the (parent) persistent context.
I have set up a 1 to many relationship on my core data entities. I am trying to show the detailview copy of the associated data. Currently I have the prepareforseague: method working with the original entity(Routines), however I am at a lose as to how to show the linked entity (RoutinesDetails).
FBCDRoutineViewController
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
// Fetch the devices from persistent data store
NSManagedObjectContext *managedObjectContext = [self managedObjectContext];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] initWithEntityName:#"Routines"];
self.routines = [[managedObjectContext executeFetchRequest:fetchRequest error:nil] mutableCopy];
[self.tableView reloadData];
}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([[segue identifier] isEqualToString:#"UpdateDevice"]) {
NSManagedObject *selectedDevice = [self.routines objectAtIndex:[[self.tableView indexPathForSelectedRow] row]];
FBCDRoutineViewController *destViewController = segue.destinationViewController;
destViewController.routines = selectedDevice;
}
FBCDRoutineDetailViewController
- (NSManagedObjectContext *)managedObjectContext {
NSManagedObjectContext *context = nil;
id delegate = [[UIApplication sharedApplication] delegate];
if ([delegate performSelector:#selector(managedObjectContext)]) {
context = [delegate managedObjectContext];
}
return context;
}
- (id)initWithStyle:(UITableViewStyle)style
{
self = [super initWithStyle:style];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
// Fetch the devices from persistent data store
NSManagedObjectContext *managedObjectContext = [self managedObjectContext];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] initWithEntityName:#"RoutinesDetails"];
self.routines = [[managedObjectContext executeFetchRequest:fetchRequest error:nil] mutableCopy];
}
- (void)viewDidLoad
{
[[self navigationController] setNavigationBarHidden:NO animated:YES];
[super viewDidLoad];
// Do any additional setup after loading the view.
if (self.routines) {
[self.testLabel setText:[self.routines valueForKey:#"routinename"]];
}
}
FBCDRoutineDetailViewController
#property (strong) NSManagedObject *routines;
This is my first time with core data and I am looking at how to show the Details entity. Am I Close to getting it working? If not can I get directed at to what I should be looking at.
Any suggestions?
If I understand your problem correctly, you want to display all RoutinesDetails objects
that are related to the Routines object passed in prepareForSegue.
Then you would declare two properties in the FBCDRoutineDetailViewController:
#property (strong) NSManagedObject *routines; // the passed object
#property (strong) NSManagedObject *routineDetails; // the displayed details objects
and fetch them like this:
NSManagedObjectContext *managedObjectContext = [self managedObjectContext];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] initWithEntityName:#"RoutinesDetails"];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"routineinfo == %#", self.routines];
[fetchRequest setPredicate:predicate];
NSError *error;
self.routineDetails = [managedObjectContext executeFetchRequest:fetchRequest error:&error];
self.routineDetails is now the data source array for the details view.
(Remark: For displaying the result set of a Core Data request in a table view,
you might also consider to use a NSFetchedResultsController.)
I think I see several problems--mostly related to the timing of these various calls.
I believe viewDidLoad is called on your detail view before the prepareForSegue is called. So your code in viewDidLoad is trying to display data about your detail item before it has been set.
Then the code in viewDidAppear looks to be overwriting the value you set in prepareForSegue, which doesn't make sense to me (although by this time the view is already displayed and it's not going to affect the label you tried to set in viewDidLoad).
Also, executeFetchRequest: returns an NSArray, not an NSManagedObject, so assigning the result of your fetch to your NSArray property is a BAD idea.