I want to implemented dependency inversion In app delegate in my app as my rootController
is my UITabBarController
but when I want to try it there is an error
Fatal error: Unexpectedly found nil while unwrapping optional value
This is my code in my appDelagate
let exploreStore = ExploreStore()
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
let rootController = (window?.rootViewController as? UITabBarController)?.children.first as? ExploreViewController
// Inject Data
rootController?.exploreStore = exploreStore
return true
}
This is my explore class
class ExploreStore {
fileprivate var allItems = [ExploreItem]()
func fetch() {
for data in loadData() {
allItems.append(ExploreItem(dict: data))
}
}
func numberOfItem() -> Int {
return allItems.count
}
func explore(at index: IndexPath) -> ExploreItem {
return allItems[index.item]
}
fileprivate func loadData() -> [[String: AnyObject]] {
guard
let path = Bundle.main.path(forResource: "ExploreData", ofType: "plist"),
let items = NSArray(contentsOfFile: path)
else { return [[:]] }
return items as! [[String: AnyObject]]
}
}
This is my exlporeViewController
var exploreStore: ExploreStore!
override func viewDidLoad() {
super.viewDidLoad()
// This is where the error found nil
exploreStore.fetch()
}
Actually the code work if I don't use dependency inversion, like my explore view controller not use force unwrapping like this
var exploreStore = ExploreStore()
but since I want gain knowledge and learn S.O.L.I.D principle using dependency inversion, I want to stick with this principle.
If I understood your question correctly you want to initialise your class at AppDelegate
class and then you want to pass it to your UITabBarController
's first children
and for that you need to make some modifications into your didFinishLaunchingWithOptions
method like shown below:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
let storyBoard: UIStoryboard = UIStoryboard(name: "Main", bundle:nil)
let vc = storyBoard.instantiateViewController(withIdentifier: "tabBar")
self.window = UIWindow(frame: UIScreen.main.bounds)
self.window?.rootViewController = vc
let myTabBar = self.window?.rootViewController as! UITabBarController
let firstViewController = myTabBar.children.first as? FirstViewController
firstViewController?.exploreStore = exploreStore
self.window?.makeKeyAndVisible()
return true
}
Here I have made some modification's because I am retrieving Info.plist
from Bundle
and your ExploreStore
will look like:
class ExploreStore {
var allItems = [ExploreItem]()
func fetch() {
if let dataObjectFromPlist = loadData() {
allItems.append(ExploreItem(dict: dataObjectFromPlist))
}
}
func numberOfItem() -> Int {
return allItems.count
}
func explore(at index: IndexPath) -> ExploreItem {
return allItems[index.item]
}
fileprivate func loadData() -> [String: AnyObject]? {
var resourceFileDictionary: [String: AnyObject]?
if let path = Bundle.main.path(forResource: "Info", ofType: "plist") {
if let dict = NSDictionary(contentsOfFile: path) as? Dictionary<String, AnyObject> {
resourceFileDictionary = dict
}
}
return resourceFileDictionary
}
}
Then in my FirstViewController
I can fetch the data from ExploreStore
class with
exploreStore.fetch()
and my code for that UIViewController
is
class FirstViewController: UIViewController {
var exploreStore: ExploreStore!
override func viewDidLoad() {
super.viewDidLoad()
exploreStore.fetch()
print(exploreStore.allItems[0].data)
}
}
Here exploreStore.allItems[0].data
will print my whole info.plist
file.
You can try it by your self with THIS demo project and check if that's the correct behaviour.
EDIT
You need to update didFinishLaunchingWithOptions
method like:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
setupDefaultColors()
let exploreStoryBoard = UIStoryboard(name: "Explore", bundle:nil)
let navigationController = exploreStoryBoard.instantiateViewController(withIdentifier: "ExploreViewControllerNavigation") as! UINavigationController
if let exploreViewController = navigationController.children.first as? ExploreViewController {
exploreViewController.store = ExploreStore()
self.window = UIWindow(frame: UIScreen.main.bounds)
self.window?.rootViewController = exploreViewController
self.window?.makeKeyAndVisible()
}
return true
}
And you also need to update ExploreStore
class as shown below:
class ExploreStore {
var allItems = [ExploreItem]()
func fetch() {
if let dataObjectFromPlist = loadData() {
allItems.append(ExploreItem(dict: dataObjectFromPlist))
}
}
func numberOfItem() -> Int {
return allItems.count
}
func explore(at index: IndexPath) -> ExploreItem {
return allItems[index.item]
}
fileprivate func loadData() -> [String: AnyObject]? {
var resourceFileDictionary: [String: AnyObject]?
if let path = Bundle.main.path(forResource: "ExploreData", ofType: "plist") {
if let dict = NSDictionary(contentsOfFile: path) as? Dictionary<String, AnyObject> {
resourceFileDictionary = dict
}
}
return resourceFileDictionary
}
}
Because from plist
you will get Dictionary<String, AnyObject>
type object.
And you will still not get data from plist
file because its added into subfolder. So you need find correct path first for your plist
.
You also needs to assign respective identifiers to navigation controller and tab bar controller.
Here is your demo project.