How can I present a view controller from my AppDelegate and have a Navigation bar added to that view with a back button to the previous view? I need to do this programmatically from my AppDelegate. Currently I can push a controller from there, but it doesn't act like a segue. It doesn't add a nav bar with a back button. Now I know I should be able to add one myself, but when I do it gets hidden. Currently I'm using pushViewController(), but I imagine that's not the best way to do it.
I had something that I think is similar, if not the same:
HIGH LEVEL VIEW
The general composition of my App (thus far, and specific to the issue at hand - note: details about classes provided for context, not required for resolution) is as follows:
UIViewController (ViewController.swift) embedded in a UINavigationController
Buttons on UIViewController segue to a view with a custom class:
ExistingLocationViewController - subclass of:
UITableViewController
One of the buttons (Add New Location) in the UINavigationController's Toolbar segues to view with another custom class:
NewLocationViewController - subclass of:
UIViewController
CLLocationManagerDelegate
UITextFieldDelegate
There are a number of other items here, but I believe the above is sufficient as the foundation for the issue at hand
RESOLUTION
In order to preserve the navigation-bar (and tool-bar) going both forward and back - I have the following code in my custom classes (note: the following is Swift-3 code, you may have to adjust for Swift-2):
override func viewDidLoad() {
super.viewDidLoad()
//...
navigationController?.isNavigationBarHidden = false
navigationController?.isToolbarHidden = false
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated) // #=# not sure if this is needed
navigationController?.isNavigationBarHidden = false
navigationController?.isToolbarHidden = false
}
You could actually omit the last two lines in viewWillDisappear, or perhaps even omit the entire override function
The net result (for me) was as depicted below:
Hope that helps.
If you want add a NavigationController in appDelegate you can do it like this,in this way,your viewcontroller is load from storyboard
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// Override point for customization after application launch.
self.window = UIWindow(frame: UIScreen.mainScreen().bounds)
let vc = UIStoryboard(name: "Main", bundle: NSBundle.mainBundle()).instantiateViewControllerWithIdentifier("vc") as! ViewController
let nav = UINavigationController(rootViewController: vc)
self.window?.rootViewController = nav
self.window?.backgroundColor = UIColor.whiteColor()
self.window?.makeKeyAndVisible()
return true
}
Related
Short question:
How can I launch and make a UITabBarController be the rootViewController of my app after starting with a Storyboard?
Long question:
I'm not a swift expert, but I managed to create a complete app using XIBs from the beginning. Now I need my app to start with a Storyboard as a new requirement to post updates to the appstore from 01/07/2020, but I never used it to build my views. It was easy to modify my app to have my Storyboard as an entry point, but the problem is that my initial view today is a TabController, and I don't know how to navigate from my initial Storyboard to my TabController.
My AppDelegate today works something like this:
var window: UIWindow?
func application(_application: UIApplication, didFinishLauchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
self.window = UIWindow(frame: UIScreen.main.bounds)
// initiate UINavigationControllers and UITabBarController here...
tabController.viewController = [nav1, nav2, nav3, nav4]
tabController.modalPresentationStyle = .fullScreen
self.window!.rootViewController = tabController
self.window!.makeKeyAndVisible()
}
All my attempts ended with a white screen after showing my Storyboard without showing my TabBar.
One of these attempts was this:
override func loadView() {
super.loadView()
// initiate tabController the same way I did in the AppDelegate
UIApplication.shared.keyWindow?.rootViewController = tabController
}
Check the value "Is initial View Controller" for it.
I have been creating my app completely from scratch programmatically, without using Storyboards.
My app is integrated with Firebase, and uses Facebook login.
My setup is fairly simple:
Launch the app -> takes you to the first VC called WelcomeViewController.
There is a check that happens in the viewDidLoad method to see if a user is already signed in and exists. If there is, it sends you straight to the second VC called FilmsViewController
The FilmsViewController is a collectionViewController that displays films. The user can press a film, and it takes them to more information about that film.
(For reference, I am already signed in with Facebook in my app)
I have a current issue, where when step 2 above happens, it transitions to the FilmsViewController, but it does it like 2 or 3 times. So you see the new VC appear like 2 or 3 times, then the content loads. If you press the Back button in the nav bar, it takes you back through the 2 or 3 viewControllers that it loaded before taking you back to the WelcomeViewController.
I have set my views up as follows.
In AppDelegate.swift:
var window: UIWindow?
var navController: UINavigationController?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
navController = UINavigationController()
let firstViewController: WelcomeViewController = WelcomeViewController()
self.navController!.pushViewController(firstViewController, animated: true)
window = UIWindow(frame: UIScreen.main.bounds)
window?.rootViewController = navController
window?.makeKeyAndVisible()
return true
}
In the WelcomeViewController in the viewDidLoad:
FIRAuth.auth()?.addStateDidChangeListener { auth, user in
if let user = user {
// User is signed in.
// Direct the user to the home screen
let toFilmListVC = FilmsViewController(collectionViewLayout: UICollectionViewFlowLayout())
self.navigationController?.pushViewController(toFilmListVC, animated: true)
} else { ...
}
}
I have looked loads for a solution - and nothing. I've only found one post on this issue, where someone said the solution was to change the class name of that controller, which I have already done and it didn't change anything.
Can anyone help me resolve this, please? Thank you.
The addStateDidChangeListener is probably being called multiple times.
You should modify it to check whether a FilmsViewController has already been pushed, to prevent pushing another one:
FIRAuth.auth()?.addStateDidChangeListener { auth, user in
if let user = user {
// User is signed in.
// Direct the user to the home screen
// Only push one FilmsViewController onto the navigation stack!
var shouldPush = true
if let navigationController = self.navigationController {
for viewController in navigationController.viewControllers {
if viewController is FilmsViewController {
shouldPush = false
}
}
}
if shouldPush {
let toFilmListVC = FilmsViewController(collectionViewLayout: UICollectionViewFlowLayout())
self.navigationController?.pushViewController(toFilmListVC, animated: true)
}
} else { ...
}
}
I know the above solutions works, but to make it look extremely simple.
here is the code i wrote in my project.
let someController = SomeController.someController()
if !(self.navigationController!.viewControllers.contains(someController)){
self.navigationController?.pushViewController(someController, animated:true)
}
only push the controller to navigation bar if the controller is not exist.
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.
With the aim to implement a splash screen that only shows once i've modified didFinishLaunchingWithOptions in order to dynamically select the appropriate view controller. The logic seems to work fine, and the view I intended to load is the one launched
However, the UI seems to be missing elements that would otherwise be displayed should I have not altered the didFinishLaunchingWithOptions function.
func application(application: UIApplication!, didFinishLaunchingWithOptions launchOptions: NSDictionary!) -> Bool
{
window = UIWindow(frame: UIScreen.mainScreen().bounds)
let storyBoard = UIStoryboard(name: "Main", bundle: nil)
var entryViewController: UIViewController?
if NSUserDefaults.standardUserDefaults().boolForKey("hasSeenWelcomeScreen") == true
{
entryViewController = storyBoard.instantiateViewControllerWithIdentifier("NavigationController") as? UIViewController
}
else
{
entryViewController = storyBoard.instantiateViewControllerWithIdentifier("WelcomeViewController") as? UIViewController
NSUserDefaults.standardUserDefaults().setValue(true, forKey: "hasSeenWelcomeScreen")
NSUserDefaults.standardUserDefaults().synchronize()
}
self.window?.rootViewController = entryViewController
self.window?.makeKeyAndVisible()
return true
}
My WelcomeViewController is a simple view with 1 label, 1 button and a movie which plays in the background (resembling Spotify/Vine's welcome screen). Debugging the code I can see the initialization methods do get executed, but is just the frame that does not seem to be displayed when I dynamically override the initial view
import UIKit
import MediaPlayer
import QuartzCore
class WelcomeViewController: UIViewController {
var moviePlayerController: MPMoviePlayerController = MPMoviePlayerController()
#IBOutlet weak var loginButton: UIButton!
#IBOutlet weak var appNameLabel: UILabel!
override func viewDidLoad()
{
super.viewDidLoad()
buildMoviePreview()
buildButtonDesign()
}
override func viewWillAppear(animated: Bool)
{
self.view.addSubview(self.moviePlayerController.view)
self.view.addSubview(self.loginButton)
self.view.addSubview(self.appNameLabel)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
override func prefersStatusBarHidden() -> Bool {
return true
}
private func buildButtonDesign()
{
loginButton.layer.borderColor = UIColor.whiteColor().CGColor
loginButton.layer.borderWidth = 2.0
loginButton.layer.cornerRadius = 7.0
}
private func buildMoviePreview()
{
let filePath = NSBundle.mainBundle().pathForResource("intro", ofType: "mov")
self.moviePlayerController.contentURL = NSURL.fileURLWithPath(filePath)
self.moviePlayerController.movieSourceType = .File
self.moviePlayerController.repeatMode = .One
self.moviePlayerController.view.frame = self.view.bounds
self.moviePlayerController.scalingMode = .AspectFill
self.moviePlayerController.controlStyle = .None
self.moviePlayerController.allowsAirPlay = false
self.moviePlayerController.shouldAutoplay = true
self.moviePlayerController.play()
}
}
For completeness, these are the discrepancies in the layout when using the XCode UI debugger. Please note that they differ even though they implement the same viewController. The only difference is that one has been programmatically set as the initial view, while the other has been set as the initial view through storyboard.
Screenshots of the rendering issue side-by-side
Your approach is... unusual. A Storyboard has a root view controller for a reason and typically at startup you would just let the application handle loading the storyboard and installing that root view controller as the window's main view controller. (The Storyboard loaded is specified in the application target's general settings as the "Main Interface")
In this case, what I would recommend is making your root view controller the "Normal" view of the application... the one you want users to see when they launch the app on a day-to-day basis.
Define your "on first launch" view controller as a separate view controller in the storyboard and add a modal segue from the root view controller to the on first launch view controller.
Then in your applicationDidFinishLaunching, if the user has never seen the first launch controller... simply ask the Storyboard to take that segue. If the user has already seen the first launch presentation that segue will be skipped.
Another issue I see with your code is in your viewWillAppear method. You should not have to add your views as subviews in viewWillAppear... those subviews should already have been set up at the time the view was loaded from the nib file.
The one exception is the view of your movie player, but your movie player is owned by a separate view controller. That separate view controller is detached from the view controller hierarchy and does not have it's own view controller methods called at the right times. (so it never receives calls like "viewWillAppear" that might tell it to get it's movie ready to play).
What you probably want to do is implement "awakeFromNib" and make sure that the movie player's view controller is a sub-controller of this view controller. (so in awakeFromNib for the WelcomeViewController use addChildViewController to make sure the movie controller is in the hierarchy).
It will be better to user 2 storyboards :
one with your welcome screen
the other one with the rest of your app
The application launching wil look like this :
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: NSDictionary?) -> Bool {
var storyBoard : UIStoryboard!
if NSUserDefaults.standardUserDefaults().boolForKey("hasSeenWelcomeScreen") == true {
changeStoryBoard("Main")
}
else {
changeStoryBoard("Welcome")
}
return true
}
func changeStoryBoard(name :String) {
var storyBoard = UIStoryboard(name:name, bundle: nil)
var viewController: AnyObject! = storyBoard.instantiateInitialViewController() ;
self.window!.rootViewController = viewController as UIViewController
}