Instance properties with default values in Swift: when are they called? - cocoa

Say I have a class like this one:
class UniverseViewController: UITableViewController {
var model = createModel()
// blah, blah...
}
When exactly will the createModel function be called? Before the init? After it?

It is called before init and viewDidLoad etc.
The following code:
struct testStruct {
init() {
println("testStruct")
}
}
let tempValue = testStruct()
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
println("Coder")
}
override func viewDidLoad() {
super.viewDidLoad()
println("viewDidLoad")
}
will give us the following output:
testStruct
Coder
viewDidLoad

Related

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:
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?
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).
I found this solution useful for my case.
I used observer to pass through data and call functions from other controller classes, now I understand that I was creating a new instance of ViewController which is not loaded. Here is my code:
ViewController.swift:
class ViewController: NSViewController {
// other codes
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(getDroppedFiles), name: NSNotification.Name(rawValue: "reloadTableViewData"), object: nil)
}
#objc func getDroppedFiles() {
DroppedFilesTableView.reloadData()
}
}
DropboxButton.swift:
class DropboxButton: NSButton {
// other codes
override func performDragOperation(_ sender: NSDraggingInfo) -> Bool {
// other codes
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "reloadTableViewData"), object: nil)
return true
}
}
And now, everything works perfectly, I can even add an userInfo: to pass data between files and classes.

Swift: EXC_BAD_ACCESS when I access class property

EXC_BAD_ACCESS occurs when I access (read or rewrite) property of class instance.
class MyUIImageView:UIImageView {
required init?(coder aDecoder: NSCoder) {
fatalError()
}
var hand: String = ""
}
#IBOutlet weak var myInstance: MyUIImageView!
func rewrite() {
...
myInstance.hand = "..." // Error!
}
func read() {
...
var tmp: String = myInstance.hand // Error!
}
Do you know how to fix this error?
・2016/1/31
Unknown class MyUIImageView in Interface Builder was found in log, and Module field of Custom Class is "None".
either myInstance isn't set or myInstance isn't a MyUIImageView

How set initial values for stored property with initializers?

This code doesn't work.
import Cocoa
class ViewController: NSViewController {
#IBOutlet weak var textField: NSTextField!
var guessScore : Int
override func viewDidLoad() {
super.viewDidLoad()
}
override init(){
super.init()
guessScore = 1
}
before calling super.init(), all your properties must be initialized (they have to have some 'default' value), Please read apple docs.
override init(){
guessScore = 1
super.init()
}
Your code produces:
So you may want to simply follow the instructions given by Xcode and override the designated initializer:
var guessScore: Int
override init?(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) {
guessScore = 1
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
}
required init?(coder: NSCoder) {
guessScore = 1
super.init(coder: coder)
}
Or directly without any initializer, provide a default value:
var guessScore: Int = 1

Initialising UIViewController subclass error

I am learning Swift and I have a compile error:
class ListScansVC: UIViewController {
#IBOutlet weak var tableView: UITableView!
internal var scans: [Scan]
internal var dataSource: ListScanDataSource
init(scans: [Scan]){
self.scans = scans
dataSource = ListScanDataSource.init(scans: scans)
super.init(nibName: nil, bundle: nil)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
dataSource.registerReusableViewsForTableView(tableView)
}
}
In another ViewController i am doing:
override func viewDidLoad() {
super.viewDidLoad()
let scan = Scan(title : "Ticket", scanDate: NSDate())
let listScanVC = ListScansVC.init(scans: [scan])
self.displayChildContent(ListScansVC)
}
func displayChildContent(content : UIViewController){
self.addChildViewController(content)
self.view.addSubview(content.view)
content.didMoveToParentViewController(self)
}
The error is:
"Cannot convert value of Type 'ListScansVC.Type' to expected argument type 'UIViewController'"
An init method in Swift like
init(scans :[Scan]){)
has be be called with
let listScanVC = ListScansVC(scans: [scan])
in the next line you have to pass the instance (starting with a lowercase letter) rather than the type
self.displayChildContent(listScanVC)
I'd recommended to use more distinctive names to avoid that type/instance confusion.

Swift Drag and Drop won't work outside Xcode

I have some code, that works in a command line tool. Now i wanted to make it a Drop-Applet to get by the terminal and paths. I am dropping some files to it and as long as the Debugger is attached it works like a charm.
So far so good, but when I start it directly (from the Xcode output directory), it seems the drag/drop is not accepted. (Animation of the file back to the origin).
class dragView : NSView, NSDraggingDestination {
required init(coder: NSCoder) {
super.init(coder: coder)
}
override init(frame: NSRect) {
super.init(frame: frame)
let types = [NSFilenamesPboardType, NSURLPboardType]
registerForDraggedTypes(types)
}
override func drawRect(dirtyRect: NSRect) {
super.drawRect(dirtyRect)
NSColor.whiteColor().set()
NSRectFill(dirtyRect)
}
override func draggingEntered(sender: NSDraggingInfo!) -> NSDragOperation {
return NSDragOperation.Copy
}
override func draggingUpdated(sender: NSDraggingInfo!) -> NSDragOperation {
return NSDragOperation.Copy
}
override func performDragOperation(sender: NSDraggingInfo!) -> Bool {
let pboard: NSPasteboard = sender.draggingPasteboard()
let array : [String] = pboard.propertyListForType(String(NSFilenamesPboardType)) as [String]
for item in array
{
...
What am I missing here?
Your code doesn't work cause you must register dragged type into init(coder:) function.
This code works fine in Swift 3
import Cocoa
class DropView : NSView {
required init?(coder: NSCoder) {
super.init(coder: coder)
let types = [NSFilenamesPboardType, NSURLPboardType]
register(forDraggedTypes: types)
self.wantsLayer = true
self.layer?.backgroundColor = NSColor.white.cgColor
}
override func draw(_ dirtyRect: NSRect) {
super.draw(dirtyRect)
// Drawing code here.
}
override func draggingEntered(_ sender: NSDraggingInfo) -> NSDragOperation {
return .copy
}
override func performDragOperation(_ sender: NSDraggingInfo) -> Bool {
guard let pasteboard = sender.draggingPasteboard().propertyList(forType: "NSFilenamesPboardType") as? NSArray,
let path = pasteboard[0] as? String
else { return false }
//GET YOUR FILE PATH !!
Swift.print("FilePath: \(path)")
return true
}
}
A more usefull example here

Resources