I realize apples documentation suggests a split view should be set as the root view controller. So essentially I have an app with a tab bar. Inside of the first index of the tab bar I have a split view. When the device's orientation is in portrait, I want the (master detail of the split) to fill the whole screen or become the detail page, but more importantly not have the option to have a master detail when the device is in portrait. So my thought is that I swap out tab bar indexes based on the devices orientation. Here's what my code looks like:
-(void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration{
if( UIInterfaceOrientationIsLandscape(toInterfaceOrientation) )
{
UISplitViewController * splitview = [[UISplitViewController alloc]init];
//
// splitview.delegate =self;
// [splitview willRotateToInterfaceOrientation:self.interfaceOrientation duration:0];
NSLog(#"LandScape!!");
NSMutableArray * array = [MyCustomwViewManager changeHomeOrientationTolandScape:[NSMutableArray arrayWithArray:self.tabBarController.viewControllers] viewController:self navigationController:self.navigationController splitView:splitview];
[self.tabBarController setViewControllers:array animated:YES];
// [self viewDidLoad];
} else {
NSMutableArray * changeSplit = [NSMutableArray arrayWithArray: self.splitViewController.viewControllers];
NSMutableArray * array = [MyCustomViewManager changeHomeOrientationToPortrait:[NSMutableArray arrayWithArray:self.tabBarController.viewControllers] viewController:[changeSplit objectAtIndex:0] navigation:[changeSplit objectAtIndex:0]];
[self.tabBarController setViewControllers:array animated:YES];
}
}
My goal is to achieve something similar to the yelp iPad app. Hopefully I am clear and make sense.
Related
in my app i am trying to make a slider using the LWSlideShow LWSlideShow
the source of images are fetched from my server after trying the solution here i stuck with error that said unbalanced call and it means that i am presenting a modal view on a view that did not completed his animation after solving this problem by putting animation to no the splashView that i present will be dismissed before the images are downloaded here is my code for further explanation:
- (IBAction)goDownload {
UIViewController *vc = [self.storyboard instantiateViewControllerWithIdentifier:#"Splash"];
[self.navigationController presentViewController:vc animated:YES completion:nil];
dispatch_async(dispatch_get_main_queue(), ^{
NSMutableArray *array = [#[] mutableCopy];
LWSlideItem *item = [LWSlideItem itemWithCaption:#""
imageUrl:#"http://code-bee.net/geeks/images/cover-1.jpg"];
[array addObject:item];
item = [LWSlideItem itemWithCaption:#""
imageUrl:#"http://code-bee.net/geeks/images/cover-2.jpg"];
[array addObject:item];
item = [LWSlideItem itemWithCaption:#""
imageUrl:#"http://code-bee.net/geeks/images/cover-3.jpg"];
[array addObject:item];
LWSlideShow *slideShow = [[LWSlideShow alloc] initWithFrame:CGRectMake(0, 0, CGRectGetWidth(self.view.bounds), 120)];
slideShow.autoresizingMask = UIViewAutoresizingFlexibleWidth;
//slideShow.delegate = self;
[self.view addSubview:slideShow];
slideShow.slideItems = array;
if ([slideShow.slideItems count] == [array count]) {
[self dismissViewControllerAnimated:YES completion:nil];
}
});
}
//
//-(void)viewWillAppear:(BOOL)animated
//{
//
// UIViewController *vc = [self.storyboard instantiateViewControllerWithIdentifier:#"Splash"];
// [self.navigationController presentViewController:vc animated:YES completion:nil];
//}
- (void)viewDidLoad {
[super viewDidLoad];
[self goDownload];
}
also you can see from the code that also i try to use viewWillAppear same thing happened what i want is when the images are downloaded the splashView need to be dismissed i dont know what i am doing wrong
Running that code from a VC anytime before viewDidAppear (like viewDidLoad, viewWillAppear) will cause the problem you describe. But you probably don't want the slide show view to appear - even for an instant - until you're done fetching the assets. This is a common problem.
The solution is to realize that the "splash screen" and the network tasks aren't just preamble, they are as much a part of your application as the slide show.
EDIT
Make that Splash vc the app's initial view controller in storyboard. Right now, the slide show vc probably looks like this:
Uncheck the "Is Initial View Controller" checkbox, find your splash view controller (in the same storyboard, I hope) and check it's box to be the initial view controller. Now your app will start up on the splash vc, like you want it.
When the splash vc done, it can present the slide show vc, or it can even replace itself (with the slide show ) as the app window's root.
To replace the UI, I use variations of this snippet...
// in the splash vc, after all of the asset loading is complete
// give what used to be your initial view controller a storyboard id
// like #"MySlideShowUI"
UIViewController *vc = [self.storyboard instantiateViewControllerWithIdentifier:#"MySlideShowUI"];
UIWindow *window = [UIApplication sharedApplication].delegate.window;
window.rootViewController = vc;
[UIView transitionWithView:window
duration:0.3
options:UIViewAnimationOptionTransitionCrossDissolve
animations:nil
completion:nil];
I'm using the CNContactPickerViewController to access contacts. And everything works fine until the part where I want to use this in Tabbar.
In previous ios versions replacing the view by the picker worked well but in ios 9 does not show anything.
When I use this code to present view the tabbar disappears.
[self presentViewController:my_picker animated:YES completion: nil];
I want have the tabbar always visible and the contacts list inside the tab.
Using ABPeoplePickerNavigationController I replace the view with the following code and it works fine.
picker = [[ABPeoplePickerNavigationController alloc] init];
NSMutableArray *controllers = [NSMutableArray arrayWithArray [self.tabBarController viewControllers]];
int index = [controllers indexOfObject:self];
[controllers replaceObjectAtIndex: index withObject: picker];
Someone has a solution for this using?
Just use these lines into your code ,
my_picker.modalPresentationStyle=UIModalPresentationOverCurrentContext;
[self presentViewController:my_picker animated:YES completion:nil];
Now the tabBar won't disappear from your screen
I am using a split view controller in a simple app. Leaving everything as default works fine. In other words, the master view controller always shows in landscape and overlays the detail view controller in portrait when the back button is pressed.
What I wanted to do was make the master view controller mimic the same functionality in landscape as it does in portrait. In other words, when the device is in landscape, I want the master view controller to be hidden until I hit the back button and then I want it to overlay the detail view controller.
I figured the best way to do this was to use the following code:
- (BOOL)splitViewController:(UISplitViewController *)svc shouldHideViewController: (UIViewController *)vc inOrientation:(UIInterfaceOrientation)orientation
{
return self.bHideMaster;
}
This worked in that it hid the master view controller in landscape mode. I then used the following code to make it reappear:
- (void)hideUnhidePagesController:(id)sender
{
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:0.30f];
UISplitViewController* spv = self.splitViewController;
// Change hide to unhide or vica versa
self.bHideMaster= !self.bHideMaster;
// Hide the button if master is visible
if(self.bHideMaster)
{
self.navigationItem.leftBarButtonItem = self.pagesBarButton;
}
else
{
self.navigationItem.leftBarButtonItem = nil;
}
[spv.view setNeedsLayout];
[spv willRotateToInterfaceOrientation:self.interfaceOrientation duration:0];
[[self.splitViewController.viewControllers lastObject] view].frame = self.splitViewController.view.frame;
[UIView commitAnimations];
}
This ALMOST worked. I have 2 problems:
The transition from hide-to-unhide and unhide-to-hide the master view controller isn't animated and is much to stark. I added animation code (see above) but it only animates the detail view controller and not the master view controller. The master appears and dis-appears instantly (leaving a black box on disappear) until the detail view controller slides over.
This also shows my second problem. I want the master view controller to overlap the detail view controller when it appears in landscape mode, leaving the detail view controller as is. Instead, it resizes the detail view controller (the same way it does in landscape mode before I started). I want the master view controller to interact the same way it does in portrait mode: Master slides in over the top of the detail controller and slides back out when it an item is selected.
If I could solve problem 2, then I don't have to worry about problem 1. It seems like there should be a method in the split view controller that would slide in the master from the left (overlapping the detail view controller). It does it in portrait mode so the code must be there. How can I call that same code in landscape mode?
Thanks!
---------EDIT 1---------
I have refactored hideUnhidePagesController and am getting closer. The window now overlays in both portrait and landscape. There is still a problem if the master is visible on rotation. It gets confused and inverts the expected behavior. I'm working on it. Here the amended code:
- (void)hideUnhidePagesController:(id)sender
{
// Change hide to unhide or vica versa
self.bMasterIsHidden= !self.bMasterIsHidden;
NSArray *controllers = self.splitViewController.viewControllers;
UIViewController *rootViewController = [controllers objectAtIndex:0];
UIView *rootView = rootViewController.view;
CGRect rootFrame = rootView.frame;
if(self.bMasterIsHidden)
{
rootFrame.origin.x -= rootFrame.size.width;
}
else
{
rootFrame.origin.x += rootFrame.size.width;
}
[UIView beginAnimations:#"hideUnhideView" context:NULL];
rootView.frame = rootFrame;
[UIView commitAnimations];
}
In ios 8.0
self.splitViewController.preferredDisplayMode = UISplitViewControllerDisplayModePrimaryHidden;
to hide master view
To get the effect you describe I had to add the following code to my DetailViewController.
- (BOOL)splitViewController:(UISplitViewController *)svc shouldHideViewController:
(UIViewController *)vc inOrientation:(UIInterfaceOrientation)orientation
{
return YES;
}
Then my split view works the same in portrait and landscape mode.
I'm putting in the code that I ended up using. Hope this helps someone else.
// ***************************************************************************************************
//
// hideUnhideMasterViewControllerButtonPressed
//
// ***************************************************************************************************
- (void)hideUnhideMasterViewControllerButtonPressed:(id)sender {
if([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone) {
[self.navigationController popViewControllerAnimated:YES];
}
else {
if(bMasterIsHidden)
[self hideMasterViewController:NO];
else
[self hideMasterViewController:YES];
}
}
// ***************************************************************************************************
//
// hideMasterViewController
//
// ***************************************************************************************************
- (void)hideMasterViewController:(BOOL)bHideMaster {
// Change hide to unhide or vica versa
self.bMasterIsHidden= !self.bMasterIsHidden;
NSArray *controllers = self.splitViewController.viewControllers;
UIViewController *rootViewController = [controllers objectAtIndex:0];
UIView *rootView = rootViewController.view;
CGRect rootFrame = rootView.frame;
if(bHideMaster) {
if(self.tapRecognizer) {
rootFrame.origin.x -= rootFrame.size.width;
[self.view removeGestureRecognizer:self.tapRecognizer];
self.tapRecognizer = nil;
}
}
else {
rootFrame.origin.x += rootFrame.size.width;
self.tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(tapRecognized:)];
self.tapRecognizer.numberOfTapsRequired = 1;
[self.view addGestureRecognizer:self.tapRecognizer];
self.tapRecognizer.delegate = self;
}
// Log resulting frame
NSString *hiddenString = self.bMasterIsHidden ? #"YES" : #"NO";
NSLog(#"Page=%# Class=%# MasterIsHidden=%# Origin(x,y)=(%f, %f) Size(width,height)=(%f, %f)", self.pageDefinition.pageName, [self class], hiddenString, rootFrame.origin.x, rootFrame.origin.y, rootFrame.size.width, rootFrame.size.height);
[UIView beginAnimations:#"hideUnhideView" context:NULL];
rootView.frame = rootFrame;
[UIView commitAnimations];
}
Maybe I am too late to answer this but... here is the solution..
You can get the reference of your masterviewcontroller from the method in every orientation change
-(BOOL)splitViewController:(UISplitViewController *)svc shouldHideViewController:(UIViewController *)vc inOrientation:(UIInterfaceOrientation)orientation {
myVCForPopOverController = vc;
//always hide the controller
return YES;
}
now you can show this "myVCForPopOverController" from any barbutton items click.
-(void)onBarButtonClick:(id)sender {
if(!self.popOverController.popoverVisible) {
self.popOverController = [[UIPopoverController alloc]initWithContentViewController:myVCForPopOverController];
[self.popOverController presentPopoverFromBarButtonItem:showDetailsBarButton permittedArrowDirections:UIPopoverArrowDirectionUp animated:YES];
self.popOverController.passthroughViews = nil;
}
else {
[self.popOverController dismissPopoverAnimated:YES];
}
}
I have implemented this and it works.
I'm using UIPageViewController and story board in my application.
When in portrait, i'm using following code to jump from one page to another page and its working fine.
int direction = UIPageViewControllerNavigationDirectionForward;
if ([self.modelController currentPage] < pagenum)
{
direction = UIPageViewControllerNavigationDirectionForward;
}
else if ([self.modelController currentPage] > pagenum)
{
direction = UIPageViewControllerNavigationDirectionReverse;
}
[self.pageViewController setViewControllers:[NSArray arrayWithObject:[self.modelController viewControllerAtIndex:pagenum storyboard:self.storyboard]] direction:direction animated:YES completion:NULL];
But the same code is not working when we are in landscape mode. How to turn the pages when in landscape?
If you look at the Page-Based Application Template in Xcode you find the following UIPageViewControllerDelegate method:
- (UIPageViewControllerSpineLocation)pageViewController:(UIPageViewController *)pageViewController spineLocationForInterfaceOrientation:(UIInterfaceOrientation)orientation
{
if (UIInterfaceOrientationIsPortrait(orientation)) {
// In portrait orientation: Set the spine position to "min" and the page view controller's view controllers array to contain just one view controller. Setting the spine position to 'UIPageViewControllerSpineLocationMid' in landscape orientation sets the doubleSided property to YES, so set it to NO here.
UIViewController *currentViewController = [self.pageViewController.viewControllers objectAtIndex:0];
NSArray *viewControllers = [NSArray arrayWithObject:currentViewController];
[self.pageViewController setViewControllers:viewControllers direction:UIPageViewControllerNavigationDirectionForward animated:YES completion:NULL];
self.pageViewController.doubleSided = NO;
return UIPageViewControllerSpineLocationMin;
}
// In landscape orientation: Set set the spine location to "mid" and the page view controller's view controllers array to contain two view controllers. If the current page is even, set it to contain the current and next view controllers; if it is odd, set the array to contain the previous and current view controllers.
DataViewController *currentViewController = [self.pageViewController.viewControllers objectAtIndex:0];
NSArray *viewControllers = nil;
NSUInteger indexOfCurrentViewController = [self.modelController indexOfViewController:currentViewController];
if (indexOfCurrentViewController == 0 || indexOfCurrentViewController % 2 == 0) {
UIViewController *nextViewController = [self.modelController pageViewController:self.pageViewController viewControllerAfterViewController:currentViewController];
viewControllers = [NSArray arrayWithObjects:currentViewController, nextViewController, nil];
} else {
UIViewController *previousViewController = [self.modelController pageViewController:self.pageViewController viewControllerBeforeViewController:currentViewController];
viewControllers = [NSArray arrayWithObjects:previousViewController, currentViewController, nil];
}
[self.pageViewController setViewControllers:viewControllers direction:UIPageViewControllerNavigationDirectionForward animated:YES completion:NULL];
return UIPageViewControllerSpineLocationMid;
}
When returning UIPageViewControllerSpineLocationMid from the above method, you are telling the UIPageViewController that it should expect to display two UIViewControllers side by side (i.e. two pages).
The key here is that you must pass the correct number of UIViewControllers when calling the UIPageViewController's setViewControllers:direction:animated:completion: method.
Showing two pages? Pass two UIViewControllers.
Showing one page? Pass one UIViewController.
The code you presented will never pass more than one UIViewController to the UIPageViewController.
If the UIPageViewControllerSpineLocation does not correspond with the amount of UIViewControllers you are passing it will crash or just do nothing.
Let me know if you need further help.
I am in kind of situation that I need to start with a tab based application and in that I need a split view for one or more tabs. But it seems that split view controller object can not be added to the tabbarController. (Although tabbar object can be added to the splitviewcontroller).
The problem can be seen otherways: I have a full screen in the left part I have a table view when any row is selected in the table a popover should come out pointing that row. Now when any row in the popover is selected the rows in this popover comes to the left under the selected row (only this row would be visible) and another popover comes out from the selected row. (Breadcrumb navigation type)
I think I am clear in what I explained. So guys any ideas or work arounds?
Please let me know if I am not clear in my question.
Thanks,
Madhup
Using the interface builder, create a split view controller and a tab bar controller and link them to your outlets:
#property (nonatomic, retain) IBOutlet UITabBarController *tabBarController;
#property (nonatomic, retain) IBOutlet UISplitViewController *splitViewController;
In your app delegate didFinishLaunchingWithOption, assign your split view controller to the tab bar controller:
splitViewController.tabBarItem = [[[UITabBarItem alloc] initWithTitle:#"Title" image:nil tag:0] autorelease];
NSArray *controllers = [NSArray arrayWithObjects:splitViewController, /* other controllers go here */ nil];
tabBarController.viewControllers = controllers;
[window addSubview:tabBarController.view];
[window makeKeyAndVisible];
This will create a tab bar controller (with only 1 tab in this case), which is displayed correctly in all orientations.
I've written up a subclass for the UISplitViewController that will listen for changes to device orientation and orient itself accordingly. With this class, I can now place split views within a UITabBarController and each split view will behave correctly upon rotation, even if it's not the frontmost tab. I've successfully deployed this in TexLege and it was approved for use in the App Store, but your mileage may vary. Please see the repository at Github.
Feel free to fork and modify it, and I'm always interested in hearing comments (or complaints) about it. https://github.com/grgcombs/IntelligentSplitViewController
I made a sample application. and found we can do it programmatically like:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
NSMutableArray *array = [NSMutableArray array];
NSMutableArray *tabArray = [NSMutableArray array];
UISplitViewController *splitViewConntroller = [[UISplitViewController alloc] init];
MainViewController *viewCont = [[MainViewController alloc] initWithNibName:#"MainViewController" bundle:nil];
[array addObject:viewCont];
[viewCont release];
viewCont = [[DetailViewController alloc] initWithNibName:#"DetailViewController" bundle:nil];
[array addObject:viewCont];
[viewCont release];
[splitViewConntroller setViewControllers:array];
[tabArray addObject:splitViewConntroller];
[splitViewConntroller release];
array = [NSMutableArray array];
splitViewConntroller = [[UISplitViewController alloc] init];
viewCont = [[MainViewController alloc] initWithNibName:#"MainViewController" bundle:nil];
[array addObject:viewCont];
[viewCont release];
viewCont = [[DetailViewController alloc] initWithNibName:#"DetailViewController" bundle:nil];
[array addObject:viewCont];
[viewCont release];
[splitViewConntroller setViewControllers:array];
[tabArray addObject:splitViewConntroller];
[splitViewConntroller release];
// Add the tab bar controller's current view as a subview of the window
[tabBarController setViewControllers:tabArray];
[window addSubview:tabBarController.view];
[window makeKeyAndVisible];
return YES;
}
Hope this helps.
To let a tabbarcontroller appear as a master view for splitviewcontroller you should rewrite tabbarcontroller so that it will support or orientations(so say, using a category for the class UITabBarController)
See my post about retrofitting split view controllers to an existing tab bar interface: http://markivsblog.blogspot.com/2010/04/retrofitting-ipad-uisplitviewcontroller.html
I created a UITabBarController subclass which properly propagates the rotation messages to all UISplitViewControllers it contains. This maintains the correct internal state of the UISplitViewControllers. However, one of the SplitViewController delegate methods is not called if the SplitViewController is not visible, so I account for this in the detail view controller viewWillAppear method. I've confirmed this works in iOS5.0 - iOS6.1
OSTabBarController.m
#import "OSTabBarController.h"
#implementation OSTabBarController
-(void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration{
[super willRotateToInterfaceOrientation:toInterfaceOrientation duration:duration];
for(UIViewController *targetController in self.viewControllers){
if(targetController != self.selectedViewController && [targetController isKindOfClass:[UISplitViewController class]]){
[targetController willRotateToInterfaceOrientation:toInterfaceOrientation duration:duration];
}
}
}
-(void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation{
[super didRotateFromInterfaceOrientation:fromInterfaceOrientation];
for(UIViewController *targetController in self.viewControllers){
if(targetController != self.selectedViewController && [targetController isKindOfClass:[UISplitViewController class]]){
[targetController didRotateFromInterfaceOrientation:fromInterfaceOrientation];
}
}
}
#end
DetailViewController
#implementation OSDetailViewController
-(void)viewWillAppear:(BOOL)animated{
//the splitViewController:willHideViewController:withBarButtonItem:forPopoverController: may not have been called
if(!UIInterfaceOrientationIsPortrait(self.interfaceOrientation)){
self.navigationItem.leftBarButtonItem = nil;
}
}
#pragma mark - UISplitViewControllerDelegate Methods
- (void)splitViewController:(UISplitViewController *)splitController willHideViewController:(UIViewController *)viewController withBarButtonItem:(UIBarButtonItem *)barButtonItem forPopoverController:(UIPopoverController *)popoverController
{
[self.navigationItem setLeftBarButtonItem:barButtonItem animated:YES];
}
- (void)splitViewController:(UISplitViewController *)splitController willShowViewController:(UIViewController *)viewController invalidatingBarButtonItem:(UIBarButtonItem *)barButtonItem
{
[self.navigationItem setLeftBarButtonItem:nil animated:YES];
}
#end
Keep in mind that OS 3.2 does not provide proper support for a splitview as a tabbar view.
You can make it "work" but it will have bugs - the biggest is that an orientation change made on another tab's view will often not propagate to the splitview tab view properly, making the view go wacky when you go back to it (left side view takes over the screen, or the barbutton item is missing, etc.).
I've reached the conclusion that I have to create my own splitview for use in a tabBarController because of this issue.
I had heard rumors that Apple was working on a fix but it's been months now and no iPad OS updates have occurred - maybe OS 4 for the iPad will address it.
You can use IB to build tabtab and modify tabs to splitviewcontroller.
-(void) makeSplitViewController {
NSMutableArray *controllers = [NSMutableArray arrayWithArray:tabBarController.viewControllers];
int index = 0;
for (UIViewController *controller in tabBarController.viewControllers) {
if ([controller.tabBarItem.title isEqualToString:#"Stock"]) {
stockDetailController = [[StockDetailController alloc] initWithNibName:#"StockDetailController" bundle:nil];
stockMasterController = [[StockMasterController alloc] initWithStyle:UITableViewStylePlain];
stockMasterController.navigationItem.title = date;
stockMasterController.stockDetailController = stockDetailController;
UINavigationController *nav = [[[UINavigationController alloc] initWithRootViewController:stockMasterController] autorelease];
splitViewController = [[UISplitViewController alloc] init];
splitViewController.tabBarItem = controller.tabBarItem;
splitViewController.viewControllers = [NSArray arrayWithObjects:nav, stockDetailController, nil];
splitViewController.delegate = stockDetailController;
[controllers replaceObjectAtIndex:index withObject:splitViewController];
}
index++;
}
tabBarController.viewControllers = controllers;
}
We succeeded in having a UISplitViewController inside a UITabViewController on iPad with iOS5+.
to make a long story short: it works:
out of the box if you accept a split also in portrait;
with a bit of
work, if you want to have the master view hidden in portrait, and
have it appear only upon tapping a button.
The trick in the second case is to use the IntelligentSplitViewController (see a few posts up, thanx Greg Combs) or similarly extend a UISplitVC, and be careful that the delegate of the subclass of the splitview controller is always a live object.
We have detailed the process on:
https://devforums.apple.com/message/763572#763572