skip to Main Content
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?

4

Answers


  1. 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.

    Login or Signup to reply.
  2. 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.

    Login or Signup to reply.
  3. 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
    
    Login or Signup to reply.
  4. 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.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search