Search code examples
macoscocoanstableviewxcode10swift4.2

Swift - Failed (found nil) calling reloadData() from another class but succeeded from self class


I'm apparently designing a drag and drop dropbox which can either select files by clicking it or dragging and dropping the files on it, and I want the selected files to be visible in a table next to it. My design logic is that whenever the user selects files from an NSOpenPanel, it passes the selected file paths into the CoreData and then an array retrieves them one by one from the CoreData, and finally, update the NSTableView's content by using reloadData().

Basically, my problem is that whenever I try to call ViewController().getDroppedFiles() from DropboxButton class, I always get a Fatal error: unexpectedly found nil while unwrapping an optional value.

My ViewController.swift:

import Cocoa

class ViewController: NSViewController, NSTableViewDataSource, NSTableViewDelegate {

    override func viewDidLoad() {
        super.viewDidLoad()
        getDroppedFiles()
    }

    @IBOutlet weak var DroppedFilesTableView: NSTableView!

    var droppedFiles: [DroppedFiles] = [] // Core Data class definition: DroppedFiles

    func numberOfRows(in tableView: NSTableView) -> Int {
        return droppedFiles.count
    }

    func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
        let droppedFilesCollection = droppedFiles[row]

        if (tableView?.identifier)!.rawValue == "fileNameColumn" {
            if let fileNameCell = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "fileNameCell")) as? NSTableCellView {
                fileNameCell.textField?.stringValue = droppedFilesCollection.fileName!
                return fileNameCell
            }
        }  else if (tableView?.identifier)!.rawValue == "filePathColumn" {
             if let filePathCell = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "filePathCell")) as? NSTableCellView {
                 filePathCell.textField?.stringValue = droppedFilesCollection.filePath!
                 return filePathCell
             }
        }
        return nil
    }

    @IBAction func DropboxClicked(_ sender: NSButton) {
        // selected file paths
        for filePath in selectedFilePaths {
            if let context = (NSApp.delegate as? AppDelegate)?.persistentContainer.viewContext {
                let droppedFilesData = DroppedFiles(context: context)
                droppedFilesData.fileName = getFileName(withPath: filePath)
                droppedFilesData.filePath = filePath
                do {
                    try context.save()
                } catch {
                    print("Unable to save core data.")
                }
            }
            getDroppedFiles()
        }
    }

    func getDroppedFiles() {
        if let context = (NSApp.delegate as? AppDelegate)?.persistentContainer.viewContext {
            do {
                try droppedFiles = context.fetch(DroppedFiles.fetchRequest())
            } catch {
                print("Unable to fetch core data.")
            }
        }
        DroppedFilesTableView.reloadData() // Fatal Error: unexpectedly found nil while unwrapping an optional value (whenever I call this function in other class)
    }


}

I'm using a push button (NSButton) as the dropbox (it has its own class), which can easily be clicked and also supports dragging options.

My DropboxButton.swift:

import Cocoa

class DropboxButton: NSButton {

    required init?(coder: NSCoder) {
        super.init(coder: coder)
        registerForDraggedTypes([NSPasteboard.PasteboardType.URL, NSPasteboard.PasteboardType.fileURL])
    }

    override func draggingEntered(_ sender: NSDraggingInfo) -> NSDragOperation {
        // some other codes
        return .copy
    }

    override func draggingExited(_ sender: NSDraggingInfo?) {
        // some other codes
    }

    override func draggingEnded(_ sender: NSDraggingInfo) {
        // some other codes
    }

    override func performDragOperation(_ sender: NSDraggingInfo) -> Bool {
        guard let pasteboard = sender.draggingPasteboard.propertyList(forType: NSPasteboard.PasteboardType(rawValue: "NSFilenamesPboardType")) as? NSArray,
            let filePaths = pasteboard as? [String] else {
                return false
        }

        for filePath in filePaths {
            if let context = (NSApp.delegate as? AppDelegate)?.persistentContainer.viewContext {
                let droppedFilesData = DroppedFiles(context: context)
                droppedFilesData.fileName = getFileName(withPath: filePath)
                droppedFilesData.filePath = filePath
                do {
                    try context.save()
                } catch {
                    print("Unable to save core data.")
                }
            }
            ViewController().getDroppedFiles() // found nil with reloadData() in ViewController.swift
        }
        return true
    }
}

And this is my interface and code logic: user interface and code logic

So, how can I reloadData() for the table view in my ViewController class from another class (DropboxButton: NSButton) so that whenever the user drags and drops files into the dropbox, the table view will reload?

P.S. To get this done means a lot to me, I really need to get this fixed in a short time, is there anyone can spend some time and help me?


Solution

  • You need to call getDroppedFiles() on a loaded instance of ViewController.

    With ViewController().getDroppedFiles() you're creating a new instance of ViewController that is not shown anywhere (so controls are not initialized resulting in the nil error).