Xcode 14中IBDesignable预览崩溃,但IBInspectable属性正常可用
解决IBDesignable XIB预览崩溃问题
我正在开发一个用于绘制扑克牌的可复用视图,该视图由XIB和Swift文件组成。包含该可复用视图的界面在模拟器中构建并运行完全正常,但在XIB文件中无法正常预览:属性检查器显示“Designables: Error”,预览区域的可复用视图处显示“PlayingCardView ‼️Designable”。
问题代码
@IBDesignable class PlayingCardView: UIView { @IBOutlet var contentView: UIView! @IBOutlet private weak var cardBackground: UIImageView! @IBOutlet private weak var topLeadingLabel: UILabel! @IBOutlet private weak var bottomTrailingLabel: UILabel! @IBInspectable var rank: String = "" { didSet { topLeadingLabel.text = rank bottomTrailingLabel.text = rank } } @IBInspectable var suit: String = "" { didSet { topLeadingLabel.text?.append("\n\(suit)") bottomTrailingLabel.text?.append("\n\(suit)") } } private var isUpsideDown: Bool = false { didSet { if isUpsideDown { topLeadingLabel.alpha = 0 bottomTrailingLabel.alpha = 0 cardBackground.alpha = 1 } else { topLeadingLabel.alpha = 1 bottomTrailingLabel.alpha = 1 cardBackground.alpha = 0 } } } override init(frame: CGRect) { super.init(frame: frame) loadNibFile() setup() } required init?(coder: NSCoder) { super.init(coder: coder) loadNibFile() setup() } func setup() { isUpsideDown = false contentView.layer.cornerRadius = 16 topLeadingLabel.text = "5\n❤️" bottomTrailingLabel.text = "5\n❤️" bottomTrailingLabel.transform = CGAffineTransform.identity.rotated(by: CGFloat.pi) let tapGesture = UITapGestureRecognizer(target: self, action: #selector(flipCard(_:))) contentView.addGestureRecognizer(tapGesture) } private func loadNibFile() { Bundle.main.loadNibNamed("PlayingCardView", owner: self) addSubview(contentView) contentView.frame = self.bounds contentView.autoresizingMask = [.flexibleWidth, .flexibleHeight] contentView.clipsToBounds = true } @objc private func flipCard(_ sender: UITapGestureRecognizer) { isUpsideDown.toggle() } }
解决方法
1. 修复XIB加载的Bundle逻辑
IB预览环境下,Bundle.main并非应用主bundle,会导致XIB加载失败。替换为当前类的Bundle:
private func loadNibFile() { let bundle = Bundle(for: type(of: self)) bundle.loadNibNamed("PlayingCardView", owner: self) addSubview(contentView) contentView.frame = self.bounds contentView.autoresizingMask = [.flexibleWidth, .flexibleHeight] contentView.clipsToBounds = true }
2. 给IBOutlet操作添加安全校验
IB预览时控件可能未完全初始化,直接访问会触发空指针崩溃,所有IBOutlet操作前加可选链:
@IBInspectable var rank: String = "" { didSet { topLeadingLabel?.text = rank bottomTrailingLabel?.text = rank } } @IBInspectable var suit: String = "" { didSet { topLeadingLabel?.text?.append("\n\(suit)") bottomTrailingLabel?.text?.append("\n\(suit)") } } private var isUpsideDown: Bool = false { didSet { topLeadingLabel?.alpha = isUpsideDown ? 0 : 1 bottomTrailingLabel?.alpha = isUpsideDown ? 0 : 1 cardBackground?.alpha = isUpsideDown ? 1 : 0 } }
3. 完善setup方法的安全调用
同样给setup里的控件操作加可选校验:
func setup() { isUpsideDown = false contentView?.layer.cornerRadius = 16 topLeadingLabel?.text = "5\n❤️" bottomTrailingLabel?.text = "5\n❤️" bottomTrailingLabel?.transform = CGAffineTransform.identity.rotated(by: CGFloat.pi) if let contentView = contentView { let tapGesture = UITapGestureRecognizer(target: self, action: #selector(flipCard(_:))) contentView.addGestureRecognizer(tapGesture) } }
4. 额外排查步骤
- 按
Cmd+Shift+K清理构建文件夹,再按Cmd+Option+Shift+K清理派生数据 - 重启Xcode后重新打开XIB预览
- 确认XIB的
File's Owner已设置为PlayingCardView,且所有IBOutlet连接无误
内容的提问来源于stack exchange,提问作者boring_ape
相关产品推荐
相关产品推荐

