I have an app where users can login / sign out and the functionality stopped working recently when I added a new SceneDelegate view and moved over the code from the AppDelegate. I'm not sure why it is not working but I suspect it has to do with using the shared delegate in my signOut function.
Something strange is happening, when I tap the sign out button nothing happens. However, when I close the app and open it again, I will be signed out.
Here is the code on my home screen for the sign out button:
#IBAction func signOutButtonTapped(_ sender: Any) {
KeychainWrapper.standard.removeObject(forKey: "accessToken")
KeychainWrapper.standard.removeObject(forKey: "userID")
// send user to splash page
let signInPage = self.storyboard?.instantiateViewController(withIdentifier: "splashController") as! splashViewController
let appDelegate = UIApplication.shared.delegate
appDelegate?.window??.rootViewController = signInPage
}
This is the code from my SceneDelegate.swift file:
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
guard let _ = (scene as? UIWindowScene) else { return }
let accessToken: String? = KeychainWrapper.standard.string(forKey: "accessToken")
// If access token exists, skip login page
if accessToken != nil {
if let windowScene = scene as? UIWindowScene {
self.window = UIWindow(windowScene: windowScene)
let mainStoryboard:UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let vc = mainStoryboard.instantiateViewController(withIdentifier: "homeTabController") as! TabBarController
self.window!.rootViewController = vc
}
}
}
}
You've answered your own question. With a scene delegate, the window belongs to the scene delegate. The app delegate window is nil. So this line does nothing:
appDelegate?.window??.rootViewController = signInPage
Figured it out - add a new var in SceneDelegate
static weak var shared: SceneDelegate?
Then replace the appDelegate.window line with
let appDelegate = SceneDelegate.shared
Related
i have an app where the user sets a username in the WelcomeViewController the first time he launches the app. This Username gets stored in Firestore. I want to change the ViewController that gets displayed when the app is opened after the username is set!
I made a function that checks if there is a username stored in FireStore and sets a boolean to true or false depending on the result. I want to change the ViewController that gets displayed as the RootViewController based on the boolean value, if true set the MainVC as RootVC and if it's false the WelcomeVC should be the RootVC.
If tried to set up a func inside the SceneDelegate func scene() but somehow i either get
a crash or a black screen when the app doesnt crash. I dont know what im doing wrong, i have tried every tutorial but nothing is working.
heres my code:
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).
self.window = UIWindow(frame: UIScreen.main.bounds)
let storyboard = UIStoryboard.init(name: "Main", bundle: nil)
let welcomeVCAsRoot = storyboard.instantiateViewController(withIdentifier: "WelcomeViewController")
let mainVCAsRoot = storyboard.instantiateViewController(withIdentifier: "MainViewController")
if User.shared.userNameOccupied == true {
self.window?.rootViewController = welcomeVCAsRoot
self.window?.makeKeyAndVisible()
} else {
self.window?.rootViewController = mainVCAsRoot
self.window?.makeKeyAndVisible()
}
guard let _ = (scene as? UIWindowScene) else { return }
}
Try to use scene
func scene(_ scene: UIScene,
willConnectTo session: UISceneSession,
options connectionOptions: UIScene.ConnectionOptions) {
guard let windowScene = (scene as? UIWindowScene)
else { fatalError() }
self.window = .init(frame: windowScene.coordinateSpace.bounds)
self.window?.windowScene = windowScene
let storyboard = UIStoryboard.init(name: "Main", bundle: nil)
let welcomeVCAsRoot = storyboard.instantiateViewController(withIdentifier: "WelcomeViewController")
let mainVCAsRoot = storyboard.instantiateViewController(withIdentifier: "MainViewController")
if User.shared.userNameOccupied == true {
self.window?.rootViewController = welcomeVCAsRoot
} else {
self.window?.rootViewController = mainVCAsRoot
}
self.window?.makeKeyAndVisible()
}
If this will not help - try to add:
class TestViewController: UIViewController {}
and call it from the method scene like:
let testController = TestViewController()
testController.backgroundColor = .blue
self.window?.rootViewController = testController
If you will receive blue screen - something wrong on the controller side, if no - something wrong with my advice, and let me know exactly type of error.
Good luck
I am trying to set up my onboarding screen in the SceneDelegate.
When I run the code below, it compiles, but just goes to a black screen.
They're many great onboarding tutorials for AppDelegate, but very few for the new SceneDelegate with iOS13. I took this tutorial and tried to apply it to SceneDelegate, but I can't get it to work: https://www.youtube.com/watch?v=y6t1woVd6RQ&t=537s
This is my scene delegate code.
var window: UIWindow?
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
let launchedBefore = UserDefaults.standard.bool(forKey: "hasLaunched")
self.window = UIWindow(frame: UIScreen.main.bounds)
let launchStoryboard = UIStoryboard(name: "Onboarding", bundle: nil)
let mainStoryboard = UIStoryboard(name: "Main", bundle: nil)
var vc: UIViewController
if launchedBefore
{
vc = mainStoryboard.instantiateInitialViewController()!
}
else
{
vc = launchStoryboard.instantiateViewController(identifier: "Onboarding")
}
UserDefaults.standard.set(true, forKey: "hasLaunched")
self.window?.rootViewController = vc
self.window?.makeKeyAndVisible()
// guard let _ = (scene as? UIWindowScene) else { return }
}
I've tried it both with commenting out the last guard statement and with not commenting it out.
You're creating the window incorrectly, so you're going to end up with a black screen. It would be a lot better to let the storyboard create the window for you, since you don't know how to do it. Just delete this line completely:
self.window = UIWindow(frame: UIScreen.main.bounds)
You can also cut this line, as it is also otiose (the storyboard will do this for you as well):
self.window?.makeKeyAndVisible()
Your sole responsibility now is to set the value of self.window?.rootViewController. Note that you do not need to say
vc = mainStoryboard.instantiateInitialViewController()!
because that is the root view controller already, given to you by the storyboard. Thus the only thing you need to do, in the architecture you've posited, is replace the root view controller in the situation where the user needs to be onboarded.
(See my github repo for a working example.)
This was solved by matt, see his answer.
Just posting the correct code below for anyone trying to do Onboarding with storyboards in ios13.
var window: UIWindow?
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
let launchedBefore = UserDefaults.standard.bool(forKey: "hasLaunched")
let launchStoryboard = UIStoryboard(name: "Onboarding", bundle: nil)
let mainStoryboard = UIStoryboard(name: "Main", bundle: nil)
var vc: UIViewController
if launchedBefore
{
vc = mainStoryboard.instantiateInitialViewController()!
}
else
{
vc = launchStoryboard.instantiateViewController(identifier: "OnboardingScene")
}
UserDefaults.standard.set(true, forKey: "hasLaunched")
self.window?.rootViewController = vc
}
my app has 2 pages(VCs) which are created and being controlled in a BossPageViewController and I want to give the user an option of going directly to second page using a Shortcut action.
now I know how to create shortcuts and how to trigger them and I also know how to open that page as root but when I open it as root I no longer can swipe to first page and also the pageDots aren't there so I should close the app and run it again to be able to swipe between pages and see pageDots.
code below is where second page shortcut triggers, I did copy and past the whole sceneDelegate here, maybe someone wants to test it:
import UIKit
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
var shortcutItemManager: UIApplicationShortcutItem?
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
if let shortcutItem = connectionOptions.shortcutItem {
shortcutItemManager = shortcutItem
}
guard let _ = (scene as? UIWindowScene) else { return }
}
func windowScene(_ windowScene: UIWindowScene, performActionFor shortcutItem: UIApplicationShortcutItem, completionHandler: #escaping (Bool) -> Void) {
shortcutItemManager = shortcutItem
}
//MARK:- Shortcuts are Triggered here.
func sceneDidBecomeActive(_ scene: UIScene) {
if let shortcutItem = shortcutItemManager {
if shortcutItem.type == "com.x.appname.openSecondPage" {
///- tag: code lines that run secondPage as root VC
if let windowScene = scene as? UIWindowScene {
let window = UIWindow(windowScene: windowScene)
let storyboard = UIStoryboard(name: "Main", bundle: nil)
window.rootViewController = storyboard.instantiateViewController(withIdentifier: "SecondPageVC")
self.window = window
window.makeKeyAndVisible()
}
shortcutItemManager = nil
}
}
}
}
I also don't want to instantiate the page as popover or modal or anything else I mean just want that page as it is and not the copy of that page. code example of what I mean as copy:
let openSecondPage = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(identifier: "SecondPageVC") as! SecondPageViewController
window?.rootViewController?.present(openSecondPage, animated: false, completion: nil)
I have been searching for a month but there wasn't anything exactly like this, they were similar but their code solution wasn't for me, that's why I did post this.
Swift 5.1
Ok by mixing and doing trial and error on different codes posted on Stackoverflow for different questions, finally I made that work for me as I wanted and I thought It's better to share it cause I know someone will need it or may have better approach to this that can share with us.
Use the code bellow where shortcut Item gets triggered or if you want to run it on app launch use it inside *****func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions)***** in sceneDelegate.
if let windowScene = scene as? UIWindowScene {
let window = UIWindow(windowScene: windowScene)
let bossPageVC = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(identifier: "BossPageVCID") as! BossPageViewController
self.window = window
UIView.transition(with: window, duration: 0.1, options: .transitionCrossDissolve, animations: {
window.rootViewController = bossPageVC
window.makeKeyAndVisible()
}, completion: { completed in
// viewControllerPages is a viewController array I created in BossPageViewController and index of 1 means the second page
if let secondPage = bossPageVC.viewControllerPages[1]
bossPageVC.setViewControllers([secondPage], direction: .forward, animated: true, completion: nil)
})
}
When using an AppDelegate, this line of code would allow me to send variables between app delegate and the view controller.
let firstName = AppDelegate.shared().givenName
What can I do for sceneDelegate?
Well I can suggest you using User Defaults like that:
// Set
UserDefaults.standard.set(self.currentZoomLevel, forKey: "zoomLevel")
// Read
if let zoomLevel = UserDefaults.standard.object(forKey: "zoomLevel") as? CGFloat {
return zoomLevel
}
Another approach should be like that
var window: UIWindow?
// On your scene delegate you have an instance variable Window
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
guard let windowScene = (scene as? UIWindowScene) else { return }
let window = UIWindow(windowScene: windowScene)
self.window = window
}
With window object, you can access the navigation controller and access your viewController directly:
var wantedViewController: DesiredViewController?
let _ = window?.rootViewController?.children.forEach({ navigation in
if let navController = navigation as? UINavigationController {
if let tabBar = navController.viewControllers.filter({ viewController in
viewController is MyTabBarController
}).first {
if let container = tabBar.children.filter({ $0 is ContainerViewController }).first as? ContainerViewController {
if let innerViewController = container.children.filter({ $0 is RootViewController }).first as? RootViewController {
wantedViewController = innerViewController.orderedViewControllers.filter({ $0 is DesiredViewController }).first as? DesiredViewController
}
}
}
}
})
This code is a little bit complex because of my structure on the project, my view controller is inside a TabController, probably yours can be not that complex and you will find the view controller easier.
Just debug and see how it happens.
All I want to do is go to the login screen if the user = nil. Otherwise it goes to the main/home section of the app which consists of the Tab Bar Controller's first view controller
Here is my app delegate:
import UIKit
import Firebase
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
FirebaseApp.configure()
let storyboard = UIStoryboard(name: "Main", bundle: nil)
if Auth.auth().currentUser == nil && !UserDefaults.standard.bool(forKey: "hasViewedWalkthrough") {
let initialViewController = storyboard.instantiateViewController(withIdentifier: "WalkthroughViewController") as? WalkthroughViewController
self.window?.rootViewController = initialViewController
} else if Auth.auth().currentUser == nil && UserDefaults.standard.bool(forKey: "hasViewedWalkthrough") {
let initialViewController = storyboard.instantiateViewController(withIdentifier: "loginViewController") as? LoginViewController
self.window?.rootViewController = initialViewController
} else {
let initialViewController = storyboard.instantiateViewController(withIdentifier: "homeTabBarController") as? MainTabBarViewController
self.window?.rootViewController = initialViewController
}
self.window?.makeKeyAndVisible()
return true
}
I am not able to get to the onboarding or login screen. I am always led to the main screen. I know the screens are rendering fine if I manually set them as initial view controllers in storyboard.
I have tried numerous solutions and none of them are working.
Update
Try this:
let storyBoard = UIStoryboard(name: "Main", bundle: nil)
let initialViewController = storyBoard.instantiateViewControllerWithIdentifier("homeTabBarController") as? TabBarViewController // whatever your swift file is called
self.window?.rootViewController = storyBoard
use that code for each statement for the if statement, obviously change the names of the view controllers to the login one respectively.
For people reading this in the future, iOS 13 introduced SceneDelegate and now all UI related stuff should be configured from that point.
I faced the same issue and solved it by using this code.
The custom window should be configured from here now.
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 sceneWindow = (scene as? UIWindowScene) else { return }
window = UIWindow(frame: sceneWindow.coordinateSpace.bounds)
window?.windowScene = sceneWindow
window?.makeKeyAndVisible()
if let navigationController = Storyboard.Main.initialViewController as? UINavigationController,
let viewController = navigationController.children.first as? CountriesViewController {
viewController.inject(CountriesViewModel(provider: CountryAPI(),
informTo: viewController),
navigator: CountriesNavigator(navigationController: navigationController))
window?.rootViewController = navigationController
}
}