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.
Related
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
}
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.
I have just made a small transition so my project loads a different storyboard as its main (did it in the info.plist).
I have my new storyboard to keep my viewController that are responsible for login screen etc. Just to make it more clear.
After the login button is tapped I want to initiate a navigationController from another storyboard:
func instantiateViewController(fromStoryboard storyboard: String, withIdentifier identifier: String) -> UIViewController! {
let storyboard = UIStoryboard(name: storyboard, bundle: nil)
let viewController = storyboard.instantiateViewControllerWithIdentifier(identifier)
return viewController
}
#IBAction func loginButtonTapped(sender: UIButton) {
let viewController = instantiateViewController(fromStoryboard: "Main", withIdentifier: "MainNavigationController")
presentViewController(viewController, animated: true, completion: nil)
}
Everything works correctly but one thing is driving me nuts.
After presenting the MainNavigationController from Main.storyboard its view hierarchy is not maintained.
What I mean is, the labels and buttons which supposed to be on top of another, full screen UIView (but are not its child and so they should remain) are now behind it.
What might be causing this and what is the simplest way to make them appear on top (as they do whey I open main.storyboard)
EDIT
I added a line of code in the rootView of the MainNavigationController in its viewDidLoad method:
self.view.sendSubviewToBack(wholeScreenView)
and it solved the problem.
However, does anybody know why do I have to code it myself and the views are not like in the Main.storyboard?
The storyboard is not configured the way you think it is. Your wholeScreenView is in fact in front of the other views, in the storyboard. The other views (the labels and buttons) are not subviews of wholeScreenView; they are subviews of the main view, and so is wholeScreenView. It is a later subview, so it is in front of them.
I'm using the Material Framework by CosmicMind. Currently I'm trying to replace the ViewController once a row is selected in the SideNavigationController. Unfortunately I can't figure out how to do this. There're similar question here on StackOverflow (#1) unfortunately the solutions don't work for me.
Following my code in the AppDelegate.swift class:
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let myViewController = storyboard.instantiateViewControllerWithIdentifier("MyViewController")
let navigationController: NavigationController = AppNavigationController(rootViewController: myViewController)
let sideNavigationController: SideNavigationController = SideNavigationController(rootViewController: navigationController, leftViewController: AppLeftViewController())
// further code
return true
}
With this code everything works fine. The ViewController (MyViewController) is shown.
Now I'm trying to replace the MyViewController with MySecondViewController however this doesn't work. Following the code I've been using:
sideNavigationController?.transitionFromRootViewController(MySecondViewController())
The result is that the Toolbar disappears and I can't close the SideNavigationController anymore. So I've tried the following:
let navigationController: NavigationController = AppNavigationController(rootViewController: MySecondViewController())
sideNavigationController?.transitionFromRootViewController(navigationController)
With this code the Toolbar is visible once again but the problem with the SideNavigationController remains -> means: It can't be closed.
tl;dr
How do I replace the rootViewController of the NavigationController properly?
The NavigationController is a subclass of UINavigationController. So changing the rootViewController is as iOS made it. For example:
navigationController?.pushViewController(MySecondViewController(), animated: true)
The SideNavigationController offers the ability to transition its rootViewController. This is different, as the SideNavigationController uses child UIViewControllers and swaps them when asked. For example:
sideNavigationController?.transitionFromRootViewController(MySecondViewController())
Sometimes, people use the sideNavigationController to early. For example, you instantiate a new UIViewController, and place the sideNavigationController code in the videDidLoad method. This won't work since the new UIViewController was not yet added to the sideNavigationController view hierarchy. To solve this, use the viewWillAppear method.
The last situation to consider is when you want to load a new UIViewController in the rootViewController of the NavigationController from the sideNavigationController. In order to do this, and let's consider that the sideNavigationController's rootViewController is the NavigationController, you would need to do this:
(sideNavigationController?.rootViewController as? NavigationController)?.pushViewController(MySecondViewController(), animated: true)
This should help you out :)
I hope this is a simple question. I have not found anything in SO that seems to fit what I'm trying to do. I am working on my first ever project so much of this is still quite new to me. The code I have pasted is from a test project I'm using before I add code to my real one.
My initial VC is the EULA. When the user accepts, I want to change the initial VC to the log in view. In this test project, I have two views with an IBAction for a Bar Button Item that will segue the project from VC to VC2. I am trying to set up code to change the initial VC in the AppDelegate with
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// Override point for customization after application launch.
let storyboard = UIStoryboard(name: "View Controller", bundle: NSBundle.mainBundle())
let licenseAccepted = ?
var vc: UIViewController?
if !licenseAccepted {
vc = storyboard.instantiateViewControllerWithIdentifier("View Controller2")
} else {
vc = storyboard.instantiateInitialViewController()
}
window = UIWindow(frame: UIScreen.mainScreen().bounds)
window?.rootViewController = vc
window?.makeKeyAndVisible()
return true
}
but this is not working. First, let licenseAccepted = ? is not correct. I found that in another set of code and I thought it might work, but no. Also, how do I set a var for if the user accepts the license in the AppDelegate? Should that be done in the initial VC, the second VC that will become the initial VC or somewhere else?
I am using Xcode 7.1 and Swift2 if that helps.
I may be way off base or I may be a keystroke or two away. I am just not getting my head around this. Your assistance will be appreciated.