I am a self-taught newcomer to iOS programming and have worked through one beginner textbook. I'm trying to publish my first mobile application so I am going back through and cleaning up my code. The textbook I used stressed 'Dependency Injection' but I've struggled adapting their simple example to a more complex application.
The app operates as a shell and retrieves/parses txt files to populate. I successfully connected my model, which retrieves/parses the data, and the TableViewController that needs populated using the following code:
MyTableViewController {
var data: Data!
}
AppDelegate {
var window: UIWindow?
let data = Data()
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
let navCon = window!.rootViewController as! MyNavigationController
let tableVC = navCon.topViewController as! MyTableViewController
tableVC.data = data
return true
}
I then embedded that NavigationController within a TabBarController because the app will have other tabs. I tried the same process of setting the rootViewController and then drilling down until I could set my data variable, but I can't find the correct way to layer the ViewControllers and keep getting the error;
'fatal error: unexpectedly found nil while unwrapping an Optional value'
I tried two different approaches:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
let tabBarCon = window!.rootViewController as! MyTabBarController
let navCon = tabBarCon.presentedViewController as! MyNavigationController
let tableVC = navCon.topViewController as! MyTableViewController
tableVC.data = data
return true
}
and
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
let tabBarCon = window!.rootViewController as! MyTabBarController
let navCon = MyNavigationController()
tabBarCon.viewControllers = [navCon]
let tableVC = navCon.topViewController as! MyTableViewController
tableVC.data = data
return true
}
Is there a solution to correct this error or am I going about this process wrong? Again, I have a file that pulls in a txt file and then creates a dictionary. I need a separate TableViewController to be able to access that dictionary to populate itself, but I want to achieve this in the most efficient and apple promoted manner, not all in the same file as I did in my first design.
Thanks for the help!
For Dependency Injection I do recommend you use Typhoon. From mine experience it's one of the best tool. This will help you to achive app asembly like:
/*
* This is the definition for our AppDelegate. Typhoon will inject the specified properties
* at application startup.
*/
public dynamic func appDelegate() -> AnyObject {
return TyphoonDefinition.withClass(AppDelegate.self) {
(definition) in
definition.injectProperty("cityDao", with: self.coreComponents.cityDao())
definition.injectProperty("rootViewController", with: self.rootViewController())
}
}
I found the solution through this thread;
Aassigning a value to a view controller from AppDelegate.swift
The result can be achieved by reconfiguring the second solution I attempted;
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
let tabBarCon = window!.rootViewController as! MyTabBarController
let tabBarRootVCs: Array = tabBarCon.viewControllers!
let navCon = tabBarRootVCs[0] as! MyNavigationController
let tableVC = navCon.topViewController as! MyTableViewController
tableVC.data = data
return true
}
Related
I'm working through this tutorial from apple. I am getting stuck on the shown below, I keep getting an error, Use of unresolved identifier 'window'
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
let splitViewController = window!.rootViewController as! UISplitViewController
let navigationController = splitViewController.viewControllers[splitViewController.viewControllers.count-1] as! UINavigationController
navigationController.topViewController!.navigationItem.leftBarButtonItem = splitViewController.displayModeButtonItem
// Add a translucent background to the primary view controller.
splitViewController.primaryBackgroundStyle = .sidebar
splitViewController.delegate = self
return true
}
any suggestions?
It is simple. Catalyst apps are by default Window Scene-based. You don’t have a window here. Apple tutorials are outdated.
The solution is to create your own window and fill it in didFinish...
The more correct way is to add your code to delegate of scenes.
I am a new iOS programmer and I am trying to make an application that involves the following flow:
-> UITabBar -> UINavController -> UITableViewController.
Initially the program was working when I have the following flow:
-> UINavController -> UITableViewController
But when I added the UITabBar (with the Embed In method)the I had two issues:
1) Casting the initial view from UITableView to UITabBarView
2) The data which have been restored from Archives of the phone are not loading in the TableView.
I managed to fix the casting issue with the UIStoryboard IDs, but I am not sure if this way I created the second problem of not passing data to the UITableView correctly.
The Casting problem has taking place at the appDelegate code. Here is the original code I had before incorporating the UITabBarView:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
let navController = window!.rootViewController as! UINavigationController
let SLBprojectController = navController.topViewController as! GR8TableView
SLBprojectController.SLBprojectDB = thisSLBprojectDB
return true
}
The problem with the above code was that it told me that it could not cast a TableViewController (GR8TableView) into a UITabBarView. I have managed to fix this by searching in the StackOverflow forums by making the following:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
let mainStoryboardIpad : UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let tabBarIntial : UITabBarController = mainStoryboardIpad.instantiateViewController(withIdentifier: "TabBar") as! UITabBarController
let navigationController:UINavigationController = mainStoryboardIpad.instantiateViewController(withIdentifier: "navController") as! UINavigationController
let navigationController1:UIViewController = mainStoryboardIpad.instantiateViewController(withIdentifier: "ViewController1")
let SLBprojectController = navigationController.topViewController as! GR8TableView
SLBprojectController.SLBprojectDB = thisSLBprojectDB
tabBarIntial.viewControllers = [navigationController, navigationController1]
tabBarIntial.selectedIndex = 0
return true
}
But after I fixed the "casting" issue then I am getting problems loading the data in the TableView. Not sure if this problem is related to the way I fixed the casting issue.
Any assistance would be much much appreciated!
The simplest answer would be to go back to logic similar to what worked for you, but with a slightly more complex relationship. For example, I created an app with a tab bar controller, a navigation controller in the first tab, and a FirstViewController embedded in the navigation controller.
Since I don't have a database, I gave the FirstViewController a single instance variable called running.
The following code finds the controller and sets the variable:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
if let tabController = window!.rootViewController as? UITabBarController {
if let navController = tabController.viewControllers![0] as? UINavigationController {
if let firstController = navController.topViewController as? FirstViewController {
firstController.running = true
} else {
print("Unexpected controller in navigation hierarchy: \(String(describing: navController.topViewController))")
}
} else {
print("Unexpected contained controller: \(String(describing: tabController.viewControllers![0]))")
}
} else {
print("Unexpected root controller: \(String(describing: window!.rootViewController))")
}
return true
}
Note that I'm not creating any new controllers. That's all done by default storyboard loading.
I believe you should be able to adapt this to your own variable and class names.
I am having an issue where I am unable to access a function in my viewcontroller.swft file when lunching from a killed app in didFinishLaunchingWithOptions.
I am trying to access the function with viewController?.loadRequestnotificationwaiting(for: url as! String) from AppDelegate which I pass data to the viewcontroller where I can do some stuff. But when I place an alert in the function in the viewcontroller loadRequestnotificationwaiting. The data is not being passed.
Now I use this same method in other areas to pass data to the viewcontroller from the appdelegate and they work fine. It seems to not work when using it in didFinishLaunchingWithOptions
Is the viewcontroller not available yet when trying to access it from didFinishLaunchingWithOptions?
AppDelegate.swift
class AppDelegate : UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate {
weak var viewController : ViewController?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
UNUserNotificationCenter.current().delegate = self
ConnectionManager.sharedInstance.observeReachability()
// Override point for customization after application launch.
FirebaseApp.configure()
registerForPushNotifications()
// opened from a push notification when the app is closed
if let userInfo = launchOptions?[.remoteNotification] as? [String : AnyObject] {
if let object = userInfo["aps"] {
let url = object["url"]
viewController?.loadRequestnotificationwaiting(for: url as! String)
}
}
return true
}
}
ViewController.swift
class ViewController: UIViewController, WKNavigationDelegate {
func loadRequestnotificationwaiting(for notification_url_wait : String) {
notification_url_final_wait = notification_url_wait
let url = URL(string: notification_url_final_wait!)
let URLrequest = URLRequest(url: url!)
self.webView.load(URLrequest)
}
}
You are relying on your AppDelegate's viewController property, but you are setting this property in your viewDidLoad method of your view controller; This is fine when a notification is received when your app is already running, since the viewController property is already set.
When a notification causes your application to be launched, the viewController property is not set and the function isn't called.
Assuming that the view controller you need is the initial view controller from your storyboard, you can get the root view controller from your app delegate's window property;
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
UNUserNotificationCenter.current().delegate = self
ConnectionManager.sharedInstance.observeReachability()
// Override point for customization after application launch.
FirebaseApp.configure()
registerForPushNotifications()
// opened from a push notification when the app is closed
if let userInfo = launchOptions?[.remoteNotification] as? [String : AnyObject] {
if let object = userInfo["aps"],
let url = object["url"] as? String,
let viewController = self.window?.rootViewController as? ViewController {
viewController.loadRequestnotificationwaiting(for: url)
}
}
return true
}
if your view controller is root, you can summon it by call UIApplication.shared.keyWindow!.rootViewController! or overwrite it with your viewController
you can raise a notification using -[NotificationCenter postNotificationName:object:userInfo:] and observe this notification name in View Controller
tried to pass data between view controllers,but keeps getting the error:
Could not cast value of type 'UIViewController' (0x10b4e4ca8) to 'StockList.AllListsTableViewController' (0x106d963f0).
I have checked to make sure that I set the classes for the view controllers but it's still not working.Here is my code:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
let navigationController = window!.rootViewController as! UITabBarController
let controller = navigationController.viewControllers![0] as! AllListsTableViewController
controller.dataModel = dataModel
return true
}
Here is my storyboard:
Thanks in advance
First make sure to set class name to the VC in IB from Identity Inspector ( AllListsTableViewController )
Second use
let tab = window!.rootViewController as! UITabBarController
let navigation = tab.viewControllers![0] as! UINavigationController
let controller = navigation.viewControllers![0] as! AllListsTableViewController
controller.dataModel = dataModel
I have searched much but with no success. I am attempting to access the TableView of the splitViewController from AppDelegate.swift. How do I do this?
This is the standard code that Apple supplies when starting a new project for a Split View Controller:
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// Override point for customization after application launch.
let splitViewController = self.window!.rootViewController as! UISplitViewController
let navigationController = splitViewController.viewControllers[splitViewController.viewControllers.count-1] as! UINavigationController
navigationController.topViewController!.navigationItem.leftBarButtonItem = splitViewController.displayModeButtonItem()
splitViewController.delegate = self
return true
}
Or perhaps it is not possible? Which I cannot believe :-)