在 Interface Builder 中使用泛型类作为自定义视图

Posted

技术标签:

【中文标题】在 Interface Builder 中使用泛型类作为自定义视图【英文标题】:Use a generic class as a custom view in Interface Builder 【发布时间】:2014-08-12 12:06:51 【问题描述】:

我有一个 UIButton 的自定义子类:

import UIKit

@IBDesignable
class MyButton: UIButton 

    var array : [String]?


它是 IBDesignable 并且我已将它设置为我的故事板中一个按钮的自定义类。我想让它成为通用的,这样数组就不必是 String 对象之一。所以,我尝试了这个:

import UIKit

@IBDesignable
class MyButton<T>: UIButton 

    var array : [T]?


但是,我不确定如何将其设置为现在在 IB 中的类。我尝试输入MyButton&lt;String&gt;MyButton&lt;Int&gt;,但Interface Builder 只是删除了尖括号部分并得到以下编译错误:

Command /Applications/Xcode6-Beta4.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/swift failed with exit code 254

有没有办法使用通用的自定义类,还是不支持?

【问题讨论】:

【参考方案1】:

Interface Builder 通过 ObjC 运行时与您的代码“对话”。因此,IB 只能访问在 ObjC 运行时中可表示的代码功能。 ObjC 不做泛型,所以 IB 没有办法告诉运行时使用泛型类的哪个特化。 (并且 Swift 泛型类在没有专门化的情况下无法实例化,因此在尝试实例化 MyButton 而不是 MyButton&lt;WhateverConcreteType&gt; 时会出错。)

(当您破坏其他东西时,您可以在 IB 中识别出 ObjC 运行时:尝试在 nib/storyboard 中使用带有插座/动作的纯 Swift 类会导致有关 ObjC 内省的运行时错误。让插座连接其相应的代码声明已更改或消失会导致有关 KVC 的运行时错误。等等。)

要忽略运行时问题并以稍微不同的方式表达...让我们回到 IB 需要了解的内容。请记住,nib 加载系统是在运行时实例化情节提要中的任何内容的系统。因此,即使您的类中采用泛型类型参数的部分不是@IBInspectable,nib 仍然需要知道要加载的泛型类的特化。因此,为了让 IB 允许您将通用类用于视图,IB 必须有一个位置让您确定 nib 应该使用的类的哪个专业化。它没有——但那会是一个很棒的feature request。

与此同时,如果您的 MyButton 类涉及一些通用存储仍然有帮助,也许您可​​以考虑让 MyButton 引用另一个包含通用存储的类。 (它必须以这样一种方式这样做,即在编译时不知道所述存储的类型——否则其他类的类型参数将不得不传播回MyButton。)

【讨论】:

虽然我理解不能在 IB 中直接使用泛型的原因,但为什么不能间接使用?例如,如果您创建了一个名为 StringButton 的子类,它继承自 MyButton,而 MyButton 本身又继承自 UIButton,那么 IB 就不需要了解有关泛型的任何信息。它只需要知道StringButton。但是,这也不起作用。更令人困惑的是我的理解是泛型是语言/编译器功能而不是运行时功能,这使得我提到的情况更加奇怪,它不起作用。你能评论一下吗? 深入了解所涉及系统的原因有很多... .具有 Swift-only 特性的类不能在这些系统中表示,包括从 Swift-only 父类继承的类,因为这些系统需要知道类的继承链才能创建实例。同样,file feature requests 如果您想查看更改。【参考方案2】:

我遇到了和第一次一样的问题,我想使用以下结构:

import UIKit

protocol MyDataObjectProtocol
   var name:String  get


class MyObjectTypeOne:MyDataObjectProtocol
    var name:String  get  return "object1"


class MyObjectTypeTwo:MyDataObjectProtocol
    var name:String  get  return "object2"


class SpecializedTableViewControllerOne: GenericTableViewController<MyObjectTypeOne> 


class SpecializedTableViewControllerTwo: GenericTableViewController<MyObjectTypeTwo> 


class GenericTableViewController<T where T:MyDataObjectProtocol>: UITableViewController 
    // some common code I don't want to be duplicated in 
    // SpecializedTableViewControllerOne and SpecializedTableViewControllerTwo
    // and that uses object of type MyDataObjectProtocol.

但如上一个答案所述,这是不可能的。所以我设法使用以下代码解决了这个问题:

import UIKit

protocol MyDataObjectProtocol
    var name:String  get


class MyObjectTypeOne:MyDataObjectProtocol
    var name:String  get  return "object1"


class MyObjectTypeTwo:MyDataObjectProtocol
    var name:String  get  return "object2"


class SpecializedTableViewControllerOne: GenericTableViewController 
    required init?(coder aDecoder: NSCoder) 
        super.init(coder: aDecoder)
        super.object = MyObjectTypeOne()
    


class SpecializedTableViewControllerTwo: GenericTableViewController 
    required init?(coder aDecoder: NSCoder) 
        super.init(coder: aDecoder)
        super.object = MyObjectTypeTwo()
    


class GenericTableViewController : UITableViewController 
    var object : MyDataObjectProtocol?

    @IBOutlet weak var label: UILabel!

    override func viewDidLoad() 
        label.text = object?.name
    


    // some common code I don't want to be duplicated in 
    // SpecializedTableViewControllerOne and SpecializedTableViewControllerTwo
    // and that uses object of type MyDataObjectProtocol.

现在,当我将SpecializedTableViewControllerOne 链接为情节提要中视图控制器的自定义类时,我的标签出口显示“object1”,如果链接到SpecializedTableViewControllerTwo,则显示“object2”

【讨论】:

【参考方案3】:

可以在Interface Builder 中使用通用UIView。假设您有:

class A<T>: UIView 

首先,您必须继承您的通用视图以使其成为非通用视图:

class B: A<String> 

继续使用Interface Builder中的B类。

之后,您可以在代码中建立@IBOutlet 连接:

@IBOutlet var b: B

它会产生错误,所以要摆脱它,让它改用UIView类,然后像这样向下转换:

@IBOutlet var b: UIView

var _b: B 
    b as! B

完成,现在您的 _b 变量的类型为 B,并且可以正常工作。

【讨论】:

以上是关于在 Interface Builder 中使用泛型类作为自定义视图的主要内容,如果未能解决你的问题,请参考以下文章

在 Interface Builder 中使用“属性”标签文本

在 Interface Builder 中使用常量

在 Interface Builder 中使用大小类更改字体大小

如何在 Xcode 6 Interface Builder 中使用模板渲染模式?

在 Swift 中使用 IBOutlet 的 UIImageView 在 Interface Builder 中查看

在 Interface Builder 中使用 Suite 将值绑定到 NSUserDefaults