I am updating my iPad app and I've got a list of options in the table of the split view controller, but it's not firing the setDetalItem is not firing. This will not be ported to the iPhone because of the form-factor (screen is too small).
From the appDelegate class:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
UISplitViewController *splitViewController = (UISplitViewController *)self.window.rootViewController;
UINavigationController *navigationController = [splitViewController.viewControllers lastObject];
splitViewController.delegate = (id)navigationController.topViewController;
UINavigationController *masterNavigationController = splitViewController.viewControllers[0];
JSLMasterViewController *controller = (JSLMasterViewController *)masterNavigationController.topViewController;
controller.managedObjectContext = self.managedObjectContext;
return YES;
}
from the masterViewController:
-(void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
JSLDetailViewController *detailView = self.detailViewController;
detailView.telegram = indexPath.row;
NSLog(#"Did Fire 1");
}
And from my detailViewController:
- (void)setDetailItem:(id)newDetailItem
{
NSLog(#"Did Fire 2");
if (_detailItem != newDetailItem) {
_detailItem = newDetailItem;
// Update the view.
[self configureView];
}
if (self.masterPopoverController != nil) {
[self.masterPopoverController dismissPopoverAnimated:YES];
}
}
- (void)configureView
{
// Update the user interface for the detail item.
if (self.detailItem) {
//self.detailDescriptionLabel.text = [[self.detailItem valueForKey:#"timeStamp"] description];
NSArray *mainQuestion = [self mainQuestionArray];
NSArray *subQuestion = [self subQuestionArray];
questionTitle.text = mainQuestion[telegram];
subQuestionOne.text = subQuestion[telegram][0];
subQuestionTwo.text = subQuestion[telegram][1];
}
}
I understand that I may need to create an instance of the detailViewController in the appDelegate, but I am unsure as to how to do this. Most of the tutorials I have found build a split view from scratch or seem to be using an older version of the mechanism. Any tips you can give me would be greatly appreciated!
You need to set detailView.detailItem in order for the - (void)setDetailItem:(id)newDetailItem to fire.
In the MasterViewController.m detailViewController is usually something like:
self.detailViewController = (DetailViewController *)[[self.splitViewController.viewControllers lastObject] topViewController];
and then in the didSelectRowAtIndexPath:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSYourCoolObject *object = _YourObjectArray[indexPath.row];
self.detailViewController.detailItem = object;
}
Turns out that, in iOS6, one needs to call, "setDetailItem" directly. You can pass objects here, although it's not necessarily what you would think. I added the following code to didSelectItemAtIndexPath:
[detailView setDetailItem:indexPath];
This fired the update to the MasterViewController and life is good.
Related
I'm trying to disable the swipe back gesture, so that swiping back in my detail view will not open the master view.
I'm aware that this question and this question exist, but I have tried all the solutions suggested and none of them have worked for me. It may be because I am on iOS 11.
To demonstrate the issue, I've created a new master-detail project in Xcode and only added the solutions which I saw in the linked questions, which is adding this:
self.splitViewController.presentsWithGesture = NO;
To the viewDidLoad of the MasterViewController and adding this
- (void)viewWillLayoutSubviews {
if ([self.navigationController respondsToSelector:#selector(interactivePopGestureRecognizer)]) {
self.navigationController.navigationController.interactivePopGestureRecognizer.enabled = NO;
}
}
To the DetailViewController
MasterViewController
#import "MasterViewController.h"
#import "DetailViewController.h"
#interface MasterViewController ()
#property NSMutableArray *objects;
#end
#implementation MasterViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
self.navigationItem.leftBarButtonItem = self.editButtonItem;
UIBarButtonItem *addButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:#selector(insertNewObject:)];
self.navigationItem.rightBarButtonItem = addButton;
self.detailViewController = (DetailViewController *)[[self.splitViewController.viewControllers lastObject] topViewController];
self.splitViewController.presentsWithGesture = NO;
}
- (void)viewWillAppear:(BOOL)animated {
self.clearsSelectionOnViewWillAppear = self.splitViewController.isCollapsed;
[super viewWillAppear:animated];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (void)insertNewObject:(id)sender {
if (!self.objects) {
self.objects = [[NSMutableArray alloc] init];
}
[self.objects insertObject:[NSDate date] atIndex:0];
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:0];
[self.tableView insertRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
}
#pragma mark - Segues
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([[segue identifier] isEqualToString:#"showDetail"]) {
NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow];
NSDate *object = self.objects[indexPath.row];
DetailViewController *controller = (DetailViewController *)[[segue destinationViewController] topViewController];
[controller setDetailItem:object];
controller.navigationItem.leftBarButtonItem = self.splitViewController.displayModeButtonItem;
controller.navigationItem.leftItemsSupplementBackButton = YES;
}
}
#pragma mark - Table View
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return self.objects.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"Cell" forIndexPath:indexPath];
NSDate *object = self.objects[indexPath.row];
cell.textLabel.text = [object description];
return cell;
}
- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath {
// Return NO if you do not want the specified item to be editable.
return YES;
}
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
if (editingStyle == UITableViewCellEditingStyleDelete) {
[self.objects removeObjectAtIndex:indexPath.row];
[tableView deleteRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationFade];
} else if (editingStyle == UITableViewCellEditingStyleInsert) {
// Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view.
}
}
#end
DetailViewController
#import "DetailViewController.h"
#interface DetailViewController ()
#end
#implementation DetailViewController
- (void)configureView {
// Update the user interface for the detail item.
if (self.detailItem) {
self.detailDescriptionLabel.text = [self.detailItem description];
}
}
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
[self configureView];
}
- (void)viewWillLayoutSubviews {
if ([self.navigationController respondsToSelector:#selector(interactivePopGestureRecognizer)]) {
self.navigationController.navigationController.interactivePopGestureRecognizer.enabled = NO;
self.navigationController.interactivePopGestureRecognizer.enabled = NO;
}
}
#pragma mark - Managing the detail item
- (void)setDetailItem:(NSDate *)newDetailItem {
if (_detailItem != newDetailItem) {
_detailItem = newDetailItem;
// Update the view.
[self configureView];
}
}
#end
AppDelegate
#import "AppDelegate.h"
#import "DetailViewController.h"
#interface AppDelegate () <UISplitViewControllerDelegate>
#end
#implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
UISplitViewController *splitViewController = (UISplitViewController *)self.window.rootViewController;
UINavigationController *navigationController = [splitViewController.viewControllers lastObject];
navigationController.topViewController.navigationItem.leftBarButtonItem = splitViewController.displayModeButtonItem;
splitViewController.delegate = self;
return YES;
}
- (void)applicationWillResignActive:(UIApplication *)application {
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
}
- (void)applicationDidEnterBackground:(UIApplication *)application {
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
}
- (void)applicationWillEnterForeground:(UIApplication *)application {
// Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
}
- (void)applicationDidBecomeActive:(UIApplication *)application {
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
}
- (void)applicationWillTerminate:(UIApplication *)application {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
}
#pragma mark - Split view
- (BOOL)splitViewController:(UISplitViewController *)splitViewController collapseSecondaryViewController:(UIViewController *)secondaryViewController ontoPrimaryViewController:(UIViewController *)primaryViewController {
if ([secondaryViewController isKindOfClass:[UINavigationController class]] && [[(UINavigationController *)secondaryViewController topViewController] isKindOfClass:[DetailViewController class]] && ([(DetailViewController *)[(UINavigationController *)secondaryViewController topViewController] detailItem] == nil)) {
// Return YES to indicate that we have handled the collapse by doing nothing; the secondary controller will be discarded.
return YES;
} else {
return NO;
}
}
#end
How do I disable the swipe back feature?
self.navigationController.interactivePopGestureRecognizer.enabled = NO;
This line definitely worked for me. Anyway, I suggest you moving it to the viewDidLoad function so it is executed just once.
For Objective-C :
- (void)viewWillAppear:(BOOL)animated {
if ([self.navigationController respondsToSelector:#selector(interactivePopGestureRecognizer)]) {
self.navigationController.interactivePopGestureRecognizer.enabled = NO;
}
}
For Swift:
self.navigationController?.interactivePopGestureRecognizer?.isEnabled = false
But it is not recommended.
Find similar question
I figured it out. I had to add this to my DetailViewController
id savedGestureRecognizerDelegate1;
id savedGestureRecognizerDelegate2;
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
if ([self.navigationController respondsToSelector:#selector(interactivePopGestureRecognizer)]) {
savedGestureRecognizerDelegate1 = self.navigationController.interactivePopGestureRecognizer.delegate;
self.navigationController.interactivePopGestureRecognizer.delegate = self;
}
if ([self.navigationController.navigationController respondsToSelector:#selector(interactivePopGestureRecognizer)]) {
savedGestureRecognizerDelegate2 = self.navigationController.navigationController.interactivePopGestureRecognizer.delegate;
self.navigationController.navigationController.interactivePopGestureRecognizer.delegate = self;
}
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
if ([self.navigationController respondsToSelector:#selector(interactivePopGestureRecognizer)]) {
self.navigationController.interactivePopGestureRecognizer.delegate = savedGestureRecognizerDelegate1;
}
if ([self.navigationController.navigationController respondsToSelector:#selector(interactivePopGestureRecognizer)]) {
self.navigationController.navigationController.interactivePopGestureRecognizer.delegate = savedGestureRecognizerDelegate2;
}
}
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
{
if (gestureRecognizer == self.navigationController.interactivePopGestureRecognizer || gestureRecognizer == self.navigationController.navigationController.interactivePopGestureRecognizer) {
return NO;
}
return YES;
}
This is because there's two navigation controllers in a split view controller. The saved gesture recognizer delegate is because of this.
Is it possible to open different view controllers depending on witch table view cell user clicks? I tried to do that with:
[self presentViewController:obj animated:YES completion:nil];
but when next view is presented, there is no navigation bar and I can't go back to table view.
EDIT:
Here is MasterViewController class that I am using
#import "MasterViewController.h"
#interface MasterViewController () {
NSArray *viewArray;
}
#end
#implementation MasterViewController
#synthesize items,itemImges;
- (void)awakeFromNib
{
if ([[[UIDevice currentDevice] systemVersion] compare:#"7" options:NSNumericSearch] != NSOrderedAscending) {
self.preferredContentSize = CGSizeMake(320.0, 480.0);
}
if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) {
self.clearsSelectionOnViewWillAppear = NO;
}
self.title = NSLocalizedString(#"MasterTitle",#"Options:");
[super awakeFromNib];
}
- (void)viewDidLoad
{
[super viewDidLoad];
items = [NSArray arrayWithObjects:#"Media Explorer",#"Live TV",#"Settings",nil];
itemImges = [NSArray arrayWithObjects:
[UIImage imageNamed:#"listicon_guide.png"],
[UIImage imageNamed:#"listicon_livetv.png"],
[UIImage imageNamed:#"listicon_settings.png"],
nil];
// Do any additional setup after loading the view, typically from a nib.
self.detailViewController = (DetailViewController *)[[self.splitViewController.viewControllers lastObject] topViewController];
MediaExpDetailViewController *DVCA = [self.storyboard instantiateViewControllerWithIdentifier:#"MediaExpDetailViewController"];
LiveTVDetailViewController *DVCB = [self.storyboard instantiateViewControllerWithIdentifier:#"LiveTVDetailViewController"];
SettingsDetailViewController *DVCC = [self.storyboard instantiateViewControllerWithIdentifier:#"SettingsDetailViewController"];
//Create Array of views
viewArray = [NSArray arrayWithObjects:DVCA, DVCB, DVCC, nil];
}
#pragma mark - Table View
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return items.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"Cell" forIndexPath:indexPath];
NSString *object = items[indexPath.row];
UIImage *image = itemImges[indexPath.row];
cell.textLabel.text = [object description];
cell.imageView.image = image;
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
//for iPad
if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) {
//something goes here
}
else { //for iPhone
UIStoryboard *mainStoryboard = [UIStoryboard storyboardWithName:#"Main_iPhone" bundle:nil];
MediaExpDetailViewController *objSynergy = (MediaExpDetailViewController *)[mainStoryboard instantiateViewControllerWithIdentifier:#"MediaExpDetailViewController"];
[self.navigationController pushViewController:objSynergy animated:YES];
}
}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
}
#end
First set Storyboard IDs for your Next View controllers in Interface Builder and then.
-(void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
// Check Row and Select Next View controller
if (indexPath.row == 1)
{
// Push Selected View
UIViewController *view1 = [self.storyboard instantiateViewControllerWithIdentifier:#"StoryboardID"];
[self.navigationController pushViewController:view1 animated:YES];
}
}
That's because
[self presentViewController:obj animated:YES completion:nil];
Presents the new view controller modally (over top of the existing visible controller). If you want to push to a new view controller using your navigation controller, you'll want to use this. Of course you'll want to make sure that the view controller you're pushing from is embedded within a UINavigationController.
[self.navigationController pushViewController:obj animated:YES];
And to answer your first question, yes it absolutely is possible. Just add some condition logic to your didSelectRowAtIndexPath: UITableViewDelegate method.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if (someCondition) {
[self.navigationController pushViewController:obj animated:YES];
}else{
[self.navigationController pushViewController:otherObj animated:YES];
}
}
Modally presented view controllers don't have navigation bars by default. You have to embed them in a UINavigationController, in order to have one. You should also implement how to dismiss the presented view controller by yourself, calling dismissViewControllerAnimated at the appropriate times.
However, I'd recommend to push your view controllers (with pushViewControllerAnimated), instead of presenting them modally, if you don't specifically need the modal functionality.
When I hit an element in my tableView it comes up with an error. I have checked all of the connections but still cant work it out. Here is my code
#import "CGViewController.h"
#import "CGAppDelegate.h"
#import "sls.h"
#import "patientController.h"
#interface CGViewController ()
#end
#implementation CGViewController
- (void)viewDidLoad
{
[super viewDidLoad];
self.title = #"Patients";
CGAppDelegate *delegate =
(CGAppDelegate *)[[UIApplication sharedApplication] delegate];
patients = delegate.patients;
self.navigationItem.rightBarButtonItem = self.editButtonItem;
UIBarButtonItem *addButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:#selector(add:)];
self.navigationItem.leftBarButtonItem = addButtonItem;
// Do any additional setup after loading the view, typically from a nib.
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#pragma mark UITableViewDataSource Methods
- (UITableViewCell *)tableView:(UITableView *)tv
cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tv dequeueReusableCellWithIdentifier:#"cell"];
if( nil == cell) {
cell = [[UITableViewCell alloc]
initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:#"cell"];
}
if (indexPath.row < patients.count) {
sls *thissls = [patients objectAtIndex:indexPath.row];
cell.textLabel.text = thissls.patientName;
}
else {
cell.textLabel.text = #"";
cell.textLabel.textColor = [UIColor lightGrayColor];
}
return cell;
}
-(void)setEditing:(BOOL)editing animated:(BOOL)animated {
if ( editing != self.editing) {
[super setEditing:editing animated:animated];
[self.tableView setEditing:editing animated:animated];
NSArray *indexes =
[NSArray arrayWithObject:
[NSIndexPath indexPathForRow:patients.count inSection:0]];
if ( editing == YES ) {
[self.tableView insertRowsAtIndexPaths:indexes withRowAnimation:UITableViewRowAnimationLeft];
} else {
[self.tableView deleteRowsAtIndexPaths:indexes withRowAnimation:UITableViewRowAnimationLeft];
}
}
[self.tableView reloadData];
}
-(UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath {
if (indexPath.row < patients.count ) {
return UITableViewCellEditingStyleDelete;
}
else {
return UITableViewCellEditingStyleNone;
}
}
- (NSInteger)tableView:
(UITableView *)tv numberOfRowsInSection:
(NSInteger)section {
NSInteger count = patients.count;
if (self.editing) {
count = count + 1;
}
return count;
}
-(void)tableView:(UITableView *)tv didSelectRowAtIndexPath:
(NSIndexPath *)indexPath {
CGAppDelegate *delegate =
(CGAppDelegate *)[[UIApplication sharedApplication] delegate];
patientController *patient = [[patientController alloc] init];
[delegate.navController pushViewController:patient animated: YES];
[tv deselectRowAtIndexPath:indexPath animated: YES];
}
-(void) tableView:(UITableView *)tv
commitEditingStyle:(UITableViewCellEditingStyle)editing forRowAtIndexPath:(NSIndexPath *)indexPath {
if (editing == UITableViewCellEditingStyleDelete) {
[patients removeObjectAtIndex:indexPath.row];
[tv deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
withRowAnimation:UITableViewRowAnimationLeft];
}
}
#end
Any ideas on what is wrong if you know please can you help as soon as possible as i am on a deadline
Thanks in advance.
The error says Thread 1:signal SIGABRT
The error is in main.m
#import <UIKit/UIKit.h>
#import "CGAppDelegate.h"
int main(int argc, char *argv[])
{
#autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([CGAppDelegate class]));
}
}
it says that the error is here
patientController *patient = [[patientController alloc] init];
here is my patientController.h file
#import <UIKit/UIKit.h>
#interface patientController : UIViewController {
NSIndexPath *index;
IBOutlet UIImageView * pictureView;
IBOutlet UILabel * descriptionView1;
IBOutlet UILabel * descriptionView2;
IBOutlet UILabel * descriptionView3;
}
- (id)initwithIndexPath:(NSIndexPath *)indexPath;
#end
and my .m file
#import "patientController.h"
#import "CGAppDelegate.h"
#import "sls.h"
#interface patientController ()
#end
#implementation patientController
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
CGAppDelegate *delegate =
(CGAppDelegate *)[[UIApplication sharedApplication] delegate];
sls *thissls = [delegate.patients objectAtIndex:index.row];
self.title = thissls.patientName;
self->descriptionView1.text = thissls.patientName;
self->descriptionView2.text = thissls.surnameName;
self->descriptionView3.text = thissls.dateOfBirth;
self->pictureView.image = thissls.patientImage;
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (id)initwithIndexPath:(NSIndexPath *)indexPath {
if ( (self == [super init]) ) {
index = indexPath;
}
return self;
}
#end
Please can anyone at all help
Use:
[self.navigationController pushViewController:patient animated: YES];
If you are currently in a navigationController (which you have to in order to push a new ViewController), then you can push your new controller by just calling these lines
-(void)tableView:(UITableView *)tv didSelectRowAtIndexPath:
(NSIndexPath *)indexPath {
patientController *patient = [[patientController alloc] init];
[self.navigationController pushViewController:patient animated: YES];
}
Use
[self.navController pushViewController:patient animated: YES];
Every viewcontroller has its navigation controller reference in itself,no need to call the application delegate to invoke it[The navigation controller Must be initialised and all the viewcontrollers must be pushed into the navigation stack until this one to make it work]
So use this to setup the navigation controller in the appdelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
//Note : this is an example set like this with your viewcontroller instance
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
// Override point for customization after application launch.
if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone) {
self.viewController = [[ViewController alloc] initWithNibName:#"ViewController_iPhone" bundle:nil];
} else {
self.viewController = [[ViewController alloc] initWithNibName:#"ViewController_iPad" bundle:nil];
}
self.window.rootViewController = [[UINavigationController alloc] initWithRootViewController:self.viewController];
[self.window makeKeyAndVisible];
return YES;
}
To check where your exception is raised, in Xcode, in the breakpoints navigation tab click on the plus button in the bottom-left corner and add an Exceptions Breakpoint. Then run the app from Xcode. The next time an exception is raised, as in your case, the execution of your app will stop and Xcode will point at the line of code where the exception is raised.
Anyway, to make you understand how navigationControllers work:
UINavigationController is a UIViewController that controls a stack of other UIViewController instances. This other view controllers are said to be in the 'navigation stack'. The top item in the navigation stack is the view controller whose view you see at a given time onto the screen. You can push (present) or pop (dismiss) a view controller on/from the navigation stack.
A UINavigationController is initialized with a 'rootViewController' which is going to be it's first view controller onto the stack and therefore the first you see when you add the navigationController onto the screen (e.g. by setting it as the appDelegate window's rootViewController). Each UIViewController on the navigation stack references it's navigationController through the property 'navigationController'.
At any given point, you can use the UINavigationController methods pushViewController:animated: and popViewControllerAnimated: or popToViewController:animated: or popToRootViewControllerAnimated: to add or remove viewControllers from the navigationStack. To see the results of this methods, of course, you should have the navigationController on screen.
For more informations refer to the UINavigationController Class Reference.
I've been toying around with state restoration. In the code below, the scroll position of the UITableViewController gets restored, however, if I were to tap through into the detail view (pushing an instance of MyViewController onto the navigation stack), when the app restarts, it always returns to the first view controller in the navigation stack (i.e. MyTableViewController). Would somebody be able to help me restore to the correct view controller (i.e. MyOtherViewController)?
AppDelegate.m
- (BOOL)launchWithOptions:(NSDictionary *)launchOptions
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
// Override point for customization after application launch.
MyTableViewController *table = [[MyTableViewController alloc] initWithStyle:UITableViewStylePlain];
table.depth = 0;
UINavigationController *navCon = [[UINavigationController alloc] initWithRootViewController:table];
navCon.restorationIdentifier = #"navigationController";
self.window.rootViewController = navCon;
self.window.backgroundColor = [UIColor whiteColor];
[self.window makeKeyAndVisible];
});
return YES;
}
- (BOOL)application:(UIApplication *)application willFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
return [self launchWithOptions:launchOptions];
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
return [self launchWithOptions:launchOptions];
}
MyTableViewController.m
- (id)initWithStyle:(UITableViewStyle)style
{
self = [super initWithStyle:style];
if(self)
{
self.restorationIdentifier = #"master";
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
self.title = #"Master";
self.tableView.restorationIdentifier = #"masterView";
}
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 5;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return 10;
}
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
return [NSString stringWithFormat:#"Section %d", section];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if(!cell)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
cell.textLabel.text = [NSString stringWithFormat:#"%d", indexPath.row];
return cell;
}
#pragma mark - Table view delegate
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
MyOtherViewController *vc = [[MyOtherViewController alloc] initWithNibName:nil bundle:nil];
[self.navigationController pushViewController:vc animated:YES];
}
MyOtherViewController.m
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
self.restorationIdentifier = #"detail";
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
self.title = #"Detail";
self.view.backgroundColor = [UIColor redColor];
self.view.restorationIdentifier = #"detailView";
}
Because you are creating your detail view controller in code, and not instantiating it from a Storyboard, you need to implement a restoration class, so the system restoration process knows how to create the detail view controller.
A restoration class is really just a factory which knows how to create a specific view controller from a restoration path. You don't actually have to create a separate class for this, we can just handle it in MyOtherViewController:
in MyOtherViewController.h, implement the protocol: UIViewControllerRestoration
Then when you set the restorationID in MyOtherViewController.m, also set the restoration class:
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
self.restorationIdentifier = #"detail";
self.restorationClass = [self class]; //SET THE RESTORATION CLASS
}
return self;
}
You then need to implement this method in the restoration class (MyOtherViewController.m)
+(UIViewController *) viewControllerWithRestorationIdentifierPath:(NSArray *)identifierComponents coder:(NSCoder *)coder
{
//At a minimum, just create an instance of the correct class.
return [[MyOtherViewController alloc] initWithNibName:nil bundle:nil];
}
That gets you as far as the restoration subsystem being able to create and push the Detail View controller onto the Navigation Stack. (What you initially asked.)
Extending the Example to handle state:
In a real app, you'd need to both save the state and handle restoring it... I added the following property definition to MyOtherViewController to simulate this:
#property (nonatomic, strong) NSString *selectedRecordId;
And I also modified MyOtherViewContoller's viewDidLoad method to set the Detail title to whatever record Id the user selected:
- (void)viewDidLoad
{
[super viewDidLoad];
// self.title = #"Detail";
self.title = self.selectedRecordId;
self.view.backgroundColor = [UIColor redColor];
self.view.restorationIdentifier = #"detailView";
}
To make the example work, I set selectedRecordId when initially pushing the Detail View from MyTableViewController:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
MyOtherViewController *vc = [[MyOtherViewController alloc] initWithNibName:nil bundle:nil];
vc.selectedRecordId = [NSString stringWithFormat:#"Section:%d, Row:%d", indexPath.section, indexPath.row];
[self.navigationController pushViewController:vc animated:YES];
}
The other missing piece are the methods in MyOtherViewController, your detail view, to save the state of Detail controller.
-(void)encodeRestorableStateWithCoder:(NSCoder *)coder
{
[coder encodeObject:self.selectedRecordId forKey:#"selectedRecordId"];
[super encodeRestorableStateWithCoder:coder];
}
-(void)decodeRestorableStateWithCoder:(NSCoder *)coder
{
self.selectedRecordId = [coder decodeObjectForKey:#"selectedRecordId"];
[super decodeRestorableStateWithCoder:coder];
}
Now this actually doesn't quite work yet. This is because the "ViewDidLoad" method is called on restoration before the decoreRestorablStateWithCoder method is. Hence the title doesn't get set before the view is displayed. To fix this, we handle either set the title for the Detail view controller in viewWillAppear: , or we can modify the viewControllerWithRestorationIdentifierPath method to restore the state when it creates the class like this:
+(UIViewController *) viewControllerWithRestorationIdentifierPath:(NSArray *)identifierComponents coder:(NSCoder *)coder
{
MyOtherViewController *controller = [[self alloc] initWithNibName:nil bundle:nil];
controller.selectedRecordId = [coder decodeObjectForKey:#"selectedRecordId"];
return controller;
}
That's it.
A few other notes...
i presume you did the following in your App Delegate even though i didn't see the code?
-(BOOL) application:(UIApplication *)application shouldSaveApplicationState:(NSCoder *)coder
{
return YES;
}
- (BOOL) application:(UIApplication *)application shouldRestoreApplicationState:(NSCoder *)coder
{
return YES;
}
I saw a bit of flakiness when running the demo code in the simulator with it correctly preserving the Scroll offset. It seemed to work occasionally for me, but not all the time. I suspect this may be because really restoration of MyTableViewController should also use a restorationClass approach, since it's instantiated in code. However I haven't tried that out yet.
My testing method, was to put the app in the background by returning to springboard (required for the app state to be saved.) , then relaunching the app from within XCode to simulate a clean start.
the only problem with your code is navigation controller
these two lines
UINavigationController *navCon = [[UINavigationController alloc] initWithRootViewController:table];
navCon.restorationIdentifier = #"navigationController";
i dont know why navigation controller never get its restoration class. i had the same problem. i found some alternative solution to create a navigation controller stand alone in storyboard and use that.
here is code
UIStoryboard* storyboard = [UIStoryboard storyboardWithName:#"MainStoryboard"
bundle:nil];
UINavigationController* navigationController = [storyboard
instantiateViewControllerWithIdentifier:
#"UINavigationController"];
navigationController.viewControllers = #[myController];
it will work fine .
just follow this http://petr-pavlik.squarespace.com/blog/2013/6/16/stare-restoration-without-storyboard
Since you are doing this in code and not via Storyboard, you will need to provide not only a restorationIdentifier but also a restorationClass to you detail view controller.
You could leave it unassigned in which case -(UIViewController *)application:(UIApplication *)application viewControllerWithRestorationIdentifierPath:(NSArray *)identifierComponents coder:(NSCoder *)coder gets called on the application delegate to make the object (view controller with a restorationIdentifier but no restorationClass).
Try the following (please note the UIViewControllerRestoration protocol):
#interface MyViewController ()<UIViewControllerRestoration>
#end
#implementation
- (id)init
{
self = [super init];
if (self) {
// Custom initialization
}
if( [self respondsToSelector:#selector(restorationIdentifier)] ){
self.restorationIdentifier = #"DetailViewController";
self.restorationClass = [self class];
}
return self;
}
#pragma mark - State restoration
+ (UIViewController *)viewControllerWithRestorationIdentifierPath:(NSArray *)identifierComponents coder:(NSCoder *)coder
{
UIViewController *viewController = [[self alloc] init];
return viewController;
}
#end
Also note that this is a very simple example, usually you might have a lot more interesting things going on in -viewControllerWithRestorationIdentifierPath:coder:
I'm trying to create an iPad application with a similar user interface to Apple's Mail application, i.e:
RootView controller (table view) on the left hand side of the split view for navigation with a multiple view hierarchy. When a table cell is selected a new table view is pushed on the left hand side
The new view on the left side can update the detail view.
I can accomplish both tasks, but not together.
I mean I can make a multi-level table view in the RootController.
Or I can make a single-level table view in the RootController which can update the detailViewController.
Can anyone tell me how to make a multi-level table in the RootController which can update a detailViewController?
There is more source code at the link but below is the method in which I presume I have to declare a new detailViewController (which has to be put in the UISplitViewController):
- (void)tableView:(UITableView *)TableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NSDictionary *dictionary = [self.tableDataSource objectAtIndex:indexPath.row];
//Get the children of the present item.
NSArray *Children = [dictionary objectForKey:#"Children"];
//
if([Children count] == 0) {
/*
Create and configure a new detail view controller appropriate for the selection.
*/
NSUInteger row = indexPath.row;
UIViewController <SubstitutableDetailViewController> *detailViewController = nil;
if (row == 0) {
FirstDetailViewController *newDetailViewController = [[FirstDetailViewController alloc]initWithNibName:#"FirstDetailView" bundle:nil];
detailViewController = newDetailViewController;
}
if (row == 1) {
SecondDetailViewController *newDetailViewController = [[SecondDetailViewController alloc]initWithNibName:#"SecondDetailView" bundle:nil];
detailViewController = newDetailViewController;
}
// Update the split view controller's view controllers array.
NSArray *viewControllers = [[NSArray alloc] initWithObjects:self.navigationController, detailViewController, nil];
splitViewController.viewControllers = viewControllers//nothing happens.....
[viewControllers release];//
}
else {
//Prepare to tableview.
RootViewController *rvController = [[RootViewController alloc]initWithNibName:#"RootViewController" bundle:[NSBundle mainBundle]];
//Increment the Current View
rvController.current_level += 1;
//Set the title;
rvController.current_title = [dictionary objectForKey:#"Title"];
//Push the new table view on the stack
[self.navigationController pushViewController:rvController animated:YES];
rvController.tableDataSource = Children;
[rvController.tableView reloadData]; //without this instrucion,items won't be loaded inside the second level of the table
[rvController release];
}
}
Sorry, but I cannot post my source code as it contains sensitive information. When I have more time available I will create a separate project and upload the code somewhere.
Here are extracts of how I have done it so far (I welcome any feedback).
The RootViewController - Note I have 4 sections in my root table.
#pragma mark -
#pragma mark Table view delegate
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
[tableView deselectRowAtIndexPath:indexPath animated:YES];
// Detail view logic
NSUInteger section = indexPath.section;
UIViewController <SubstitutableDetailViewController> *detailViewController = nil;
if (section == 2) {
ProductSearchDetailView *viewController = [[ProductSearchDetailView alloc] initWithNibName:#"ProductSearchDetailView" bundle:nil];
detailViewController = viewController;
//[viewController release];
}
else {
DetailViewController *defaultDetailViewController = [[DetailViewController alloc] initWithNibName:#"DetailView" bundle:nil];
detailViewController = defaultDetailViewController;
//[defaultDetailViewController release];
}
// Navigation logic
switch (section) {
case 0:
{
break;
}
case 1:
{
break;
}
case 2:
{
// new Navigation view
ProductSearchViewController *viewController = [[ProductSearchViewController alloc] initWithNibName:#"ProductSearchViewController" bundle:nil];
viewController.navigationItem.backBarButtonItem.title = #"Back";
[self.navigationController pushViewController:viewController animated:YES];
[viewController release];
break;
}
case 3:
{
StoreLocatorNavController *viewController = [[StoreLocatorNavController alloc] initWithNibName:#"StoreLocatorNavController" bundle:nil];
viewController.navigationItem.backBarButtonItem.title = #"Back";
[self.navigationController pushViewController:viewController animated:YES];
[viewController release];
break;
}
}
// Update the split view controller's view controllers array.
NSArray *viewControllers = [[NSArray alloc] initWithObjects:self.navigationController, detailViewController, nil];
splitViewController.viewControllers = viewControllers;
[viewControllers release];
// Dismiss the popover if it's present.
if (popoverController != nil) {
[popoverController dismissPopoverAnimated:YES];
}
// Configure the new view controller's popover button (after the view has been displayed and its toolbar/navigation bar has been created).
if (rootPopoverButtonItem != nil) {
[detailViewController showRootPopoverButtonItem:self.rootPopoverButtonItem];
}
[detailViewController release];
}
NSNotificationCenter part
Add this to ProductSearchViewController:
#pragma mark -
#pragma mark Table view delegate
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NSDictionary *itemAtIndex = (NSDictionary *)[self.productResults objectAtIndex:indexPath.row];
[[NSNotificationCenter defaultCenter] postNotificationName:#"updateProduct" object:itemAtIndex];
}
And finally, add this to ProductSearchDetailViewController:
- (void)viewDidLoad {
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(updateTheProductDetails:) name:#"updateProduct" object:nil];
}
- (void)updateTheProductDetails:(NSNotification *)notification {
NSDictionary *productDictionary = [NSDictionary dictionaryWithDictionary:[notification object]];
// product name
_productName.text = [productDictionary objectForKey:#"ProductDescription"];
}
Hope it helps!