I had a UISplitViewController with the following layout, consisting of a single master and detail view controller.
This worked fine for a basic split view with single views, but I needed to support multiple segues from the UITableViewController (Master View) and not load the detail views until data is passed; or else the app will crash because of optional errors.
I tried by having a set up like so;
This loads a blank ViewController as the detail view when the UISplitViewController loads, and when a row is selected the I have a detail segue to the other view controllers, which should appear as a detail view in the UISplitViewController.
This unfortunately does not work exactly, all the data is passed and loaded without crashes but the detail segues actually load the view controllers within the master view window of the split view not the detail view.
Kind of like this,
How can I have multiple detail view controllers which are not loaded until initiating a segue from the master view UITableViewController and open in the detail window ?
Here's the code from the MasterViewController
override func viewDidLoad() {
super.viewDidLoad()
self.splitViewController!.delegate = self;
self.splitViewController!.preferredDisplayMode = UISplitViewControllerDisplayMode.AllVisible
self.extendedLayoutIncludesOpaqueBars = true
}
func splitViewController(splitViewController: UISplitViewController, collapseSecondaryViewController secondaryViewController: UIViewController, ontoPrimaryViewController primaryViewController: UIViewController) -> Bool {
return true
}
If the segues were already there then try removing the segues from the master view to the other detail views and recreating them using a detail segue.
Related
I want to pass data from a view controller to tabbar controller, and then from this tab bar controller to its view controller with using their class. I succeeded to transfer from view controller to tabbar controller using segue. However, I cannot transfer data from tabbar controller to its one of the view controller.
Any idea/documentation will be appreciated. Here is the screenshot about what I want to do from xcode
screenshot-xcode
Take a look at the documentation for the tab bar controller, in particular the viewControllers property.
That property is an array of UIViewControllers, in the order they appear in the tab bar, so you can pick the one you need (viewControllers[0] from your screen shot), cast it to your specific view controller subclass and then pass it your data.
At last, I am able to solve the issue, many thanks to the answer. Here is the detailed explanation for beginners like me:
This is the source controller class, which is a tabbar controller and it transfers the data:
class SourceTC: UITabBarController {
var dataTransferFrom = "transfer this string"
override func viewDidLoad() {
super.viewDidLoad()
let finalVC = self.viewControllers![0] as! DestinationVC //first view controller in the tabbar
finalVC.dataTransferTo = dataTransferFrom
}
}
and this is the destination controller class, which is a view controller under tabbar controller and it gets the transferred data:
class DestinationVC: UIViewController {
var dataTransferTo = ""
override func viewDidLoad() {
super.viewDidLoad()
print(dataTransferTo)
}
}
I currently have parental "menu" TableView with UINavigationBar and from each cell there is a segues by reference outlet to 3 similar Views with different information.
In each View there is a buttons to other 2 Views.
With every button's segue opens another View.
The problem:
From every View UINavigationBar's back button returns me to previous View but i tries to make back button to "menu".
Additional Bar Button Item and segue from it makes very close effect but segue animation is not like in UINavigationController.
How I could clean UINavigationBar transitions history in segue to initial View?
You can try pop to root view controller or You can edit navigation controller viewControllers property and remove/add some VC in between.
You can try Unwind Segue mechanism too.
Here are some methods(function) that navigation controller providing for pop operations. They are returning optional UIViewController (intance) from it’s navigation stack, that is popped.
open func popViewController(animated: Bool) -> UIViewController? // Returns the popped controller.
open func popToViewController(_ viewController: UIViewController, animated: Bool) -> [UIViewController]? // Pops view controllers until the one specified is on top. Returns the popped controllers.
open func popToRootViewController(animated: Bool) -> [UIViewController]?
Here is sample code as a solution to your query::
// if you want to back to root of your app
if let rootNavigationController = self.window?.rootViewController as? UINavigationController {
rootNavigationController.popToRootViewControllerAnimated(true)
}
// But if you want to back to root of your current navigation
if let viewcontroller = self.storyboard?.instantiateViewController(withIdentifier: "NewViewController") as? NewViewController { // or instantiate view controller using any other method
viewcontroller.navigationController?.popToRootViewControllerAnimated(true)
}
I'm running into an issue where after changing the rootViewController on my UINavigationController and changing it back to my original UINavigationController, a UISplitViewController begins to show both it's master and detail view in a phone device on compact/portrait orientation (so not only on plus size phones, but also others).
Basic overview of architecture:
A TabBarController houses several tabs. One of these tabs is a UISplitViewController. I currently override the following to ensure that the MasterViewController is shown on compact orientations:
func splitViewController(_ splitViewController: UISplitViewController, collapseSecondary secondaryViewController: UIViewController, onto primaryViewController: UIViewController) -> Bool {
// this prevents phone from going straight to detail on showing the split view controller
return true
}
This works fine and displays the master on portrait as expected. At any point pressing a button on another tab can create a new UINavigationController instance and display it, in which I'm doing the below to change the rootViewController to the newly created UINavigationController to display:
let appDelegate = UIApplication.shared.delegate
appDelegate?.window??.rootViewController = newNavVC
On dismiss, I'm just swapping the UINavigationController back to the original one through the same code above. However, once I do this one time (create nav/display/dismiss), and I switch my tab back to the one with the UISplitViewController, it changes itself to show a side-by-side master detail view. I didn't know this was possible in portrait mode for compact sizing. I tried changing to any of the 4 preferred display modes in the UISplitViewController, but that didn't fix it.
Below is what it looks like (iPhone 6 simulator), am I missing delegates or misunderstanding collapsing?
Before:
After:
You can replace the the logic that assigned the rootViewController with the code snippet found at this link:
Leaking views when changing rootViewController inside transitionWithView
Basically you just create an extension for the UIWindow class that will set the root view controller correctly.
extension UIWindow {
/// Fix for https://stackoverflow.com/a/27153956/849645
func set(rootViewController newRootViewController: UIViewController, withTransition transition: CATransition? = nil) {
let previousViewController = rootViewController
if let transition = transition {
// Add the transition
layer.add(transition, forKey: kCATransition)
}
rootViewController = newRootViewController
// Update status bar appearance using the new view controllers appearance - animate if needed
if UIView.areAnimationsEnabled {
UIView.animate(withDuration: CATransaction.animationDuration()) {
newRootViewController.setNeedsStatusBarAppearanceUpdate()
}
} else {
newRootViewController.setNeedsStatusBarAppearanceUpdate()
}
/// The presenting view controllers view doesn't get removed from the window as its currently transistioning and presenting a view controller
if let transitionViewClass = NSClassFromString("UITransitionView") {
for subview in subviews where subview.isKind(of: transitionViewClass) {
subview.removeFromSuperview()
}
}
if let previousViewController = previousViewController {
// Allow the view controller to be deallocated
previousViewController.dismiss(animated: false) {
// Remove the root view in case its still showing
previousViewController.view.removeFromSuperview()
}
}
}
I have been following the XLpagerTabStrip cocoapods extension to set up a tab bar at the top of my view controller (https://github.com/xmartlabs/XLPagerTabStrip). I am implementing the ButtonBarPagerTabStripViewController and have followed the steps exactly, but the UIScrollView is not displaying the child view controllers.
I think this is because in the example the child view controllers are set up programmatically rather than in the storyboard, whereas mine are currently set up in the storyboard, and hence are displaying no content, yet there is no way that I can see to connect the child view controllers to the ButtonBarController via the storyboard.
Is there a way to display the child view controllers using the storyboard, or must it be done programmatically?
The GitHub page says to implement the following function (But this doesnt seem to work if your child view controllers are set up via the storyboard):
override public func viewControllersForPagerTabStrip(pagerTabStripController: PagerTabStripViewController) -> [UIViewController]
{
return [MyChildViewController(), MySecondChildViewController()]
}
You need to load controller from storybard:
override public func viewControllersForPagerTabStrip(pagerTabStripController: PagerTabStripViewController) -> [UIViewController]
{
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let childOneVC = storyboard.instantiateViewControllerWithIdentifier("childOneVC")
let childTwoVC = storyboard.instantiateViewControllerWithIdentifier("childTwoVC")
return [childOneVC, childTwoVC]
}
I have 3 UISplitViewControllers with different master views, but they have the same detail view. All of them are connected in Storyboard.
All UISplitViewControllers are nested in UITabBarViewController, so I switch between them via tab bar items.
The problem is, when I switch to another tab (another UISplitViewController) detail view disappears, I see only master view and a place for detail view is filled with dark gray (see pic). I don't want to reload detail view after switching, just leave it as is on the right side of the screen.
I'm not sure what code I need to provide, so if you need any, ask, I'll add it to question.
Thanks for any help!
Cause
My first hypothesis was that the if you share a detail view controller between two distinct UISplitViewControllers, that correspond to two tabs of a UITabController, two separate detail view controllers are created.
This is confirmed with a test project with this layout:
Root View Controller is a DetailViewController. When I put a breakpoint inside viewDidLoad(_:), it gets hit twice and printing shows that two different instances of DetailViewController are created:
(lldb) po self
<TestTabSplit.DetailTableViewController: 0x7fbd10eb9cd0>
(lldb) po self
<TestTabSplit.DetailTableViewController: 0x7fbd10ebc700>
Solution
Use a shared container view controller as the detail view controller of the two UISplitViewControllers.
Your new storyboard layout will look like this:
Give your detail view controller (in this case a navigation controller), a Storyboard ID:
Next, in your app delegate, instantiate the detail view controller:
// Add a variable to reference from elsewhere.
var sharedNavigationController: UINavigationController!
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// Override point for customization after application launch.
sharedNavigationController = UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("SharedID") as! UINavigationController
return true
}
Finally, the container view controller, ContainerViewController, is just a subclass of UIViewController with the following contents:
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
let sharedNavigationController = appDelegate.sharedNavigationController
addChildViewController(sharedNavigationController)
sharedNavigationController.view.frame = view.bounds
view.addSubview(sharedNavigationController.view)
sharedNavigationController.didMoveToParentViewController(self)
}
With this setup, you'll find that the same detail view controller instance is share between tabs and modifications in one tab are persisted when you change to a new tab.