I am attempting to inject an object into a UIViewController from the AppDelegate but I am not sure that I am doing it correctly. Please can someone advise. I get an error when I start my application at the line of code marked 'THE ERROR OCCURS HERE'.
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Create ItemStore instance
let itemStoreObject = ItemStore()
let storyBoard: UIStoryboard = UIStoryboard.init(name: "Main", bundle: nil)
let testController = storyBoard.instantiateViewController(withIdentifier: "testTableController") as! TestTableViewController
testController.itemstore = itemStoreObject
return true
}
ItemStore:
import UIKit
class ItemStore {
var allItems = ["Thanh", "David", "Tommy", "Maria"]
}
TestTableViewController:
class TestTableViewController: UIViewController, UITableViewDelegate, UISearchBarDelegate, UITableViewDataSource{
#IBOutlet var myTableView: UITableView!
var itemstore: ItemStore!
override func viewDidLoad() {
super.viewDidLoad()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print("numberOfRowsSection ...")
return itemstore.allItems.count // THE ERROR OCCURS HERE.
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
print("cellForRow ...")
// Get a new or recycled cell
let cell = UITableViewCell(style: .value1, reuseIdentifier: "UITableViewCell")
let name = itemstore.allItems[indexPath.row]
cell.textLabel?.text = name
return cell
}
}
I get the following error message (marked in the line 'THE ERROR OCCURS HERE'):
fatal error: unexpectedly found nil while unwrapping an Optional value
(lldb)
You instantiate a view controller in AppDelegate, but the system will create another instance of that view controller class and hence display an instance of the class that has no itemstore property initialised.
You either have to make itemstore a type variable instead of an instance variable or if you only need this functionality for your root view controller, you have to instantiate the itemstore variable for your root view controller instance, which you know will be the one used by your navigation controller.
Related
I want to modalViewController's table cell data pass to the main View.
Using Protocol delegate. but
Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value
This error occur where i remark at the ModalViewController.
Using Protocol method is wrong?.
I use this protocol code another mini project textField's text data pass back and forward. In there it works well.
So i use it in cell data pass
What am i Miss ? Please help.
Bottom Link is my Simulator.
ModalTableViewDataPass
**MainViewController**
import UIKit
class ViewController: UIViewController, UITextFieldDelegate {
#IBOutlet weak var show: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
show.placeholder = "HAHA"
show.delegate = self
show.inputView = UIView()
show.addTarget(self, action: #selector(textFieldDidBeginEditing(_:)), for: .touchUpInside)
// Do any additional setup after loading the view.
}
#objc func textFieldDidBeginEditing(_ textField: UITextField) {
if textField == show {
moveToFindAdrVC()
print("주소 검색해라잉")
}
}
func moveToFindAdrVC() {
let modalVC = storyboard?.instantiateViewController(identifier: "ModalViewController") as? ModalViewController
self.present(modalVC!, animated: true, completion: nil)
}
}
extension ViewController: PassDataToVc {
func passData(str: String){
show.text = str
}
}
ModalTableView
import UIKit
class ModalViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var data = ["as","df","qw","er"]
var delegate: PassDataToVc!
#IBOutlet weak var table: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
table.delegate = self
table.dataSource = self
// Do any additional setup after loading the view.
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return data.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
cell.textLabel?.text = data[indexPath.row]
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let passData = data[indexPath.row]
let sb = storyboard?.instantiateViewController(identifier: "ViewController") as? ViewController
//sb?.show.text = passData
delegate.passData(str: passData)//Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value
self.dismiss(animated: true, completion: nil)
}
}
protocol PassDataToVc {
func passData(str: String)
}
delegate in ModalViewController is never set, hence it's nil.
But there is a more convenient solution, protocol/delegate is not needed at all.
Delete the code related to protocol/delegate
extension ViewController: PassDataToVc {
func passData(str: String){
show.text = str
}
}
var delegate: PassDataToVc!
and replace
delegate.passData(str: passData)
with
(presentingViewController as? ViewController)?.show.text = passData
My application has a common base class for all table controllers, and I'm experiencing a strange bug when I define a generic subclass of that table controller base class. The method numberOfSections(in:) never gets called if and only if my subclass is generic.
Below is the smallest reproduction I could make:
class BaseTableViewController: UIViewController {
let tableView: UITableView
init(style: UITableViewStyle) {
self.tableView = UITableView(frame: .zero, style: style)
super.init(nibName: nil, bundle: nil)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// MARK: - Overridden methods
override func viewDidLoad() {
super. viewDidLoad()
self.tableView.frame = self.view.bounds
self.tableView.delegate = self
self.tableView.dataSource = self
self.view.addSubview(self.tableView)
}
}
extension BaseTableViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
return UITableViewCell(style: .default, reuseIdentifier: nil)
}
}
extension BaseTableViewController: UITableViewDelegate {
}
Here's the very simple generic subclass:
class ViewController<X>: BaseTableViewController {
let data: X
init(data: X) {
self.data = data
super.init(style: .grouped)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func numberOfSections(in tableView: UITableView) -> Int {
// THIS IS NEVER CALLED!
print("called numberOfSections")
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print("called numberOfRows for section \(section)")
return 2
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
print("cellFor: (\(indexPath.section), \(indexPath.row))")
let cell = UITableViewCell(style: .default, reuseIdentifier: nil)
cell.textLabel!.text = "foo \(indexPath.row) \(String(describing: self.data))"
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("didSelect: (\(indexPath.section), \(indexPath.row))")
self.tableView.deselectRow(at: indexPath, animated: true)
}
}
If I create a simple app that does nothing but display ViewController:
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
self.window = UIWindow(frame: UIScreen.main.bounds)
let nav = UINavigationController(rootViewController: ViewController(data: 3))
self.window?.rootViewController = nav
self.window?.makeKeyAndVisible()
return true
}
}
The table draws correctly but numberOfSections(in:) is never called! As a result, the table only shows one section (presumably because, according to the docs, UITableView uses 1 for this value if the method isn't implemented).
However, if I remove the generic declaration from the class:
class ViewController: CustomTableViewController {
let data: Int
init(data: Int) {
....
}
// ...
}
then numberOfSections DOES get called!
This behavior doesn't make any sense to me. I can work around it by defining numberOfSections in CustomTableViewController and then having ViewController explicitly override that function, but that doesn't seem like the correct solution: I would have to do it for any method in UITableViewDataSource that has this problem.
This is a bug / shortcoming within the generic subsystem of swift, in conjunction with optional (and therefore: #objc) protocol functions.
Solution first
You'll have to specify #objc for all the optional protocol implementations in your subclass. If there is a naming difference between the Objective C selector and the swift function name, you'll also have to specify the Objective C selector name in parantheses like #objc (numberOfSectionsInTableView:)
#objc (numberOfSectionsInTableView:)
func numberOfSections(in tableView: UITableView) -> Int {
// this is now called!
print("called numberOfSections")
return 1
}
For non-generic subclasses, this already has been fixed in Swift 4, but obviously not for generic subclasses.
Reproduce
You can reproduce it quite easy in a playground:
import Foundation
#objc protocol DoItProtocol {
#objc optional func doIt()
}
class Base : NSObject, DoItProtocol {
func baseMethod() {
let theDoer = self as DoItProtocol
theDoer.doIt!() // Will crash if used by GenericSubclass<X>
}
}
class NormalSubclass : Base {
var value:Int
init(val:Int) {
self.value = val
}
func doIt() {
print("Doing \(value)")
}
}
class GenericSubclass<X> : Base {
var value:X
init(val:X) {
self.value = val
}
func doIt() {
print("Doing \(value)")
}
}
Now when we use it without generics, everything works find:
let normal = NormalSubclass(val:42)
normal.doIt() // Doing 42
normal.baseMethod() // Doing 42
When using a generic subclass, the baseMethod call crashes:
let generic = GenericSubclass(val:5)
generic.doIt() // Doing 5
generic.baseMethod() // error: Execution was interrupted, reason: signal SIGABRT.
Interestingly, the doIt selector could not be found in the GenericSubclass, although we just called it before:
2018-01-14 22:23:16.234745+0100 GenericTableViewControllerSubclass[13234:3471799] -[TtGC34GenericTableViewControllerSubclass15GenericSubclassSi doIt]: unrecognized selector sent to instance 0x60800001a8d0
2018-01-14 22:23:16.243702+0100 GenericTableViewControllerSubclass[13234:3471799] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[TtGC34GenericTableViewControllerSubclass15GenericSubclassSi doIt]: unrecognized selector sent to instance 0x60800001a8d0'
(error message taken from a "real" project)
So somehow the selector (e.g. Objective C method name) cannot be found.
Workaround: Add #objc to the subclass, as before. In this case we don't even need to specify a distinct method name, since the swift func name equals the Objective C selector name:
class GenericSubclass<X> : Base {
var value:X
init(val:X) {
self.value = val
}
#objc
func doIt() {
print("Doing \(value)")
}
}
let generic = GenericSubclass(val:5)
generic.doIt() // Doing 5
generic.baseMethod() // Doing 5
If you provide default implementations of the delegate methods (numberOfSections(in:), etc.) in your base class and override them in your subclasses where appropriate, they will be called:
extension BaseTableViewController: UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
...
class ViewController<X>: BaseTableViewController {
...
override func numberOfSections(in tableView: UITableView) -> Int {
// now this method gets called :)
print("called numberOfSections")
return 1
}
...
An alternative approach would be to develop your base class based on UITableViewController which already brings most of the things you need (a table view, delegate conformance, and default implementations of the delegate methods).
EDIT
As pointed out in the comments, the main point of my solution is of course what the OP explicitly didn't want to do, sorry for that... in my defense, it was a lengthy post ;) Still, until someone with a deeper understanding of Swift's type system comes around and sheds some light on the issue, I'm afraid that it still is the best thing you can do if you don't wand to fall back to UITableViewController.
I am new to swift (and new to StackOverflow) and learning to use nib files now as I did quite a bit of learning with storyboard already.
In storyboard we can instantiate segues and unwindSegues to fetch data from one VC to another. How can I do the same WITHOUT using storyboard?
I currently have a scrollview(MainViewController.xib) that loads a tableview(ContactView.xib) on it. I have an add button overlaying the tableview which is currently unresponsive.
I want to be able to load another view (AddContact.xib) when I click on the add button to add a new data to my tableview and then unwind to reflect the new data in the table, but WITHOUT using storyboard.
I can provide my code if needed, but am only looking for someone to point me in the correct direction. I know this can be achieved using Navigation Controllers but I can't seem to find a fitting tutorial for it (most are old and use Obj-C. I am unfamiliar with Obj-C).
I even looked at some similar questions like: this and this
but they failed to answer my question.
Any help is appreciated. Thank you.
My AppDelegate:
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
var mainVC: MainViewController? = nil
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
mainVC = MainViewController(nibName: "MainViewController", bundle: nil)
let frame = UIScreen.main.bounds
window = UIWindow(frame: frame)
window!.rootViewController = mainVC
window!.makeKeyAndVisible()
return true
}
MainViewController.swift:
class MainViewController: UIViewController {
#IBOutlet weak var scrollView: UIScrollView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let contactView: ContactView = ContactView(nibName: "ContactView", bundle: nil)
let dialerView: DialerView = DialerView(nibName: "DialerView", bundle: nil)
self.addChildViewController(dialerView)
self.scrollView.addSubview(dialerView.view)
dialerView.didMove(toParentViewController: self)
self.addChildViewController(contactView)
self.scrollView.addSubview(contactView.view)
contactView.didMove(toParentViewController: self)
var contactViewFrame : CGRect = contactView.view.frame
contactViewFrame.origin.x = self.view.frame.width
contactView.view.frame = contactViewFrame
self.scrollView.contentSize = CGSize(width: self.view.frame.width * 2, height: self.view.frame.height)
}
}
My ContactView.xib:
class ContactView: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var searchBar: UISearchBar!
var names = ["Rohan", "Rahul", "Sneh", "Paa", "Maa", "Vatsal", "Manmohan"]
var numbers = ["9830000001", "9830000002", "9830000003", "9830000004", "9830000005", "9830000006", "9830000007"]
let navVC: UINavigationController? = nil
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return names.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
tableView.rowHeight = CGFloat(60)
tableView.register(UINib(nibName: "ContactCell", bundle: nil), forCellReuseIdentifier: "Contact")
let cell = tableView.dequeueReusableCell(withIdentifier: "Contact", for: indexPath) as! ContactCell
cell.nameLabel.text = names[indexPath.row]
cell.numLabel.text = numbers[indexPath.row]
return cell
}
// Make delete-able in scroll view
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == UITableViewCellEditingStyle.delete {
names.remove(at: indexPath.row)
numbers.remove(at: indexPath.row)
tableView.reloadData()
}
}
My AddContact.xib only has the outlets defined.
Edit: I found a solution thanks to the commenters.
To Anyone wondering how I did it, I'll post the steps below:
Step 1: Create a protocol (I did so in my MainViewController.xib)
protocol NewContactDelegate {
func add_Contact (name: String, num: String)
}
Step 2: Instantiate a delegate for it in the senderVC. AddContact.xib was the senderVC for me
class AddContact: UIViewController {
var delegate: NewContactDelegate? = nil
#IBOutlet weak var nameField: UITextField!
#IBOutlet weak var numField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func saveContact(_ sender: UIButton) {
if delegate != nil {
if let name = nameField?.text, let num = numField?.text {
delegate?.add_Contact(name: name, num: num)
dismiss(animated: true, completion: nil)
}
}
}
Step 3: Make ReceivingVC conform to the delegate and accept data.
class ContactView: ....., NewContactDelegate {
func add_Contact(name: String, num: String) {
names.append(name)
numbers.append(num)
addressBook.reloadData() // This is my TableView outlet
}
}
**
NOTE: Remember to assert the delegate to a non-nil value in the RecievingVC to make sure #IBAction in SenderVC works as expected.
**
In Xcode 6, is it possible to use a .swift file as the 1st view the user will see when they open my app?
The file below is for a table view as I prefer to use swift instead of IB (too many views makes IB look messy).
import UIKit
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var tableView: UITableView!
let items = ["Hello 1", "Hello 2", "Hello 3"]
override func viewDidLoad() {
super.viewDidLoad()
self.view.frame = CGRect(x: 0, y: 0, width: 320, height: 480)
self.tableView = UITableView(frame:self.view.frame)
self.tableView!.dataSource = self
self.tableView!.registerClass(UITableViewCell.self, forCellReuseIdentifier: "cell")
self.view.addSubview(self.tableView)
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int{
return self.items.count;
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell{
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as UITableViewCell
cell.textLabel.text = "\(self.items[indexPath.row])"
return cell
}
}
var ctrl = ViewController()
According to Apple's View Controller Programming Guide, "When working with view controllers directly, you must write code that instantiates the view controller, configures it, and displays it." However it also states "You gain none of the benefits of storyboards, meaning you have to implement additional code to configure and display the new view controller." That being said if you still desire to do this, this is how you do it (in your app delegate):
var window: UIWindow?
var viewController: ViewController?
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
window = UIWindow(frame: UIScreen.mainScreen().bounds)
window?.backgroundColor = UIColor.whiteColor()
viewController = ViewController()
// Any additional setup
window?.rootViewController = viewController
window?.makeKeyAndVisible()
return true
}
I am attempting to pass data from one viewcontroller to another using a Tab Bar Controller. I have implemented a custom Tab Bar Controller class. Here is my code for this class:
class CustomTabBarControllerClass: UITabBarController, UITabBarControllerDelegate {
override func awakeFromNib() {
self.delegate = self;
}
func tabBarController(tabBarController: UITabBarController, didSelectViewController viewController: UIViewController) {
var logView = SecondViewController()
logView.log.append("Testing 123")
}
}
As you can see in my code, I am creating an instance of SecondViewController with the logView variable. In my SecondViewController class, I have a log array setup that will hold the value being passed from my CustomTabBarControllerClass class. Here is my code for my SecondViewController.
class SecondViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
var log = [String]()
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return log!.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("logCell", forIndexPath: indexPath) as UITableViewCell
cell.textLabel.text = log![indexPath.row]
return cell
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
println(log!) //fatal error: unexpectedly found nil while unwrapping an Optional value
}
}
In my viewDidLoad() function, I am attempting to print the log to the console with println(log!). When this code runs, I am presented with following error: fatal error: unexpectedly found nil while unwrapping an Optional value. So how would I go about passing data between the two viewcontrollers?
Update
The didSelectViewController function has been updated with the code below, however, I am still receiving the same error message.
func tabBarController(tabBarController: UITabBarController, didSelectViewController viewController: UIViewController) {
var logView = self.viewControllers![0] as SecondViewController
logView.log?.append("Testing 123")
}
Your tab bar controller already has an instance of SecondViewController, so you shouldn't be instantiating a new one. Use the tab bar controller's viewControllers property to access the one you want (presumably, from the name, the one at index 1).
class ViewController: UIViewController {
var log = [String]()
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
println(log)
}
}
In the tab bar controller,
class RDTabBarController: UITabBarController , UITabBarControllerDelegate{
override func viewDidLoad() {
super.viewDidLoad()
self.delegate = self
}
func tabBarController(tabBarController: UITabBarController, didSelectViewController viewController: UIViewController) {
var logView = self.viewControllers![1] as ViewController
logView.log.append("Testing 123")
}
}