Swift OSX - Delegate protocol function returns nil, crash when expanding textbox value
I'm working on an OSX app with Swift that uses an NSSplitView that contains two view controllers: "TableViewController" and "EntryViewController". I am using delegates to pass a custom NSObject ("Entry") when clicking from TableViewController to SplitViewController, then back to EntryViewController.
This is my problem . When an Entry object is received in the EntryViewController, any attempt to assign its properties to the text field results in an unexpectedly encountered nil error, no matter that the IBOutlets are correctly linked and that it can both print the Entry.property value and the text field string value (provided that it is in another unrelated function).
I have tried many ways to solve this problem, so the current configuration might be a little tricky. Relationships between delegates directly from the VC Table in Entry VC caused the same problems.
Is there a way the IBOutlets don't connect even though the view has loaded before the delegate is called? I've read a lot of articles about delegation - mostly for iOS - and still can't find the root of my problems. I'll be the first to admit that my understanding of Swift is a bit piecemeal, so I'm open to the possibility that what I'm trying to do is just bad / hacky coding and that I should try something completely different.
Thank you for your help!
TableViewController:
protocol SplitViewSelectionDelegate: class {
func sendSelection(_ entrySelection: NSObject)
}
class TableViewController: NSViewController {
@IBOutlet weak var searchField: NSSearchField!
@IBOutlet var tableArrayController: NSArrayController!
@IBOutlet weak var tableView: NSTableView!
var sendDelegate: SplitViewSelectionDelegate?
dynamic var dataArray = [Entry]()
// load array from .plist array of dictionaries
func getItems(){
let home = FileManager.default.homeDirectoryForCurrentUser
let path = "Documents/resources.plist"
let urlUse = home.appendingPathComponent(path)
let referenceArray = NSArray(contentsOf: urlUse)
dataArray = [Entry]()
for item in referenceArray! {
let headwordValue = (item as AnyObject).value(forKey: "headword") as! String
let defValue = (item as AnyObject).value(forKey: "definition") as! String
let notesValue = (item as AnyObject).value(forKey: "notes") as! String
dataArray.append(Entry(headword: headwordValue, definition: defValue, notes: notesValue))
}
}
override func viewDidLoad() {
super.viewDidLoad()
self.sendDelegate = SplitViewController()
getItems()
print("TVC loaded")
// Do any additional setup after loading the view.
}
// send selection forward to entryviewcontroller
@IBAction func tableViewSelection(_ sender: Any) {
let index = tableArrayController.selectionIndex
let array = tableArrayController.arrangedObjects as! Array<Any>
let obj: Entry
let arraySize = array.count
if index <= arraySize {
obj = array[index] as! Entry
print(index)
print(obj)
sendDelegate?.sendSelection(obj)
}
else {
print("index unassigned")
}
}
}
SplitViewController:
protocol EntryViewSelectionDelegate: class {
func sendSecondSelection(_ entrySelection: NSObject)
}
class SplitViewController: NSSplitViewController, SplitViewSelectionDelegate {
var delegate: EntryViewSelectionDelegate?
@IBOutlet weak var mySplitView: NSSplitView!
var leftPane: NSViewController?
var contentView: NSViewController?
var entrySelectionObject: NSObject!
override func viewDidLoad() {
super.viewDidLoad()
// assign tableview and entryview as child view controllers
let story = self.storyboard
leftPane = story?.instantiateController(withIdentifier: "TableViewController") as! TableViewController?
contentView = story?.instantiateController(withIdentifier: "EntryViewController") as! EntryViewController?
self.addChildViewController(leftPane!)
self.addChildViewController(contentView!)
print("SVC loaded")
}
func sendSelection(_ entrySelection: NSObject) {
self.delegate = EntryViewController() //if this goes in viewDidLoad, then delegate is never called/assigned
entrySelectionObject = entrySelection
print("SVC:", entrySelectionObject!)
let obj = entrySelectionObject!
delegate?.sendSecondSelection(obj)
}
}
And finally the EntryViewController:
class EntryViewController: NSViewController, EntryViewSelectionDelegate {
@IBOutlet weak var definitionField: NSTextField!
@IBOutlet weak var notesField: NSTextField!
@IBOutlet weak var entryField: NSTextField!
var entryObject: Entry!
override func viewDidLoad() {
super.viewDidLoad()
print("EVC loaded")
}
func sendSecondSelection(_ entrySelection: NSObject) {
self.entryObject = entrySelection as! Entry
print("EVC:", entryObject)
print(entryObject.headword)
// The Error gets thrown here:
entryField.stringValue = entryObject.headword
}
}
source to share
You don't need a delegate / protocol as there is a reference to EntryViewController
( contentView
) - by the way, the instance created with EntryViewController()
is not an instance in viewDidLoad
.
Just use the link contentView
:
func sendSelection(_ entrySelection: NSObject) {
contentView?.sendSecondSelection(entrySelection)
}
source to share