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
Related
I have a NSTableView with 2 columns bound with a custom type (SelectedFiles) array as File Name and File Path, after clicking the header, I want it to sort the data in ascending / descending order, I tried these codes with NSSortDescriptor:
class ViewController: NSViewController, NSTableViewDataSource, NSTableViewDelegate {
override func viewDidLoad() {
super.viewDidLoad()
let fileNameSortDescriptor = NSSortDescriptor(key: "fileName", ascending: true, selector: #selector(NSString.localizedStandardCompare(_:)))
tableView.tableColumns[0].sortDescriptorPrototype = fileNameSortDescriptor
// other codes
}
}
extension ViewController: NSTableViewDataSource, NSTableViewDelegate {
func tableView(_ tableView: NSTableView, sortDescriptorsDidChange oldDescriptors: [NSSortDescriptor]) {
let selectedFilesArray = NSMutableArray(array: selectedFiles)
selectedFilesArray.sort(using: tableView.sortDescriptors) // Signal SIGABRT
selectedFiles = selectedFilesArray as! [SelectedFiles]
tableView.reloadData()
}
}
My custom collection for the data in table view:
struct SelectedFiles: CustomStringConvertible {
let fileName: String
let filePath: String
var description: String {
return "\(fileName) at path \(filePath)"
}
}
var selectedFiles: [SelectedFiles] = []
It turns out it doesn't work at all, IDK if its anything wrong with my code or I'm missing something.
So, I came up with this awkward solution:
var tableViewSortingOrder = ComparisonResult.orderedAscending
extension ViewController: NSTableViewDataSource, NSTableViewDelegate {
func tableView(_ tableView: NSTableView, sortDescriptorsDidChange oldDescriptors: [NSSortDescriptor]) {
switch tableViewSortingOrder {
case .orderedAscending:
tableViewSortingOrder = .orderedDescending
selectedFiles.sort { (previous, next) -> Bool in
return previous.fileName.compare(next.fileName) == tableViewSortingOrder
}
default:
tableViewSortingOrder = .orderedAscending
selectedFiles.sort { (previous, next) -> Bool in
return previous.fileName.compare(next.fileName) == tableViewSortingOrder
}
tableView.reloadData()
}
}
After I changed to this solution, it worked perfectly as it switches swiftly between ascending / descending order. But, when it comes to deleting objects in the collection, it throws Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value when I'm trying to delete multiple objects from both collection and table view with some specific files.
So, I'm thinking if I should change a way of achieving this header sorting thing by using NSSortDescriptor (use the old-fashioned way by correcting my first method) in order to get away from this issue, I have to admit that my second way is a bit of awkward (is more like a plan C).
I've red through multiple StackOverflow posts on this topic and I tried all of their ways, especially this one, I am not using CoreData which its solutions does not work for my situation.
Anyone can help point out the way please? 😊
I red the guide to NSTableView from Apple Developer Site and few other StackOverflow posts, I found myself a workable solution for Swift 4:
I set the sortDescriptorPrototype to fileNameSortDescriptor in viewDidLoad() under ViewController class.
class ViewController: NSViewController {
override func viewDidLoad()
super.viewDidLoad()
let fileNameSortDescriptor = NSSortDescriptor(key: "fileName", ascending: true, selector: #selector(NSString.localizedStandardCompare))
let tableColumn = tableView.tableColumn(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "fileNameColumn"))!
tableColumn.sortDescriptorPrototype = fileNameSortDescriptor
// other codes
}
}
And then I added an inheritance from NSObject and inserted #objcMembers to prevent warning: Object <#object#> of class '<#class#>' does not implement methodSignatureForSelector: -- trouble ahead from occurring and then cause Signal SIGABRT while calling selectedFiles.sort(using: tableView.sortDescriptors) (Reference: Object X of class Y does not implement methodSignatureForSelector in Swift).
#objcMembers class SelectedFiles: NSObject {
let fileName: String
let filePath: String
override var description: String {
return "\(fileName) at path \(filePath)"
init(fileName: String, filePath: String) {
self.fileName = fileName
self.filePath = filePath
}
}
Here's the code for tableView(_:sortDescriptorsDidChange:) in NSTableViewDataSource:
extension ViewController: NSTableViewDataSource {
func tableView(_ tableView: NSTableView, sortDescriptorsDidChange oldDescriptors: [NSSortDescriptor]) {
var selectedFilesArray = NSArray(array: selectedFiles)
selectedFilesArray = selectedFilesArray.sortedArray(using: tableView.sortDescriptors) as NSArray
selectedFiles = selectedFilesArray as! [SelectedFiles]
tableView.reloadData()
}
}
Now, everything works perfectly fine.
I am new to Swift and trying to learn how to implement NSTreeController with NSOutlineView. I've been following several guides which shows such examples, but I keep getting an error. I followed step by step and/or try to run their source codes if available, but I was getting same error. I come to think there is some change in Swift 4 which makes these Swift 3 examples to produce error. As there are not many examples done in Swift 4, I decided I'd give a try by asking the question here.
The error I'm getting is:
this class is not key value coding-compliant for the key isLeaf.
I believe that error is coming from the key path set up for NSTreeController:
However I am not sure what needs to be done to fix the error.
I have simple model class called Year.
class Year: NSObject {
var name: String
init(name: String) {
self.name = name
}
func isLeaf() -> Bool {
return true
}
}
My view controller looks like this.
class ViewController: NSViewController, NSOutlineViewDataSource, NSOutlineViewDelegate {
#IBOutlet weak var outlineView: NSOutlineView!
#IBOutlet var treeController: NSTreeController!
override func viewDidLoad() {
super.viewDidLoad()
addData()
outlineView.delegate = self
outlineView.dataSource = self
}
func addData() {
let root = ["name": "Year", "isLeaf": false] as [String : Any]
let dict: NSMutableDictionary = NSMutableDictionary(dictionary: root)
dict.setObject([Year(name: "1999"), Year(name: "2000")], forKey: "children" as NSCopying)
treeController.addObject(dict)
}
func isHeader(item: Any) -> Bool {
if let item = item as? NSTreeNode {
return !(item.representedObject is Year)
} else {
return !(item is Year)
}
}
func outlineView(_ outlineView: NSOutlineView, viewFor tableColumn: NSTableColumn?, item: Any) -> NSView? {
if isHeader(item: item) {
return outlineView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "HeaderCell"), owner: self)!
} else {
return outlineView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "DataCell"), owner: self)!
}
}
}
When I run the program, it causes no issue, but when I expand the node to show the two children of the root, it is giving the error I mentioned above.
Because is isLeaf is used in KVO by NSOutlineView, you have to add #objc in front of isLeaf function:
#objc func isLeaf() -> Bool {
return true
}
The class to which you are binding needs to be KVO compliant.
So, it needs to be a subclass of NSObject.
And the objc runtime needs access.
One way to do this:
#objcMembers
class FileSystemItem: NSObject {
Or, you can annotate each field/function with #objc
Full Example
I was just wondering how would I be able to use a searched barcode to fetch using Core Data in Swift. I'm basically passing a barcode to a static func method, but how would I be able to use that to fetch the data from the Core Data?
Here is the barcode when detected:
func barcodeDetected(code: String) {
// Let the user know we've found something.
let alert = UIAlertController(title: "Found a Barcode!", message: code, preferredStyle: UIAlertControllerStyle.Alert)
alert.addAction(UIAlertAction(title: "Search", style: UIAlertActionStyle.Destructive, handler: { action in
// Remove the spaces.
let trimmedCode = code.stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceCharacterSet())
// EAN or UPC?
// Check for added "0" at beginning of code.
let trimmedCodeString = "\(trimmedCode)"
var trimmedCodeNoZero: String
if trimmedCodeString.hasPrefix("0") && trimmedCodeString.characters.count > 1 {
trimmedCodeNoZero = String(trimmedCodeString.characters.dropFirst())
// Send the doctored barcode
ProductDetailsViewController.searchCode(trimmedCodeNoZero)
} else {
// Send the doctored barcode
ProductDetailsViewController.searchCode(trimmedCodeString)
}
self.navigationController?.popViewControllerAnimated(true)
}))
self.presentViewController(alert, animated: true, completion: nil)
}
My Product Class:
import UIKit
import Foundation
import CoreData
class ProductDetailsViewController: UIViewController, NSFetchedResultsControllerDelegate {
#IBOutlet weak var productLabel: UILabel!
#IBOutlet weak var priceLabel: UILabel!
#IBAction func addProduct(sender: AnyObject) {
let AppDel = UIApplication.sharedApplication().delegate as? AppDelegate
let context:NSManagedObjectContext = (AppDel?.managedObjectContext)!
let ent = NSEntityDescription.entityForName("Products", inManagedObjectContext: context)
var newProduct = ProductItem(entity: ent!, insertIntoManagedObjectContext: context)
newProduct.title = productLabel.text
//newProduct.price = priceLabel.text
/*context.save(nil)
print(newProduct)
print("Object Saved")*/
}
private(set) var PRODUCT_NAME = ""
private(set) var PRODUCT_PRICE = ""
private var menuItems:[ProductItem] = []
static func searchCode(codeNumber: String) -> String{
let barcodeNumber = codeNumber
return barcodeNumber
}
deinit{
NSNotificationCenter.defaultCenter().removeObserver(self)
}
override func viewDidLoad() {
super.viewDidLoad()
productLabel.text = "Scan a Product"
priceLabel.text = ""
NSNotificationCenter.defaultCenter().addObserver(self, selector: "setLabels:", name: "ProductNotification", object: nil)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
I already added the items into Core Data successfully and was able to load all items into a table in my app. Now with the barcode scanned I want to be able to just load the products with the barcode and i'm stuck on that part. As you can see my static fun searchCode is receiving the barcode from barcodeDetected but what should I do next to fetch it? Thanks.
EDIT:
Core Data Entity
import Foundation
import CoreData
#objc(ProductItem)
class ProductItem: NSManagedObject{
#NSManaged var barcodeNum:String?
#NSManaged var box_height:NSNumber?
#NSManaged var box_length:NSNumber?
#NSManaged var box_width:NSNumber?
#NSManaged var price:NSNumber?
#NSManaged var sku:String?
#NSManaged var weight:NSNumber?
#NSManaged var title:String?
}
To fetch the correct ProductItem, you need to use a predicate (see the Apple Documentation here). In your case, you could use something like this:
let AppDel = UIApplication.sharedApplication().delegate as? AppDelegate
let context:NSManagedObjectContext = (AppDel?.managedObjectContext)!
let fetchRequest = NSFetchRequest(entityName: "ProductItem")
fetchRequest.predicate = NSPredicate(format: "barcodeNum == %#",codeNumber)
let results = try! context.executeFetchRequest(fetchRequest) as! [ProductItem]
if results.count > 0 { // great, you found (at least one) matching item
let scannedProduct = results[0]
// from here you can access the attributes of the product
// such as title, price, sku, etc.
...
} else { // not found
...
}
Note that I've use try! for brevity, but in practice you should use proper do ... catch syntax and handle any errors.
I'm not clear why you are using a static func in the ProductDetailsViewController; a common approach would be to use the above fetch within your barcodeDetected method, and then to segue to the ProductDetailsViewController passing the relevant ProductItem for display/editing or whatever. Or to display an alert view if the product was not found.
My code worked fine in Xcode 6.2. After the update to Xcode 6.3 I had some Nullabilty Errors.
I could solve these errors after I downloaded the Parse SDK 1.7.1. So I deleted the old Parse framework files in my project and pasted the new ones into it. Additional I convert my code to the latest swift syntax "Edit/Convert/latest swift syntax". Now I haven't problems with Nullabilty Errors but several others.
In my project I have a simple Tableviewcontroller with the following code:
import UIKit
class HaendlerTableViewController: PFQueryTableViewController {
// Initialise the PFQueryTable tableview
override init!(style: UITableViewStyle, className: String!) { //1. Falialbe initialize init/style:className:)' cannot override a non-failable initializer
super.init(style: style, className: className)
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
// Configure the PFQueryTableView
self.parseClassName = "Haendler"
self.textKey = "name"
self.pullToRefreshEnabled = true
self.paginationEnabled = false
}
// Define the query that will provide the data for the table view
override func queryForTable() -> PFQuery! { //2. Ovverriding method with selector queryForTable has incompatitble typ () -> PFQuery
var query = PFQuery(className: "Haendler")
query.orderByAscending("name")
return query
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath, object: PFObject) -> PFTableViewCell { //3. Ovverriding method with selector 'tableView:cellForRowAtindexPath:object:' has incompatible type '(UITableView, NSIndexPath, PFObject) -> PFTableViewCell
var cell = tableView.dequeueReusableCellWithIdentifier("HaendlerCell") as! HaendlerCell!
if cell == nil {
cell = HaendlerCell(style: UITableViewCellStyle.Default, reuseIdentifier: "Cell")
}
// Extract values from the PFObject to display in the table cell
cell.haendlerName.text = object["name"] as! String!
var thumbnail = object["logo"] as! PFFile
var initialThumbnail = UIImage(named: "haendler")
cell.haendlerBild.image = initialThumbnail
cell.haendlerBild.file = thumbnail
cell.haendlerBild.loadInBackground()
return cell
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
var detailScene = segue.destinationViewController as! HaendlerDetailViewController
// Pass the selected object to the destination view controller.
if let indexPath = self.tableView.indexPathForSelectedRow() {
let row = Int(indexPath.row)
detailScene.currentObject = objects[row] as? PFObject //4. Could not find an overload for 'subscript' that accepts the supplied agruments
}
}
}
I wrote the errors in a comment on the right side of the code and
below.
Falialbe initialize init/style:className:)' cannot override a non-failable initializer
Ovverriding method with selector queryForTable has incompatitble typ () -> PFQuery
Ovverriding method with selector 'tableView:cellForRowAtindexPath:object:' has incompatible type '(UITableView, NSIndexPath, PFObject) -> PFTableViewCell
Could not find an overload for 'subscript' that accepts the supplied agruments
I have the same errors when I make a new Swift project from the Parse Quickstart and add one Tableviewcontroller. In my old project was an objective-C bridging header which one I deleted because I had the oppurtunity to add the Parse SDK 1.7.1 directly in my Swift project.
Now I need help because I don't see what I have to change..
PS: Sorry for the mix of German and English code I'll adjust it once the project is running again
I had the same issue as I just updated Xcode to 6.3 about 20 minutes ago.
For your 2nd error, remove the '!' after 'PFQuery'. So it should now look like..
override func queryForTable() -> PFQuery {
This solved my problem in regards to that specific error.
I never used an init method as you did in your first error, but try removing it and see what you get. My PFQueryTableViewController works fine without it.
Had the same issues.
To solve the first initialise issue remove the '!' after 'override init'. Should look like this:
// Initialise the PFQueryTable tableview
override init(style: UITableViewStyle, className: String!) { //1. Falialbe initialize init/style:className:)' cannot override a non-failable initializer
super.init(style: style, className: className)
}
Do the same for the 2nd error after 'PFQuery'
override func queryForTable() -> PFQuery {
Hope its helpful. Since the latest update unwrapping elements usually needs to be revised for possible errors.
I have had bad luck finding any examples on the web that closely match what I am trying to do. I am trying to using NSPageController to view and switch between multiple NSPageControllers. My steps.
I create a new OS X swift project
I add an object to the ViewController and make it of NSPageController class.
I add two buttons, one I label "Next" and the other one I label "Back" for the transitions.
I link the buttons to the NSPageController object as navigateForward and navigateBack actions.
I create an outlet in the custom NSViewController class for the NSPageController object and add the specific NSPageController delegate methods.
I add two additional view controllers in storyboard and create an identifier for them to reference back in my custom view controller class: Wizard1, Wizard2.
import Cocoa
class ViewController: NSViewController, NSPageControllerDelegate {
#IBOutlet var myPageController: NSPageController!
override func viewDidLoad() {
super.viewDidLoad()
let vc1: AnyObject? = self.storyboard!.instantiateControllerWithIdentifier("Wizard1")
let vc2: AnyObject? = self.storyboard!.instantiateControllerWithIdentifier("Wizard2")
self.myPageController.arrangedObjects.append(vc1!)
self.myPageController.arrangedObjects.append(vc2!)
// Do any additional setup after loading the view.
}
override init?(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) {
myPageController = NSPageController()
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil?)
}
required init?(coder aDecoder: NSCoder) {
myPageController = NSPageController()
super.init(coder:aDecoder)
}
override var representedObject: AnyObject? {
didSet {
// Update the view, if already loaded.
}
}
func pageController(pageController: NSPageController, identifierForObject object: AnyObject!) -> String! {
return "View"
}
func pageController(pageController: NSPageController, viewControllerForIdentifier identifier: String!) -> NSViewController! {
let vc1: AnyObject? = self.storyboard!.instantiateControllerWithIdentifier("Wizard1")
return vc1 as NSViewController
}
func pageController(pageController: NSPageController, prepareViewController viewController: NSViewController!, withObject object: AnyObject!) {
viewController.representedObject = object
}
func pageControllerDidEndLiveTransition(pageController: NSPageController) {
pageController.completeTransition()
}
func pageControllerWillStartLiveTransition(pageController: NSPageController) {
self.presentViewControllerAsModalWindow(self.storyboard?.instantiateControllerWithIdentifier("Wizard2") as NSViewController)
}
}
The error I get when pressing the Next button is:
-[NSNib initWithNibNamed:bundle:] could not load the nibName: NSPageController in bundle (null).
Perhaps you are trying to load a nib with the wrong name in AppDelegate.m or wherever you are initializing your page controller.
Otherwise you have missed creating a .xib file and to name it NSPageController. When creating a Cocoa Touch Class there is a checkbox to also create an xib file for your class if needed.
This line is responsible for the error:
myPageController = NSPageController()
You're trying to initialize a view controller without a nib, that's why it does not work. By default the NSViewController's name is taken to identify the nib that corresponds to it. In your case it is "NSPageController".