extension ActionSheetViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return sheetActions.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: TableCellIds.ActionSheet.actionSheetTableCellIdentifier, for: indexPath) as! ActionsSheetCell
cell.actionCellLabel.text = "My cell content goes here"
return cell
}
}
Above code gives me 'Force Cast Violation: Force casts should be avoided. (force_cast)' error. How can I avoid it?
Some force-casts are unavoidable, especially when interacting with Objective C, which has a much more dynamic/loose type system.
In some cases like this, a force-cast would be self-explanatory. If it crashes, clearly you're either:
getting back nil (meaning there's no view with that reuse identifier),
or you're getting back the wrong type (meaning the cell exists, but you reconfigured its type).
In either case your app is critically mis-configured, and there's no much graceful recovery you can do besides fixing the bug in the first place.
For this particular context, I use a helper extension like this (it's for AppKit, but it's easy enough to adapt). It checks for the two conditions above, and renders more helpful error messages.
public extension NSTableView {
/// A helper function to help with type-casting the result of `makeView(wihtIdentifier:owner:)`
/// - Parameters:
/// - id: The `id` as you would pass to `makeView(wihtIdentifier:owner:)`
/// - owner: The `owner` as you would pass to `makeView(wihtIdentifier:owner:)`
/// - ofType: The type to which to cast the result of `makeView(wihtIdentifier:owner:)`
/// - Returns: The resulting view, casted to a `T`. It's not an optional, since that type error wouldn't really be recoverable
/// at runtime, anyway.
func makeView<T>(
withIdentifier id: NSUserInterfaceItemIdentifier,
owner: Any?,
ofType: T.Type
) -> T {
guard let view = self.makeView(withIdentifier: id, owner: owner) else {
fatalError("This \(type(of: self)) didn't have a column with identifier \"\(id.rawValue)\"")
}
guard let castedView = view as? T else {
fatalError("""
Found a view for identifier \"\(id.rawValue)\",
but it had type: \(type(of: view))
and not the expected type: \(T.self)
""")
}
return castedView
}
}
Honestly, after I got experienced enough with the NSTableView APIs, investigating these issues became second nature, and I don't find this extension as useful. Still, it could save some debugging and frustration for devs who are new the platform.
The force cast is actually correct in this situation.
The point here is that you really don't want to proceed if you can't do the cast, because you must return a real cell and if it's the wrong class, the app is broken and you have no cell, so crashing is fine.
But the linter doesn't realize that. The usual way to get this past the linter is to do a guard let with as?, along with a fatalError in the else. That has the same effect, and the linter will buy into it.
I really like the approach suggested by Alexander at https://stackoverflow.com/a/67222587/341994 - here's an iOS modification of it:
extension UITableView {
func dequeue<T:UITableViewCell>(withIdentifier id:String, for ip: IndexPath) -> T {
guard let cell = self.dequeueReusableCell(withIdentifier: id, for: ip) as? T else {
fatalError("could not cast cell")
}
return cell
}
}
So now you can say e.g.:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell : MyTableViewCell = tableView.dequeue(withIdentifier: "cell", for: indexPath)
// ...
return cell
}
And everyone is happy including the linter. No forced unwraps anywhere and the cast is performed automatically thanks to the generic and the explicit type declaration.
As others have said, a force cast is appropriate in this case, because if it fails, it means you have a critical error in your source code.
To make SwiftLint accept the cast, you can surround the statement with comments as described in this issue in the SwiftLint repo:
// swiftlint:disable force_cast
let cell = tableView.dequeueReusableCell(withIdentifier: TableCellIds.ActionSheet.actionSheetTableCellIdentifier, for: indexPath) as! ActionsSheetCell
// swiftlint:enable force_cast
The right thing to do is: remove force_cast from swift lint’s configuration file. And be professional: only write force casts where you mean “unwrap or fatal error”. Having to “get around the linter” is a pointless waste of developer time.
Related
A few folks asked this question before, yet no answer was accepted.
I have a UITableViewCell that contains a UITextField.
If I click slightly outside of the textField the row highlights. I want to prevent this.
To prevent it I do the following:
override func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath! {
return nil
}
This works perfectly fine. Here is my question. The author of the tutorial states:
Note that returning nil from a method is only allowed if there is a
question mark (or exclamation point) behind the return type.
What does he mean that you can put an exclamation mark behind the optional return type? Why doesn't the program crash? Returning nil after I place an exclamation mark after the IndexPath return type doesn't crash. I thought ! would explicitly unwrap the nil and fail?
As of Swift 3, “Implicitly unwrapped optional” is not a separate type,
but an attribute on the declaration of a regular/strong optional.
For the details, see SE-0054 Abolish ImplicitlyUnwrappedOptional type.
A function with an IUO return type can return nil,
and assigning the return value to a variable makes that a regular
optional:
func foo() -> Int! {
return nil
}
let x = foo() // Type of `x` is `Int?`
print(x) // nil
Only if evaluation as an optional is not possible then the value
will be forced-unwrapped (and cause a runtime exception is the
value is nil):
let y = 1 + foo() // Fatal error: Unexpectedly found nil while unwrapping an Optional value
In your case, your
override func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath!
method overrides the UITableViewController method
func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath?
and can return nil. This does not crash unless the caller unwraps
the value.
Remark: The above is meant as an explanation why your code compiles
and works. Generally, I do not see a good reason to use implicitly
unwrapped optional return types. The main use-cases of IUOs are
stated in SE-0054:
The ImplicitlyUnwrappedOptional ("IUO") type is a valuable tool for importing Objective-C APIs where the nullability of a parameter or return type is unspecified. It also represents a convenient mechanism for working through definite initialization problems in initializers.
One way to think this as a choise of the API Implementor. If implementor handles the input arguments it will not be any problem to the API User.
Lets create a drawing text class which just prints at console.
class TextDrawer {
var mustBeText: String!
func renderText(string: String) {
print(string)
}
func renderSafely() {
renderText(string: self.mustBeText ?? "Nothing found to be rendered")
// or
if let safeString = self.mustBeText {
renderText(string: safeString)
}
}
func renderUnsafely() {
renderText(string: mustBeText)
}
}
We have defined the mustBeText as String! means we are allowed to expect any string as well as nil argument.
Now, as we create a instance of the class as below:
let textDrawer = TextDrawer()
textDrawer.mustBeText = nil // this is allowed since the `mustBeText` is `String!`.
textDrawer.renderSafely() // prints -- Nothing found to be rendered
textDrawer.renderUnsafely() // crashes at runtime.
The renderUnsafaly() will crash since its not handling the nil case.
I have a one-section collection view and would like to implement Drag and Drop to allow reordering of the items. The CollectionViewItem has several textviews showing properties form my Parameter objects. Reading the doc I need to implement the NSCollectionView delegate:
func collectionView(_ collectionView: NSCollectionView, pasteboardWriterForItemAt indexPath: IndexPath) -> NSPasteboardWriting? {
let parameter = parameterForIndexPath(indexPath: indexPath)
return parameter // throws an error "Cannot convert return expression of type 'Parameter' to return type 'NSPasteboardWriting?'"
}
I have not found any information understandable for me describing the nature of the NSPasteboardWriting object. So, I have no idea how to proceed...
What is the NSPasteboardWriting object and what do I need to write in the pasteboard?
Thanks!
Disclaimer: I have struggled to find anything out there explaining this in a way that made sense to me, especially for Swift, and have had to piece the following together with a great deal of difficulty. If you know better, please tell me and I will correct it!
The "pasteboardwriter" methods (such as the one in your question) must return something identifiable for the item about to be dragged, that can be written to a pasteboard. The drag and drop methods then pass around this pasteboard item.
Most examples I've seen simply use a string representation of the object. You need this so that in the acceptDrop method you can get your hands back on the originating object (the item being dragged). Then you can re-order that item's position, or whatever action you need to take with it.
Drag and drop involves four principal steps. I'm currently doing this with a sourcelist view, so I will use that example instead of your collection view.
in viewDidLoad() register the sourcelist view to accept dropped objects. Do this by telling it which pasteboard type(s) it should accept.
// Register for the dropped object types we can accept.
sourceList.register(forDraggedTypes: [REORDER_SOURCELIST_PASTEBOARD_TYPE])
Here I'm using a custom type, REORDER_SOURCELIST_PASTEBOARD_TYPE that I define as a constant like so:
`let REORDER_SOURCELIST_PASTEBOARD_TYPE = "com.yourdomain.sourcelist.item"`
...where the value is something unique to your app ie yourdomain should be changed to something specific to your app eg com.myapp.sourcelist.item.
I define this outside any class (so it can be accessed from several classes) like so:
import Cocoa
let REORDER_SOURCELIST_PASTEBOARD_TYPE = "com.yourdomain.sourcelist.item"`
class Something {
// ...etc...
implement the view's pasteboardWriterForItem method. This varies slightly depending on the view you're using (i.e. sourcelist, collection view or whatever). For a sourcelist it looks like this:
// Return a pasteboard writer if this outlineview's item should be able to
// drag-and-drop.
func outlineView(_ outlineView: NSOutlineView, pasteboardWriterForItem item: Any) -> NSPasteboardWriting? {
let pbItem = NSPasteboardItem()
// See if the item is of the draggable kind. If so, set the pasteboard item.
if let draggableThing = ((item as? NSTreeNode)?.representedObject) as? DraggableThing {
pbItem.setString(draggableThing.uuid, forType: REORDER_SOURCELIST_PASTEBOARD_TYPE)
return pbItem;
}
return nil
}
The most notable part of that is draggableThing.uuid which is simply a string that can uniquely identify the dragged object via its pasteboard.
Figure out if your dragged item(s) can be dropped on the proposed item at the index given, and if so, return the kind of drop that should be.
func outlineView(_ outlineView: NSOutlineView, validateDrop info: NSDraggingInfo, proposedItem item: Any?, proposedChildIndex index: Int) -> NSDragOperation {
// Get the pasteboard types that this dragging item can offer. If none
// then bail out.
guard let draggingTypes = info.draggingPasteboard().types else {
return []
}
if draggingTypes.contains(REORDER_SOURCELIST_PASTEBOARD_TYPE) {
if index >= 0 && item != nil {
return .move
}
}
return []
}
Process the drop event. Do things such as moving the dragged item(s) to their new position in the data model and reload the view, or move the rows in the view.
func outlineView(_ outlineView: NSOutlineView, acceptDrop info: NSDraggingInfo, item: Any?, childIndex index: Int) -> Bool {
let pasteboard = info.draggingPasteboard()
let uuid = pasteboard.string(forType: REORDER_SOURCELIST_PASTEBOARD_TYPE)
// Go through each of the tree nodes, to see if this uuid is one of them.
var sourceNode: NSTreeNode?
if let item = item as? NSTreeNode, item.children != nil {
for node in item.children! {
if let collection = node.representedObject as? Collection {
if collection.uuid == uuid {
sourceNode = node
}
}
}
}
if sourceNode == nil {
return false
}
// Reorder the items.
let indexArr: [Int] = [1, index]
let toIndexPath = NSIndexPath(indexes: indexArr, length: 2)
treeController.move(sourceNode!, to: toIndexPath as IndexPath)
return true
}
Aside: The Cocoa mandate that we use pasteboard items for drag and drop seems very unnecessary to me --- why it can't simply pass around the originating (i.e. dragged) object I don't know! Obviously some drags originate outside the application, but for those that originate inside it, surely passing the object around would save all the hoop-jumping with the pasteboard.
The NSPasteboardWriting protocol provides methods that NSPasteboard (well, technically anyone, I guess) can use to generate different representations of an object for transferring around pasteboards, which is an older Apple concept that is used for copy/paste (hence Pasteboard) and, apparently, drag and drop in some cases.
It seems that, basically, a custom implementation of the protocol needs to implement methods that:
tell what UTI types (Apple's way of identifying file types [JPEG, GIF, TXT, DOCX, etc], similar to MIME-types—and that's a fun Google search 😬) your type can be transformed into
writeableTypes(for:) & writingOptions(forType:pasteboard:) to a lesser extent
provide a representation of your class for each of the UTI types you claimed to support
pasteboardPropertyList(forType:)
The other answer provides a straightforward implementation of this protocol for use within a single app.
But practically?
The Cocoa framework classes NSString, NSAttributedString, NSURL, NSColor, NSSound, NSImage, and NSPasteboardItem implement this protocol.
So if you've got a draggable item that can be completely represented as a URL (or String, or Color, or Sound, or Image, etc), just take the URL you have and cast it to NSPasteboardWriting?:
func collectionView(_ collectionView: NSCollectionView, pasteboardWriterForItemAt indexPath: IndexPath) -> NSPasteboardWriting? {
let url: URL? = someCodeToGetTheURL(for: indexPath) // or NSURL
return url as NSPasteboardWriting?
}
This is not helpful if you have a complicated type, but I hope it's helpful if you have a collection view of images or some other basic item.
Just updated to the newer FirebaseUI Pod - a few things have changed but one of the big ones is the way that the FUI Table View works. I had it working well on an older version but am struggling with this below - and the lack of documentation/examples.
self.dataSource = FUITableViewDataSource(query: <#T##FIRDatabaseQuery#>, view: <#T##UITableView#>, populateCell: <#T##(UITableView, IndexPath, FIRDataSnapshot) -> UITableViewCell#>)
I don't understand where the indexpath is being called from. Do I need to make a seperate NSIndexPath to pass into that? I also don't really understand where this is supposed to live - previously, with it was FirebaseTableViewDataSource, I would set it in my viewDidLoad, and it would create the cells etc straight from that. It almost now looks as though it needs to live in my cellForRowAtIndexPath. Does anyone have any advice on this?
The test for this latest version uses a tableView:bind: method (seems like a UITableView class extension they made) and I was able to get it to work.
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
let firebaseRef = FIRDatabase.database().reference().child(/*insert path for list here*/)
let query = firebaseRef.queryOrderedByKey() /*or a more sophisticated query of your choice*/
let dataSource = self.tableView.bind(to: query, populateCell: { (tableView: UITableView, indexPath: IndexPath, snapshot: FIRDataSnapshot) -> UITableViewCell in
let cell = tableView.dequeueReusableCell(withIdentifier: "cellIdentifier", for: indexPath)
let value = snapshot.value as! NSDictionary
let someProp = value["someProp"] as? String ?? ""
cell.textLabel?.text = someProp
return cell
})
}
Also make sure you are observing your query for changes or else the tableView won't get populated
self.query?.observe(.value, with: { snapshot in
})
I have a pretty complicated table view setup and I resolved to use a block structure for creating and selecting the cells to simplify the future development and changes.
The structure I'm using looks like this:
var dataSource: [(
cells:[ (type: DetailSection, createCell: ((indexPath: NSIndexPath) -> UITableViewCell), selectCell: ((indexPath: NSIndexPath) -> ())?, value: Value?)],
sectionHeader: (Int -> UITableViewHeaderFooterView)?,
sectionFooter: (Int -> UITableViewHeaderFooterView)?
)] = []
I can then set up the table in a setup function and make my delegate methods fairly simple
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = dataSource[indexPath.section].cells[indexPath.row].createCell(indexPath:indexPath)
return cell
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return dataSource[section].cells.count
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return dataSource.count
}
I have made a similar setup before in another TVC
var otherVCDataSource: [[ (type: DetailSection, createCell: ((indexPath: NSIndexPath) -> UITableViewCell), selectCell: ((indexPath: NSIndexPath) -> ())?)]] = []
This solution has worked great.
The current dataSource with the sectionHead and footer however gives me a EXC_BAD_ACCESS every time I try to access the indexPath in one of the createCell blocks.
createCell: {
(indexPath) in
let cell:CompactExerciseCell = self.tableView.dequeueReusableCellWithIdentifier(self.compactExerciseCellName, forIndexPath:indexPath) as! CompactExerciseCell
cell.nameLabel.text = "\(indexPath.row)"
cell.layoutMargins = UIEdgeInsetsZero
return cell
}
The app always crashes on
self.tableView.dequeueReusableCellWithIdentifier(self.compactExerciseCellName, forIndexPath:indexPath)
What am I missing here? Why can't I access the indexPath in the new structure when it works fine in the old structure? What is different in the memory management between this tuple and the array?
UPDATE:
So I had a deadline to keep and I finally had to give up and rework the data structure.
My first attempt was to instead of sending the indexPath as a parameter send the row and section and rebuild an indexPath inside the block. This worked for everything inside the data structure but if I pushed another view controller on a cell click I got another extremely weird crash (some malloc error, which is strange as I use ARC) when dequeuing cells in the next VC.
I tried to dig around in this crash as well but there was no more time to spend on this so I had to move on to another solution.
Instead of this tuple-array [([],,)] I made two arrays; one for the cells and one for the headers and footers. This structure removed the problem of the indexPath crash but I still had the issue in the next VC that didn't stop crashing when dequeueing the cells.
The final solution, or workaround, was to access the cell creator and selector "safely" with this extension:
extension Array {
subscript (safe index: Int) -> Element? {
return indices ~= index ? self[index] : nil
}
}
basically the return statement in the tableView delegate functions then looks like this:
return dataSource[safe:indexPath.section]?[safe:indexPath.row]?.createCell?(indexPath: indexPath)
instead of
return dataSource[indexPath.section][indexPath.row].createCell?(indexPath: indexPath)
I can't see how it makes any difference to the next VC as the cell shouldn't even exist if there was an issue with executing nil or looking for non existing indexes in the data structure but this still solved the problem I was having with the dequeueing of cells in the next VC.
I still have no clue why the change of data structure and the safe extension for getting values from an array helps and if someone has any idea I would be happy to hear it but I can not at this time experiment more with the solution. My guess is that the safe access of the values reallocated the values somehow and stopped them from being released. Maybe the tuple kept the compiler from understanding that the values should be kept in memory or maybe I just have a ghost in my code somewhere. I hope one day I can go back and dig through it in more detail...
This is NOT an answer to the question but rather a workaround if someone ends up in this hole and has to get out:
First use this extension for array:
extension Array {
subscript (safe index: Int) -> Element? {
return indices ~= index ? self[index] : nil
}
}
And then in the table view delegate functions use the extension like this
let cell = dataSource[safe:indexPath.section]?[safe:indexPath.row]?.createCell?(indexPath: indexPath)
If this does not work remove the tuple from the data structure and you should have a working solution.
I wish you better luck with this issue than I had.
you have to register your tableview cell for particular cell idntifier in viewdidload.
eg.tableview.registerNib(UINib(nibName: "cell_nib_name", bundle: NSBundle.mainBundle()), forCellReuseIdentifier: "cell_identifier");
for deque cell
let cell:CompactExerciseCell = self.tableView.dequeueReusableCellWithIdentifier(self.compactExerciseCellName, forIndexPath:indexPath) as! CompactExerciseCell
like this.
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as UITableViewCell
var query = PFQuery(className:"category")
let object = objects[indexPath.row] as String
query.whereKey("type", equalTo:"DRUM")
query.findObjectsInBackgroundWithBlock {
(objects: [AnyObject]!, error: NSError!) -> Void in
if error == nil {
for object in objects {
NSLog("%#", object.objectId)
let abc = object["link"]
println("the web is \(abc)")
cell.textLabel!.text = "\(abc)"
}
} else {
NSLog("Error: %# %#", error, error.userInfo!)
}
}
return cell
}
after add the let object = objects[indexPath.row] as String can't load the view, delete the line show only one row successfully.
First I advise you to get your cell data outside cellForRowAtIndexPath. This function is not a good place to receive data from parse. Make another function and create a class variable and put handle getting data from there.
let object = objects[indexPath.row] as String
for object in objects
Try not to use same variable names for different stuff, as they will confuse you.
This line is not contributing to anything at the moment it seems. Try deleting it:
let object = objects[indexPath.row] as String
First lets have principles in mind. Don't ever update UI from a separate thread, its behavior is unexpected or undefined. It works or works weird.
Second, the problem you have is the when the VC gets loaded the tableView's datasource is called there and then on the main thread. Now you tried to add something on the cell by doing a Async call in separate thread which will take time and main thread is not waiting when the call to parse is being done. If you have difficulty in Async please take a look at the documentation its really important to get a good grasp of the few terms and the principles.
The thing is your main thread runs top to bottom without waiting each call to server thats async in the cell generation. So the result of that call will post later on and you are not posting on main thread too.
Moreover, i would suggest you don't do this approach for big projects or manageable code base. I generally do is:
when the view loads call the Parse with the needed information
Wait for that on a computed variable which i will observe to reload table views once I'm conformed i have the data.
Initially table view will have 0 rows and thats fine. Ill make a spinner dance during that time.
I hope i made some issues clear. Hope it helps you. Cheers!
//a computed var that is initialized to empty array of string or anything you like
//we are observing the value of datas. Observer Pattern.
var datas = [String](){
didSet{
dispatch_async(dispatch_get_main_queue(), {
//we might be called from the parse block which executes in seperate thread
tableView.reloadData()
})
}
}
func viewDidLoad(){
super.viewDidLoad()
//call the parse to fetch the data and store in the above variable
//when this succeeds then the table will be reloaded automatically
getDataFromParse()
}
//get the data: make it specific to your needs
func getDataFromParse(){
var query = PFQuery(className:"category")
//let object = objects[indexPath.row] as String //where do you use this in this block
var tempHolder = [String]()
query.whereKey("type", equalTo:"DRUM")
query.findObjectsInBackgroundWithBlock {
(objects: [AnyObject]?, error: NSError?) -> Void in
if error == nil && objects != nil {
for object in objects!{
//dont forget to cast it to PFObject
let abc = (object as! PFObject).objectForKey("link") as? String ?? "" //or as! String
println("the web is \(abc)")
tempHolder.append(abc)
}
} else {
print("error") //do some checks here
}
}
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! UITableViewCell
cell.textLabel!.text = datas[indexPath.row]
return cell
}