On firstStart of the application I want the user to start at ViewController1 (VC1) and after completing the onboarding process and a button gets clicked in ViewController3 (VC3) should the first item of the Tab Bar controller be the new initial view controller.
The first launch works but when closing the app and upon reopening the app everything goes black.
When I set VC3 as the new initial viewController everything works fine.
My SceneDelegate
var window: UIWindow?
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
guard let windowScene = (scene as? UIWindowScene) else { return }
self.window = UIWindow(windowScene: windowScene)
//self.window = UIWindow(frame: UIScreen.main.bounds)
// Is it the apps first start?
let firstView = UserDefaults.standard.string(forKey: "firstStart")
let storyboard = UIStoryboard(name: "Main", bundle: nil)
// Create the root view controller as needed
guard let vc = storyboard.instantiateViewController(identifier: firstView ?? "Welcome") as? ViewController else {
print("ViewController not found")
return
}
let nc = UINavigationController(rootViewController: vc)
// Create the window.
let win = UIWindow(windowScene: windowScene)
win.rootViewController = nc
win.makeKeyAndVisible()
window = win
}
My button:
#IBAction func buttonActivate(_ sender: Any) {
// Change start View to "Home"
UserDefaults.standard.set("Home", forKey: "firstStart")
}
I've tried with changing keys in Info.plist and read through UIApplicationSceneManifest but without any success. Tried having an additional navigation controller as tab bar controller item 2 shows but same result with a black screen.
You shouldn't try and change the window, instead change the .rootViewController.
VC1 is the entry point in storyboard which means that the initial view controller your app starts with every time, during the "didFinishLaunchingWithOptions" in app-delegate you can ask whether the user has been promoted with the onboarding and in that case switch the rootViewController as follows:
func showMainStoryboard() {
let homeViewController = UIStoryboard(name: "StoryboardName", bundle: Bundle.main).instantiateInitialViewController()
window?.rootViewController = homeViewController
}
In order for the following example to work for you, you must create a separate storyboard: 1 for the onboarding and 1 for main.
Flow #1:
The first time user installs the app he is promoted with the onboarding VC after he clicked the button on VC3 you switch the rootViewController on that event.
Flow #2:
After the user clicked the button and reentered the app he won't be promoted with the onboarding but routed to the "main" VC, a.k.a Start in your case.
Related
i have a button in viewcontroller which navigates to editPage viewcontrooler
i want to use pushNavigation
i did below code in button action method
#IBAction func editBtn(_ sender: Any) {
let storyBoard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let newViewController = storyBoard.instantiateViewController(withIdentifier: "editProfile") as! editProfile
let navigation = UINavigationController(rootViewController: newViewController)
self.navigationController?.pushViewController(navigation, animated: true)
}
but when i click on button it dosent navigate
i dont have navigation controler in storyboard.......
i dont want to present view controler
Looks like you are setting your viewController as initail View Controller from storyboard but do not embed this in navigation controller , either embed your viewController in navigationController and set this navigation controller as initialViewController or follow this approach as you don't want to do it from storyboard:
In SceneDelegate , write :
var window: UIWindow?
and then in this method :
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
// Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
// If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
// This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).
guard let _ = (scene as? UIWindowScene) else { return }
//Setup user state
if let vc = UIStoryboard.init(name: "YOUR_STORYBOARD_NAME", bundle: nil).instantiateViewController(withIdentifier: "VIEW_CONTROLLER_IDENTIFIER") as? ViewController {
let nav = UINavigationController(rootViewController: vc)
window?.rootViewController = nav
window?.makeKeyAndVisible()
}
}
for IOS < 13, in AppDelegate.swift :
var window : UIWindow?
//MARK:- DID FINISH LAUNCHING WITH OPTIONS : CALLED EVERYTIME WHEN APPLICATION LAUNCHES
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
if let vc = UIStoryboard.init(name: "YOUR_STORYBOARD_NAME", bundle: nil).instantiateViewController(withIdentifier: "VIEW_CONTROLLER_IDENTIFIER") as? ViewController {
let nav = UINavigationController(rootViewController: vc)
window?.rootViewController = nav
window?.makeKeyAndVisible()
}
return true
}
I have 2 storyboards: Onboarding and Main. Everything works great as far as execution goes. The issue is that I have loading screen at the start of Main.storyboard that looks different depending on which storyboard launches.
Scenario 1: New user launches the app, it goes through Onboarding.storyboard then Main.storyboard launches and the loading screen looks as it should like this: Correct Screen
Scenario 2: User launches the app for the second time, therefore Onboarding.storyboard is not launched: Loading screen is off centre like this: Wrong Screen
Long story short: If there is no Onboarding.storyboard, it doesn't look right. The issue is only shown on iPad landscape; portrait looks fine.
All of my loading screen code is inside my first view controller of Main.Storyboard so I'm guessing that the issue is the order in which the code is called?
Onboarding.Storyboard only has 1 view controller
Main.Storyboard goes: TabBarController -> NavController -> FirstVC
I placed my SceneDelegate code below along with the loading screen code of FirstVC.
In case you want to dig in deeper, I'm using RevealingSplashView available on github. Since I don't think that's the issue I didn't get into that further.
SceneDelegate:
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
guard let windowScene = (scene as? UIWindowScene) else { return }
window = UIWindow(frame: windowScene.coordinateSpace.bounds)
window?.windowScene = windowScene
let onboardingStoryboard = UIStoryboard(name: "Onboarding", bundle: nil)
let mainStoryboard = UIStoryboard(name: "Main", bundle: nil)
var vc: UIViewController
if UserDefaults.standard.value(forKey: "firstTimer") == nil {
vc = onboardingStoryboard.instantiateInitialViewController()!
} else {
vc = mainStoryboard.instantiateInitialViewController()!
}
self.window?.rootViewController = vc
self.window?.makeKeyAndVisible()
}
FirstVC relevant code:
class firstVC: UIViewController {
var revealingSplashView : RevealingSplashView!
override func viewDidLoad() {
super.viewDidLoad()
showLoadingScreen()
}
func showLoadingScreen() {
revealingSplashView = RevealingSplashView(iconImage: UIImage(named: "Icon")!, iconInitialSize: CGSize(width: 150, height: 150), backgroundImage: UIImage(named: "loadBackground")!)
revealingSplashView.animationType = .heartBeat
revealingSplashView.startAnimation()
view.addSubview(revealingSplashView)
}
Check to see that your layout constraints are the same in both of your storyboards. I'd recommend just setting them to center horizontal and center vertical on both of them.
In your scene delegate callback, why are you calling instantiateInitialViewController() twice per storyboard?
In my iOS 12 project, I used storyboards to kick things off. In app delegate didFinishLaunching, I would override and set the window if and only if it was a new user. So my "Main" storyboard would kick in most of the time.
Same technique could be applied here. Check out the Info.plist values you need to set for UIScene storyboard to work. You can override the window if you want to show the first time onboarding, otherwise let the system instantiate your main storyboard.
Further testing revealed that if I return SceneDelegate to its "factory default" state, therefore launching Main.storyboard, I had no issues.
To fix the issue I simply changed my SceneDelegate to:
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
guard let windowScene = (scene as? UIWindowScene) else { return }
if UserDefaults.standard.value(forKey: "firstTimer") == nil {
window = UIWindow(frame: windowScene.coordinateSpace.bounds)
window?.windowScene = windowScene
let onboardingStoryboard = UIStoryboard(name: "Onboarding", bundle: nil)
var vc: UIViewController
vc = onboardingStoryboard.instantiateInitialViewController()!
self.window?.rootViewController = vc
self.window?.makeKeyAndVisible()
}
}
This is the storyboard layout I have right now
So what I need is to check if the user is already authenticated when the app launches.
If he is not logged in set the root view controller to the navigation controller with the login and register forms.
If he is logged in set it to the tab bar view controller.
I tried a lot of different solutions but none of them worked. It just kept setting the view controller to the one marked with "Is Initial View Controller".
This is the code I tried in the AppDelegate:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
FirebaseApp.configure()
self.window = UIWindow(frame: UIScreen.main.bounds)
let storyboard = UIStoryboard.init(name: "Main", bundle: nil)
var viewController: UIViewController
if isAuthenticated() {
viewController = storyboard.instantiateViewController(withIdentifier: Constants.Storyboards.homeViewController) as! UITabBarController
} else {
viewController = storyboard.instantiateViewController(withIdentifier: Constants.Storyboards.authViewController) as! UINavigationController
}
self.window?.rootViewController = viewController
self.window?.makeKeyAndVisible()
return true
}
I can do this by pressing a button easily, but I want it to happen without the user needing to do something.
EDIT: Thanks to #LukaCefarin and #Francesco Deliro I managed to find out that the problem was. I was using XCode 11 and the rootViewController had to be set in the SceneDelegate.swift
This is what my code in the SceneDelegate.swift looks like for anyone who has a similar issue:
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
guard let _ = (scene as? UIWindowScene) else { return }
self.window = UIWindow(windowScene: scene as! UIWindowScene)
let storyboard = UIStoryboard.init(name: "Main", bundle: nil)
var viewController: UIViewController;
if isAuthenticated() {
viewController = storyboard.instantiateViewController(withIdentifier: "HomeVC")
} else {
viewController = storyboard.instantiateViewController(withIdentifier: "AuthVC")
}
self.window?.rootViewController = viewController
self.window?.makeKeyAndVisible()
}
EDIT:
This solution and setting the window rootViewController in the AppDelegate work for versions prior to Xcode11 and iOS13. As suggested in the comments by Luka Cefarin, if you are using Xcode11 and iOS13 you have to set the window rootViewController in the SceneDelegate.swift file.
You have to remove the Main Interface and to uncheck the initial view controller in the storyboard:
Before
After
First of all, let me introduce what I'm trying to do. Second... I'm new to Swift/iOS development.
What I'm trying to do is:
When I open the APP, it loads the LoginVC. When the user logs in, the TableSelectionVC loads. If the user selects a cell, it goes to the Home screen with the selected values. If the user taps on the bell (blue arrows) the app should go to AlarmVC.
It should work, but it doesn't. The AlarmVC says that self.revealViewController() is nil. BUT If I go to alarmVC through the menu option (red arrows) it loads normally and the Menu is shown. Also, if I choose the option in green, it goes to the TableSelectionVC and if I tap the icon, it goes to alarmVC and it doesn't crash. The problem is if I go from LoginVC to TableSelectionVC and tap on the icon, the it will crash.
I think it is the way that I'm setting the views, instantiating a controller and setting the window.rootViewController.
In my AppDelegate I have the following functions:
func changeRootViewControllerToSWRevealViewController () {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let controller = storyboard.instantiateViewController(withIdentifier: "SWRevealViewController")
controller.modalTransitionStyle = .flipHorizontal
if let window = self.window {
window.rootViewController = controller
}
}
func changeRootViewControllerToPlantSelectionVC () {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let controller = storyboard.instantiateViewController(withIdentifier: "navPlantSelectionVC")
controller.modalTransitionStyle = .flipHorizontal
if let window = self.window {
window.rootViewController = controller
}
}
When the user logs in the app, the following function is executed:
static func goToPlantSelection() {
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {
//there should be an alert error
return
}
appDelegate.changeRootViewControllerToPlantSelectionVC()
}
After that, the PlantSelectionVC is shown and if the user taps on an cell the appDelegate.changeRootViewControllerToSWRevealViewController() is executed and the HomeVC is shown.
If the user taps on the icon, it tries to go to the AlarmVC but it crashes, like I said. I think I'm doing something wrong with binding the SWRevealViewController with LoginVC and TableSelectionVC.
The following code in AlarmVC tries to execute:
static func setupMenuToggle(button: UIBarButtonItem, viewController: UIViewController) {
button.target = viewController.revealViewController()
viewController.revealViewController().rearViewRevealWidth = viewController.view.bounds.size.width * 0.9
button.action = #selector(SWRevealViewController.revealToggle(_:))
}
But is shows the error in the third line: found nil while unwrapping an Optional value
Can anyone help ?
I fixed it. I set the login screen as sw_front for the swRevealViewController and after that, when the user logs in I would change the swRevealViewController front view controller with
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let controller = storyboard.instantiateViewController(withIdentifier: "navPlantSelectionVC")
self.revealViewController().setFront(controller, animated: true)
It is working now.
At some point in my app I need to go back to my main (i.e. home) ViewController. So I need to travel backwards into my navigation stack which by now is full with ViewControllers and NavigationControllers. So I do the following:
// to dismiss the current VC
self.navigationController?.popViewControllerAnimated(true)
// to go back home
UIApplication.sharedApplication().keyWindow?.rootViewController = homeNC
homeNC is a global var pointing to a NavigationController containing a storyboard instance of my main "home" ViewController.
It works, but partially. Meaning, it does take me back to my home ViewController but it then becomes unresponsive to touches and scrolling. Nothing responds, neither the navigationBar buttons nor the TableViewCells inside the home NC/VC. Xcode doesn't crash, just sits there waiting.
I can't figure out why. Can anyone think what I'm missing here?
** EDIT **
Maybe it's because I am embedding a nested VC inside my main (home) VC. Here's the code and what else I tried since:
homeNC = self.navigationController!
homeVC = self
let discoverVC: UIViewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("DiscoverVC") as UIViewController
// embed discoverVC in this VC
self.addChildViewController(discoverVC)
self.view.addSubview(discoverVC.view)
discoverVC.didMoveToParentViewController(self)
// store self NC for using it by "thank you" VC to come back to home after payment
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
// navigates back but becomes unresponsive
appDelegate.window?.rootViewController = homeVC
// navigates back but becomes unresponsive
appDelegate.window?.rootViewController = homeNC
I also tried saving a reference to the appdelegate window with the following:
// Initialise sideMenu
window = UIWindow(frame: UIScreen.mainScreen().bounds)
let sideMenu = SSASideMenu(contentViewController: UINavigationController(rootViewController: MainViewController()), leftMenuViewController: LeftMenuViewController())
window!.rootViewController = sideMenu
window!.makeKeyAndVisible()
// get a reference to appdelegate's window so that we can navigate back later
mainWindow = window!
but again it doesn't navigate back when I do:
appDelegate.window?.rootViewController = mainWindow.rootViewController
Try this. I will work for you.
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
appDelegate.window?.rootViewController = mainVC
EDIT
I read out your edited question. Problem is occurring due to SSASideMenu. I updated my answer and I hope now it will work for you. You have to make two changes. First in AppDelegate.swift, make property of SSASideMenu like this:
var window: UIWindow?
var sideMenu: SSASideMenu!
Now your code will be changed to:
sideMenu = SSASideMenu(contentViewController: UINavigationController(rootViewController: MainViewController()), leftMenuViewController: LeftMenuViewController())
Now if you want to change the view-controller, you have to change the contentViewController of SSASideMenu like this:
func showDiscoverVC() {
//Instantiate ViewController
let discoverVC: UIViewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("DiscoverVC") as UIViewController
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
appDelegate.sideMenu.contentViewController = UINavigationController(rootViewController: discoverVC)
}