One detail view for several UISplitViews - ios

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.

Related

How do I transfer back to a view controller from a view controller that is not connected to other view controllers?

I am working on an app for iOS that has a view controller not connected to other view controllers. Currently I am trying to get a button to transfer back to a login/signup view controller from the main view controller that is instantiated after the user has logged in. The view controller that is instantiated after the user has logged in is not connected to the other view controllers. The way I get to the unconnected view controller is with
func transitionToHome()
{
let homeViewController = storyboard?.instantiateViewController(identifier: Constants.Storyboard.homeViewControllers) as? homeViewController
view.window?.rootViewController = homeViewController
view.window?.makeKeyAndVisible()}
I am also including an image of the view controllers so that it is easier to understand how I have it set up photo of the view controllers
I have tried hooking up the "log out" button to transition back to the login/signup view controller but that causes a separate, smaller scene to be brought up that I can just swipe away and be back at the main view controller. I have also tried using the code above to transfer back to the login/sign up view controller, but that caused the same problem.
Looks like you will need to reset the view.window?.rootViewController to login/signup view controller. Something like this
view.window?.rootViewController = LoginViewController()
view.window?.makeKeyAndVisible()}
Although you can swap the rootViewController after app launch, it may just be simpler to arrange view controllers so they all link back to a single rootViewController.
If you want an initial login screen that is skipped on subsequent launches of the app, then you could make the login view controller the rootViewController and in viewDidAppear() jump straight to the main view controller when the login is not required. Then you can unwind the segue for "logout".
In the app delegate:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
let loginViewController = UIStoryboard(name: "Login", bundle: nil).instantiateInitialViewController()
self.window?.rootViewController = loginViewController
self.window?.makeKeyAndVisible()
}
Then in LoginViewController:
func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if alreadyAuthenticated {
performSegue(withIdentifier: SegueIdentifier.goToMain, sender: self)
}
}
Other combinations like this are possible depending on your exact needs. There may be some benefit in that you can access the default presentation and dismiss animations, which would be much harder to implement if you are swapping the rootViewController.

How I could clean UINavigationBar transitions history?

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)
}

Switching UIViewController without presenting it modally or stacking it on top of each other

I know there are two ways to show a new UIViewController in Swift. There are:
self.present(controllerToPresent, animated: true, completion: nil)
and
self.performSegue(withIdentifier: "controllerToPresent", sender: nil)
But both of them show the new UIViewController on top of the other. Assume I don't want to stack controllers on each other rather than just switch the controllers. The new presented UIViewController should be the new root-controller. An example for this would be a login page. Once a user logged in I don't use the login-controller anymore, so why would I like to stack the new controller on top of it. So the question is, is there a method to switch (not stacking) UIViewControllers?
Furthermore I want to know what happens to the memory that was allocated for a new instance of an UIViewController when I use one of these two functions above. I'm not sure if at some time ARC frees the memory or if I run out of memory at some time calling these functions too often.
There are many ways to do what you want...
One approach, since you comment that you want animation:
Use a "container" view as your "root" view controller
On launch, check if user is "logged in"
If not logged in, instantiate "login" view controller, and use addChildViewController() and addSubview() to show your "login" view.
Else, if already logged in on launch, instantiate "main" view controller, and use addChildViewController() and addSubview() to show your "main" view.
In the case of 3, when user completes the log=on process, instantiate "main" view controller, and use addChildViewController()... then addSubview(), but add it hidden and/or off-screen, and use a UIView animation to replace the "login" view with the "main" view... then remove the login view and controller from memory (removeFromSuperview, removeFromParentViewController, set vc reference to nil, etc).
If at some point you want to "log-off" and return to the login screen, do the same thing... instantiate loginVC, addsubview, animate subview, remove mainVC.
Specifically, for the case of (as you mentioned as an example of what are looking for):
An example for this would be a login page. Once a user logged in I
don't use the login-controller anymore
You would need to determine the desired rootViewController in the app delegate, example:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// let's assume that you impelemnted to logic of how to determine whether the user loggedin or not,
// by using 'isLoggedin' flag:
if let wnwrappedWindow = self.window {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
if isLoggedin {
let rootHomeVC = storyboard.instantiateViewControllerWithIdentifier("HomeViewController") as! HomeViewController
//...
wnwrappedWindow.rootViewController = rootHomeVC
} else {
let rootloginVC = storyboard.instantiateViewControllerWithIdentifier("HomeViewController") as! HomeViewController
//...
wnwrappedWindow.rootViewController = rootloginVC
}
}
return true
}
In case of you want to change the root view controller in the login view controller, you could implement the following code when it is a success login:
let ad = UIApplication.shared.delegate as! AppDelegate
if let window = ad.window {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let rootHomeVC = storyboard.instantiateViewControllerWithIdentifier("HomeViewController") as! HomeViewController
//...
window.rootViewController = rootHomeVC
}

How to navigate to different view controllers based on slide out menu

I'm new to Swift and have been trying various thing for many hours now.
Here's how my app looks like:
Ideally I want them to click an option from this pop out menu that will take them to another view. Right now, I'm on the QuotesTimelineViewController but say I want to click on the bookmark button then I'd want to go to the SavedQuotesViewController.
Here is how my storyboard looks like:
The QuotesViewController has a NavigationController embedded in it. When you click the menu button it goes to the NavigationSideController which is presented modally. Now I don't know how to make the segues for what I want to accomplish next.
Here is my code for the NavigationSideController so right now what happens when you click on an icon in the navigationsidebar it dismisses that animation and the navigationsidebar goes away. Maybe you can see all the commented out code.
And here's some relevant code (I think) from QuotesTimelineViewController:
But there could be something I'm missing so if you want to look at my whole repo it's here: https://github.com/mayaah/ios-decal-proj4
Any guidance would be so helpful! Eventually I'd like that whenever I click on an icon in the navigationsidebar the view controller corresponding to that icon opens up. The most progress i've made so far was getting a window hierarchy error! I guess because when I click on an icon, QuotesTimelineViewController isn't the at the top of the stack or something I don't know.
I can propose two solutions here
Use a side menu third party library.
There are lot of things to take care of while creating a app side
menu including nested heirarchy/rotations, shadows etc. The third
party libraries will take care of them easily and are well tested
code. You can choose the one you like from cocoacontrols
Use a Navigation Controller and change Active VC on table selection
The basic idea is to make a new navigationController in your app delegate and make all different type of entry points.
so in your app delegate, declare a new UINavigationController variable.
You wont be using the storyboard initialVC in this approach.
in didFinishLaunching Method of app delegate
var navController:UINavigationController!
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// Override point for customization after application launch.
let storyboard = UIStoryboard(name: "Main", bundle: nil)
// make sure to set storyboard id in storyboard for these VC
let startingVC = storyboard.instantiateViewControllerWithIdentifier("QuotesTimeLine");
navController = UINavigationController(rootViewController: startingVC)
self.window!.rootViewController = navController
return true
}
and then in your side menu, you update the navController's viewControllers with new VC
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
// make sure to set storyboard id in storyboard for these VC
let startingVC = storyboard.instantiateViewControllerWithIdentifier("NewVC");
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
appDelegate.navController.viewControllers = [startingVC]
dismissViewControllerAnimated(true, completion: nil)
}
This is still rough but gives the idea of what you need to manage the heirarchy.
Let me know if any clarification is needed.

UISplitViewController detail view controller loads in master view window

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.

Resources