I have seen a lot of other stackoverflow posts related to this error, but I'm not able to understand it (and thus, don't know how to solve) in my case.
Most of the responses in stackoverflow posts (like this one, for example) involve multiple view controllers and how you push one view controller into the stack before the previous one is finished.
But in my case, I only have a single view controller with very very very minimal UI - I got this error in a sample test code.
There's only one view controller in the project,
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
NSLog(TAG + "ViewController.viewDidLoad")
super.viewDidLoad()
view.backgroundColor = .systemBlue
}
}
The view controller is initialized in scene(willConnectTo:options) and the UI is displayed as shown below. But there is a custom class (Presentation) in between.
This is my scene(willConnectTo:options)
// uses Presentation class to display UI
func scene(_ pUIScene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
NSLog(TAG + "SceneDelegate.scene(willConnectTo:options)")
AppDelegate.sForegroundScene = pUIScene
var user_interface = Presentation()
user_interface.CreateWindow()
}
and this is my Presentation class,
class Presentation {
var window: UIWindow?
init() {
NSLog(TAG + "Presentation.init")
}
func CreateWindow() {
NSLog(TAG + "Presentation.CreateWindow")
guard let winScene = (AppDelegate.sForegroundScene as? UIWindowScene) else {
NSLog(TAG + "Error in obtaining UIWindowScene!")
return
}
window = UIWindow(windowScene: winScene)
window?.rootViewController = ViewController()
window?.makeKeyAndVisible()
}
}
Now, if I remove the Presentation class and directly initialise the ViewController and set UI in scene(willConnectTo:option) - as shown below, it works as expected - I get a blue screen.
// Presentation class is not used
func scene(_ pUIScene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
NSLog(TAG + "SceneDelegate.scene(willConnectTo:options)")
AppDelegate.sForegroundScene = pUIScene
guard let winScene = (AppDelegate.sForegroundScene as? UIWindowScene) else {
NSLog(TAG + "Error in obtaining UIWindowScene!")
return
}
window = UIWindow(windowScene: winScene)
window?.rootViewController = ViewController()
window?.makeKeyAndVisible()
}
Why does moving the UI code to a different class cause this 'Unbalanced Calls to begin/end appearance transitions' issue??
Its because I was trying to set my view controller to a random window variable (in Presentation) and not the one associated with the SceneDelegate (and thus, the scene).
After the following changes, it worked.
In SceneDelegate, associate the window to the scene object, which can later be retrieved by Presentation.
func scene(_ pUIScene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
NSLog(TAG + "SceneDelegate.scene(willConnectTo:options)")
guard let winScene:UIWindowScene = (pUIScene as? UIWindowScene) else {
NSLog((TAG + "Error in obtaining UIWindowScene"))
return
}
window = UIWindow(windowScene:winScene)
AppDelegate.sForegroundScene = winScene
let user_interface = Presentation()
user_interface.CreateWindow()
}
In Presentation,
func CreateWindow() {
NSLog(TAG + "Presentation.CreateWindow")
guard var winScene:UIWindowScene = AppDelegate.sForegroundScene else {
NSLog(TAG + "Unable to obtain UIWindowScene")
return
}
NSLog(TAG + "Number of windows = %d", winScene.windows.count) // Logs 1 because the window is associated with the scene in SceneDelegate
window = winScene.windows.first
window?.rootViewController = ViewController()
window?.makeKeyAndVisible()
}
Related
I have a root viewController that is the main viewController that users can navigate to when they have been authenticated. Right now, when the app loads the root viewController, it checks if the user is logged in and navigates to the login viewController if the user isn't authenticated.
The problem is I don't know how to prevent the user from returning to the main viewController without authenticating. Right now, they are able to return via back button if I use Show, or by gesture if I use Present Modally or Show Detail. I have searched for hours and haven't found the answer to this problem. It would be easy if I could just remove the root viewController instance with something like finish() like in Android, and then create a new one once the user logs in but I'm not sure if that's even possible. How can I solve this?
Here is the code that presents my LoginViewController:
override func viewDidLoad() {
super.viewDidLoad()
if (!isUserSignedIn()) {
performSegue(withIdentifier: "pairedDevicesToLogin", sender: self)
}
navigationController?.navigationBar.isHidden = false
}
For anyone struggling with navigation flow for authentication in iOS this is what worked for me.
With iOS 13 and later, authentication code should go in SceneDelegate instead of AppDelegate like this:
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 windowScene = (scene as? UIWindowScene) else { return }
let window = UIWindow(windowScene: windowScene)
let storyboard = UIStoryboard(name: "Main", bundle: nil)
_ = Auth.auth().addStateDidChangeListener { (auth, user) in
if ((user) != nil) {
self.window?.rootViewController = storyboard.instantiateViewController(withIdentifier: "myMainViewController")
} else {
self.window?.rootViewController = storyboard.instantiateViewController(withIdentifier: "loginViewController")
}
}
}
This will allow the app to decide what viewController to load first based off of the authentication results.
I try to introduce VIP architecture to my project and do not fully understand the examples that I find, I follow this info https://www.netguru.com/blog/clean-swift-ios-architecture-pattern, and examine the source code inside I am confused about the idea behind, this is the reusable idea of a creating this component or one of the way how to build this component?
Example:
// Scene Factory
protocol SceneFactory {
var configurator: LoginSceneConfigurator! { get set }
func makeLoginScene() -> UIViewController
}
final class DefaultSceneFactory: SceneFactory {
var configurator: LoginSceneConfigurator!
func makeLoginScene() -> UIViewController {
let vc = LoginSceneViewController()
return configurator.configured(vc)
}
// Configurator
protocol LoginSceneConfigurator {
func configured(_ vc: LoginSceneViewController) -> LoginSceneViewController
}
final class DefaultLoginSceneConfigurator: LoginSceneConfigurator {
private var sceneFactory: SceneFactory
init(sceneFactory: SceneFactory) {
self.sceneFactory = sceneFactory
}
#discardableResult
func configured(_ vc: LoginSceneViewController) -> LoginSceneViewController {
sceneFactory.configurator = self
let service = DefaultAuthService(
networkManager: DefaultNetworkManager(session: MockNetworkSession())
)
let authWorker = LoginSceneAuthWorker(service: service)
let interactor = LoginSceneInteractor()
let presenter = LoginScenePresenter()
let router = LoginSceneRouter(sceneFactory: sceneFactory)
router.source = vc
presenter.viewController = vc
interactor.presenter = presenter
interactor.authWorker = authWorker
vc.interactor = interactor
vc.router = router
return vc
}
}
In my case, I use a simple little construction and don't know if this is the correct way or not, when I copy the idea and try to implement it in my project, this does not work. I have also attached the source code of my project on google https://drive.google.com/file/d/1DcTxDXNl8idp2C3HLs5ggixdRVTE1UGY/view?usp=sharing
My example:
protocol HomeViewProtocol {
func reloadView(_ bucketLists: [TestData])
func showSearchResult(result: String)
func progressState()
}
extension HomeViewController: HomeViewProtocol {
func progressState() {
views?.loader.startAnimating()
}
func showSearchResult(result: String) {
router?.showSearchMsg(result)
views?.loader.stopAnimating()
}
func reloadView(_ bucketLists: [TestData]) {
self.bucketLists = bucketLists
views?.label.text = bucketLists.last?.title
}
}
class HomeViewController: UIViewController {
private var bucketLists: [TestData] = []
var interactor: HomeInteractorProtocol?
var router: HomeViewControllerRouter?
var views: HomeViewContollerViews?
override func viewDidLoad() {
super.viewDidLoad()
configurator()
views?.setupUI()
views?.setupConstraint()
views?.setupNavigationControllerElemenst()
}
private func configurator() {
let searchWorker: HomeViewControllerSearchWorkerProtocol = HomeViewControllerSearchWorker()
var presenter: HomePresenterProtocol = HomePresenter()
presenter.viewController = self
interactor = HomeInteractor()
interactor?.presenter = presenter
interactor?.searchWorker = searchWorker
router = HomeViewControllerRouter()
router?.source = self
views = HomeViewContollerViews()
views?.source = self
}
}
I have continued researching this question, and like assumption can say that the most convenient way this is to create a static func for configuration, I guess this is a classic approach, all info that is described in the question is the kind of variation to producing a "Configurator", an approach that I found in the article, was to complex for me, and static func it's classic. These thoughts are based on examining a few projects with VIP and VIPER architecture, if you have any other ideas or thoughts, I will be appreciated them. My project with VIP architecture and the project that I found with VIPER architecture will be attached, If you only have been begun to familiarize yourself with this architecture, these projects will be interesting for you
P.S. A little code that I didn't delete in my project also contained an example of Dependency injection/inversion, but I guess will be interesting for someone, these are the example by <Swift Book & Aleksey Chechil>
My code:
class HomeVCConfigurator {
class func configure() -> HomeViewController {
let viewController = HomeViewController()
let searchWorker: HomeViewControllerSearchWorkerProtocol = HomeViewControllerSearchWorker()
var presenter: HomePresenterProtocol = HomePresenter()
presenter.viewController = viewController
viewController.interactor = HomeInteractor()
viewController.interactor?.presenter = presenter
viewController.interactor?.searchWorker = searchWorker
viewController.router = HomeViewControllerRouter()
viewController.router?.source = viewController
viewController.views = HomeViewContollerViews()
viewController.views?.source = viewController
return viewController
}
}
Scene Delegate:
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 windowScene = (scene as? UIWindowScene) else { return }
let window = UIWindow(windowScene: windowScene)
let vc = HomeVCConfigurator.configure()
vc.id = 5
let navigatioContoller = UINavigationController(rootViewController: vc)
window.rootViewController = navigatioContoller// Your initial view controller.
window.makeKeyAndVisible()
self.window = window
}
Beginner Alert: I am doing Swift 11.6 with IOS 13.6. I am a beginner in Swift and I'm trying to check if my user has previously logged in or not. For this, I have used User Defaults, which seems to be working fine. The problem is when I initialize my view controller in Scenedelgate, it seems to not be working - I only get a black screen.
Another note is that I have a side menu on my home screen, so I'm trying to initialize the UINavigationController and then the HomeViewController inside of that
Here is my code for the scene delegate:
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
var storyboard: UIStoryboard?
var view: UIView?
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
guard let _ = (scene as? UIWindowScene) else {return}
let def = UserDefaults.standard
let is_authenticated = def.bool(forKey: "is_authenticated") // return false if not found or stored value
if is_authenticated {
// user logged in
self.view?.window?.rootViewController = UINavigationController(rootViewController: HomeViewController())
self.view?.window?.makeKeyAndVisible()
}
}
Edit 1:
Here's how my user defaults function looks like:
func saveLoggedState() {
let def = UserDefaults.standard
def.set(true, forKey: "is_authenticated") // save true flag to UserDefaults
def.synchronize()
}
Edit 2:
Here's how my simulator looks
Edit 3:
Here's how my view controller looks
Edit 4:
So here's the problem. I decided to try programmatically setting the home view controller to white, which obviously worked. So then what/s confusing me is that I can't see anything I put in my HomeViewController nor in my SideMenuNavigationController. I put a button and a label, but they're not showing up.
Thank you for any advice that you can give. I'm a complete newbie so I don't really know what I'm doing when it comes to this. Any help appreciated.
Thanks!
This line is wrong:
self.view?.window?.rootViewController = UINavigationController(rootViewController: HomeViewController())
The problem, in particular, is this phrase in your code:
HomeViewController()
That creates a new blank HomeViewController, not the instance you designed in the storyboard. To get that instance, tell the storyboard to instantiate it for you.
Try to add windowScene and present the root controller correctly like this:
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
guard let windowScene = (scene as? UIWindowScene) else {return}
let def = UserDefaults.standard
let is_authenticated = def.bool(forKey: "is_authenticated") // return false if not found or stored value
if is_authenticated {
// user logged in
window = UIWindow(windowScene: windowScene)
window?.makeKeyAndVisible()
let controller = UINavigationController(rootViewController: HomeViewController())
window?.rootViewController = controller
}
}
if your user defaults work correct the HomeViewController() show you...
I am porting an iPad app using Mac Catalyst. I am trying to open a View Controller in a new window.
If I were using strictly AppKit I could do something as described in this post. However, since I am using UIKit, there is no showWindow() method available.
This article states that this is possible by adding AppKit in a new bundle in the project (which I did), however it doesn't explain the specifics on how to actually present the new window. It reads...
Another thing you cannot quite do is spawn a new NSWindow with a UIKit view hierarchy. However, your UIKit code has the ability to spawn a new window scene, and your AppKit code has the ability to take the resulting NSWindow it's presented in and hijack it to do whatever you want with it, so in that sense you could spawn UIKit windows for auxiliary palettes and all kinds of other features.
Anyone know how to implement what is explained in this article?
TL;DR: How do I open a UIViewController as a new separate NSWindow with Mac Catalyst?
EDIT : ADDED INFO ON HOW TO HAVE ADDITIONAL DIFFERENT WINDOWS LIKE PANELS
In order to support multiple windows on the mac, all you need to do is to follow supporting multiple windows on the iPad.
You can find all the information you need in this WWDC session starting minute 22:28, but to sum it up what you need to do is to support the new Scene lifecycle model.
Start by editing your target and checking the support multiple window checkmark
Once you do that, click the configure option which should take you to your info.plist.
Make sure you have the proper entry for Application Scene Manifest
Create a new swift file called SceneDelegate.swift and just paste into it the following boilerplate code
import UIKit
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).
// Create the SwiftUI view that provides the window contents.
guard let _ = (scene as? UIWindowScene) else { return }
}
func sceneDidDisconnect(_ scene: UIScene) {
// Called as the scene is being released by the system.
// This occurs shortly after the scene enters the background, or when its session is discarded.
// Release any resources associated with this scene that can be re-created the next time the scene connects.
// The scene may re-connect later, as its session was not neccessarily discarded (see `application:didDiscardSceneSessions` instead).
}
func sceneDidBecomeActive(_ scene: UIScene) {
// Called when the scene has moved from an inactive state to an active state.
// Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive.
}
func sceneWillResignActive(_ scene: UIScene) {
// Called when the scene will move from an active state to an inactive state.
// This may occur due to temporary interruptions (ex. an incoming phone call).
}
func sceneWillEnterForeground(_ scene: UIScene) {
// Called as the scene transitions from the background to the foreground.
// Use this method to undo the changes made on entering the background.
}
func sceneDidEnterBackground(_ scene: UIScene) {
// Called as the scene transitions from the foreground to the background.
// Use this method to save data, release shared resources, and store enough scene-specific state information
// to restore the scene back to its current state.
}
}
And you're basically done. Run your app, and hit command + N to create as many new windows as you want.
If you want to create a new window in code you can use this:
#IBAction func newWindow(_ sender: Any) {
UIApplication.shared.requestSceneSessionActivation(nil, userActivity: nil, options: nil) { (error) in
//
}
}
And now we get to the big mystery of how to create additional windows
The key to this is to create multiple scene types in the app. You can do it in info.plist which I couldn't get to work properly or in the AppDelegate.
Lets change the function to create a new window to:
#IBAction func newWindow(_ sender: Any) {
var activity = NSUserActivity(activityType: "panel")
UIApplication.shared.requestSceneSessionActivation(nil, userActivity: activity, options: nil) { (error) in
}
}
Create a new storyboard for your new scene, create at least one viewcontroller and make sure to set in as the initalviewcontroller in the storyboard.
Lets add to the appdelegate the following function:
func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
if options.userActivities.first?.activityType == "panel" {
let configuration = UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
configuration.delegateClass = CustomSceneDelegate.self
configuration.storyboard = UIStoryboard(name: "CustomScene", bundle: Bundle.main)
return configuration
} else {
let configuration = UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
configuration.delegateClass = SceneDelegate.self
configuration.storyboard = UIStoryboard(name: "Main", bundle: Bundle.main)
return configuration
}
}
By setting the userActivity when requesting a scene we can know which scene to create and create the configuration for it accordingly. New Window from the menu or CMD+N will still create your default new window, but the new window button will now create the UI from your new storyboard.
And tada:
With SwiftUI you can do it like this (thanks to Ron Sebro):
1. Activate multiple window support:
2. Request a new Scene:
struct ContentView: View {
var body: some View {
VStack {
// Open window type 1
Button(action: {
UIApplication.shared.requestSceneSessionActivation(nil,
userActivity: NSUserActivity(activityType: "window1"),
options: nil,
errorHandler: nil)
}) {
Text("Open new window - Type 1")
}
// Open window type 2
Button(action: {
UIApplication.shared.requestSceneSessionActivation(nil,
userActivity: NSUserActivity(activityType: "window2"),
options: nil,
errorHandler: nil)
}) {
Text("Open new window - Type 2")
}
}
}
}
3. Create your new window views:
struct Window1: View {
var body: some View {
Text("Window1")
}
}
struct Window2: View {
var body: some View {
Text("Window2")
}
}
4. Change SceneDelegate.swift:
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
if let windowScene = scene as? UIWindowScene {
let window = UIWindow(windowScene: windowScene)
if connectionOptions.userActivities.first?.activityType == "window1" {
window.rootViewController = UIHostingController(rootView: Window1())
} else if connectionOptions.userActivities.first?.activityType == "window2" {
window.rootViewController = UIHostingController(rootView: Window2())
} else {
window.rootViewController = UIHostingController(rootView: ContentView())
}
self.window = window
window.makeKeyAndVisible()
}
}
How can an app access the XIB or storyboard used for its launch screen? The XIB is not in the main bundle (ex: NSBundle.mainBundle().pathsForResourcesOfType(nil, inDirectory: "")). This is especially unexpected since "Launch Screen.xib" is listed in the "Copy Bundle Resources" build phase but doesn't show ip in the bundle, so Xcode must be treating it specially.
If LaunchScreen is storyboard and not a xib, Use the following code.
let launchScreen = UIStoryboard(name: "LaunchScreen", bundle: nil).instantiateInitialViewController()
if let launchView = launchScreen?.view {
view.addSubview(launchView)
}
As Xib is not in the main bundle getting path returns nil, But you can get the XIB of the launch screen without the help of path using method
let launchScreenNib = UINib(nibName: "LaunchScreen", bundle: nil)
or
You can load get views from XIB as
// Swift
let objects = NSBundle.mainBundle().loadNibNamed("LaunchScreen", owner: self, options: nil)
let view = objects[0] as UIView
// Obj C
NSArray *objects = [[NSBundle mainBundle] loadNibNamed:#"LaunchScreen" owner:self options:nil];
UIView *view = [objects objectAtIndex:0];
Following up to my own question, it looks like toggling "Use as Launch Screen" off and on caused Xcode to build a NIB file for the launch screen.
After doing a complete clean (removed DerivedData) and deleting the app from the device, I toggled off "Use as Launch Screen" for the main view controller in LaunchScreen.xib. Running the app caused it to launch without a launch screen, but the build was now creating LaunchScreen.nib.
Did a complete clean again and deleted the app from the device. Toggled "Use as Launch Screen" back on and rebuilt. In the app bundle under the new DerivedData folder, LaunchScreen.nib was still there.
bundle.loadNibNamed(...) worked fine now.
Here's a Swift 5, SceneDelegate implementation
This implementation also detects the LaunchScreen's configured name from the info.plist file.
//
// SceneDelegate.swift
// Extended Launch Screen Example
//
// Created by Leslie Godwin on 2020/04/24.
//
import UIKit
class SceneDelegate: UIResponder, UIWindowSceneDelegate
{
var window: UIWindow?
var splashWindow: 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 }
}
func sceneDidDisconnect(_ scene: UIScene)
{
// Called as the scene is being released by the system.
// This occurs shortly after the scene enters the background, or when its session is discarded.
// Release any resources associated with this scene that can be re-created the next time the scene connects.
// The scene may re-connect later, as its session was not neccessarily discarded (see `application:didDiscardSceneSessions` instead).
}
func sceneDidBecomeActive(_ scene: UIScene)
{
// ⏳Extend Splash screen only if `splashWindow` is `nil` otherwise it is already shown.
//
if let launchStoryboardName = InfoPList.launchStoryboardName,
let windowScene = self.window?.windowScene
{
splashWindow = splashWindow ??
{
let window = UIWindow(windowScene: windowScene)
window.windowLevel = .statusBar
let storyboard = UIStoryboard(name: launchStoryboardName, bundle: nil)
window.rootViewController = storyboard.instantiateInitialViewController()
window.isHidden = false
// ⏳Wait for 5 seconds, then remove.
//
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(5))
{
UIView.animate(withDuration: -1, // system default
animations:
{
self.splashWindow?.alpha = 0
},
completion:
{ _ in
self.splashWindow?.isHidden = true
self.splashWindow = nil
}
)
}
return window
}()
}
// Called when the scene has moved from an inactive state to an active state.
// Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive.
}
func sceneWillResignActive(_ scene: UIScene) {
// Called when the scene will move from an active state to an inactive state.
// This may occur due to temporary interruptions (ex. an incoming phone call).
}
func sceneWillEnterForeground(_ scene: UIScene) {
// Called as the scene transitions from the background to the foreground.
// Use this method to undo the changes made on entering the background.
}
func sceneDidEnterBackground(_ scene: UIScene) {
// Called as the scene transitions from the foreground to the background.
// Use this method to save data, release shared resources, and store enough scene-specific state information
// to restore the scene back to its current state.
}
}
//
// PList Helper
//
struct InfoPList
{
private static func value(for name: String) -> String? { Bundle.main.object(forInfoDictionaryKey: name) as? String }
static var bundleIdentifier: String? { self.value(for: "CFBundleIdentifier") }
static var bundleDisplayName: String? { self.value(for: "CFBundleDisplayName") }
static var bundleShortVersionString: String? { self.value(for: "CFBundleShortVersionString") }
static var bundleVersion: String? { self.value(for: "CFBundleVersion") }
static var launchStoryboardName: String? { self.value(for: "UILaunchStoryboardName") }
static var mainStoryboardName: String? { self.value(for: "UIMainStoryboardFile") }
}