Error while assigning self to tableview datasource - swift

This is the error Xcode outputs
Unexpectedly found nil while unwrapping an Optional value
I have a viewcontroller that has a tableview and a few buttons; the buttons allow me to insert or remove data. It seems that when I click on Add (which brings up a new viewcontroller via segue as a sheet) the app crashes with the error above. Clicking on remove doesn't have this affect. So it has to do with something regarding the new viewcontroller as a guess. The console doesn't go further into the error other than printing out (lldb)
Here's my code
override func viewDidLoad() {
super.viewDidLoad()
alarmTableView.dataSource = self //error occurs here
alarmTableView.delegate = self //if i remove the above line if will occur here too.
}
My Viewcontroller which the above viewDidLoad func is embedded lists the protocols I need
class ViewController: NSViewController, NSTableViewDelegate, NSTableViewDataSource {
#IBOutlet weak var addAlarm: NSButton!
#IBOutlet weak var resetDataButton: NSButton!
#IBOutlet var alarmArrayController: NSArrayController!
#IBOutlet weak var alarmTableView: NSTableView!
#IBOutlet weak var deleteAll: NSButton!
#objc let moc: NSManagedObjectContext
required init?(coder: NSCoder) {
self.moc = CoreDataHandler.getContext()
super.init(coder: coder)
}
override func prepare(for segue: NSStoryboardSegue, sender: Any?) {
let destinationController = segue.destinationController as! AddAlarmViewController
//pass data to next controller here
}
#IBAction func deleteAllAction(_ sender: Any) {
if (alarmTableView.selectedRow >= 0) {
if (CoreDataHandler.deleteAllObjectsInEntity(entityName: "Alarm")) {
//remove from nsarray controller
for object in alarmArrayController.arrangedObjects as! [Alarm] {
print(object)
alarmArrayController.removeObject(object)
}
alarmTableView.reloadData()
}
}
else {
printInfo(str: "There are no alarms to delete")
}
}
/* Response to the remove alarm button - It removes a selected alarm object from the table */
#IBAction func resetDataAction(_ sender: Any) {
if (alarmTableView.selectedRow >= 0) {
let selectedAlarm = self.alarmArrayController.selectedObjects.first as! Alarm
alarmArrayController.remove(atArrangedObjectIndex: alarmTableView.selectedRow)
CoreDataHandler.deleteObjectInEntity(entityName: "Alarm", obj: selectedAlarm)
alarmTableView.reloadData()
}
else {
//will need a warning or play a sound.
printInfo(str: "Please select an alarm")
}
}
override func viewDidLoad() {
super.viewDidLoad()
printInfo(str: "viewdidload")
print(alarmTableView)
if (alarmTableView != nil) {
printInfo(str: "AlarmTableView Is initialised")
alarmTableView.dataSource = self
alarmTableView.delegate = self
}
else {
printInfo(str: "AlarmTableView is not initialised")
}
}
override var representedObject: Any? {
didSet {
// Update the view, if already loaded.
}
}
func printInfo(str: String) {
print("ViewController: \(str)")
}
func tableView(_ tableView: NSTableView, heightOfRow row: Int) -> CGFloat {
return 100.0
}
}
class AddAlarmViewController: ViewController {
#IBOutlet weak var closeButton: NSButton!
override func viewDidLoad() {
super.viewDidLoad()
// Do view setup here.
printClassInfo(str: "viewDidLoad")
CoreDataHandler.saveTestData()
}
#IBAction func closeButtonAction(_ sender: Any) {
self.dismissViewController(self)
}
func printClassInfo(str: String) {
print("AddAlarmViewController \(str)")
}
}
If I remove the lines where the error occurs the app run fine. But I want to override the delegate and datasource and use the functions to further customise the table. I'm also using Cocoa Bindings.
Why am I getting this error?
Update
I haven't solved it yet, but i placed a couple of print statements in my viewDidLoad function. It seems that when the app is first loaded, the table view is initialised. But after when I clicked on the Add button, the table view is then set to nil for some odd reason, as if another table view has been initialised. However the data is still visible

Problem:
class AddAlarmViewController: ViewController {
//...
override func viewDidLoad() {
super.viewDidLoad()
//...
}
}
Your AddAlarmViewController is a subclass of ViewController instead of NSViewController.
In AddAlarmViewController's viewDidLoad you call super.viewDidLoad() which basically calls ViewController's viewDidLoad.
But... in this case ViewController is a new instance as the super class of AddAlarmViewController and none of it's properties are initialized.
Whatever it be, it's probably not what you want.
Solution:
class AddAlarmViewController: NSViewController {
//... rest as it is
}

Related

How can I use a variable defined within a function in the main View Controller in another View Controller?

I'm developing a simple game (my first iOS app!) and am trying to link a variable between two View Controllers. In the first view controller, I have a textfield where the user can type in any number they choose. In the second View Controller, I would like users to be able to generate any number between 1 and the number they entered by pressing a button and be able to keep doing so. However, I am not able to use the "upperBound" variable holding the user-entered value in ViewController2.
I've tried using prepare for segue but it's not working, and I've snooped around stackoverflow and tried a couple of methods without quite knowing what I'm doing to no avail.
(UPDATED) ViewController:
class ViewController: UIViewController, UITextFieldDelegate {
//MARK: Properties
#IBOutlet weak var numberOfPages: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
// Handle the text field’s user input through delegate callbacks.
numberOfPages.delegate = self
}
//MARK: UITextFieldDelegate
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
// Hide the keyboard.
textField.resignFirstResponder()
return true
}
func textFieldDidEndEditing(_ textField: UITextField) {
//Save number entered and then randomly select a number within bounds
}
//MARK: Actions
var upperBound: Int?
#IBAction func setUpperBound(_ sender: UIButton) {
upperBound = Int(numberOfPages.text!)
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!) {
// Create a variable that you want to send
var newUpperBound = Int(upperBound!)
// Create a new variable to store the instance of ViewController2
let destinationVC = segue.destination as! ViewController2
destinationVC.upperBound = newUpperBound
}
}
(UPDATED) ViewController2:
class ViewController2: UIViewController, UITextFieldDelegate {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Get the new view controller using segue.destination.
// Pass the selected object to the new view controller.
}
*/
//Mark: Actions
#IBAction func roller(_ sender: UIButton) {
//Generate random number
let randomNumber = Int.random(in: 0 ..< upperBound)
}
var upperBound: Int?
}
With this code, I'm getting an error on line 34 of ViewController2 that reads "Use of unresolved identifier upperBound". Additionally, there is an issue on line 40 of ViewController that reads "immutable value upperBound was never used". I would expect to be able to generate a random value between 1 and the entered number so that I can keep working and add more features to my app (like printing these random values etc)
ViewController
import UIKit
class ViewController: UIViewController, UITextFieldDelegate {
#IBOutlet weak var numberOfPages: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
numberOfPages.delegate = self
}
//MARK: UITextFieldDelegate
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
// Hide the keyboard.
textField.resignFirstResponder()
return true
}
func textFieldDidEndEditing(_ textField: UITextField) {
//Save number entered and then randomly select a number within bounds
}
//MARK: Actions
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if numberOfPages.text == ""{
print("Please enter number")
return
}
let upperBound: Int? = Int(numberOfPages.text ?? "0")
if upperBound != 0{
if segue.identifier == "mySegue"{
let vc = segue.destination as! ViewController2
vc.upperBound = upperBound
}
}
}
}
ViewController2
import UIKit
class ViewController2: UIViewController {
#IBOutlet weak var lbl_UpperBound: UILabel!
#IBOutlet weak var btn_Generate: UIButton!
#IBOutlet weak var lbl_Random: UILabel!
var upperBound: Int?
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
lbl_UpperBound.text = "Upper Bound - \(upperBound ?? 0)"
btn_Generate.addTarget(self, action: #selector(roller), for: .touchUpInside)
lbl_Random.text = ""
}
#objc func roller(_ sender: UIButton) {
//Generate random number
let randomNumber = Int.random(in: 0 ..< (upperBound ?? 1))
lbl_Random.text = "\(randomNumber)"
}
}
Also Don't forget to name the Segue

Having issues passing a delegate from NSWindowController subclass to my ViewController

I'm having issues with passing a custom protocol (MainWindowControllerProtocol) to the EditorViewController from the MainWindowController, which is subclass of NSWindowController. Please help.
EditorViewController.swift
extension EditorViewController: MainWindowControllerProtocol {
func didOpenFile() {
print("TODO: Open File") // never called, but it should be
}
}
class EditorViewController: NSViewController {
// - IBOutlets
#IBOutlet weak var treeOutlineView: NSOutlineView!
#IBOutlet var codeTextView: NSTextView!
#IBOutlet weak var counterTextField: NSTextField!
#IBOutlet weak var languageTextField: NSTextField!
//public var editor = Editor()
//var rootNode: Node?
override func viewDidLoad() {
super.viewDidLoad()
// Do view setup here.
//rootNode = Path(Path.userDownloads).node
// Issue is here
if let windowController = NSApplication.shared.mainWindow?.windowController as? MainWindowController {
windowController.delegate = self
}
else {
print("Doesnt work") // prints this
}
//treeOutlineView.reloadData()
}
}
MainWindowController
public protocol MainWindowControllerProtocol {
func didOpenFile()
}
class MainWindowController: NSWindowController {
var delegate: MainWindowControllerProtocol?
override func windowDidLoad() {
super.windowDidLoad()
// Implement this method to handle any initialization after your window controller's window has been loaded from its nib file.
}
#IBAction func openFile(_ sender: Any) {
print("In here") // this is called?
delegate?.didOpenFile() // but this never is apparently
}
}
Maybe this topic should help.
This method might return nil if the application’s nib file hasn’t
finished loading, if the receiver is not active, or if the application
is hidden.
Have you checked if NSApplication.shared.mainWindow is nil or just NSApplication.shared.mainWindow?.windowController cannot be casted to your controller class ?

Custom keyboard is crashing the app - Swift

I'm doing a test of a custom keyboard. This is what I need:
It has to have two UITextFields. Cannot be labels.
The keyboard is an embedded UIView.
The default keyboard should be disabled.
It cannot be a keyboard extension.
Not sure why the app is crashing. PS: Not all the keys are on the code yet. Here is an image of what I'm trying to do and the two View Controllers.
Edit: The error is: Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value
First ViewController:
import UIKit
class HomeVC: UIViewController, ButtonTapDelegate {
#IBOutlet var textField1: UITextField!
#IBOutlet var textField2: UITextField!
#IBOutlet var keyboardView: UIView!
var buttonPressed = [String]()
override func viewDidLoad() {
addKeyboard(view: keyboardView)
buttonPressed = [String]()
textField1.inputView = UIView()
textField2.inputView = UIView()
}
func addKeyboard(view: UIView) {
let keyboard = KeyboardVC(nibName: "KeyboardVC", bundle: nil)
view.addSubview(keyboard.view)
addChild(keyboard)
}
func didTapButton(sender: UIButton) {
if sender.tag == 5 {
textField1.text?.append(contentsOf: " ")
} else if sender.tag == 6 {
textField1.text?.removeAll()
buttonPressed = [String]()
} else {
let val = sender.titleLabel?.text
textField1.text?.append(contentsOf: val!)
}
self.textField1.text = buttonPressed.joined(separator: "")
}
}
Here is the second View Controller:
import UIKit
protocol ButtonTapDelegate {
func didTapButton(sender: UIButton)
}
class KeyboardVC: UIViewController {
var delegate: ButtonTapDelegate!
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func buttons(_ sender: UIButton) {
delegate.didTapButton(sender: sender)
print(sender)
}
}
var delegate: ButtonTapDelegate!
An implicitly unwrapped optional is essentially a promise that you're definitely going to give the variable a value before you try to access it. The problem in this case is that you haven't done that. Most likely, you want to do this in your first view controller:
func addKeyboard(view: UIView) {
let keyboard = KeyboardVC(nibName: "KeyboardVC", bundle: nil)
keyboard.delegate = self // Now "delegate" will have a value before the function gets called
view.addSubview(keyboard.view)
addChild(keyboard)
}

How to set NSButton.isEnabled from subclassed NSTextField

I'm very new to Swift MacOS programming and have been learning by writing small test applications.
The aim of this application is to enable the pushbutton when the 2nd textfield has the focus, and disable it when it is not focused.
I have found that by subclassing the NSTextField I can override becomeFirstResponder() however don't know how to set the button to be disabled from the subclass.
ViewController:
class ViewController: NSViewController {
#IBOutlet public weak var pushButton: NSButton!
#IBOutlet weak var textField3: NSTextField!
#IBOutlet weak var textField2: GSTextField!
#IBOutlet weak var textField1: NSTextField!
override func viewDidLoad() {
super.viewDidLoad()
textField2.delegate = self
// Do any additional setup after loading the view.
}
override var representedObject: Any? {
didSet {
// Update the view, if already loaded.
}
}
func chgButton(onoff: Bool){
pushButton.isEnabled = onoff
}
}
// When the field completes editing make the pushbutton disabled.
extension ViewController: NSTextFieldDelegate {
override func controlTextDidEndEditing(_ obj: Notification) {
print("did end")
chgButton(onoff: false)
}
}
GSTextField.Swift
class GSTextField: NSTextField {
override func becomeFirstResponder() -> Bool {
print("GSTextField Firstresponder")
////*** I need to set the button to be enabled here
return super.becomeFirstResponder()
}
}
Your NSTextField subclass needs to be able to communicate with the pushButton. The easiest way to do this is to pass a reference to the pushButton to your text field and then update the push button from there.
Update your ViewController like this:
override func viewDidLoad() {
super.viewDidLoad()
textField2.delegate = self
textField2.pushButton = pushButton
// Do any additional setup after loading the view.
}
And your GSTextField like this:
class GSTextField: NSTextField {
weak var pushButton: NSButton?
override func becomeFirstResponder() -> Bool {
print("GSTextField Firstresponder")
pushButton?.isEnabled = true
return super.becomeFirstResponder()
}
override func resignFirstResponder() -> Bool {
pushButton?.isEnabled = false
return super.resignFirstResponder()
}
}
It should be noted that while this works fine in this toy example, this is a sub-optimal solution to this problem because it tightly couples the pushButton and the GSTextField. A better solution would be to use delegation to communicate the focus changes to the ViewController, and let the ViewController handle the updates.
Your GSTextField would look like this:
protocol FocusObservable: class {
func didGainFocus(sender: Any)
func didLoseFocus(sender: Any)
}
class GSTextField: NSTextField {
weak var focusDelegate: FocusObservable?
override func becomeFirstResponder() -> Bool {
print("GSTextField Firstresponder")
focusDelegate?.didGainFocus(sender: self)
return super.becomeFirstResponder()
}
override func resignFirstResponder() -> Bool {
focusDelegate?.didLoseFocus(sender: self)
return super.resignFirstResponder()
}
}
And then you would add protocol conformance to the ViewController:
extension ViewController: FocusObservable {
func didGainFocus(sender: Any) {
pushButton.isEnabled = true
}
func didLoseFocus(sender: Any) {
pushButton.isEnabled = false
}
}
and set the focusDelegate of the text field:
override func viewDidLoad() {
super.viewDidLoad()
textField2.delegate = self
textField2.focusDelegate = self
// Do any additional setup after loading the view.
}

How can I pass data from a parent view controller to an embedded view controller in Swift?

I have a view controller embedded in another VC.
I would like to get the value of a variable from the main VC inside the embedded one. Specifically, I would like to change the text of label2 based on the value of label1.
I tried with "prepareForSegue", but it seems it's not triggered for embedded view controllers. I tried to isolate the problem in a test project:
Code for main VC:
class MyViewController: UIViewController {
#IBOutlet weak var label1: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
label1.text = "Hello"
}
}
Code for embedded VC:
class EmbeddedVC: UIViewController {
#IBOutlet weak var label2: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
}
}
Thanks for your help :)
A way to achiеve this is to get the child view controller instance in the parent's viewDidLoad. It appears that the parent's viewDidLoad: gets called after the child's viewDidLoad:, which means the label is already created in the child's view.
override func viewDidLoad() {
super.viewDidLoad()
if let childVC = self.childViewControllers.first as? ChildVC {
childVC.someLabel.text = "I'm here. Aye-aye."
}
}
First of all you can't set directly EmbeddedVC's lable2.text In prepareForSegue
because call sequence following below
MainVC's prepareForSeque this time EmbeddedVC's label2 is nil
EmbeddedVC's viewDidLoad called then label2 loaded
MainVC's viewDidLoad called then label1 loaded
so if you assign MainVC's label1.text to EmbeddedVC's label2.text in prepareForSeque
both label1 and label2 are nil so did not work
There are two way to solve this question
First Solution
MainViewController has EmbeddedVC and when MainVC's viewDidLoad called, assign label1.text to embeddedVC.label2.text
class MyViewController: UIViewController {
#IBOutlet weak var label1: UILabel!
var embeddedVC: EmbeddedViewController? = nil
override func viewDidLoad() {
super.viewDidLoad()
label1.text = "Hello"
embeddedVC?.label2.text = label1.text
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let embeddedVC = segue.destination as? EmbeddedViewController {
self.embeddedVC = embeddedVC
}
}
}
class EmbeddedViewController: UIViewController {
#IBOutlet weak var label2: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
}
}
Second Solution, use protocol and get MainVC's label text when viewWillAppear or viewDidAppear (later viewDidLoad called)
protocol EmbeddedVCDelegate: class {
func labelText() -> String?
}
class MyViewController: UIViewController, EmbeddedVCDelegate {
#IBOutlet weak var label1: UILabel!
// MARK: EmbeddedVCDelegate
func labelText() -> String? {
return label1.text
}
override func viewDidLoad() {
super.viewDidLoad()
label1.text = "Hello"
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let embeddedVC = segue.destination as? EmbeddedViewController {
embeddedVC.delegate = self
}
}
}
class EmbeddedViewController: UIViewController {
#IBOutlet weak var label2: UILabel!
weak var delegate: EmbeddedVCDelegate? = nil
override func viewDidLoad() {
super.viewDidLoad()
}
override viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
label2.text = delegate?.labelText()
}
}
You should try to use prepareForSegue like this:
if segue.identifier == "identifier" {
guard let destinationViewController = segue.destination as? VC2 else { return }
destinationViewController.label2.text = mytext
}
Where the segue identifier you assign in storyboard