UISplitView with Multiple Detail Views (with Storyboard) - ios

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.

Related

UiSplitViewController App without storyboards

I am new to iOS development and need a bit of help. I have searched high and low and cannot find the answer. It's more like I don't understand what is happening. My issue is, I am trying to code a Master-Detail App without using storyboards. This helps me get a better understanding of how ViewControllers are managed and handled. I am trying to present the DetailViewController after an item is selected in a tableView in the MasterViewController on iPhone. On iPad it works well because both views are side by side.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
//Pass data to detailViewController
if ([_detailDelegate respondsToSelector:#selector(item:selectedAtIndexPath:)]) {
[_detailDelegate item:[[BNRItemStore sharedStore] findItemAtIndex:indexPath.row] selectedAtIndexPath:indexPath];
}
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone ) {
// show DetailViewController
NSArray *viewControllers = self.splitViewController.viewControllers;
UINavigationController *detailNavigationViewController = [viewControllers objectAtIndex:1];
[self showDetailViewController:detailNavigationViewController sender:nil];
}
}
In a storyboard the selected ViewCell has a show detail segue to the detailViewController. How can I achieve the same thing without using storyboards?
My AppDelegate code for a better picture
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
UISplitViewController *splitViewController = [[UISplitViewController alloc] init];
self.window.rootViewController = splitViewController;
UINavigationController *masterNavigationController = [[UINavigationController alloc]init];
UINavigationController *detailNavigationController = [[UINavigationController alloc]init];
MasterTableViewController *masterTableViewController = [[MasterTableViewController alloc] init];
DetailViewController *detailViewController = [[DetailViewController alloc] init];
masterTableViewController.detailDelegate = detailViewController;
detailViewController.masterDelegate = masterTableViewController;
masterNavigationController.viewControllers = #[masterTableViewController];
detailNavigationController.viewControllers = #[detailViewController];
[splitViewController setViewControllers:#[masterNavigationController,detailNavigationController]];
[self.window makeKeyAndVisible];
return YES;
}
Thanks.
I was able to solve this issue after reading this post. When the UISplitViewController detects that it is in portrait mode on an iPhone it removes the DetailViewController from its viewControllers property. When it is in landscape mode its add the DetailViewController(Secondary Controller)back to the viewControllers property.
Solution:
Added a strong reference property in the MasterViewController(Primary Controller) to store the DetailViewController.
When an item is selected in the MasterViewController table, I checked the UISplitViewController viewControllers property and if the DetailViewController was missing from the Array, I pushed the DetailViewController onto the Parent Controller(UINavigationViewController)stack.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
DetailViewController *detailController = (DetailViewController *) self.detailViewController.viewControllers[0];
[detailController item:[[BNRItemStore sharedStore] findItemAtIndex:indexPath.row] selectedAtIndexPath:indexPath];
if (self.splitViewController.viewControllers.count == 1) {
[self.navigationController pushViewController:self.detailViewController animated:YES];
}
}
Try changing:
[self showDetailViewController:detailNavigationViewController sender:nil];
to:
[self.splitViewController showDetailViewController:detailNavigationViewController sender:nil];

How to open specific ViewController from AppDelegate?

I know this question has been asked many times. But I am a newbie and I was not able to find any solutions to help me in my condition. Here is a brief explanation:
I have an app with a UITabBarController as root.
Inside the app delegate I check if user is already logged in. If yes I will open root controller.
self.window.rootViewController = [[UIStoryboard storyboardWithName:#"Main" bundle:[NSBundle mainBundle]] instantiateInitialViewController];
My application is working fine. But now I need to implement notification. When I get notification content inside didFinishLaunchingWithOptions:
NSDictionary *notificationPayload = launchOptions[UIApplicationLaunchOptionsRemoteNotificationKey];
NSString *messageType = [notificationPayload objectForKey:#"messageType"];
Now if there is a message:
if (messageType.length > 0 )
{
//Here based on message I need to open different tabs of TabBarViewController
UITabBarController *tabBarController = (UITabBarController *)self.window.rootViewController;
//RootTabBarViewController *listingVC = [[RootTabBarViewController alloc] init];
[(UINavigationController *)self.window.rootViewController pushViewController:tabBarController animated:YES];
}
This piece of code above did not work for me.
And also I don't know how to open different tabs and give value to their badges form here. It always navigate to the first index tab with these code. I have seen other answers say:
self.tabBarController.selectedIndex = 2;
But did not work for me. I have implemented an UITabBarController class and I can set value for the each tab item badges from there but I get my notificationPayLoad in AppDelegate.
As you told that you are using TabBarController with storyboard. Then why are you initialising again? You can just do as following
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
if([[NSUserDefaults standardUserDefaults]boolForKey:#"Selected"])
{
[[NSUserDefaults standardUserDefaults]setBool:NO forKey:#"Selected"];
UITabBarController *tabController = (UITabBarController*)self.window.rootViewController;
tabController.selectedIndex=1;
}else{
[[NSUserDefaults standardUserDefaults]setBool:YES forKey:#"Selected"];
UITabBarController *tabController = (UITabBarController*)self.window.rootViewController;
tabController.selectedIndex=0;
}
return YES;
}
Here I am just showing a demo code for accessing TabbarController at different selectedIndex within didFinishLaunching.
Assuming that you have designed(drag n dropped UITabBar/ UITabBarController) in storyboard.
if (messageType.length > 0 )
{
//Here based on message I need to open different tabs of TabBarViewController
NSUInteger indexOfRequiredTab = 2;// pass the index of controller here
id rootObject = self.window.rootViewController;
if ([rootObject isKindOfClass:[UITabBarController class]]) {
UITabBarController *tabBarControllerObject = (UITabBarController *)rootObject;
if (indexOfRequiredTab != NSNotFound && indexOfRequiredTab < tabBarControllerObject.tabBar.items.count) {
tabBarControllerObject.tabBar.items[indexOfRequiredTab].badgeValue = #"2"; //to set badge value
tabBarControllerObject.selectedIndex = indexOfRequiredTab; //Setting this property changes the selected view controller to the one at the given index in the viewControllers array
}
}
}
Views designed on story board are already initialized, we can access them using the IBOutlet and change their properties.

Objective C view controller property not saving

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.

Pushing ViewController from app delegate

I am using a storyboard in Xcode 5, which appears as so:
My requirement is to push a ViewController (VIEW1 or VIEW2) into view from the app delegate. Essentially it should not matter what view is presently on the screen -- I would just like to make a ViewController appear when the app delegate picks up an external event.
In order to try and achieve this, I have property references to both the TabBarCtrl-Products and NavCtrl-ProductA in my app delegate.
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
_tabBarProducts = [[UIStoryboard storyboardWithName:#"Main" bundle:nil] instantiateViewControllerWithIdentifier:#"sidTabBarProducts"];
NSArray *tabvcs = _tabBarProducts.viewControllers;
for (id controller in tabvcs){
if ([controller isKindOfClass:[VCNavControl_ProductA class]]) {
_navControllerProductA = controller;
break;
}
}
return YES;
}
The app delegate method to push VIEW2 is:
-(void)showVCVIEW2
{
VC_V2 *targetvc = nil;
targetvc = [[UIStoryboard storyboardWithName:#"Main" bundle:nil] instantiateViewControllerWithIdentifier:#"sidView2"];
[[AppDelegate sharedInstance].navControllerProductA pushViewController:targetvc animated:NO];
}
This works OK when VIEW1 is showing at the time showVCVIEW2 is called, however it does not work when ViewCtrl-ProductB is showing. I can see that the new instance of targetvc has been added to the AppDelegate _navControllerProductA's stack, however it does not display.
(Regarding the setting of the the app delegate's rootViewController, I set this to _tabBarProducts after the VC-Splash and VC-Setup ViewCtrls have finished).
I would appreciate very much if anyone can give me an idea on how to achieve this. I suspect my problems stem from having a NavCtrl in a TabBarCtrl, but I do not know a way around this.
Your problem is that the navigation controller you are pushing on is not in the view hierarchy.
You instead could try setting the tabBarController's selected index like this:
[self.tabBarController setSelectedIndex:1];

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