在 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<String>
或MyButton<Int>
,但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<WhateverConcreteType>
时会出错。)
(当您破坏其他东西时,您可以在 IB 中识别出 ObjC 运行时:尝试在 nib/storyboard 中使用带有插座/动作的纯 Swift 类会导致有关 ObjC 内省的运行时错误。让插座连接其相应的代码声明已更改或消失会导致有关 KVC 的运行时错误。等等。)
要忽略运行时问题并以稍微不同的方式表达...让我们回到 IB 需要了解的内容。请记住,nib 加载系统是在运行时实例化情节提要中的任何内容的系统。因此,即使您的类中采用泛型类型参数的部分不是@IBInspectable
,nib 仍然需要知道要加载的泛型类的特化。因此,为了让 IB 允许您将通用类用于视图,IB 必须有一个位置让您确定 nib 应该使用的类的哪个专业化。它没有——但那会是一个很棒的feature request。
与此同时,如果您的 MyButton
类涉及一些通用存储仍然有帮助,也许您可以考虑让 MyButton
引用另一个包含通用存储的类。 (它必须以这样一种方式这样做,即在编译时不知道所述存储的类型——否则其他类的类型参数将不得不传播回MyButton
。)
【讨论】:
虽然我理解不能在 IB 中直接使用泛型的原因,但为什么不能间接使用?例如,如果您创建了一个名为 StringButton 的子类,它继承自 MyButton我遇到了和第一次一样的问题,我想使用以下结构:
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 中使用大小类更改字体大小
如何在 Xcode 6 Interface Builder 中使用模板渲染模式?