Is it possible to create an application that loads its initial view from a storyboard in a framework?
Let's say I have a framework, titled MyCoolFramework. This framework has a storyboard called Home.storyboard that is the initial view of an application.
Is there a way to load it into the application at launch?
My AppDelegate looks like this:
import UIKit
import MyCoolFramework
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
self.window = UIWindow(frame: UIScreen.mainScreen().bounds)
let bundle = NSBundle(identifier: "com.myprojects.MyCoolFramework")
let homeStoryboard = UIStoryboard(name: "Home", bundle: bundle)
let initialView = homeStoryboard.instantiateInitialViewController()
self.window?.rootViewController = initialView
self.window?.makeKeyAndVisible()
return true
}
}
It seems that at this point in the application the application does not have access to the framework or its contents. Anyone know of the proper way of doing this? This is the companion test-app for a framework I'm developing.
I have deleted the main storyboard directive in the info.plist
I think its likely to be the path to your framework isn't correct.
You can try this (sorry Objective-C I'm sure you can convert to Swift)
[NSBundle bundleForClass: a name of your class in the framework]
Or if that doesn't work, add a method to your framework which returns the path of its own bundle and the code above as the implementation of that method.
NSBundle:mainBundle and NSBundle:bundleForClass will return different paths when called from within a framework (though if called from a library they will be the same). So experiment until you get the correct path for your storyboard.
Related
I am developing an app with a single view and some labels. The app fails at the viewdidload() func and does not proceed further. The code is listed below.
override func viewDidLoad() {
super.viewDidLoad()
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyHundredMeters
locationManager.requestWhenInUseAuthorization()
locationManager.startUpdatingLocation()
}
The error is '[framework] CUIThemeStore: No theme registered with id=0'
I am not using any themes or external frameworks in my code. I am running Xcode 10 on MacOS Mojave. I checked the setting in Xcode to see if it is referring to any external frameworks and I could not find any. Any help is very much appreciated.
I also got the same issue.
Simply move your image(s) to Assets.xcassets folder.
I had the same issue. It took place because I messed up with window property in the AppDelegate class.
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
let mainScreen = TaskNumber1()
mainScreen.title = "Task nunber 1"
let navigationController = UINavigationController(rootViewController: mainScreen)
let window = UIWindow(frame: UIScreen.main.bounds)
window.rootViewController = navigationController
window.makeKeyAndVisible()
return true
}
I've just created new window property and there was not any warnings from Xcode that I tried to create the constant with the same name as one was created already. But I had just black screen and the same error as you. So in my case solution was simple. To replace that window code snippet with the code below:
window = UIWindow(frame: UIScreen.main.bounds)
window?.rootViewController = navigationController
window?.makeKeyAndVisible()
Now I am simply not creating new window variable.
In my case, there was nothing to do with themes. So the reason of you getting this error is could be in AppDelegate class as well. Hope it will help somebody
I have UIViewController in Main.storyboard and set it's identifier to settings. I reference Main.storyboard and get the view controller in AppDelegate and then set the value of a variable which is located in view controller but it returns nil.
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
let mainStoryboardIpad : UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
if let settingViewController = mainStoryboardIpad.instantiateViewController(withIdentifier: "settings") as UIViewController? {
let settingVC = settingViewController as! SettingViewController
settingVC.moc = persistendContainer.viewContext
}
return true
}
And I have following variable in SettingViewController:
var moc: NSManagedObjectContext!
I confirmed that I am able to get Main.storyboard, SettingViewController and persistentContainer.viewContext has value.
You are instantiating a new settings UIViewController successfully, but then nothing happens with it after that. Later on your app will create a Settings viewController through the storyboard, but that will be a different one.
The better way to do this is to create an accessible place for your viewController to access the moc variable rather than trying to pass it in in advance. This is usually done for you if you let Xcode create the project with CoreData set up. You can say (UIApplication.shared.delegate as! AppDelegate).managedObjectContext from any viewController in your app and get a pointer to that moc.
I got some logic I want to do in my LaunchScreen, and if the check is alright, I want to segue to a viewController and if not I want to segue to another, is that possible?
I got some logic I want to do in my LaunchScreen
Now that I understand the question, I can answer it: don't. Do your time-consuming logic later. Your job is to launch fast. You need to get out of applicationDidFinishLaunchingWithOptions, get out of viewDidLoad, and launch.
What you show at that point is up to you; if you have time-consuming stuff (in your case, it sounds like you're networking or doing something else that takes time while you load up the data source for a table) and you want to show a special view controller that covers the time with a spinning activity view or something, fine. But during actual launch is not the time to do that.
No you cant code for launchScreen.Storyboard, The reason why :- when your launchScreen.storyboard shows the app is still loading.
Simply put: You cant access your app when it is displaying launchScreen.storyboard, all you can do is make a UI/UX for that not execute any code for it.
Alternative:- Make a viewController that appears as a first viewController check your logic there and do things from there accordingly!
Reference : - https://stackoverflow.com/a/27642160/6297658
Your LaunchScreen is shown while your app is loading. Go to your AppDelgate:
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: NSDictionary?) -> Bool {
window.rootViewController = //your root view controller that you have figured out with logic
return true
}
Run your check in didFinishLaunchingWithOptions() and use that to "jump" directly to a specific vc. Here's an example using userDefaults, but of course you can replace that with whatever check you're running.
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// Do some logic
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let welcomeVC = storyboard.instantiateViewControllerWithIdentifier("WelcomeNavController")
self.window = UIWindow(frame: UIScreen.mainScreen().bounds)
self.window?.rootViewController = welcomeVC
self.window?.makeKeyAndVisible()
}
}
Add this function into the AppDelegate:
func initialVC(storyboardID: String) {
let mainStoryboard : UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let initialViewController : UIViewController = mainStoryboard.instantiateViewControllerWithIdentifier("\(storyboardID)") as UIViewController
self.window?.makeKeyAndVisible()
if storyboardID == "tabBarVC" {
self.window = UIWindow(frame: UIScreen.mainScreen().bounds)
self.window?.rootViewController = initialViewController
} else {
let navController = UINavigationController(rootViewController: initialViewController)
self.window = UIWindow(frame: UIScreen.mainScreen().bounds)
self.window?.rootViewController = navController
}
}
In the didFinishLaunchingWithOptions method inside of the AppDelegate, you can add this:
if currentUser != nil {
initialVC("tabBarVC")
} else {
initialVC("loginVC")
}
You can see in my example, I am either loading the main app VC or the Login VC depending on if the user is logged in. In your case, You can use an if - else statement and do the logic within the initialVC function.
Note: When I call for the loginVC to be loaded, I have to load the navigationController because the loginVC is embedded in a navigationController. For the tabBarVC, I don't embed the navController because it isn't needed.
I have this swift code:
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject : AnyObject]?) -> Bool {
...
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let controller = storyboard.instantiateInitialViewController()!
if (controller is InlineMainViewController ){
mainViewController = controller as! InlineMainViewController
}
window?.rootViewController = controller
window?.makeKeyAndVisible()
}
when my viewController has this attribute:
#objc(ViewController) // match the ObjC symbol name inside Storyboard
public class InlineMainViewController: UIViewController,
(controller is InlineMainViewController ) returns false
and when it doesn't have this attribute:
public class InlineMainViewController: UIViewController,
(controller is InlineMainViewController ) returns true
My Main.sotryboard is connected to InlineMainViewController
I took #objc(ViewController) from Google analytics github example
From Swift Type Compatibility
You can use the #objc attribute if your Swift class doesn’t inherit
from an Objective-C class, or if you want to change the name of a
symbol in your interface as it’s exposed to Objective-C code.
This code:
#objc(ViewController)
Will cause the class following this attribute to have the name in the parentheses when it is imported into Objective-C code through the generated header, as discussed in Importing Swift into Objective-C
To import Swift code into Objective-C from the same target
Import the Swift code from that target into any Objective-C .m file
within that target using this syntax and substituting the appropriate
name:
#import "ProductModuleName-Swift.h"
I have an universal app I am writing and I initially wrote it using two storyboard files. I had code in the App Delegate didFinishLaunchingWithOptions routine to decide which storyboard to load and away it would go. I have since realised that that was a silly thing to do because I had duplicate code, so I have deleted one storyboard and made the other universal. I have fixed up the VCs to point at the right class and everything. However, my app now refuses to launch. When I run it in the sim, it gives me the error
The app delegate must implement the window property if it wants to use a main storyboard file.
.
Here is my code in App Delegate:
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// Override point for customization after application launch.
var deviceIdiom = UIDevice.currentDevice().userInterfaceIdiom
if deviceIdiom == UIUserInterfaceIdiom.Phone {
strDevice = "iPhone"
} else if deviceIdiom == UIUserInterfaceIdiom.Pad {
strDevice = "iPad"
} else {
strDevice = "Unknown"
}
return true
}
What am I doing wrong?
(Questions I've already looked at but didn't help):
Unique Issue displaying first storyboard scene in Xcode
The app delegate must implement the window property if it wants to use a main storyboard file
Managing two storyboards in App Delegate
How to manually set which storyboard view to show in app delegate
Swift - Load storyboard programatically
Your app delegate needs to start like such:
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
//...
}