1

I have a xib file which I have been able to load successfully:

    let xib = MyBundle.bundle.loadNibNamed("MyXib", owner: self, options: nil)!
    let confirmView = xib[0] as! MyXib

This will load and appear correctly on screen.

The class is set correctly in the xib and it casts correctly when checked in code. The file owner is also set correctly.

However, if I ever drag an object from the xib file to its class (e.g. set a button reference) then the app will crash as soon as this xib is loaded.

*** Terminating app due to uncaught exception 'NSUnknownKeyException', reason: [MyXib 0x7fcaf0d3eb50 setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key button.

The init is called:

required init?(coder: NSCoder) {
    super.init(coder: coder)
}

Is called so I know it finishes initializing.

There are no broken outlets or anything funky. I am literally just dragging a button and creating an outlet and then running. If I delete the outlet it will work. If I add any outlet again to anything it will crash with this same issue.

Any suggestions as to what else might be the cause? I've already looked through the main causes of this.

enter image description here

enter image description here

Solution

As suggested below I removed the file owner, deleted all the outlets and used the exact loading syntax DonMag suggested.

Aggressor
  • 13,323
  • 24
  • 103
  • 182
  • Ah yes the silent downvote. At least explain your issue if you are going to downvote. – Aggressor Jun 09 '20 at 16:40
  • The error message looks a little odd. When I've had this, I believe it has told me which class it was complaining about in the `[ setValue:forUndefinedKey:]` part. – Phillip Mills Jun 09 '20 at 16:46
  • Yea sorry the tags were messing up the formatting. It does referenced my class in the error – Aggressor Jun 09 '20 at 16:49
  • And the MyXib class really does have an outlet named "button".... Strange. – Phillip Mills Jun 09 '20 at 16:50
  • The file is also linked because if I go to file owner, and click the -> arrow it will take me to the class. If I add an outlet, it shows up as a filled in dot. I also opened the file text and purged everything, got it loading, then just added a basic outlet reference and again it crashes as soon as its loaded – Aggressor Jun 09 '20 at 16:53

2 Answers2

2

To use your XIB that way, you want the XIB's "root view" to be set to your custom class:

enter image description here

You'll need to un-do and re-do your @IBOutlet connection(s) after making that change.

Here's an example XIB (named BasicXIBView.xib):

<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="16096" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
    <device id="retina3_5" orientation="portrait" appearance="light"/>
    <dependencies>
        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="16087"/>
        <capability name="Safe area layout guides" minToolsVersion="9.0"/>
        <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
    </dependencies>
    <objects>
        <placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
        <placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
        <view contentMode="scaleToFill" id="iN0-l3-epB" customClass="BasicXIBView" customModule="PassBackNavController" customModuleProvider="target">
            <rect key="frame" x="0.0" y="0.0" width="320" height="93"/>
            <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
            <subviews>
                <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="kuh-dy-Zhq">
                    <rect key="frame" x="8" y="8" width="304" height="77"/>
                    <subviews>
                        <button opaque="NO" contentMode="scaleToFill" verticalHuggingPriority="1000" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="8KY-Pe-K0s">
                            <rect key="frame" x="162" y="20" width="122" height="37"/>
                            <color key="backgroundColor" red="1" green="0.14913141730000001" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
                            <state key="normal" title="Cancel"/>
                            <connections>
                                <action selector="cancelTapped:" destination="iN0-l3-epB" eventType="touchUpInside" id="w6M-G8-kcb"/>
                            </connections>
                        </button>
                        <button opaque="NO" contentMode="scaleToFill" verticalHuggingPriority="1000" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="ny7-lN-ZrV">
                            <rect key="frame" x="20" y="20" width="122" height="37"/>
                            <color key="backgroundColor" red="1" green="0.14913141730000001" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
                            <state key="normal" title="Continue"/>
                            <connections>
                                <action selector="continueTapped:" destination="iN0-l3-epB" eventType="touchUpInside" id="32u-xJ-uxC"/>
                            </connections>
                        </button>
                    </subviews>
                    <color key="backgroundColor" red="0.46202266219999999" green="0.83828371759999998" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
                    <constraints>
                        <constraint firstItem="ny7-lN-ZrV" firstAttribute="leading" secondItem="kuh-dy-Zhq" secondAttribute="leading" constant="20" id="2rf-kC-UJW"/>
                        <constraint firstItem="ny7-lN-ZrV" firstAttribute="width" secondItem="8KY-Pe-K0s" secondAttribute="width" id="4wL-HV-gXu"/>
                        <constraint firstItem="8KY-Pe-K0s" firstAttribute="leading" secondItem="ny7-lN-ZrV" secondAttribute="trailing" constant="20" id="DM1-NU-W8P"/>
                        <constraint firstAttribute="trailing" secondItem="8KY-Pe-K0s" secondAttribute="trailing" constant="20" id="cyP-o4-Bac"/>
                        <constraint firstAttribute="bottom" secondItem="ny7-lN-ZrV" secondAttribute="bottom" constant="20" id="iaW-ir-x5w"/>
                        <constraint firstAttribute="bottom" secondItem="8KY-Pe-K0s" secondAttribute="bottom" constant="20" id="m9V-Vf-AAA"/>
                        <constraint firstItem="8KY-Pe-K0s" firstAttribute="top" secondItem="kuh-dy-Zhq" secondAttribute="top" constant="20" id="q8j-Ce-ubu"/>
                        <constraint firstItem="ny7-lN-ZrV" firstAttribute="top" secondItem="kuh-dy-Zhq" secondAttribute="top" constant="20" id="rBK-uY-4NU"/>
                    </constraints>
                </view>
            </subviews>
            <color key="backgroundColor" red="1" green="0.83234566450000003" blue="0.47320586440000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
            <constraints>
                <constraint firstItem="kuh-dy-Zhq" firstAttribute="leading" secondItem="vUN-kp-3ea" secondAttribute="leading" constant="8" id="0ST-Ya-cGb"/>
                <constraint firstItem="vUN-kp-3ea" firstAttribute="trailing" secondItem="kuh-dy-Zhq" secondAttribute="trailing" constant="8" id="3zW-8F-7Ae"/>
                <constraint firstItem="vUN-kp-3ea" firstAttribute="bottom" secondItem="kuh-dy-Zhq" secondAttribute="bottom" constant="8" id="7GJ-Vc-C2u"/>
                <constraint firstItem="kuh-dy-Zhq" firstAttribute="top" secondItem="vUN-kp-3ea" secondAttribute="top" constant="8" id="peG-dz-caP"/>
            </constraints>
            <freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
            <viewLayoutGuide key="safeArea" id="vUN-kp-3ea"/>
            <connections>
                <outlet property="backgroundView" destination="kuh-dy-Zhq" id="GTo-Hw-kwM"/>
            </connections>
            <point key="canvasLocation" x="138.75" y="-89.375"/>
        </view>
    </objects>
</document>

and here is code for the class and example use in a view controller:

class BasicXIBView: UIView {

    @IBOutlet var backgroundView: UIView!

    @IBAction func continueTapped(_ sender: Any) {
        print("Continue Button Tapped!")
    }

    @IBAction func cancelTapped(_ sender: Any) {
        print("Cancel Button Tapped!")
    }

}

class TestXIBViewController: UIViewController {

    var confirmView: BasicXIBView!

    override func viewDidLoad() {
        super.viewDidLoad()

        let xib = Bundle.main.loadNibNamed("BasicXIBView", owner: self, options: nil)!

        // make sure it loads correctly
        guard let v = xib[0] as? BasicXIBView else {
            fatalError("XIB setup incorrectly!")
        }

        // use it as our class's confirmView
        confirmView = v

        // add it
        view.addSubview(confirmView)

        // use auto-layout
        confirmView.translatesAutoresizingMaskIntoConstraints = false

        // respect safe-area
        let g = view.safeAreaLayoutGuide

        // constrain it centered X and Y,
        //  80% of the width
        //  use its internal constraints to determine its height
        NSLayoutConstraint.activate([
            confirmView.centerYAnchor.constraint(equalTo: g.centerYAnchor),
            confirmView.centerXAnchor.constraint(equalTo: g.centerXAnchor),
            confirmView.widthAnchor.constraint(equalTo: g.widthAnchor, multiplier: 0.8),
        ])

        // demo that we have access to backgroundView in the XIB
        confirmView.backgroundView.backgroundColor = .green

    }

}
DonMag
  • 69,424
  • 5
  • 50
  • 86
  • I had both the file owner and that top level view set to the class. I never tried just setting the top level view to the class and leaving the file owner blank though – Aggressor Jun 09 '20 at 20:06
  • As soon as I reference the buttons it crashes again :/ – Aggressor Jun 09 '20 at 20:17
  • Did you delete the previously set outlets? Did you try my example? – DonMag Jun 09 '20 at 20:18
  • I did, but I will try creating it from scratch again – Aggressor Jun 09 '20 at 20:18
  • Hmmm I am not sure why it works all of the sudden, but when I changed my loading syntax to your suggestion it has started working. My previous load code: func loadViewFromNib() -> U_MakePaymentConfirmation! { let bundle = Bundle(for: type(of: self)) let nib = UINib(nibName: "MakePaymentConfirmation", bundle: bundle) return nib.instantiate(withOwner: self, options: nil).first as! U_MakePaymentConfirmation } Did not work. Do you have any idea what the difference is? – Aggressor Jun 09 '20 at 21:01
  • Also note, as you suggested fresh outlets MUST be re-created. If I ever connected a button back to an old outlet it crashes. My final setup had no file owner in the Xib file, and the top level View object referenced my custom class and fresh outlets created for everything and used your exact loading syntax (which failed with my loading syntax) – Aggressor Jun 09 '20 at 21:02
  • @Aggressor - hard to say why your `loadViewFromNib()` func wasn't working without seeing your full code (for the classes, plus xib source, etc). Maybe you had a typo in the file name? – DonMag Jun 09 '20 at 21:30
  • No because the xib would load but its outlets were nil. Regardless thank you for the help this was (and is) very confusing – Aggressor Jun 09 '20 at 22:50
0

I think you don't want to set the owner of the nib. Unless the case is that the self object is the same class as the XIB top level object

What happens when you use this style of instantiation?

let nib = NSNib(nibNamed: "MyXib", bundle: nil)
var array: NSArray?
if let nib = nib {
  let result = nib.instantiate(withOwner: nil, topLevelObjects: &array)
  if result, let array = array { //first object is nominally the one you want }
}

or using your style:

let xib = MyBundle.bundle.loadNibNamed("MyXib", owner: nil, options: nil)!
let confirmView = xib[0] as! MyXib
Warren Burton
  • 17,451
  • 3
  • 53
  • 73
  • Let me take a look and try. I was having a viewcontroller call this xib and I tried passing in the viewcontroller as the owner. I will try using a nil owner instead as you suggest – Aggressor Jun 09 '20 at 16:57
  • Same issue. I also added a screen shot above which shows the background outlet being set. When I try this I still get the error: *** Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[ setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key background.' – Aggressor Jun 09 '20 at 17:04
  • Although in this case is says its an NSObject which is interesting, as before it would say the class name – Aggressor Jun 09 '20 at 17:05
  • In the XIB , is the files owner placeholder set to `MyXib` or whatever your class is? – Warren Burton Jun 09 '20 at 17:12
  • Yes, I added a screen shot as well. Also note, clicking the -> brings me to the class so I know its connected – Aggressor Jun 09 '20 at 17:15
  • Does your extracted array have more than one item. i.e is the assumption that the first item is the requested object correct? – Warren Burton Jun 09 '20 at 17:43
  • It just has the one item – Aggressor Jun 09 '20 at 18:07