【问题标题】:Use a generic class as a custom view in Interface Builder在 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

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

【问题讨论】:

    标签: ios generics swift interface-builder xcode6


    【解决方案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,并且可以正常工作。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2023-03-18
        • 2010-12-17
        • 2014-10-20
        • 1970-01-01
        • 2016-08-04
        • 1970-01-01
        相关资源
        最近更新 更多