I have implemented a UISplitViewController and all works fine. What I want to do is on iPhone devices only show the detailView not the masterView as the first view controller. I realise I can create a segue from the master view to the detail view in the masters viewDidLoad method however this feels a bit hacky to me. Maybe this is the only way to achieve what I want?
I have looked at the documentation for the UISplitViewControllerDelegate particularly this function however I don't feel I grasped what this actually is doing. I have also set the UISplitViewController as the delegate and set allVisible and tried all the other options in the viewDidLoad of my SplitViewController sub class
self.delegate = self
self.preferredDisplayMode = .allVisible
func splitViewController(_ splitViewController: UISplitViewController, collapseSecondary secondaryViewController: UIViewController, onto primaryViewController: UIViewController) -> Bool {
return true
}
If it helps the detailViewController heirachy in the storyboard is SplitViewController > UINavigationController > myDetailViewController
What you need to do is to use the splitviewcontroller delegate function
func splitViewController(_ splitViewController: UISplitViewController, collapseSecondary secondaryViewController:UIViewController, onto primaryViewController:UIViewController) -> Bool
In there you can push your second controller into your first navigation controller and return true. Returning true means that you're gonna handle the transition. e.g.
func splitViewController(_ splitViewController: UISplitViewController, collapseSecondary secondaryViewController:UIViewController, onto primaryViewController:UIViewController) -> Bool {
if let detailViewController = secondaryViewController as? YourSecondViewController, let primaryNV = primaryViewController as? UINavigationController {
primaryNV.pushViewController(detailViewController, animated: false)
returns true // I handle it myself.
}
return false // let the iOS handles it.
}
If you need more clarification, please let me know. I'll try to explain it better. cheers!.
Related
I have what seems to be a very common setup in my universal application, with a root UISplitViewController, using a UITabBarController as a masterViewController, and then I want to:
either push the detail view controller onto the stack if I'm on a vertical iPhone
show the detail controller in the detailViewController of the UISplitViewController on lanscape iPhone 6+ and other larger screens like iPads and such
To that effect, I have exactly the same setup as the ones described in all those discussions that mention a similar issue:
UINavigationController inside a UITabBarController inside a UISplitViewController presented modally on iPhone
iOS8 TabbarController inside a UISplitviewController Master
Adaptive show detail segue transformed to modal instead of push on iPhone when master view controller is a UITabBarController
But none of the solutions mentioned in those questions works. Some of them create an infinite recursive loop and an EXC_BAD_ACCESS. And the latest one I tried simply keeps presenting the detail view controller modally instead of pushing it onto the stack on iPhones. What I did is create a custom UISplitViewController subclass as such:
class RootSplitViewController: UISplitViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.delegate = self
}
}
extension RootSplitViewController: UISplitViewControllerDelegate {
func splitViewController(_ splitViewController: UISplitViewController, showDetail vc: UIViewController, sender: Any?) -> Bool {
if let tabController = splitViewController.viewControllers[0] as? UITabBarController {
if(splitViewController.traitCollection.horizontalSizeClass == .compact) {
tabController.selectedViewController?.show(vc, sender: sender)
} else {
splitViewController.viewControllers = [tabController, vc]
}
}
return true
}
func splitViewController(_ splitViewController: UISplitViewController, separateSecondaryFrom primaryViewController: UIViewController) -> UIViewController? {
if let tabController = splitViewController.viewControllers[0] as? UITabBarController {
if let navController = tabController.selectedViewController as? UINavigationController {
return navController.popViewController(animated: false)
} else {
return nil
}
} else {
return nil
}
}
}
And here is the code in the master view controller to show the detail view controller:
self.performSegue(withIdentifier: "showReference", sender: ["tags": tags, "reference": reference])
Where tags and reference where loaded from Firebase. And of course the "showReference" segue is of the "Show Detail (e.g. Replace)" kind.
The first delegate method is called correctly, as evidenced by the breakpoint that gets hit there when I click an item in the list inside the UITabBarController. And yet the detail view controller still presents modally on iPhone. No problem on iPad though: the detail view controller appears on the right, as expected.
Most of the answers mentioned above are pretty old and some of the solutions are implemented in Objective-C so maybe I did something wrong in the conversion, or something changed in the UISplitViewController implementation since then.
Does anyone have any suggestion?
I figured it out. In fact, it was related to the target view controller I was trying to show. Of the 2 methods I was overriding in UISplitViewControllerDelegate, only the first one was called:
func splitViewController(_ splitViewController: UISplitViewController, showDetail vc: UIViewController, sender: Any?) -> Bool {
if let tabController = splitViewController.viewControllers[0] as? UITabBarController {
if(splitViewController.traitCollection.horizontalSizeClass == .compact) {
tabController.selectedViewController?.show(vc, sender: sender)
} else {
splitViewController.viewControllers = [tabController, vc]
}
}
return true
}
But the view controller I was showing in the first branch of the test was already embedded into a UINavigationController, so I was essentially showing a UINavigationController into another one, and in that case the modal made more sense. So in that case I needed to show the top view controller of the UINavigationController, which I assume was the purpose of the second method I'm overriding in the delegate, but it was never called. So I did it right there with the following implementation:
extension RootSplitViewController: UISplitViewControllerDelegate {
func splitViewController(_ splitViewController: UISplitViewController, showDetail vc: UIViewController, sender: Any?) -> Bool {
if let tabController = splitViewController.viewControllers[0] as? UITabBarController {
if(splitViewController.traitCollection.horizontalSizeClass == .compact) {
if let navController = vc as? UINavigationController, let actualVc = navController.topViewController {
tabController.selectedViewController?.show(actualVc, sender: sender)
navController.popViewController(animated: false)
} else {
tabController.selectedViewController?.show(vc, sender: sender)
}
} else {
splitViewController.viewControllers = [tabController, vc]
}
}
return true
}
}
And that seems to work perfectly, both on iPhones and iPads
Having you tried ShowDetailViewController Method to change the detail view controller in split view controller.
splitViewController.showDetailViewController(vc, sender: self)
If in case your view controller does not contain navigation controller you can also embed it in a navigation controller.
let nav = UINavigationController.init(rootViewController: vc)
splitViewController.showDetailViewController(nav, sender: self)
I have a splitview with Master (showing table view) and Details (Showing detail data). When I launch my app it shows detailview first with no data but I need to show masterview first.
I know that there are some answers but nothing works for me.
Any solution?
Try this:
class MySplitViewControllerShowingMasterViewFirst: UISplitViewController, UISplitViewControllerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
self.delegate = self
}
func splitViewController(_ splitViewController: UISplitViewController, collapseSecondary secondaryViewController: UIViewController, onto primaryViewController: UIViewController) -> Bool {
return true
}
}
iOS 10/Swift:
Using SplitViewController on an iPhone the user sees the detail view when the app loads (whether in portrait or landscape both have compact width). How can you change this to load the master view on startup?
Note that when you load in a Regular Width view (ie: iPhone 6s Plus landscape) we want the Split View to continue to be shown (and not master).
You should use method
func splitViewController(_ splitViewController: UISplitViewController, collapseSecondary secondaryViewController: UIViewController, onto primaryViewController: UIViewController) -> Bool
which provided by UISplitViewControllerDelegate
You can define a custom UISplitViewController and assign it to your split view in the storyboard:
import UIKit
class MainSplitViewController: UISplitViewController, UISplitViewControllerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
self.delegate = self
}
func splitViewController(_ splitViewController: UISplitViewController, collapseSecondary secondaryViewController: UIViewController, onto primaryViewController: UIViewController) -> Bool {
return true
}
}
Okay i have a Universal single view application with a UITabbarController as the initial ViewController. i have a UISplitViewController as an item in one of the tabs. the SplitViewController has a navigationController as its master segue, which has a viewController with a uitableView in it and if you click a cell it "shows" the detail view (I've tried the show detail segue also). the splitViewControllers detail view controller segue goes to the detail view controller.
my problem is when i go to the tab with the splitViewController in it it shows the detail first and not the master(same for both ipad and iphone). i have spent hours reading and watching different tutorials and looking at questions on here and cant find a solution.
code i have tried(in a custom split viewcontroller class and viewcontroller with the tableView class):
func splitViewController(splitViewController: UISplitViewController, collapseSecondaryViewController secondaryViewController: UIViewController, ontoPrimaryViewController primaryViewController: UIViewController) -> Bool {
return true
}
override func viewWillAppear(animated: Bool) {
splitViewController?.delegate = self
self.splitViewController!.delegate = self;
self.splitViewController!.preferredDisplayMode = UISplitViewControllerDisplayMode.AllVisible
if UIDevice.currentDevice().userInterfaceIdiom == .Phone {
if let con = self.splitViewController {
con.preferredDisplayMode = .PrimaryOverlay
print("phone")
//^this code doesnt run when i run it on my iphone
}
} else if UIDevice.currentDevice().userInterfaceIdiom == .Pad {
if let spec = self.splitViewController {
spec.preferredDisplayMode = .AllVisible
}
} else {
if let tit = self.splitViewController {
tit.preferredDisplayMode = .Automatic
}
}
}
//controller with tableView class declaration
class OutfitTable : UIViewController, UITableViewDelegate, UITableViewDataSource, UISplitViewControllerDelegate {
//custom splitViewController class declaration
class SplitViewController: UISplitViewController, UISplitViewControllerDelegate
screenshot of main storyboard:
im sorry if this is unnecessarily descriptive i just want to make sure i get all the information out there.
Anything helps. Thank You in advance
In XCode 6, if you create a new project based on the Master-Detail Application template, you get a universal storyboard that is supposed to be good for all devices.
When selecting a cell in the master view, the detail view is updated via an adaptive "show detail" segue. On an iPhone 4, 5, 6 or 6+ in portrait, this segue will take the form of a push as expected. On an iPad or an iPhone 6+ in landscape, it will cause the detail view to be updated as expected.
Now, if you insert a UITabBarController as the master view controller which has a tab to the original master view controller, the adaptive segue that occurs when selecting a cell in the master view does not behave as expected on iPhones. Instead of getting a push transition, you now get a modal transition. How can I fix that? Seems odd that this is not supported by default.
I found the following post useful: iOS8 TabbarController inside a UISplitviewController Master
But when using the suggested method, I don't get the right behaviour on an iPhone 6 Plus when I rotate to landscape after a push in portrait. The content of the detail view appears in the master view which is not surprising since that's what the suggested solution does.
Thanks!
Re-watching videos from WWDC14 I think I've found a better answer.
Use a custom UISplitViewController (subclass)
Override the showDetailViewController operation
Use the traitCollection to determine the class of the UISplitViewController
If the horizontal class is Compact, get the navigationController to call showViewController
Here is the the code of the custom UISplitViewController :
import UIKit
class CustomSplitViewController: UISplitViewController {
override func showDetailViewController(vc: UIViewController!, sender: AnyObject!) {
if (self.traitCollection.horizontalSizeClass == UIUserInterfaceSizeClass.Compact) {
if let tabBarController = self.viewControllers[0] as? UITabBarController {
if let navigationController = tabBarController.selectedViewController as? UINavigationController {
navigationController.showViewController(vc, sender: sender)
return
}
}
}
super.showDetailViewController(vc, sender: sender)
}
}
Do not forget to the set the custom class in the storyboard.
Tested in the simulator of iPhone 6, iPhone 6+ and iPad Air and worked as expected.
Unfortunately, the selected answer didn't work for me. However, I did eventually manage to solve the problem:
Subclass UISplitViewController and set the new class in Interface Builder.
Make the new class conform to UISplitViewControllerDelegate:
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.delegate = self
}
Implement these two methods:
func splitViewController(_ splitViewController: UISplitViewController,
collapseSecondary secondaryViewController:UIViewController,
onto primaryViewController:UIViewController) -> Bool {
return true
}
func splitViewController(_ splitViewController: UISplitViewController,
showDetail vc: UIViewController,
sender: Any?) -> Bool {
if splitViewController.isCollapsed {
guard let tabBarController = splitViewController.viewControllers.first as? UITabBarController else { return false }
guard let selectedNavigationViewController = tabBarController.selectedViewController as? UINavigationController else { return false }
// Push view controller
var detailViewController = vc
if let navController = vc as? UINavigationController, let topViewController = navController.topViewController {
detailViewController = topViewController
}
selectedNavigationViewController.pushViewController(detailViewController, animated: true)
return true
}
return false
}
The docs state when the split controller is collapsed, it handles showDetail by calling show on the master view controller, which in your case is a tab controller. You need to forward that on to the child nav controller as follows:
Make a tab controller subclass.
In the storyboard set the tab controller to use the new subclass.
Add this method to the subclass:
- (void)showViewController:(UIViewController *)vc sender:(id)sender{
[self.viewControllers.firstObject showViewController:vc sender:sender];
}
This forwards it on to the nav controller in the first tab.