Value of type 'AppDelegate' has no member 'window' - ios

Im trying to use the 3D touch Quick Actions and I'm setting it up copying VEA Software code. In his sample code it works perfectly but when I try to add it to my app I get some unusual errors. I am new to coding and swift so please explain as much as possible. Thanks. Below I have the code which is in my app delegate.
This is where I'm getting the error (self.window?):
#available(iOS 9.0, *)
func handleShortcutItem(shortcutItem: UIApplicationShortcutItem) -> Bool
{
var handled = false
var window: UIWindow?
guard ShortcutIdentifier(fullType: shortcutItem.type) != nil else { return false }
guard let shortcutType = shortcutItem.type as String? else { return false }
switch (shortcutType)
{
case ShortcutIdentifier.First.type:
handled = true
var window = UIWindow?()
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let navVC = storyboard.instantiateViewControllerWithIdentifier("ProjectPage") as! UINavigationController
// Error on line below
self.window?.rootViewController?.presentViewController(navVC, animated: true, completion: nil)
break
case ShortcutIdentifier.Second.type:
handled = true
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let navVC = storyboard.instantiateViewControllerWithIdentifier("ContactPageView") as! UINavigationController
// Error on line below
self.window?.rootViewController?.presentViewController(navVC, animated: true, completion: nil)
break
case ShortcutIdentifier.Third.type:
handled = true
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let navVC = storyboard.instantiateViewControllerWithIdentifier("ViewController") as! UINavigationController
// Error on line below
self.window?.rootViewController?.presentViewController(navVC, animated: true, completion: nil)
break
default:
break
}
return handled
}

Xcode 12.0 Swift 5.0
At the moment you need to:
Remove the “Application Scene Manifest” from info.plist file;
Remove scene delegate class;
Remove scene related methods in AppDelegate class;
If missing, add the property var window: UIWindow? to your AppDelegate class.
Add some logic in func application(_: UIApplication, didFinishLaunchingWithOptions _: [UIApplication.LaunchOptionsKey: Any]?).
Example of implementation when you need to support iOS 12 and 13:
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_: UIApplication, didFinishLaunchingWithOptions _: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
configureInitialViewController()
return true
}
private func configureInitialViewController() {
let initialViewController: UIViewController
let storyboard = UIStoryboard(name: "Main", bundle: nil)
window = UIWindow()
if (condition) {
let mainViewController = storyboard.instantiateViewController(withIdentifier: "mainForm")
initialViewController = mainViewController
} else {
let loginViewController = storyboard.instantiateViewController(withIdentifier: "loginForm")
initialViewController = loginViewController
}
window?.rootViewController = initialViewController
window?.makeKeyAndVisible()
}
}

In iOS 13, Xcode 11, the sceneDelegate handles the functionality of UIScene and window. The window performs properly when used in the sceneDelegate.

When you are using Scene view then windows object is in the scene delegate. Copy window object and place it in the app delegate and it will work.

if your project already has SceneDelegate file ,then you need to use it insead of AppDelegate , like this way :
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 }
let mainStoryBoard = UIStoryboard(name: "Main", bundle: nil)
let viewController = mainStoryBoard.instantiateViewController(withIdentifier: "setCountry")
window?.rootViewController = viewController
}

Sometimes when you have an issue in AppDelegate it can be solved with a quick Product -> Clean. Hope this helps.

This worked for me.
Add the following code inside SceneDelegate's first function's, func scene(...), if statement, if let windowScene = scene as? UIWindowScene {..Add Below Code Here..}.
Make sure to add import MediaPlayer at the top of the file.
let volumeView = MPVolumeView()
volumeView.alpha = 0.000001
window.addSubview(volumeView)
At the end your code should be:
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
// Create the SwiftUI view that provides the window contents.
let contentView = ContentView()
// Use a UIHostingController as window root view controller.
if let windowScene = scene as? UIWindowScene {
let window = UIWindow(windowScene: windowScene)
window.rootViewController = UIHostingController(rootView: contentView)
self.window = window
window.makeKeyAndVisible()
// MARK: Hide Volume HUD View
let volumeView = MPVolumeView()
volumeView.alpha = 0.000001
window.addSubview(volumeView)
}
}

first you need to specify which shortcut you need to use , cuz there are two types of shortcuts : dynamic and static and let say that you choose the static that can be done and added from the property list info then you have to handle the actions for each shortcut in the app delegate file in your project
func application(_ application: UIApplication, performActionFor shortcutItem: UIApplicationShortcutItem, completionHandler: #escaping (Bool) -> Void) {
let storyboard = UIStoryboard(name: "Main", bundle: Bundle.main)
let mainTabController = storyboard.instantiateViewController(withIdentifier: "Tab_Bar") as? Tab_Bar
let loginViewController = storyboard.instantiateViewController(withIdentifier: "LoginVC") as? LoginVC
let doctor = storyboard.instantiateViewController(withIdentifier: "DrTabController") as! DrTabController
getData(key: "token") { (token) in
getData(key: "type") { (type) in
if token != "" && type != "" , type == "2" {
self.window?.rootViewController = mainTabController
if #available(iOS 13.0, *) {
self.window?.overrideUserInterfaceStyle = .light
} else {
self.window?.makeKeyAndVisible()
}
if shortcutItem.type == "appointMent" {
mainTabController?.selectedIndex = 1
} else if shortcutItem.type == "Chatting" {
mainTabController?.selectedIndex = 2
}
} else if token != "" && type != "" , type == "1"{
self.window?.rootViewController = doctor
if #available(iOS 13.0, *) {
self.window?.overrideUserInterfaceStyle = .light
} else {
self.window?.makeKeyAndVisible()
}
if shortcutItem.type == "appointMent" {
mainTabController?.selectedIndex = 1
} else if shortcutItem.type == "Chatting" {
mainTabController?.selectedIndex = 2
}
} else if token == "" && type == "" {
self.window?.rootViewController = loginViewController
if #available(iOS 13.0, *) {
self.window?.overrideUserInterfaceStyle = .light
} else {
self.window?.makeKeyAndVisible()
}
if shortcutItem.type == "appointMent" {
mainTabController?.selectedIndex = 1
} else if shortcutItem.type == "Chatting" {
mainTabController?.selectedIndex = 2
}
}
}
}
}
this will be work so fine for you but first you have to determine the rootViewController we can say like :
let storyboard = UIStoryboard(name: "Main", bundle: Bundle.main)
self.window?.rootViewController = mainTabController
if #available(iOS 13.0, *) {
self.window?.overrideUserInterfaceStyle = .light
} else {
self.window?.makeKeyAndVisible()
}
if shortcutItem.type == "appointMent" {
mainTabController?.selectedIndex = 1
} else if shortcutItem.type == "Chatting" {
mainTabController?.selectedIndex = 2
}

Related

How to dismiss view controller from SceneDelegate?

I am working on an ios App (Swift 5) and I am trying to show a screen if the app is offline and then dismisses when the user re-connects.
I am expecting the OfflineViewController to appear when the user is offline, and the last screen the user on to appear if they are connected.
What is happening is the OfflineViewController is appearing when I disconnect from the network, however it does not go away when I connect back to the network. I tried adding a button to dismiss and this does not work either.
I've attached my code below, any idea what am I doing wrong?
SceneDelegate
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
let reachability = try! Reachability()
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
guard let _ = (scene as? UIWindowScene) else { return }
// Send to homepage if logged in, otherwise login screen.
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
}
}
reachability.whenUnreachable = { [self] _ in
print("Not reachable (Scene delegate)")
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyboard.instantiateViewController(withIdentifier: "OfflineViewController") as! OfflineViewController
vc.modalPresentationStyle = .fullScreen
guard let firstScene = UIApplication.shared.connectedScenes.first as? UIWindowScene else {
return
}
guard let firstWindow = firstScene.windows.first else {
return
}
let rootVC = firstWindow.rootViewController
rootVC?.dismiss(animated: true, completion: nil)
rootVC!.present(vc, animated: true, completion: nil)
}
do {
try reachability.startNotifier()
} catch {
print("Unable to start notifier")
}
}
}
OfflineViewController
import UIKit
class OfflineViewController: UIViewController {
let reachability = try! Reachability()
override func viewDidLoad() {
super.viewDidLoad()
do {
try reachability.startNotifier()
} catch {
print("Unable to start notifier")
}
}
#IBAction func hitRefresh(_ sender: Any) {
reachability.whenReachable = { reachability in
self.dismiss(animated: true, completion: nil)
}
}
}
I would start be removing all of the reachability code from OfflineViewController. The logic to dismiss belongs with the logic to present.
Then update the reachability code in the scene delegate with a whenReachable block that dismisses the current OfflineViewController.
You should also avoid using UIApplication.shared.connectedScenes in the scene delegate code. You already know the scene. No need to go find it.
The updated whenUnreachable:
reachability.whenUnreachable = { [self] _ in
print("Not reachable (Scene delegate)")
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyboard.instantiateViewController(withIdentifier: "OfflineViewController") as! OfflineViewController
vc.modalPresentationStyle = .fullScreen
guard let winScene = (scene as? UIWindowScene) else { return }
guard let firstWindow = winScene.windows.first else {
return
}
let rootVC = firstWindow.rootViewController
rootVC?.dismiss(animated: true, completion: nil)
rootVC?.present(vc, animated: true, completion: nil)
}
The added whenReachable:
reachability.whenReachable = { [self] _ in
print("Reachable (Scene delegate)")
guard let winScene = (scene as? UIWindowScene) else { return }
guard let firstWindow = winScene.windows.first else {
return
}
let rootVC = firstWindow.rootViewController
rootVC?.dismiss(animated: true, completion: nil)
}

Initial ViewController with Firebase bug fix

I've strange bug I cannot understand how to solve. When I set which ViewController should be opened depends if users is registered and I put this code in SceneDelegate I get black screen for a moment before appearing ViewController
import UIKit
import FirebaseAuth
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
let storyBoard = UIStoryboard(name: "Main", bundle: nil)
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
if let user = Auth.auth().currentUser {
FirestoreService.shared.getUserData(user: user) { (result) in
switch result {
case .success(let muser):
let navigationController = self.storyBoard.instantiateViewController(withIdentifier: "Navigation") as! UINavigationController
let conroller = self.storyBoard.instantiateViewController(withIdentifier: "MainController") as! MainController
navigationController.viewControllers = [conroller]
self.window?.rootViewController = navigationController
case .failure(_):
let conroller = self.storyBoard.instantiateViewController(withIdentifier: "SignUpController") as! SignUpController
self.window?.rootViewController = conroller
}
}
} else {
print(3)
let conroller = storyBoard.instantiateViewController(withIdentifier: "SignUpController") as! SignUpController
self.window?.rootViewController = conroller
}
window?.makeKeyAndVisible()
}
But when I put code for appearing ViewController outside the firebase it works as it should. How this can be fixed?
I think there are 2 ways to solve this issue.
1. Using a splash screen:
Don't make the decision of showing a login screen or main screen in scene(_:, session, connectionOptions). Instead, create a new view controller called SplashScreenViewController. Set your app's logo in its center. And then in its viewDidLoad() move your logic to check if the user is already logged in:
func chooseAndPresentStartScreen() {
let storyBoard = UIStoryboard(name: "Main", bundle: nil)
if let user = Auth.auth().currentUser {
FirestoreService.shared.getUserData(user: user) { (result) in
switch result {
case .success(let muser):
let navigationController = storyBoard.instantiateViewController(withIdentifier: "Navigation") as! UINavigationController
let conroller = storyBoard.instantiateViewController(withIdentifier: "MainController") as! MainController
navigationController.viewControllers = [conroller]
self.present(navigationController, animated: true, completion: nil)
case .failure(_):
let conroller = storyBoard.instantiateViewController(withIdentifier: "SignUpController") as! SignUpController
self.present(conroller, animated: true, completion: nil)
}
}
} else {
print(3)
let conroller = storyBoard.instantiateViewController(withIdentifier: "SignUpController") as! SignUpController
self.present(conroller, animated: true, completion: nil)
}
}
2. Using User Defaults:
Define a global variable for the key used to save user default value.
let isUserLoggedInKey: String = "IsUserLoggedIn"
After the user logs-in:
let defaults = UserDefaults.standard
defaults.setValue(true, forKey: isUserLoggedInKey)
Then in your scene(_:, session, connectionOptions), check if this value is set.
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 defaults = UserDefaults.standard
let isLoggedIn = defaults.bool(forKey: isUserLoggedInKey)
if isLoggedIn {
let navigationController = self.storyBoard.instantiateViewController(withIdentifier: "Navigation") as! UINavigationController
let conroller = self.storyBoard.instantiateViewController(withIdentifier: "MainController") as! MainController
navigationController.viewControllers = [conroller]
self.window?.rootViewController = navigationController
} else {
let conroller = self.storyBoard.instantiateViewController(withIdentifier: "SignUpController") as! SignUpController
self.window?.rootViewController = conroller
}
window?.makeKeyAndVisible()
}
Don't forget to set isLoggedInKey to false when the user logs out.
let defaults = UserDefaults.standard
defaults.setValue(false, forKey: isUserLoggedInKey)
If you don't want to manually save value for isUserLoggedInKey when the user logs in and logs out, you can also use a state change listener. It takes a callback that is triggered when the user logs in or logs out. Add this in your Login view controller:
Auth.auth().addStateDidChangeListener { (auth, user) in
let defaults = UserDefaults.standard
defaults.setValue(user != nil, forKey: isUserLoggedInKey)
}

Switch between the login view and the dashboard view does't working after user logged in

let storyboard = UIStoryboard(name: "Main", bundle: nil)
let dashboard = storyboard.instantiateViewController(identifier: "DashboardViewController")
(UIApplication.shared.connectedScenes.first?.delegate as? SceneDelegate)?.changeRootViewController(_vc: dashboard)
}
UIApplication.shared.delegate
appDelegate?.window??.rootViewController = dashboard
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
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 }
let storyboard = UIStoryboard(name: "Main", bundle: nil)
if let loggedUsername = UserDefaults.standard.string(forKey: "user"){
let dashboard = storyboard.instantiateViewController(identifier: "DashboardViewController")
window?.rootViewController = dashboard
}
else{
let loginView = storyboard.instantiateViewController(identifier: "ViewController")
window?.rootViewController = loginView
}
}
I have also tried this but it did not work too
func changeRootViewController( _vc: UIViewController, animted: Bool = true){
guard let window = self.window else {
return
}
UIView.transition(with: window,
duration: 0.5,
options: .transitionCurlDown,
animations: nil,
completion: nil)
}
var window: UIWindow?
var navigationC: UINavigationController?
private(set) static var shared: SceneDelegate?
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 }
let storyboard = UIStoryboard(name: "Main", bundle: nil)
if let loggedUsername = UserDefaults.standard.string(forKey: "user"){
let dashboardvc = storyboard.instantiateViewController(withIdentifier: "DashboardViewController") as! DashboardViewController
self.navigationC = UINavigationController(rootViewController: dashboardvc)
} else{
let loginvc = storyboard.instantiateViewController(withIdentifier: "ViewController") as! ViewController
self.navigationC = UINavigationController(rootViewController: loginvc)
}
self.navigationC!.setNavigationBarHidden(true, animated: false)
self.window?.clipsToBounds = true
self.window?.rootViewController = navigationC
self.window?.makeKeyAndVisible()
Self.shared = self
}
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
var navigationC : UINavigationController?
private(set) static var shared: SceneDelegate?
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 }
if UserDefaults.value(forKey: "isLogin") != nil {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let dashboard = storyboard.instantiateViewController(withIdentifier: "DashboardViewController") as! DashboardViewController
self.navigationC = UINavigationController(rootViewController: dashboard)
}else{
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let loginPage = storyboard.instantiateViewController(withIdentifier: "ViewController") as!ViewController
self.navigationC = UINavigationController(rootViewController: loginPage)
}
self.navigationC!.setNavigationBarHidden(true, animated: false)
self.window?.clipsToBounds = true
self.window?.rootViewController = navigationC
self.window?.makeKeyAndVisible()
Self.shared = self
}
do{
let json = try JSONSerialization.jsonObject(with: data!, options: .allowFragments) as?NSDictionary
if let parseJSON = json {
let accessToken = parseJSON["token"] as? String
let username = parseJSON["user"] as? String
_ = UserDefaults(suiteName: username)
print("Access token: \(String(accessToken!))")
let saveAccessToken: Bool = KeychainWrapper.standard.set(accessToken!, forKey: "accessToken")
let saveUsername: Bool = KeychainWrapper.standard.set(username!, forKey: "username")
if(accessToken?.isEmpty)! {
self.displayMessage(userMessage: "Inloggen is niet success, probeer later")
return
}
DispatchQueue.main.async {
USERDEFAULT.set(true, forKey: "isLogin")
let dashboard = self.storyboard?.instantiateViewController(identifier: "DashboardViewController") as! DashboardViewController
self.navigationController?.pushViewController(dashboard, animated: true)
}
}
do{
let json = try JSONSerialization.jsonObject(with: data!, options: .allowFragments) as?NSDictionary
if let parseJSON = json {
let accessToken = parseJSON["token"] as? String
let username = parseJSON["user"] as? String
_ = UserDefaults(suiteName: username)
print("Access token: \(String(accessToken!))")
let saveAccessToken: Bool = KeychainWrapper.standard.set(accessToken!, forKey: "accessToken")
let saveUsername: Bool = KeychainWrapper.standard.set(username!, forKey: "username")
if(accessToken?.isEmpty)! {
self.displayMessage(userMessage: "Inloggen is niet success, probeer later")
return
}
DispatchQueue.main.async {
let defaults = UserDefaults.standard
defaults.set(true, forKey: "isLogin")
let dashboard = self.storyboard?.instantiateViewController(identifier: "DashboardViewController") as! DashboardViewController
self.navigationController?.pushViewController(dashboard, animated: true)
}
}
}catch{
self.removeActivityIndicator(activityIndicator: myactivit)
self.displayMessage(userMessage: "Probeer later")
return
}
}
\ this my SceneDelegate file
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
var navigationC : UINavigationController?
private(set) static var shared: SceneDelegate?
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 }
if UserDefaults.value(forKey: "isLogin") != nil {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let dashboard = storyboard.instantiateViewController(withIdentifier: "DashboardViewController") as! DashboardViewController
self.navigationC = UINavigationController(rootViewController: dashboard)
}else{
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let loginPage = storyboard.instantiateViewController(withIdentifier: "ViewController") as!ViewController
self.navigationC = UINavigationController(rootViewController: loginPage)
}
self.navigationC!.setNavigationBarHidden(true, animated: false)
self.window?.clipsToBounds = true
self.window?.rootViewController = navigationC
self.window?.makeKeyAndVisible()
Self.shared = self
}
func changeRootViewController( _vc: UIViewController, animted: Bool = true){
guard let window = self.window else {
return
}
UIView.transition(with: window,
duration: 0.5,
options: .transitionCurlDown,
animations: nil,
completion: nil)
}
\ I get this error
Exception NSException * "[<NSUserDefaults 0x7fff86b8bd58> valueForUndefinedKey:]: this class is not key value coding-compliant for the key isLogin." 0x0000600001abc930
name __NSCFConstantString * "NSUnknownKeyException" 0x00007fff801e7c90
reason __NSCFString * "[<NSUserDefaults 0x7fff86b8bd58> valueForUndefinedKey:]: this class is not key value coding-compliant for the key isLogin." 0x00006000026e8090
userInfo __NSDictionaryI * 2 key/value pairs 0x00006000001a4d40
reserved __NSDictionaryM * 2 key/value pairs 0x00006000014c0ac0
DispatchQueue.main.async {
let dashboard = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "DashboardViewController") as! DashboardViewController
self.view.window?.rootViewController = dashboard
self.view.window?.makeKeyAndVisible()
}

how can I make the uiviewcontroller visible only once during first run of the app (e.g. tutorial)?

I'm creating an iOS swift app and I want to show tutorial screen when user runs the app for the very first time. Later on, with each run of the app the tutorial should be hidden and another view controller should be visible as a starting point. So far my storyboard looks like this:
It contains two screens of tutorial (1st and last) and tab bar (which is a main window of my app).
As for now, in storyboard I chose the tab bar to be an initial view controller:
And with that approach the tutorial screen is never seen. How can I show it only once on first launch app and then skip it each time user opens the app?
In didFinishLaunchingWithOptions method of AppDelegate check for NSUserDefaults value like this way.
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: NSDictionary?) -> Bool {
let defaults = NSUserDefaults.standardUserDefaults()
if defaults.objectForKey("isFirstTime") == nil {
defaults.setObject("No", forKey:"isFirstTime")
let storyboard = UIStoryboard(name: "main", bundle: nil) //Write your storyboard name
let viewController = storyboard.instantiateViewControllerWithIdentifier("ViewController") as! ViewController
self.window.rootViewController = viewController
self.window.makeKeyAndVisible()
}
return true
}
Note: I have created the object of ViewController you need to create the object of your FirstPage tutorial screen after that assign it to the rootViewController.
For swift 4 make these changes.
let defaults = UserDefaults.standard
if defaults.object(forKey: "isFirstTime") == nil {
defaults.set("No", forKey:"isFirstTime")
defaults.synchronize()
...
}
Simplified Swift 4 version of this answer.
https://stackoverflow.com/a/39353299/1565913
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: NSDictionary?) -> Bool {
if !UserDefaults.standard.bool(forKey: "didSee") {
UserDefaults.standard.set(true, forKey: "didSee")
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let viewController = storyboard.instantiateViewController(withIdentifier: "YourViewController")
self.window?.rootViewController = viewController
self.window?.makeKeyAndVisible()
}
return true
}
Add this is Scene Delegate
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
guard let _ = (scene as? UIWindowScene) else { return }
if UserDefaults.standard.bool(forKey: "introLaunched") == false{
UserDefaults.standard.set(true, forKey: "introLaunched")
let storyboard = UIStoryboard(name: "Main", bundle: .main)
let vc = storyboard.instantiateViewController(identifier: "IntroScreenViewController") as! IntroScreenViewController
self.window?.rootViewController = UINavigationController(rootViewController: vc)
} else {
let storyboard = UIStoryboard(name: "Main", bundle: .main)
let vc = storyboard.instantiateViewController(withIdentifier: "ViewController") as! ViewController
self.window?.rootViewController = UINavigationController(rootViewController: vc)
}
}

Opening view controller from app delegate using swift

I am trying to create a push notification which determines which view to open according to information obtained from the push.
I have managed to get the information from the push, but I am now struggling to get the view to open
Looking at other stack overflow questions I have the following currently:
App Delegate Did finish loading:
//Extract the notification data
if let notificationPayload = launchOptions?[UIApplicationLaunchOptionsRemoteNotificationKey] as? NSDictionary {
// Get which page to open
let viewload = notificationPayload["view"] as? NSString
let storyBoard : UIStoryboard = UIStoryboard(name: "Main", bundle:nil)
//Load correct view
if viewload == "circles" {
var viewController = self.window?.rootViewController?.storyboard?.instantiateViewControllerWithIdentifier("Circles") as! UIViewController
self.window?.rootViewController = viewController
}
}
Currently this is failing on the var ViewController = self... line.
You have to set ViewController StoryBoardId property as below image.
open viewController using coding as below in swift
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
let mainStoryboardIpad : UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let initialViewControlleripad : UIViewController = mainStoryboardIpad.instantiateViewControllerWithIdentifier("Circles") as UIViewController
self.window = UIWindow(frame: UIScreen.main.bounds)
self.window?.rootViewController = initialViewControlleripad
self.window?.makeKeyAndVisible()
return true
}
For iOS 13+ (based on an article by dev2qa)
Open SceneDelegate.swift and add following
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
// If this scene's self.window is nil then set a new UIWindow object to it.
self.window = self.window ?? UIWindow()
// Set this scene's window's background color.
self.window!.backgroundColor = UIColor.red
// Create a ViewController object and set it as the scene's window's root view controller.
self.window!.rootViewController = ViewController()
// Make this scene's window be visible.
self.window!.makeKeyAndVisible()
guard scene is UIWindowScene else { return }
}
There is an open-source navigation utility which attempts to make this easier. Example
Swift 3:
This is my preferred approach when presenting a new viewController from the current viewController through the AppDelegate. This way you don't have to completely tear down your view hierarchy when handling a push notification or universal link
if let controller = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "someController") as? SomeController {
if let window = self.window, let rootViewController = window.rootViewController {
var currentController = rootViewController
while let presentedController = currentController.presentedViewController {
currentController = presentedController
}
currentController.present(controller, animated: true, completion: nil)
}
}
Swift 3
To present the view together with the navigation controller:
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let viewController = storyboard.instantiateViewController(withIdentifier :"InboxViewController") as! InboxViewController
let navController = UINavigationController.init(rootViewController: viewController)
if let window = self.window, let rootViewController = window.rootViewController {
var currentController = rootViewController
while let presentedController = currentController.presentedViewController {
currentController = presentedController
}
currentController.present(navController, animated: true, completion: nil)
}
First Initialize the window
self.window = UIWindow(frame: UIScreen.mainScreen().bounds)
let storyBoard = UIStoryboard(name: "Main", bundle: nil)
For setting rootViewController inside AppDelegate Class
let viewController = storyBoard.instantiateViewControllerWithIdentifier("Circles") as UIViewController
self.window?.rootViewController = viewController
self.window?.makeKeyAndVisible()
There is a swift 4 version
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions:
[UIApplicationLaunchOptionsKey: Any]?) -> Bool {
let mainStoryboardIpad : UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let initialViewControlleripad : UIViewController = mainStoryboardIpad.instantiateViewController(withIdentifier: "Circles") as UIViewController
self.window = UIWindow(frame: UIScreen.main.bounds)
self.window?.rootViewController = initialViewControlleripad
self.window?.makeKeyAndVisible()
return true}
In Swift 3
let mainStoryboard : UIStoryboard = UIStoryboard(name: StorybordName, bundle: nil)
let initialViewControlleripad : UIViewController = mainStoryboard.instantiateViewController(withIdentifier: identifierName) as UIViewController
if let navigationController = self.window?.rootViewController as? UINavigationController
{
navigationController.pushViewController(initialViewControlleripad, animated: animation)
}
else
{
print("Navigation Controller not Found")
}
I'd say creating UIWindow each time you want to change rootViewController is bad idea. After couple changes of rootVC (using upper solution) you are gonna have many UIWindows in your app at one time.
In my opinion better solution is:
Get new rootVC: let rootVC = UIStoryboard(name: "StoryboardName", bundle: nil).instantiateViewControllerWithIdentifier("newRootVCIdentifier") as UIViewController
Set frame for new rootVC from UIScreen's bounds: rootVC.view.frame = UIScreen.mainScreen().bounds
Set new root controller for current window (here with animation): UIView.transitionWithView(self.window!, duration: 0.5, options: .TransitionCrossDissolve, animations: {
self.window!.rootViewController = rootVC
}, completion: nil)
Done!
You don't need method window?.makeKeyAndVisible(), cause this solution works on current app window.
Swift 3 SWRevealViewController
self.window = UIWindow(frame: UIScreen.main.bounds)
let storyBoard = UIStoryboard(name: "Main", bundle: nil)
let viewController = storyBoard.instantiateViewController(withIdentifier: "SWRevealViewController") as! SWRevealViewController
self.window?.rootViewController = viewController
self.window?.makeKeyAndVisible()
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let destinationViewController = storyboard.instantiateViewController(withIdentifier: "LandVC") as! LandingPageVC
destinationViewController.webpageURL = NotificationAdvertisement._htmlpackagePath
destinationViewController.adID = NotificationAdvertisement._adID
destinationViewController.toneID = NotificationAdvertisement.toneID
let navigationController = self.window?.rootViewController as! UIViewController
navigationController.showDetailViewController(destinationViewController, sender: Any?.self)
SWIFT 4
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let destinationViewController = storyboard.instantiateViewController(withIdentifier: "LandVC") as! LandingPageVC
destinationViewController.webpageURL = NotificationAdvertisement._htmlpackagePath
destinationViewController.adID = NotificationAdvertisement._adID
destinationViewController.toneID = NotificationAdvertisement.toneID
let navigationController = self.window?.rootViewController as! UIViewController
navigationController.showDetailViewController(destinationViewController, sender: Any?.self)
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
guard let windowScene = (scene as? UIWindowScene) else { return }
self.window = UIWindow(windowScene: windowScene)
let mainStoryboard : UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let vc : UIViewController = mainStoryboard.instantiateViewController(withIdentifier: "LoginViewController")
let rootNC = UINavigationController(rootViewController: vc)
self.window?.rootViewController = rootNC
self.window?.makeKeyAndVisible()
}

Resources