Storyboard IBOutlet / IBAction Exists

ID

swift.storyboard_iboutlet_exists

Severity

low

Remediation Complexity

trivial

Remediation Risk

low

Remediation Effort

low

Resource

Reliability

Language

Swift

Tags

reliability, suspicious-code

Description

Reports <outlet>, <outletCollection> and <action> connections in .storyboard and .xib files whose name does not match any property or method declared on the owning customClass. Interface Builder binds these connections at runtime via Key-Value Coding; a name that the destination class does not declare raises setValue:forUndefinedKey: (outlets) or -[Class doesNotRecognizeSelector:] (actions) when the scene is instantiated, and the app crashes on the first navigation to it.

<viewController customClass="HomeViewController" ...>
  <connections>
    <outlet property="missingLabel" destination="abc"/>   <!-- FLAW: not on HomeViewController -->
    <action selector="missingTapped:" destination="abc"/> <!-- FLAW: not on HomeViewController -->
    <outlet property="titleLabel" destination="def"/>     <!-- OK -->
  </connections>
</viewController>

Rationale

Connection names are typed as strings in the XIB/storyboard XML and are not validated by the Swift compiler. Renames, deletions, and copy-paste from other scenes silently leave stale outlet / action references behind. The bug only surfaces the first time the screen is presented — usually in QA, occasionally in production.

The rule resolves each connection’s owner by walking the XML tree upward to the nearest ancestor with a customClass attribute, then checks the Swift type model for a matching property (outlet) or method (action). Selector arguments are stripped — multi-arg selectors like tableView:didSelectRowAtIndexPath: are matched against the method name tableView.

Connections whose owner cannot be resolved are skipped: external modules (customModuleProvider != "target") are out of view, and missing customClass references are already reported by swift.storyboard_custom_class_exists.

Remediation

Either declare the expected @IBOutlet / @IBAction on the owning Swift class, or open the storyboard in Xcode, right-click the source view in the Connections Inspector, and delete the stale connection.