Initialising UIViewController subclass error - swift2

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.

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.

Initializing Custom UITableViewCell with parameters

I am trying to create an array of UITableViewCells and before I append the cells I need to initialize them with parameters. When I try to initialize the cell I get an error that the two properties in the class were never defined. But after I defined them I get an error that the variables were used before being initialized. Here is the class
class SimpleCellClassTableViewCell: UITableViewCell {
#IBOutlet var artist: UILabel!
#IBOutlet var picture: UIImageView!
#IBOutlet var songTitle: UILabel!
#IBOutlet var sender: UILabel!
var audioFile: AnyObject? = nil
var mediaType: songType! = nil
var id: NSNumber! = nil
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
func configureCell(Title title: NSString?,
File audioFile: AnyObject?,
Type mediaType: songType,
Artist artist: NSString?,
Image image: UIImage?,
Sender sender: NSString?,
ID id: NSNumber?) -> UITableViewCell {
self.audioFile = audioFile
self.mediaType = mediaType
if let newSender = sender{
self.sender.text = newSender as String
}
if let newTitle = title{
self.songTitle.text = newTitle as String
}
if let newImage = image {
self.picture.image = newImage
}
if let newArtist = artist {
self.artist.text = newArtist as String
}
if let newId = id {
self.id = newId as NSNumber
}
return self
}
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
And this is where Im trying to initialize and then add values to it with configure cell method:
let newSongCell = SimpleCellClassTableViewCell.init(style: .Default, reuseIdentifier: "SimpleCell")
newSongCell.configureCell(Title: setTitle,
File: setAudioFile,
Type: setMediaType,
Artist: setArtist,
Image: setImage,
Sender: setSender,
ID: setId)
The parameters for File and Type are the ones throwing the same error. Also if I need to use the initializer with NSCoder what should I put as the argument?
There are several problems with your code. I suggest you to look some examples where UITableView. Apple docs or github are good sources for this.
Issue #1
You do not need to override designated `UITableViewCell's initializers (below) because you do nothing in the override.
public init(style: UITableViewCellStyle, reuseIdentifier: String?)
public init?(coder aDecoder: NSCoder)
Issue #2
Your code does not reuse the cell object which is bad.
Issue #3
In Swift init is not used on the call site, so your cell's initialization code (considering also issue #2) should be
var simpleCell = tableView.dequeueReusableCellWithIdentifier("SimpleCell")
if simpleCell == nil {
let cell: SimpleCellClassTableViewCell = SimpleCellClassTableViewCell(style: .Default, reuseIdentifier: "SimpleCell")
cell.configureCell(Title: "test",
File: "",
Type: 2,
Artist: "test",
Image: UIImage(),
Sender: "test",
ID: 2)
simpleCell = cell
}
return simpleCell!
Issue #4
Do not name function parameters with capitalised first letter (Title, File, etcetera). This might be confused with a type. Instead, use title, file, etc.. Again, there are a lot of examples out there.
Try to fix this issues. This might help.
I don't see if audioFile and mediaType properties are defined wrong. There should be no error with them. If you use .xib file - usually you should not use explicit initializer. And for sure you must not do this in your case, because you trying to use outlets. When you use init(style:reuseIdentifier:) you miss your .xib file and all UI. Assume that .xib filename is SimpleCellClassTableViewCell.xib, Custom Class for UITableViewCell in Identity Inspector is set to SimpleCellClassTableViewCell, reuse identifier is set to "SimpleCell". I offer you use this code for initializing your cell:
guard let newSongCell = UINib(nibName: "SimpleCellClassTableViewCell", bundle: nil).instantiateWithOwner(nil, options: nil).first as? SimpleCellClassTableViewCell
else
{
fatalError("Error loading SimpleCellClassTableViewCell")
}
Usually you should not use initializer with NSCoder explicitly. This initializer used by storyboard or .nib file.
At last, if you use outlets from storyboard - then you shouldn't try to get cell instance by any initializer at all. You should use UITableView's dequeueReusableCellWithIdentifier(forIndexPath:) method.
That was all about your direct question, but in fact you rarely need to initialize UITableViewCell by yourself. If you do, probably you do something wrong. Assume again, that you use .xib file, then in most cases you simply register your file in viewDidLoad of your table view controller
tableView.registerNib(UINib(nibName: "SimpleCellClassTableViewCell", bundle: nil), forCellReuseIdentifier: "SimpleCell")
and then your tableView will initialize or reuse SimpleCellClassTableViewCell instances for you by dequeueReusableCellWithIdentifier(forIndexPath:) method

Swift 2.1 Delegation with NSObject

I'm working through an example below and received an error message:
Cannot convert value of type 'NSObject -> () -> CentralViewController
to expected argument type 'TransferServiceDelegate?'
I'm trying to complete a delegation, it is erring out when I'm trying to initialize 'scanner'. Any help would be appreciated..thanks!:
import UIKit
import Foundation
protocol TransferServiceScannerDelegate: NSObjectProtocol {
func didStartScan()
func didStopScan()
func didTransferData(data: NSData?)
}
class TransferServiceScanner: NSObject{
weak var delegate: TransferServiceScannerDelegate?
init(delegate: TransferServiceScannerDelegate?) {
self.delegate = delegate
super.init()
}
}
class CentralViewController: UIViewController,
TransferServiceScannerDelegate {
*let scanner: TransferServiceScanner = TransferServiceScanner(self)*
func didStartScan(){}
func didStopScan(){}
func didTransferData(data: NSData?){}
}
First way:
lazy var scanner: TransferServiceScanner = {
let scanner = TransferServiceScanner(delegate: self)
return scanner
}()
I don't think use delegate in init is a good way and necessary, you also can do like this:
class TransferServiceScanner: NSObject{
weak var delegate: TransferServiceScannerDelegate?
init(delegate: TransferServiceScannerDelegate?) {
self.delegate = delegate
super.init()
}
override init() {
super.init()
}
}
class CentralViewController: UIViewController,
TransferServiceScannerDelegate {
var scanner: TransferServiceScanner = TransferServiceScanner()
func didStartScan(){}
func didStopScan(){}
func didTransferData(data: NSData?){}
override func viewDidLoad() {
super.viewDidLoad()
scanner.delegate = self
}
}

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

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

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

Resources