Objective C view controller property not saving - ios

Hi I am trying to set my UIViewController's managed object context, but the object context is not saved. Here's the code:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
UIStoryboard *mainStoryboard = [UIStoryboard storyboardWithName:#"Main" bundle: nil];
my_TableViewController *viewController = [mainStoryboard instantiateViewControllerWithIdentifier:#"coretut"];
if ([viewController isKindOfClass:[my_TableViewController class]]) {
[viewController setOManagedObjectContext:self.managedObjectContext];
}
NSLog(#"%#", self.managedObjectContext);
NSLog(#"%#", viewController.oManagedObjectContext);
}
The output for the following is
Apple_Tutorial[11241:461826] <NSManagedObjectContext: 0x7fb558d86600>
Apple_Tutorial[11241:461826] <NSManagedObjectContext: 0x7fb558d86600>
However when i call
NSLog(#"%#", self.oManagedObjectContext);
In viewDidLoad() for my_TableViewController the output is null. oManagedObjectContext is declared as (strong, nonatomic). Does anyone know why oManagedObjectContext went to null?
viewDidLoad code:
- (void)viewDidLoad {
[super viewDidLoad];
UINib *nib = [UINib nibWithNibName:#"my_TableViewCell" bundle:nil];
[[self tableView] registerNib:nib forCellReuseIdentifier:#"tableViewCell"];
NSLog(#"%#", self.oManagedObjectContext);
}

The problem is that didFinishLaunchingWithOptions is instantiating a new view controller and then doing nothing with it (i.e. discarding it). So you're looking at two different view controller instances.
You might have the app delegate set the root view controller's oManagedObjectContext:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
ViewController *controller = (id)self.window.rootViewController;
NSAssert([controller isKindOfClass:[ViewController class]], #"Root controller should be `ViewController`, but is %#", controller);
controller.oManagedObjectContext = self.managedObjectContext;
return YES;
}
Clearly, if your view controller in question isn't the root controller (e.g. if it's in some container view controller, such as navigation controller, tab bar controller, custom container controller, etc.) then you'd have to tweak the above code to navigate through that hierarchy to find your view controller class.

Related

Switching to an arbitrary view using `UINavigationController`

I have an iPhone app that uses a UINavigationController that is created as so:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Create navigation controller and initialize it with the menu view controller.
navigationController = [[UINavigationController alloc] initWithRootViewController:[[MenuViewController alloc] init]];
navigationController.navigationBar.hidden = YES;
navigationController.toolbar.hidden = YES;
// Create main window and initialize it with navigation view controller.
window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
[window setRootViewController:navigationController];
[window makeKeyAndVisible];
return YES;
}
From there things usually happen in a sequence similar to the following:
Push SelectDifficultyViewController
Push GameViewController
Push GameOverViewController
Pop to root (MenuViewController)
Instead of popping to the root in step 4, how would I go about switching to a new instance of GameViewController.
I currently have the following but it just returns me to the root:
[self.navigationController popToRootViewControllerAnimated:NO];
[self.navigationController pushViewController:[[GameViewController alloc] initWithStuff:stuff] animated:NO];
As it turns out, the line [self.navigationController popToRootViewControllerAnimated:NO] will result in self.navigationController being nil, which is why the subsequent push to the navigationController does nothing. To fix this, store a local copy of the navigation controller and use that to push after popping to root. Credit for this answer comes from here:
UINavigationController popToRootViewController, and then immediately push a new view
mehinger's answer solved my problem, but I wanted to make it easier to use, so I made the following category.
UINavigationController+PopAndPush.h
#interface UINavigationController(PopAndPush)
- (void)popAndPushToViewController:(UIViewController *)controller animated:(BOOL)animated;
- (void)popAndPushToViewController:(UIViewController *)controller withCustomTransition:(CustomViewAnimationTransition)transition;
#end
UINavigationController+PopAndPush.m
#import "UINavigationController+PopAndPush.h"
#implementation UINavigationController(PopAndPush)
- (void)popAndPushToViewController:(UIViewController *)controller animated:(BOOL)animated {
[self popToRootViewControllerAnimated:NO];
[self pushViewController:controller animated:animated];
}
- (void)popAndPushToViewController:(UIViewController *)controller withCustomTransition:(CustomViewAnimationTransition)transition {
[self popToRootViewControllerAnimated:NO];
[self pushViewController:controller withCustomTransition:transition subtype:nil];
}
#end

UIViewControllers being created but not shown in state restoration

I have an app that is not working properly with state restoration. It previously did, but as I started moving away from the storyboard it stopped.
My app starts with a LoginViewController that is the starting view controller in my storyboard. If the login is successful, then it tries to add two FolderViewController to a navigation controller. This is so that the visible folder is one level deep already. This is done in the following code:
UINavigationController *foldersController = [[UINavigationController alloc] initWithNavigationBarClass:nil toolbarClass:nil];
foldersController.restorationIdentifier = #"FolderNavigationController";
FolderViewController *root = [storyboard instantiateViewControllerWithIdentifier:#"FolderView"];
root.folderId = 0;
FolderViewController *fvc = [storyboard instantiateViewControllerWithIdentifier:#"FolderView"];
fvc.folderId = 1;
[foldersController setViewControllers:#[root, fvc] animated:YES];
[self presentViewController:foldersController animated:YES completion:nil];
The FolderViewController has this awakeFromNib
- (void)awakeFromNib
{
[super awakeFromNib];
self.restorationClass = [self class]; // If we don't have this, then viewControllerWithRestorationIdentifierPath won't be called.
}
Within the storyboard the FolderViewController has a restorationIdentifier set. When I press the Home button, the app is suspended. My restoration calls in FolderViewController are being called:
// This is being called
- (void)encodeRestorableStateWithCoder:(NSCoder *)coder
{
[super encodeRestorableStateWithCoder:coder];
[coder encodeInt64:self.folderId forKey:#"folderId"];
}
The problem now is when I try and restore. I stop the app in my debugger and then start it up again. This kicks off the restoration process.
First, my viewControllerWithRestorationIdentifierPath:coder: for my LoginViewController is called. This doesn't do much, and its use is optional. I've tried removing it and I don't have any ill effect.
+ (UIViewController*) viewControllerWithRestorationIdentifierPath:(NSArray *)identifierComponents coder:(NSCoder *)coder
{
LoginViewController* vc;
UIStoryboard* sb = [coder decodeObjectForKey:UIStateRestorationViewControllerStoryboardKey];
if (sb)
{
vc = (LoginViewController *)[sb instantiateViewControllerWithIdentifier:#"LoginViewController"];
vc.restorationIdentifier = [identifierComponents lastObject];
vc.restorationClass = [LoginViewController class];
}
return vc;
}
Next, the viewControllerWithRestorationIdentifierPath:coder: for my FolderViewController is called:
// This is being called
+ (UIViewController*) viewControllerWithRestorationIdentifierPath:(NSArray *)identifierComponents coder:(NSCoder *)coder
{
FolderViewController* vc;
UIStoryboard* sb = [coder decodeObjectForKey:UIStateRestorationViewControllerStoryboardKey];
if (sb)
{
vc = (FolderViewController *)[sb instantiateViewControllerWithIdentifier:#"FolderView"];
vc.restorationIdentifier = [identifierComponents lastObject];
vc.restorationClass = [FolderViewController class];
vc.folderId = [coder decodeInt32ForKey:#"folderId"];
}
return vc;
}
I've previously had a decodeRestorableStateWithCoder: as well, and it did get called. However, since it's setup in the viewControllerWithRestorationIdentifierPath:coder:, it wasn't necessary to keep it around.
All of these things are being called the appropriate number of times. But in the end, the only view controller that is displayed in the LoginViewController. Why are my FolderViewControllers not being displayed. Is there a missing setup that I need to do in my LoginViewController to attach the view controllers that I manually added previously?
Edit
After reading http://aplus.rs/2013/state-restoration-for-modal-view-controllers/ which seemed relevant, I added the following code to the App delegate:
- (UIViewController *)application:(UIApplication *)application viewControllerWithRestorationIdentifierPath:(NSArray *)identifierComponents coder:(NSCoder *)coder
{
if ([identifierComponents.lastObject isEqualToString:#"FolderNavigationController"])
{
UINavigationController *nc = [[UINavigationController alloc] init];
nc.restorationIdentifier = #"FolderNavigationController";
return nc;
}
else
return nil;
}
I think the App is happier now, but it still isn't restoring properly. Now I get this error in my log:
Warning: Attempt to present <UINavigationController: 0xbaacf50> on <LoginViewController: 0xbaa1260> whose view is not in the window hierarchy!
It's something different.
I had similar issue. My stack of view controllers was pretty simple: root view with table view -> some details view -> some edit details view. No navigation views, views are modal. And it did not work.
Turned out, the issue was that, viewControllerWithRestorationIdentifierPath() in the root view controller should look like this:
+ (UIViewController *) viewControllerWithRestorationIdentifierPath:(NSArray *)identifierComponents
coder:(NSCoder *)coder
{
NSDictionary *myRestorableObj = [coder decodeObjectForKey:#"myRestorableObj"];
// #todo Add more sanity checks for internal structures of # myRestorableObj here
if (myRestorableObj == nil)
return nil;
return [[UIApplication sharedApplication] delegate].window.rootViewController;
}
Instantiating new root view controller is wrong. It creates new stack of view controllers that the stored children view controllers down the stack do not belong to.
All the other children view controller should be created as usually, with the following code:
UIViewController *vc = [storyboard instantiateViewControllerWithIdentifier:#"MyChildViewController"];
Hope this helps.
You need to assign different restoration identifiers to different FolderViewController objects.
For example:
FolderViewController *folderViewController1 = // initialize object ;
FolderViewController *folderViewController2 = // initialize object ;
folderViewController1. restorationIdentifier = #"folderViewController1";
folderViewController2. restorationIdentifier = #"folderViewController2";
I tried the code above and it worked fine.

UISplitView with Multiple Detail Views (with Storyboard)

I've been trying to create a version of this code using a storyboard:
I want to be able to switch between two different detail views, depending on the cell selected in the navigation table. I've tried to implement this by creating a SplitViewManager with a custom setter method that swaps out the detail views each time a different cell is selected. This is the same approach that Apple's sample code uses. The SplitViewManager follows the delegate.
I think my issue is that I haven't connected my splitViewController.delegate to anything, so I can't assign the splitViewManager to anything either. But I can't figure out what I would even connect the delegate to in the storyboard. Please let me know if I'm being an idiot here (almost definitely). Thanks!
My code is below:
DFMAppDelegate.m:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.splitViewController = (UISplitViewController *)self.window.rootViewController;
self.splitViewManager = (DFMSplitViewManager *)self.splitViewController.delegate;
NSLog(#"split view controller: %#", self.splitViewController); // not null
NSLog(#"split view controller delegate: %#", self.splitViewController.delegate); // is null
NSLog(#"split view manager: %#", self.splitViewManager); // is null.
// But i'm not sure how to assign splitViewController.delegate or splitViewManager in the storyboard.
return YES;
}
DFMSplitViewManager.m:
- (void)setDetailViewController:(UIViewController<SubstitutableDetailViewController> *)detailViewController
{
self.detailViewController = detailViewController;
// Update the split view controller's view controllers array.
// This causes the new detail view controller to be displayed.
UIViewController *navigationViewController = [self.splitViewController.viewControllers objectAtIndex:0];
NSArray *viewControllers = [[NSArray alloc] initWithObjects:navigationViewController, self.detailViewController, nil];
self.splitViewController.viewControllers = viewControllers;
}
DFMMasterViewController.m:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
DFMAppDelegate *appDelegate = (DFMAppDelegate *)[[UIApplication sharedApplication] delegate];
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"Main" bundle:nil];
if (indexPath.row == 0) {
NSLog(#"clicked cell 1");
DFMDetailViewController *detailViewController = [storyboard instantiateViewControllerWithIdentifier:#"ViewController"];
[appDelegate.splitViewManager setDetailViewController:detailViewController];
}
else {
NSLog(#"clicked cell 2");
DFMDetailCollectionViewController *detailCollectionViewController = [storyboard instantiateViewControllerWithIdentifier:#"CollectionViewController"];
[appDelegate.splitViewManager setDetailViewController:detailCollectionViewController];
}
}
Turns out you can use the interface builder to add NSObjects to View Controllers. Once I did that, I changed the NSObject's class to DFMSplitViewManager, set it as the SplitViewController's delegate, and it was pretty straight forward from there.
I am facing exactly the same problem as yours. Don't know wether you find out the solution or not, here is the solution I found.
Use following code in your AppDelegate.m
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Override point for customization after application launch.
UISplitViewController *splitViewController = (UISplitViewController *)self.window.rootViewController;
self.detailViewManager = [[DetailViewManager alloc] init];
self.detailViewManager.splitViewController = splitViewController;
self.detailViewManager.detailViewController = splitViewController.viewControllers.lastObject;
splitViewController.delegate = self.detailViewManager;
if ([splitViewController respondsToSelector:#selector(setPresentsWithGesture:)])
[splitViewController setPresentsWithGesture:YES];
return YES;
}
The rest of code is the same as what Apple has provided.
Basically, self.detailViewManager is our split view controller, when you select cell in table, self.detailViewManager will reset the detail view (if I'm not wrong). I'm new to Xcode, so anyone please correct me if I'm wrong.
Here is the solution link, answered by hallmark.

appdelegate how to pushViewController with storyboard

In my app delegate I am pushing the user to a view controller if the user details are saved
AppDelegate.m
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
// cached user
PFUser *currentUser = [PFUser currentUser];
if (currentUser)
{
NSLog(#"current user signing and looking for VC %#", currentUser);
// A user was cached, so skip straight to the main view
UINavigationController *navigationController = (UINavigationController *)self.window.rootViewController;
TaskSelectionViewController*viewController = [[TaskSelectionViewController alloc] initWithUser:currentUser];
[navigationController pushViewController:viewController animated:YES];
NSLog(#"VC found");
} else {
NSLog(#"VC not found");
}
TaskSelectionViewController.m
-(id)initWithUser:(PFUser *)currentUser
{
NSLog(#"current user %#", currentUser);
NSLog(#"task Selection initwithuser");
return self;
}
The push is working but all I get is the NavigationController bar at the top and a black Screen.
what is missing ??
thanks for your help.
OK done using instantiateViewControllerWithIdentifier:#"ID"
UINavigationController *navigationController = (UINavigationController *)self.window.rootViewController;
UIStoryboard *mainStoryboard = [UIStoryboard storyboardWithName:#"MainStoryboard" bundle: nil];
TaskSelectionViewController *controller = (TaskSelectionViewController*)[mainStoryboard instantiateViewControllerWithIdentifier: #"task"];
[navigationController pushViewController:controller animated:YES];
In the -(id)initWithUser:(PFUser *)currentUser of TaskSelectionViewController you should call [super init] or something similar to have your ViewController initialized properly. This solved a similar problem for me at least.
Look at Apple's examples for reference implementations of initialization methods of view controllers.

Setting up a UINavigation Controller on a UIViewController as rootView

I had a tabBarApplication that I have as a template which has a UINavigation template on each tab.
I want to use that sample (sort of) and convert it into a single UIViewController in a nother application. I have presented 2 pieces of code, the first one is my template and the latter is what I am trying to make. Could anyone please give me some hints or help with how to that right? I keep getting errors, but they do not make sense to me as the tabBarApp does not need it a viewController declared.
First Code (the example):
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
[self setupFetchedResultsController];
if (![[self.fetchedResultsController fetchedObjects] count] > 0 ) {
NSLog(#"!!!!! ~~> There's nothing in the database so defaults will be inserted");
[self importCoreDataDefaultRoles];
}
else {
NSLog(#"There's stuff in the database so skipping the import of default data");
}
// The Tab Bar
UITabBarController *tabBarController = (UITabBarController *)self.window.rootViewController;
// The Two Navigation Controllers attached to the Tab Bar (At Tab Bar Indexes 0 and 1)
UINavigationController *personsTVCnav = [[tabBarController viewControllers] objectAtIndex:0];
UINavigationController *rolesTVCnav = [[tabBarController viewControllers] objectAtIndex:1];
return YES;
}
Second Code (what I am trying to make):
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
[self setupFetchedResultsController];
if (![[self.fetchedResultsController fetchedObjects] count] > 0 ) {
NSLog(#"!!!!! ~~> There's nothing in the database so defaults will be inserted");
[self importCoreDataDefaultRoles];
}
else {
UIViewController *mainViewController = (UIViewController *)self.window.rootViewController;
UINavigationController *readingsTVCnav = [[mainViewController viewController] objectAtIndex:0];
// Override point for customization after application launch.
return YES;
}
The fetching parts of the code are related to Core Data which already set up and working.
The reason for this change is that I want to have a plain view controller set up as the initial screen rather than the tabBarConfiguration.
Cheers Jeff
EDIT: I have added an image for clarity
From what you describe and depict in your image, you have the navigation controller set up as your app's root view controller. So you can access it (if you need to) in your app delegate's didFinishLaunchingWithOptions: method as follows:
UINavigationController *navController = (UINavigationController *)self.window.rootViewController;
Although for your app I don't think there's any reason you need to reference the nav controller from your app delegate. All the setup you need (with the exception of your code data code, which you say is already working) can be handled through the storyboard.
In your nagivationController's root viewController (the one with all the buttons) you should set up a segue from each button to the approperiate viewController in your storyboard. Bure sure to set then up as "push" segues so that it will push the view controller onto your navigationController's navigation stack. If there is any particular setup you need to do for the child view controller when the segue happens, you can implement the prepareForSegue: method like this:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:#"ShowMyViewController"]) {
MyViewController *vc = (MyViewController*)segue.destinationViewController;
vc.title = #"My Title";
vc.someProperty = #"Some Value";
}
}
You can (and should) identify each of your segues in the storyboard with a unique identifier so that you can identify them when this method is called.

Resources