UISplitViewController how to show the master view? - ios

I need a ridiculously simple thing - in one of the detail views of my UISplitViewController I have a button. Clicking it should show/open master view. That's it. Is it even possible?
P.S. it should work for all the layouts (iphone & ipad) and orientations. Even if the detail view part is a navigation and I am deep inside several pages, just want to open master view. You can assume iOS8+.
EDIT: Just to clarify what I meant by "deep inside several pages". Here is my storyboard screenshot:
Suppose I have a button in Detail Page 2 which should show the master. Setting the preferredDisplayMode works only for non-compact sizes, like iPad. On iPhone 6, for example, nothing changes after setting it. The back button points on Detail Page 1 so even swiping doesn't open master, it goes to previous page in detail navigation. I noticed that in this mode there is no split view at all, it is simulated by a navigation controller. So the questions is: is what I need possible at all or am I wrong trying to conceptually treat it as a "left drawer" which can be opened in any case and device?

At iOS8+ you can change visibility of the master view using
an animatable property preferredDisplayMode
#property (nonatomic) UISplitViewControllerDisplayMode preferredDisplayMode
Universal way to change visibility for all iOS versions is overriding delegate method
- (BOOL)splitViewController:(UISplitViewController *)svc shouldHideViewController:(UIViewController *)vc inOrientation:(UIInterfaceOrientation)orientation
{
return _needsHideMasterView;
}
Here _needsHideMasterView is BOOL ivar which can be changed in your code to hide master view. For example,
- (void)hideMasterView:(BOOL)needsHide
{
_needsHideMasterView = needsHide;
[splitViewController.view setNeedsLayout];
[splitViewController.view layoutIfNeeded];
}

try setting preferredDisplayMode to UISplitViewControllerDisplayModeAllVisible like
self.splitViewController.preferredDisplayMode = UISplitViewControllerDisplayModeAllVisible;

Related

Using splitviewcontroller on iPhone portrays detail view first

I'm currently developing for iOS 8 and developing an app with the new adaptive framework. The weird part is when I use the the splitviewcontroller on iPhone with this storyboard configuration the app does not start with the master view controller but the detail controller. Is this a bug and how could I be able to fix it?
This does only happen if the navigationController that envelops the master exists, if I remove it the app starts with master controller.
The thing to realise is that when a split view controller app starts on iPhone 6 Plus in portrait, it is just showing the split view controller in its collapsed state. By default this has the detailed view pushed above any view controllers in the primary navigation controller.
The way to stop a particular view (such as a blank detail view you may initially show on iPad) from being displayed at launch, or indeed after any rotation to portrait, is to handle this in the splitViewController:collapseSecondaryViewController:ontoPrimaryViewController: delegate method. This will be called at launch on iPhone or iPhone 6 Plus in portrait before presentation.
Doing this, you shouldn't need to have any device specific code.
In its simplest form this would look like:
- (BOOL)splitViewController:(UISplitViewController *)splitViewController collapseSecondaryViewController:(UIViewController *)secondaryViewController ontoPrimaryViewController:(UIViewController *)primaryViewController
{
if ([secondaryViewController isKindOfClass:[BlankViewController class]])
{
// If our secondary controller is blank, do the collapse ourself by doing nothing.
return YES;
}
// Otherwise, use the default behaviour.
return NO;
}
Obviously, you then need to do the reverse in splitViewController:separateSecondaryViewControllerFromPrimaryViewController: to create and return a BlankViewController for the new secondary view if you don't want your topmost primary view controller to end up on the detail side after split view expands.
Be aware mixing your own implementation with Apple's in these methods, they do some crazy stuff like embedding UINavigationControllers inside other UINavigationControllers. See my related answer here: https://stackoverflow.com/a/26090823/4089333
UPDATE: Michael Wybrow's answer is better.
I ran into this issue and found this to work:
splitViewController.viewControllers =
UIScreen.mainScreen.traitCollection.horizontalSizeClass == UIUserInterfaceSizeClassCompact ?
#[ leftNavigationController ] :
#[ leftNavigationController, rightNavigationController ]
;
And in the splitview delegate:
- (UIViewController *)primaryViewControllerForCollapsingSplitViewController:(UISplitViewController *)splitViewController
{
return leftNavigationController;
}
- (UIViewController *)primaryViewControllerForExpandingSplitViewController:(UISplitViewController *)splitViewController
{
return leftNavigationController;
}
- (UIViewController *)splitViewController:(UISplitViewController *)splitViewController separateSecondaryViewControllerFromPrimaryViewController:(UIViewController *)primaryViewController
{
return rightNavigationController;
}
It's horrible, I know. But it does the right thing, especially on the iPhone 6 Plus, which is very tricky to get right.
UPDATE: Michael Wybrow's answer is better.
It is probably an error due to the SplitViewController being just exclusive to the iPad. Also when you have it in the portrait orientation, it displays the detail view by default and the master view will show as a bar. You will have to change it using some method like splitViewController:shouldHideViewController:inOrientation
Here is an Apple document referring your problem
https://developer.apple.com/library/ios/documentation/uikit/reference/UISplitViewController_class/Reference/Reference.html
I hope this helps!

iPhone how to display a different view controller in response to device rotation?

I've noticed that most consumer-friendly Android and iPhone fitness apps have two interface modes - in portrait mode the user gets more detailed information, but when the user turns the device to landscape mode, a full screen graph is added to cover the entire screen.
I'm interested in how to implement transition to a different view controller in response to device rotation on iPhone. My initial thoughts are to intercept (willRotateToInterfaceOrientation event, then get the app delegate and add a full screen graph view controller to the window).
Is there a better way of turning an iPhone rotation into a transition to another view controller? Like hiding the status bar and pushing a modal view controller in landscape mode with animation?
First ask yourself whether you really need a separate view controller. One view controller can easily hide or unhide a graph.
If this graph needs its own view conroller then you could use a container view that contains the graph which refers to its own view conroller. That is what container views are made for.
The "Master" view controller then would just hide and unhide the container view in response to rotation events (and layout them accordingly etc. pp.)
If you prefer to add or remove the container view from its super view (most probably self.view from the "Master" view controller's point of view) then do that instead of hiding and unhiding. That is probably most appropriate.
The upside of this appoach would be that it works regardless of the navigaiton structure you are in, regardless of whether the rotated view controller was pushed or presented modally, regardless of whether you are in a tab bar driven app or a single view app, whether you are using storyboard, works with IB as well as programmatically, etc. pp.
There is nothing wrong with fetching the window instance from the app's delegate. I just don't see the need for doing so. Seems rather complicated to me compared to the alternatives.
The willRotateToInterfaceOrientation method works well.
In addition to switching views, two other useful things you might want to do in there are:
1) Hide/Show the status bar. (I like to hide it in landscape)
[[UIApplication sharedApplication] setStatusBarHidden:UIInterfaceOrientationIsLandscape(toInterfaceOrientation) withAnimation:UIStatusBarAnimationSlide];
2) Hide/Show any UINavigationBar. (Maybe your landscape view will benefit from the extra height)
[self.navigationController setNavigationBarHidden:UIInterfaceOrientationIsLandscape(toInterfaceOrientation) animated:YES];
You could have one view controller that has the willRotateToInterfaceOrientation method, and that viewcontroller has two other viewcontrollers as variables.
Once the device rotates, you switch the viewcontrollers' views (very crude code example:)
-(void)willRotateToInterfaceOrientation: (UIInterfaceOrientation)orientation duration:(NSTimeInterval)duration {
if ((orientation == UIInterfaceOrientationLandscapeLeft) || (orientation == UIInterfaceOrientationLandscapeRight)) {
[self.secondViewController.view removeFromSuperView];
self.firstViewController.view.frame = self.bounds;
[self.view addSubView:self.firstViewController.view];
} else {
[self.firstViewController.view removeFromSuperView];
self.secondViewController.view.frame = self.bounds;
[self.view addSubView:self.secondViewController.view];
}
}

How to hide the master popover view on first-time app load?

I have nice helper functions that allow me to show/hide the master popover view controller. However, I can not for the life of me figure out how to hide it on initialization, so that it is hidden when the app first starts.
I've tried a couple of things (such as trying to dismiss is from the viewLoaded or viewDidAppear) but these throw strange errors (e.g. too made slider counts...etc).
Now I'm starting to believe there must be a simpler, and the right way, to do this.
Are you trying to hide the splitviewController Master Popover? Which orientation would make this question more specific. I will assume you are in fact trying to hide the SplitView MasterPopover in landscape (since it should already be hidden in portrait).
Do this:
Your detailViewController should have UISplitViewControllerDelegate. And simply just drop this code in:
- (BOOL)splitViewController:(UISplitViewController *)svc shouldHideViewController:(UIViewController *)vc inOrientation:(UIInterfaceOrientation)orientation{
return YES;
}
This will make popoverView hidden when the app starts up. Let me know, if it works for you.

Split view controller that is only sometimes split?

I am working on an iPad app that seems like a natural fit for using a Master / Detail UISplitViewController for portrait and companion detail controller / popover for navigation.
But... I would sometimes like to use the full screen for the detail controller in portrait as well, turning the master into a popover here as well.
Is there any best practices or sample code explaining how this can be done?
Is a UISplitViewController the appropriate root view?
Any tips that focus on using iOS 5 and segues are especially appreciated. Thanks!
I have discovered that this is possible under iOS 5.
Use the following function in your UISplitViewController delegate:
- (BOOL)splitViewController:(UISplitViewController *)svc shouldHideViewController:(UIViewController *)vc inOrientation:(UIInterfaceOrientation)orientation
{
return YES;
}
Return YES even in landscape view, and the SplitViewController will function with a popover-style interface just like in portrait. If you want to revert back to the normal split view behavior, use this function to return NO in landscape.

Showing both Master and Detail View in portrait mode in MGSplitViewController

I am currently integrating MGSplitViewController in one of my application and its working properly.
But i want to modify the way it is displaying currently in portrait mode.So i want in portrait mode whenever application open it should display both master as well as detail view controller.So on pressing the navigation bar button it will again hide and show the left root view controller.
So i have changed the code to
- (BOOL)shouldShowMasterForInterfaceOrientation:(UIInterfaceOrientation)theOrientation
{
// Returns YES if master view should be shown directly embedded in the splitview, instead of hidden in a popover.
//return ((UIInterfaceOrientationIsLandscape(theOrientation)) ? _showsMasterInLandscape : _showsMasterInPortrait);
return YES;
}
Now both portrait and landscape mode is showing both root and detail view controller as i wants but the problem is navigation bar button is not working to hide & show left root view controller.
Any body have done this?
Instead of modifying the MGSplitViewController source or subclassing it, you can use the showsMasterInPortrait property of the MGSplitViewController to toggle the master on and off from your application code. This has always worked fine for me.
Update with specifics:
I wouldn't use the bar button item that the split view controller provides - it's not overly useful for our purposes. Instead, set up your own button, with an associated action which toggles the showsMasterInPortrait property of your split view controller. To get to the latter, wire up an outlet property. You'll also need an outlet for the button itself if you're going to hide the button in landscape. Make sure that's wired up correctly in IB, too.
In the header, that means something like this:
#property(nonatomic,assign) IBOutlet MGSplitViewController* splitVC;
#property(nonatomic,assign) IBOutlet UIBarButtonItem* toggleButton;
- (IBAction)toggleMasterViewTouched:(id)sender;
And in the class definition:
#synthesize splitVC, toggleButton;
- (IBAction)toggleMasterViewTouched:(id)sender
{
BOOL master_shown = !self.splitVC.showsMasterInPortrait;
// Note: toggle the button's label text and/or icon between "hide" and "show" versions
self.toggleButton.title = master_shown ? #"Hide Master" : #"Show Master";
self.splitVC.showsMasterInPortrait = master_shown;
}
If you only want the button to appear in portrait, you'll also need to hide it on autorotation, so respond to the rotation event (still in the detail controller):
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
// hide when in landscape, show when in portrait
self.toggleButton.hidden = UIInterfaceOrientationIsLandscape(toInterfaceOrientation);
}
And that should hopefully be all. You'll also want to set up defaults for showsMasterInPortrait and the toggle button label and visibility somewhere, probably in viewDidLoad.

Resources