【问题标题】:Is it possible to create an IBOutlet in a non-view class?是否可以在非视图类中创建 IBOutlet?
【发布时间】:2017-10-23 06:21:24
【问题描述】:

我正在创建一个自定义呈现控制器,用于在呈现视图控制器时使背景变暗。演示控制器在过渡开始时添加了几个子视图,效果很好。

但是,我想在 Interface Builder 中设置 chrome(演示文稿“框架”),因为这样布局更容易。因此,我创建了一个用于设计 chrome 的 XIB 文件。它包括一个半透明的背景视图和左上角的 ❌ 按钮,用于关闭呈现的视图控制器。对于这些子视图我需要在我的演示控制器中使用插座和操作(这不是UIViewController 子类)。

为了实现这一点,我在 Interface Builder 和实例化视图时的代码中将 XIB 文件的所有者设置为我的自定义演示控制器:

lazy var dimmingView = Bundle.main.loadNibNamed("PresentationChromeView", 
                                   owner: self, 
                                   options: nil)?.first 
                                   as! UIView

然后我通过 CTRL+拖动到我的演示控制器创建了相应的出口和操作:

@IBOutlet var closeButton: UIButton!

@IBAction func closeButtonTapped(_ sender: Any) {
    presentingViewController.dismiss(animated: true, completion: nil)
}

但是,在运行时应用程序崩溃,因为 UIKit 找不到插座键,并且在删除插座时,操作方法不会被触发。所以在这两种情况下都没有建立连接。

Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<_SwiftValue 0x600000458810> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key closeButton.'

我能想到这不起作用的唯一原因是,不允许使用不继承自 UIViewUIViewController 的类创建出口和操作。

这个假设正确吗?

有没有办法使用非视图(-控制器)类创建出口和操作?

【问题讨论】:

  • 听起来您需要将“ChromeView”实例化为子视图控制器......然后使用委托模式将“X 按钮”点击“传递给执行实际演示的视图控制器”。
  • 如果我不让它运行,我会退回到这种模式。但是,如果@floschliep 是正确的您可以在任何继承自 NSObject 的类中创建 IBOutlets,那么我只是想知道为什么这种方法不起作用。
  • 嗯,创建 IBOutlets 和实例化连接这些出口的代码是有区别的。我对你的结构有点困惑?您在 XIB 中有一个 UIView + UIButton ... XIB 的所有者 不是 UIView 或 UIViewController 子类?问题是...如果您所做的只是从 XIB 加载 UIView(和子视图),但您不加载/实例化与其关联的代码,则 IBOutlet 和 IBAction 不存在.
  • 嗯...我重新阅读了您的问题,现在我有点困惑...您说您将 XIB 文件的所有者设置为“自定义演示控制器”-但是 那个 控制器不是UIViewUIViewController 子类?你能举一个小例子来说明你在做什么(并抛出错误)吗?
  • 是的,这正是问题所在:是否可以将UIViewUIViewController 子类以外的类设置为视图的文件所有者(以便在那里创建出口和操作) ?

标签: ios uiview ibaction iboutlet uipresentationcontroller


【解决方案1】:

您可以在任何继承自 NSObject 的类中创建 IBOutlets。这里的问题似乎是您没有在界面生成器中设置自定义类:

'[<NSObject 0x60800001a940> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key closeButton.'

在解码您的 Nib 时,NSCoder 尝试在 NSObject 的实例上设置 closeButton 属性,当然它没有此属性。您要么没有指定自定义类,要么建立了无效的连接。

【讨论】:

  • 确实为 XIB 文件的所有者设置了自定义类。 (否则我将无法将出口拖到文件中。)出口和操作都与文件的所有者相关。
  • 对不起,我在loadNibNamed()函数中为所有者参数传递nil时不小心粘贴了显示的日志输出。当我通过self 时,我得到[&lt;_SwiftValue 0x600000458810&gt; setValue:forUndefinedKey:] ...。但是你的观点是正确的,建立连接的类似乎是错误的。相应地更新了我的帖子。
  • 您能否在示例项目中重现此问题?我刚刚尝试过,它对我来说非常有效。也许您在从头开始设置时会发现问题。
  • 这是我的示例项目:github.com/mischa-hildebrand/PresentationControllerExperiment。不幸的是,我在那里遇到了同样的崩溃。如果您有一个工作项目,您介意分享它以隔离问题吗?
  • 问题出在这一行:lazy var dimmingView = Bundle.main.loadNibNamed("DimmedPresentationView", owner: self, options: nil)?.first as! UIViewself 指的是类而不是这里的对象。如果您以这种方式延迟加载变量,它将起作用:lazy var dimmingView: UIView = { let bundle = Bundle.main.loadNibNamed("DimmedPresentationView", owner: self, options: nil)! return bundle.first as! UIView }()
【解决方案2】:

好的...主要问题是XIB / NIB文件必须实例化,而不仅仅是加载第一项。

所有这些变化都在DimmingPresentationController.swift:

// don't load it here...
//lazy var dimmingView = Bundle.main.loadNibNamed("DimmedPresentationView", owner: self, options: nil)?.first as! UIView
var dimmingView: UIView!

那么……

private func addAndSetupSubviews() {
    guard let presentedView = presentedView else {
        return
    }

    // Create a UINib object for a nib (in the main bundle)
    let nib = UINib(nibName: "DimmedPresentationView", bundle: nil)

    // Instante the objects in the UINib
    let topLevelObjects = nib.instantiate(withOwner: self, options: nil)

    // Use the top level objects directly...
    guard let v = topLevelObjects[0] as? UIView else {
        return
    }
    dimmingView = v

    // add the dimming view - to the presentedView - below any of the presentedView's subviews
    presentedView.insertSubview(dimmingView, at: 0)

    dimmingView.alpha = 0
    dimmingView.frame = presentingViewController.view.bounds

}

应该这样做。如果它不适合您,我可以向您的 GitHub 存储库添加一个分支(很确定我没有进行任何其他更改)。

【讨论】:

  • 您的代码运行良好,但您的解释有误为什么。不管你是在UINib 上调用instantiate() 还是使用BundleloadNibNamed() 函数——这只是两种不同的方法(参见:Resource Programming Guide)。
  • 但是,它为我指明了正确的方向:不知何故(出于某种原因我仍然必须弄清楚)dimmingViewtype UIView 不可能是推断。将显式类型注释添加到属性声明修复了崩溃。所有出口和动作现在都已正确设置。感谢您帮助我在这里找到问题! ?
  • 啊——你说得对……我已经有一段时间没有使用 xibs 了,当我这样做时,我总是将它们作为类的一部分加载(而且它是 Obj-C,而不是迅速)。很高兴你解决了。
【解决方案3】:

导致应用崩溃的问题是无法推断dimmingView类型(这很奇怪,因为编译器没有报错)。将显式类型注释添加到属性的声明中修复了崩溃。所以我简单地替换了这一行

lazy var dimmingView = Bundle.main.loadNibNamed("PresentationChromeView", 
                                   owner: self, 
                                   options: nil)?.first 
                                   as! UIView

用那一行:

lazy var dimmingView: UIView = Bundle.main.loadNibNamed("PresentationChromeView", 
                                           owner: self, 
                                           options: nil)?.first 
                                           as! UIView

出口和行动现在已正确连接。

为什么这种类型推断不起作用,或者为什么这能解决问题对我来说仍然是一个谜,我仍然愿意解释。 ?❓

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-04-03
    • 1970-01-01
    • 2020-07-13
    • 1970-01-01
    • 2015-09-20
    • 2016-09-11
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多