I have a tab bar in my app and one of the buttons is "Profile" which should present one VC if user not authorized and another if user already authorized.
So... Things work, but not correctly. Looks like I messed up some root controllers. Sometimes I cant pop up vc or change the color of a nav bar title.
I create a custom navigation controller with that code inside
if NSUserDefaults.standardUserDefaults().objectForKey("userId") != nil {
let newController: UserViewController = self.storyboard?.instantiateViewControllerWithIdentifier("userViewController") as! UserViewController
self.setViewControllers([newController], animated: false)
} else {
let newController: LoginViewController = self.storyboard?.instantiateViewControllerWithIdentifier("loginViewController") as! LoginViewController
self.setViewControllers([newController], animated: false)
}
Is there a way to implement in correctly?
You're trying to instantiate a Navigation Controller - which (as #PierreMB had mentioned) won't work. You should instead create two regular View Controllers with storyboard ID's "userViewController" and "loginViewController", and instantiate them.
Create a UINavigationController subclass, and put this code (which you wrote, slightly modified) in its viewWillAppear() method (as a bonus, you can pass the 'animated' parameter free of charge):
if NSUserDefaults.standardUserDefaults().objectForKey("userId") != nil {
let newController = self.storyboard?.instantiateViewControllerWithIdentifier("userViewController") // drop the 'as! UINavigationController'
self.setViewControllers([newController], animated: animated)
} else {
let newController = self.storyboard?.instantiateViewControllerWithIdentifier("loginViewController") // drop the 'as! UINavigationController'
self.setViewControllers([newController], animated: animated)
}
The function instantiateViewControllerWithIdentifier() by default returns a UIViewController, which is what you really want to display.
Are you trying to put a UINavigationController as a root of another UINavigationController ? Because it is prohibited and doesn't work well.
Related
I have in my project tow view controller and I linked the first VC with navigation controller but the problem is : I used present to go to second VC (that mean I didn't use segue) ... how can I set back to first VC in navigation Controller by code (without segue) .
picture from my storyboard
my code :
let storyboard = self.storyboard
let viewcontroller = storyboard?.instantiateViewController(withIdentifier: "contact_detail") as! ViewController2
viewcontroller.arr2 = arr
present(viewcontroller, animated: true, completion: nil)
With the dismiss method it will work.
Dismisses the view controller that was presented modally by the view controller. (Apple Docs)
self.dismiss(animated: true, completion: nil)
If you are presenting a viewcontroller with present method, you can dismiss it with dismiss method.
If you are adding any view controller with push method then only it will get added to your navigation stack and you can remove it by calling popviewcontroller on it's back action.
Here, you are presenting a viewcontroller, hence it will not get added to your first navigation stack and you can not remove it with pop action on click of back.
If you are looking for a back button like feature on presented view controller, you can add a back button in toolbar, dismiss a viewcontroller on back button action, and can animate it like a popnavigation while dismissing.
extension UINavigationController {
public func removeViewController(classes : [String]) {
var vcs = [UIViewControllers]()
for viewController in self.viewControllers {
let name = viewController.className
if !classes.contains(name) {
vcs.append(viewController)
}
}
if classes.count < vcs.count {
self.viewControllers = vcs
}
}
}
now think you have 4 viewControllers , A, B, C, D, you want to remove B and C and Move Back To A
In D's View Controller
override func viewDidLoad() {
super.viewDidLoad()
//your works
let viewControllersToRemove = [String(describing: type(of:B)), String(describing: type(of:C))]
navigationController.removeViewControoler(classes : viewControllersToRemove)
}
I solved this by using pushViewController
self.navigationController?.pushViewController(MyViewController, animated: true)
I came across a library call Motion on GitHub which provide sets of transition on view and controllers.
Based on the website, to use the animation on navigation controller we need to create a new navigation controller class like below, and I apply the new navigation controller. But when it failed and not pushing to the new view controller.
the GitHub link is https://github.com/CosmicMind/Motion
class AppNavigationController: UINavigationController {
open override func viewDidLoad() {
super.viewDidLoad()
isMotionEnabled = true
motionNavigationTransitionType = .zoom
}
}
then on the main view:
let navigation = AppNavigationController()
navigation.pushViewController(newViewController, animated: false)
If you're using Motion's animated NavigationController throughout your app, you can set it this way in your AppDelegate's didFinishLaunchingWithOptions method.
let newViewController = NewViewController(nibName: "NewViewController",bundle: nil)
let navigation = AppNavigationController(rootViewController: newViewController!)
self.window?.rootViewController = navigation
self.window?.makeKeyAndVisible()
UPDATE:
If you want to create a separate NavigationController and push specific UIViewController to it, then you need to present the navigation controller instead of pushing it. See below.
let motionNavController = AppNavigationController(rootViewController: galleryViewController)
motionNavController.motionNavigationTransitionType = .none
motionNavController.isNavigationBarHidden = true
self.present(motionNavController, animated: true, completion: nil)
I would like to process code when a ViewController is no longer visible due to presenting a new ViewController.
I cannot use ViewWillDisappear etc since the controller is not technically ever dismissed from the stack - you just can't see it.
What process can I use so that code runs when the controller is no longer visible (i.e. topmost) and when it becomes visible again?
EDIT:
Seems some confusion here - not sure why.
I have a viewcontroller.
I use the following code to present another controller
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let navController = storyboard.instantiateViewControllerWithIdentifier("NavController") as! UINavigationController
let thisController = navController.viewControllers[0] as! MyController
self.presentViewController(navController, animated: true, completion: nil)
This controller does not trigger a viewWillDisappear on the previous controller since the previous view is not removed - just hidden.
I need to process code when this view is hidden (i.e. not visible) and, more importantly, process code when it becomes visible again.
When presenting a UIViewController if the presentation style has been set to UIModalPresentationOverCurrentContext it doesn't call the viewWillDisappear and related methods as the view never disappears or gets hidden.
A simple test to check if thats the case would be to set the NavController that you are using to have a clear background color. If you do this and present the NavController and you can still view the first UIViewController below your NavController content. Then you are using UIModalPresentationOverCurrentContext and that is why the viewDidDisappear isn't called.
Have a look at the answer referenced by Serghei Catraniuc (https://stackoverflow.com/a/30787112/4539192).
EDIT: This is in Swift 3, you can adjust your method accordingly if you're using an older version of Swift
If you won't be able to figure out why viewDidAppear and viewDidDisappear are not called, here's a workaround
protocol MyControllerDelegate {
func myControllerWillDismiss()
}
class MyController: UIViewController {
var delegate: MyControllerDelegate?
// your controller logic here
func dismiss() { // call this method when you want to dismiss your view controller
// inform delegate on dismiss that you're about to dismiss
delegate?.myControllerWillDismiss()
dismiss(animated: true, completion: nil)
}
}
class PresentingController: UIViewController, MyControllerDelegate {
func functionInWhichYouPresentMyController() {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let navController = storyboard.instantiateViewController(withIdentifier: "NavController") as! UINavigationController
let thisController = navController.viewControllers[0] as! MyController
thisController.delegate = self // assign self as delegate
present(navController, animated: true, completion: {
// place your code that you want executed when it disappears here
})
}
func myControllerWillDismiss() {
// this method will be called now when MyController will dismiss
// place your code that you want executed when it re-appears here
}
}
Firstly, thanks to Serghei for his time in helping work through this.
To clarify, both my potential presented controllers were set to Full Screen presentation style in the storyboard, however one was being set to Custom via a piece of pasted code dealing with the presentation. I can't find the error with the other.
However, if I force a presentation style of Full Screen as part of the presenting process then all is ok.
Hopefully my frustrating afternoon can help to save someone else's - always try to understand the implications and processes involved in pasted snippets.
I'm currently writing an app where we are launching a walkthrough the first time the user opens the app. At the end of it, we require the user to fill in a few details, and to do so I would like him to press a button to get redirected to the settings page.
The thing is, this page is one level down the navigation controller (from the landing page). As it stands, I can correctly instantiate the landing page, but the redirection to the settings page never happens.
let mainView = self.storyboard?.instantiateViewControllerWithIdentifier("NavCtrl")
self.presentViewController(mainView!, animated: false, completion: nil)
// the above works correctly and sends us to the landing screen
// (rootView of the navigation controller)
// the following lines never have any effect though
let settingsView = self.storyboard?.instantiateViewControllerWithIdentifier("Settings View")
self.navigationController?.pushViewController(settingsView!, animated: false)
I think it's because I'm trying to call .pushViewController before the storyboard had time to instiate either the first or second view.
So I have a few questions:
Is there a way to, indeed, instantiate a view and then navigate to another one right after it has been instantiated ? (this in order to keep the navigation stack and maintain accurate nav bar behaviour)
If there isn't, would it be possible to programmatically populate the navigation stack so that I would only need to instantiate the settings view ? This in order to still have the back button in the nav bar that would send to the landing screen ?
Thanks in advance guys
Ok thanks to #Leonardo for showing me the correct direction !
I solved the issue by doing the following in appDelegate:
/*
* Override window root view and set it to a newly initialized one
* view: StoryboardID of view to display
* navigateTo: if true, set the root view to NavCtrl and then navigate to the desired view
*/
func setWindowViewTo(view: String, navigateTo: Bool) {
//initalize storyboard & window programmatically
window = UIWindow.init(frame: UIScreen.mainScreen().bounds)
let storyboard = UIStoryboard(name: "Main", bundle: nil)
//if true, navigate from landing page to specified view through navigationController
if(navigateTo) {
//instantiate the navigation controller
let navCtrl = storyboard.instantiateViewControllerWithIdentifier("NavCtrl") as! UINavigationController
//instantiate the landing page & the page we wish to navigate to
let landingView = storyboard.instantiateViewControllerWithIdentifier("Main View")
let vc = storyboard.instantiateViewControllerWithIdentifier(view)
//manually set the navigation stack to landing view + view to navigate to
navCtrl.setViewControllers([landingView, vc], animated: false)
//replace the rootViewController to the navigation controller
window!.rootViewController = navCtrl
//make it work
window!.makeKeyAndVisible()
} else {
window!.rootViewController = storyboard.instantiateViewControllerWithIdentifier(view)
window!.makeKeyAndVisible()
}
}
The important step was to indeed force downcast as! UINavigationController when instantiating the NavigationController with the storyboard.instantiateViewControllerWithIdentifier() method.
Then it's just a case to correctly instantiate the views of the navigation stack you want, and finally calling navCtrl.setViewControllers([view1, view2], animate: false).
Thanks all for your help !
You can use the - setViewControllers:animated: method to set the navigation stack of a UINavigationController
Here's the reference
But I don't think that's your problem, if I undestood your code correctly, it should be
//This is the navigation controller
if let mainView = self.storyboard?.instantiateViewControllerWithIdentifier("NavCtrl"){
//Nav being modally presented
self.presentViewController(mainView, animated: false, completion: nil)
// Instantiate the settings view
if let settingsView = self.storyboard?.instantiateViewControllerWithIdentifier("Settings View"){
//Push it to the navigation controller presented
mainView.pushViewController(settingsView, animated: false)
}
else{
//Can't Instantiate, deal with error
}
}
else{
//Can't Instantiate, deal with error
}
I have been trying to pop my view controller to one of previous view controllers in my view stack. Assume that, There are firstVC, secondVC, thirdVC and fourthVC viewcontrollers in my view stack. The current view controller is fourth one, and there is a tableview in fourthVC. If user delete all the rows in tableview, I should direct the user to secondVC. I had an idea that I would create another navigationcontroller and present it with presentViewController command. However, this is not a solution for my problem. Because I thougt that a navigation problem appears for this case. How can I find best solution for this case ?
Thank you for your answers,
Best regards
Instead of doing a generic popViewControllerAnimated: call, use popToViewController:animated:. You could detect if the user has deleted all of the rows in which case, do something like this (otherwise just pop one view controller):
let viewControllers: [UIViewController] = self.navigationController!.viewControllers as [UIViewController];
self.navigationController!.popToViewController(viewControllers[viewControllers.count - 2], animated: true);
If you want to pop to a specific view control and dont know the count to go back you can use this:
let viewControllers: [UIViewController] = self.navigationController!.viewControllers as! [UIViewController];
for aViewController in viewControllers {
if(aViewController is ViewControllerYouWantToGoTo){
self.navigationController!.popToViewController(aViewController, animated: true);
}
}
After lots of effort I have created swift extension of back to a particular view controller in Swift 3.0.
extension UINavigationController {
func backToViewController(viewController: Swift.AnyClass) {
for element in viewControllers as Array {
if element.isKind(of: viewController) {
self.popToViewController(element, animated: true)
break
}
}
}
}
Method calling:
self.navigationController?.backToViewController(viewController: BarCodeScannerVC.self)
Here you can do this by the find the controller from the UINavigationController stack and with the help of for loop and check the condition of your desire controller and if the condition gets fulfilled it will pop to the destionation controller.
let viewControllersStack: [UIViewController] = self.navigationController!.viewControllers
for firstViewcontroller in viewControllersStack
{
if firstViewcontroller is desireViewController
{
self.navigationController!.popToViewController(firstViewcontroller, animated: true)
}
}