Xcode 11 Unknown class in Interface Builder file.

Hi


I recently updated to Xcode 11 GM seed. However I've noticed that the dreaded "Unknown class in Interface Builder file" is crashing my app. I haven't changed any class names or storyboards. Interestingly the app runs perfectly in teh simulator, but crashed on my phone.


Here is what is being printed in the output window:

MyAppName[9513:4230222] Unknown class _TtC10MyApp24SlideTableViewController in Interface Builder file.

Could not cast value of type 'UIViewController' (0x1ebe282b0) to 'MyApp.SlideTableViewController' (0x104d05e08).

MyAppName[9513:4230222] Could not cast value of type 'UIViewController' (0x1ebe282b0) to 'MyApp.SlideTableViewController' (0x104d05e08).


I've deleted the class and recreated, removed the View Controller from the story board, made sure the view controller is references correctly as is the target, but the problem persists and I'm out of ideas.

Is there a "reset" of the storyboard to reference the elements? Or some other way to resolve this?


Many thanks


Craig

Answered by craigaps in 384005022

Found a solution that worked for me. In short, try removing the

Class
,
Module
, and
Storyboard ID
attributes of the
YourViewController
in the storyboard, and then re-add them back. This updates some values in the storyboard source code.


Hope it helps someone others.

Did you do a clean Build folder (Product menu) ?


Did you also restart the Mac after upgrade to XCode 11 GM ? You could.


Finally, there is a new GM 2 seed which corrected some bugs (did not read about your point however).


If that's not enough, could you post the code where you get the error as well as the definition of the class of the involved objects ?

Hi

Thanks for the reply. I've done a clean build and deleted the files and folders in the derived data folder and installed GM 2 ssed and rebooted the Mac. I read the release notes for the GM 2 seed and saw a similar error under the Reality Composer heading, but the suggested workaround didn't really apply even though I did "manually" link my custom framework as a binary in the build phrase.


With that said, here is code that will crash the device but run ok on the simulator:


let storyboard = UIStoryboard(name: "Verification", bundle: nil)
 let slideViewController = storyboard.instantiateViewController(withIdentifier: "Slide") as! SlideTableViewController

The code to SlideTableViewController:


import VerifyKit
import UIKit

class SlideTableViewController: UIViewController {
    // MARK: Instance Properties

    /// The heights of the slide view.
    let closedHeight: CGFloat = UIApplication.shared.keyWindow!.safeAreaInsets.bottom + 90
    let openHeight: CGFloat = UIApplication.shared.keyWindow!.frame.height - UIApplication.shared.keyWindow!.safeAreaInsets.top - 140

    /// The mid point between the `closedHeight` and `openHeight` of the slide view.
    lazy var midPoint = (openHeight + closedHeight) / 2

    /// The sticky points to which the slide view should snap to.
    lazy var middleStickyPoints: [CGFloat] = [midPoint]

    /// The sorted array of all sticky points, including `closedHeight` and `openHeight`.
    lazy var allStickyPoints = ([closedHeight, openHeight] + middleStickyPoints).sorted()

    /// The transaction.
    var transaction: PendingTransaction?

    /// The data to display in the slide table view.
    private var dataToDisplay: [(title: String, body: String)]?

    /// The bottom cosntraint of the slide view to animate.
    lazy var bottomConstraint = view.superview!.bottomAnchor.constraint(equalTo: view.topAnchor, constant: closedHeight)

    /// The grip bar.
    let gripBar = UIView()

    /// The close button.
    let closeButton = UIButton()

    /// The dim view.
    let dimView = UIView()

    /// The parent view controller.
    lazy var parentVC = parent as! ChallengeViewController

    /// The open state of the slide view.
    var isOpen: Bool = false {
        didSet {
            if isOpen {
                // Move approve/deny buttons to top of screen.
                parentVC.buttonCollectionViewConstraint.constant = openHeight - UIApplication.shared.keyWindow!.safeAreaInsets.bottom + 20
            } else {
                // Move approve/deny buttons to bottom of screen.
                parentVC.buttonCollectionViewConstraint.constant = 110
            }
        }
    }

    // MARK: Control References

    @IBOutlet var previewLabel: UILabel!
    @IBOutlet var tableView: UITableView!

    // MARK: View functions and events

    /// Called after the controller'€™s view is loaded into memory.
    override func viewDidLoad() {
        super.viewDidLoad()

        tableView.delegate = self
        tableView.dataSource = self

        // Hide the last separator line by adding a footer view.
        tableView.tableFooterView = UIView(frame: CGRect(origin: .zero, size: CGSize(width: 0, height: 1)))

        // Pan the slide view.
        let panGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(panSlideView))
        view.addGestureRecognizer(panGestureRecognizer)

        // Activate slide table view constraints.
        tableView.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([tableView.widthAnchor.constraint(equalTo: view.widthAnchor),
                                     tableView.heightAnchor.constraint(equalToConstant: openHeight - 26),
                                     tableView.topAnchor.constraint(equalTo: view.topAnchor, constant: 26)])

        // Display the grip bar.
        gripBar.layer.cornerRadius = 3
        view.addSubview(gripBar)

        // Activate grip bar constraints.
        gripBar.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([gripBar.topAnchor.constraint(equalTo: view.topAnchor, constant: 10),
                                     gripBar.centerXAnchor.constraint(equalTo: view.centerXAnchor),
                                     gripBar.heightAnchor.constraint(equalToConstant: 6),
                                     gripBar.widthAnchor.constraint(equalToConstant: 42)])

        // Display the close button.
        closeButton.setImage(UIImage(named: "Close")?.withRenderingMode(.alwaysTemplate), for: .normal)
        closeButton.layer.cornerRadius = 16
        closeButton.addTarget(self, action: #selector(closeSlideView), for: .touchUpInside)
        view.addSubview(closeButton)

        // Activate close button constraints.
        closeButton.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([closeButton.topAnchor.constraint(equalTo: view.topAnchor, constant: 16),
                                     view.trailingAnchor.constraint(equalTo: closeButton.trailingAnchor, constant: 16),
                                     closeButton.widthAnchor.constraint(equalToConstant: 32),
                                     closeButton.heightAnchor.constraint(equalToConstant: 32)])
    }

    /// Notifies the view controller that its view is about to be added to a view hierarchy.
    /// - parameter animated: If true, the view is being added to the window using an animation.
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)

        // Set up dim view.
        dimView.frame = view.bounds
        dimView.backgroundColor = ThemeManager.shared.current.dimBackgroundColor
        parentVC.view.insertSubview(dimView, belowSubview: parentVC.buttonCollectionView)

        // Activate slide view constraints.
        view.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([bottomConstraint,
                                     view.leadingAnchor.constraint(equalTo: view.superview!.leadingAnchor),
                                     view.trailingAnchor.constraint(equalTo: view.superview!.trailingAnchor),
                                     view.bottomAnchor.constraint(equalTo: view.superview!.bottomAnchor)])

        // Get the table representation of the transaction data.
        dataToDisplay = transaction?.tableRepresentation

        // Set the preview label's text to the transaction's type.
        if let type = transaction?.additionalData[.type] {
            previewLabel.text = "\(type.capitalized) \(String.localize("ChallengeInfo"))"
        }
    }

    
    /// Called when the user taps the `closeButton`.
    @objc func closeSlideView() {
        animateSlideView(to: allStickyPoints[0])
    }

    /// Handles the pan gestures on the slide view to animate its states.
    /// - parameter recognizer: The gesture recognizer of the pan event.
    @objc func panSlideView(_ recognizer: UIPanGestureRecognizer) {
        switch recognizer.state {
        case .changed:
            let translation = recognizer.translation(in: view)
            let panPosition = bottomConstraint.constant - translation.y

            // Restrict the pan between `closedHeight` and `openHeight`.
            if panPosition > closedHeight, panPosition < openHeight {
                bottomConstraint.constant = panPosition
                recognizer.setTranslation(.zero, in: view)
            }
        case .ended:
            // Animate the slide view.
            let velocity = recognizer.velocity(in: view)
            animateSlideView(with: velocity.y)
        default:
            break
        }
    }

    /// Handles the animations of the slide view to its closest sticky point.
    /// - parameter velocity: The velocity the slide view was released with.
    private func animateSlideView(with velocity: CGFloat) {
        let velocityThreshold: CGFloat = 300
        var closestIndex = allStickyPoints.closestIndex(to: bottomConstraint.constant)!

        if velocity < -velocityThreshold {
            // Swipe up velocity threshold exceeded, snap to next sticky point.
            closestIndex = min(allStickyPoints.count - 1, closestIndex + 1)
        } else if velocity > velocityThreshold {
            // Swipe down velocity threshold exceeded, snap to previous sticky point.
            closestIndex = max(0, closestIndex - 1)
        }

        // Animate the slide view's bottom constraint to its closest sticky point index.
        animateSlideView(to: allStickyPoints[closestIndex])
    }

    /// Handles the animations of the slide view to the given sticky point index.
    /// - parameter point: The point to animate the slide view to.
    private func animateSlideView(to point: CGFloat) {
        isOpen = point == allStickyPoints.last

        let transitionAnimator = UIViewPropertyAnimator(duration: 0.5, dampingRatio: 0.75, animations: {
            self.bottomConstraint.constant = point
            self.view.superview!.layoutIfNeeded()
        })
        transitionAnimator.startAnimation()
    }

    /// Returns the fraction completed by the slide view between the given `start` and `end` points, based on the slide view's current position.
    /// - parameter start: The starting point.
    /// - parameter end: The ending point.
    func fractionCompleteBySlideView(from start: CGFloat, to end: CGFloat) -> CGFloat {
        return (bottomConstraint.constant - start) / (end - start)
    }

    /// Called to notify the view controller that its view has just laid out its subviews.
    override func viewDidLayoutSubviews() {
        // Animates the `closeButton` and `dimView` opacity.
        closeButton.alpha = fractionCompleteBySlideView(from: closedHeight, to: midPoint)
        dimView.alpha = fractionCompleteBySlideView(from: midPoint, to: openHeight)

        // Animates the `tableView` and `previewLabel` opacity.
        tableView.alpha = fractionCompleteBySlideView(from: closedHeight, to: midPoint)
        previewLabel.alpha = fractionCompleteBySlideView(from: midPoint, to: closedHeight)

        // Animate roundness of top left and top right corners.
        view.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
        view.layer.cornerRadius = 16 * fractionCompleteBySlideView(from: closedHeight, to: openHeight)
    }
}

The reference to PendingTransaction class is located in VerifyKit framework. I can't understand why this view controller fails but others are display ok in both devive and simulator.


Appreciate the help


Craig

Accepted Answer

Found a solution that worked for me. In short, try removing the

Class
,
Module
, and
Storyboard ID
attributes of the
YourViewController
in the storyboard, and then re-add them back. This updates some values in the storyboard source code.


Hope it helps someone others.

19
You should check the Module of the storyboard file in Identity Inspector, check if it is the same with your project and not "none" or something else.

This question is driving me crazy After searching on the internet, I couldn't find any solution to my problem. After comparing with other projects, I found that my project module name is a bit different. I changed the product module name to the default $(PRODUCT_NAME:c99extidentifier) and everything went fine Hope this helps someone else.

That Person is Right

Craigaps' answer is spot on (remove class, module, etc. and put them in again).

Why?

Back in the day, Module could be "none" and everything worked.

Once you meddle with your Nib (Xib, or storyboard, or whatever you have), you can no longer do that. Your Storyboard file might still be good even in 2023 (as mine was) but once you muck with it, you have to follow that advice.

Old

Class: YourClass
Module: None (grayed out)
[not checked] inherit Module from Target

.

New

Class: YourClass
Module: Some module (still grayed out)
[checked] inherit Module from Target

.

[By the way this should be totally obvious to an AI someday, but never to a human who is not part of the Xcode team.]

Xcode 11 Unknown class in Interface Builder file.
 
 
Q