【问题标题】:Generic class when inherit from UICollectionViewDataSource in swiftswift中从UICollectionViewDataSource继承时的泛型类
【发布时间】:2015-07-03 14:32:37
【问题描述】:

当我尝试创建一个在 swift 中实现 UICollectionViewDataSource 的通用类时,它说我的类不符合协议(有时 Xcode 崩溃)。

这是否意味着我们不能为 UICollectionView 创建通用数据提供者并且我们必须复制代码?

这是通用代码:

// Enum protocol
protocol OptionsEnumProtocol
{
    typealias T
    static var allValues:[T] {get set}
    var description: String {get}
    func iconName() -> String
}

// enum : list of first available options
enum Options: String, OptionsEnumProtocol
{
    typealias T = Options

    case Color = "Color"
    case Image = "Image"
    case Shadow = "Shadow"

    static var allValues:[Options] = [Color, Image, Shadow]

    var description: String {
        return self.rawValue
    }

    func iconName() -> String
    {
        var returnValue = ""

        switch(self)
        {
            case .Color: returnValue = "color_icon"
            case .Image: returnValue = "image_icon"
            case .Shadow: returnValue = "shadow_icon"
        }

        return returnValue
    }
}

// class to use as the uicollectionview datasource and delegate
class OptionsDataProvider<T>: NSObject, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout
{
    private let items = T.allValues

    func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int
    {
        return items.count
    }

    func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell
    {
        let cell = collectionView.dequeueReusableCellWithReuseIdentifier(OptionsCellReuseIdentifier, forIndexPath: indexPath) as! GenericIconLabelCell

        let item = self.items[indexPath.row]
        // Configure the cell
        cell.iconFileName = item.iconName()
        cell.labelView.text = item.description

        return cell
    }
}

但因为失败了,我不得不改用这种非通用形式:

enum Options: String
{
    case Color = "Color"
    case Image = "Image"
    case Shadow = "Shadow"

    static var allValues:[Options] = [Color, Image, Shadow]

    var description: String {
        return self.rawValue
    }

    func iconName() -> String
    {
        var returnValue = ""

        switch(self)
        {
            case .Color: returnValue = "color_icon"
            case .Image: returnValue = "image_icon"
            case .Shadow: returnValue = "shadow_icon"
        }

        return returnValue
    }
}

class OptionsDataProvider: NSObject, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout
{
    private let items = Options.allValues

    func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int
    {
        return items.count
    }

    func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell
    {
        let cell = collectionView.dequeueReusableCellWithReuseIdentifier(OptionsCellReuseIdentifier, forIndexPath: indexPath) as! GenericIconLabelCell

        let item = self.items[indexPath.row]
        // Configure the cell
        cell.iconFileName = item.iconName()
        cell.labelView.text = item.description

        return cell
    }
}

这使我有义务为我拥有的每种枚举类型复制该类。

确切的错误:

【问题讨论】:

  • 为什么要投反对票?我可以有理由吗?
  • 您能否说明您想要实现的目标?我问是因为如果您创建一个采用 UICollectionViewDelegate 和 UICollectionViewDataSource 的所有方法的 NSObject 子类,您将能够将其用作集合视图的委托和数据源。所以,不清楚你想要实现什么
  • 是的,我已经将它用作我的 uicollectionview 的委托和数据源。为此,它有效。问题是我将这个“委托/数据源”类复制到我的每个数据类型。我的意思是,我使用枚举作为数据,我必须为每个枚举创建一个类。但我只想创建一个“委托/数据源”并将枚举类型指定为 T 参数。我会更新我的问题让你理解。
  • 我更新了我的问题,让您更好地了解我想要实现的目标。

标签: ios xcode swift generics uicollectionview


【解决方案1】:

你是对的,写一个泛型类是不可能的。但是,我找到了一种解决方法。它不使用枚举,所以也许你觉得它不是很有用。然而,它实现了你想要的——你得到了一个集合视图数据源,它可以与不同的类一起使用,提供必要的数据。代码如下:

protocol OptionsProviderProtocol
{
    func allValues() -> [OptionsItem]
}

class OptionsItem:NSObject {
    let itemDescription:String
    let iconName:String

    init(iconName:String,description:String) {
        self.itemDescription = description
        self.iconName = iconName
    }
}

// class stores first available options
class Options: NSObject, OptionsProviderProtocol
{

    let color = OptionsItem(iconName: "color_icon", description: "Color")
    let image = OptionsItem(iconName: "image_icon", description: "Image")
    let shadow = OptionsItem(iconName: "shadow_icon", description: "Shadow")

    func allValues() -> [OptionsItem] {
        return [color, image, shadow]
    }
}

// class to use as the uicollectionview datasource and delegate
class OptionsDataProvider: NSObject, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout
{
    private var items:[OptionsItem] = []

    convenience init(optionsProvider:OptionsProviderProtocol) {
        self.items = optionsProvider.allValues()
    }

    func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int
    {
        return items.count
    }

    func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell
    {
        let cell = collectionView.dequeueReusableCellWithReuseIdentifier(OptionsCellReuseIdentifier, forIndexPath: indexPath) as! GenericIconLabelCell

        let item = self.items[indexPath.row]
        // Configure the cell
        cell.iconFileName = item.iconName()
        cell.labelView.text = item.description

        return cell
    }
}

如果您有任何问题,请告诉我。

【讨论】:

  • 谢谢Andriy,我认为这是一个可以接受的解决方法,即使它不如枚举方便。当我在条件下使用它时,我只需要添加一种 id 来识别 OptionsItem 类型。我注意到它是公认的答案,但为了赏金,我等着看是否有人用枚举解决它。
  • 无论如何我想知道它是否会在以后的 swift 版本中使用通用代码和枚举,因为对我来说它看起来像一个错误。
  • @Dragouf 我认为这更像是一个“环境限制”,而不是一个错误,您不能像使用通常的 objc 对象一样使用泛型操作。虽然错误消息可能更具体:) 但无论如何,您想要的实际上是可能的(请参阅my answer)。
  • @Andriy,我将接受的答案更改为 Nevs12 答案,因为他在解决方案中使用了泛型。
【解决方案2】:

当您从协议继承时,您必须实现所有必需的方法。 Swift 2 将对此有所改变。也许你真的想从一个类继承。

【讨论】:

  • 我已经这样做了,这不是问题。我没有写所有的方法来简化。当我删除 T 它编译没有任何问题。
【解决方案3】:

当我尝试从 NSOperation 类继承 Generic 类时,我遇到了类似的 problem/question。 xCode 没有给我编译错误,因为没有涉及任何协议,而是我的 override func main() 根本没有被调用:)

无论如何...如果您关注workaround 那位先生。 Topal Sergey 建议,您可以相对轻松地实现您想要的。

class ViewController: UIViewController {

    @IBOutlet weak var collectionView: UICollectionView?
    private var defaultDataProvider = OptionsDataProvider<Options>()

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.

        collectionView?.delegate = defaultDataProvider
        collectionView?.dataSource = defaultDataProvider
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
}


// Enum protocol
protocol OptionsEnumProtocol {
    static var allValues: [OptionsEnumProtocol] {get set}
    var description: String {get}
    func iconName() -> String
}

// enum : list of first available options
enum Options: String, OptionsEnumProtocol {
    case Color = "Color"
    case Image = "Image"
    case Shadow = "Shadow"

    static var allValues: [OptionsEnumProtocol] = [Color, Image, Shadow]

    var description: String {
        return self.rawValue
    }

    func iconName() -> String
    {
        var returnValue = ""

        switch(self)
        {
        case .Color: returnValue = "color_icon"
        case .Image: returnValue = "image_icon"
        case .Shadow: returnValue = "shadow_icon"
        }

        return returnValue
    }
}

class OptionsDataProviderWrapper: NSObject, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {

    // MARK: protocols' funcs

    final func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return wrapperCollectionView(collectionView, numberOfItemsInSection: section)
    }

    final func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
        return wrapperCollectionView(collectionView, cellForItemAtIndexPath: indexPath)
    }

    // MARK: for override

    func wrapperCollectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return 0
    }

    func wrapperCollectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
        return UICollectionViewCell()
    }
}

class OptionsDataProvider<T: OptionsEnumProtocol>: OptionsDataProviderWrapper {
    private let items = T.allValues

    override func wrapperCollectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return items.count
    }

    override func wrapperCollectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCellWithReuseIdentifier("reuseId", forIndexPath: indexPath) as! GenericIconLabelCell

        let item = self.items[indexPath.row]
        cell.labelView?.text = item.description

        return cell
    }
}

class GenericIconLabelCell: UICollectionViewCell {
    @IBOutlet weak var labelView: UILabel?
}

这里的关键是创建OptionsDataProviderWrapper,它不是通用的并且实现了你的所有协议。它做的唯一一件事 - 它将调用重定向到另一个函数,如 func wrapperCollectionView...

现在您可以从此OptionsDataProviderWrapper 继承您的通用类并覆盖该包装函数。

注意:您必须完全覆盖包装函数,因为与我的 NSOperation 问题类似,不会在您的通用子类中调用原生 func collectionView... 函数。这就是为什么我用final 标记原生函数。

【讨论】:

  • 我对你的回答有疑问,它说我在调用 T.allValues 时 T.T 不能转换为 [T]。我等着解决这个问题再给你赏金
  • @Dragouf 是的。当我自己尝试代码时,我看到了这个问题。但似乎问题不在于解决方案本身,而在于泛型的使用方式。问题的原因应该在这里:OptionsEnumProtocol中的typealias T。我不太确定它需要什么,但我稍微更改了协议和枚举,它开始运行良好。我已经更新了我的答案以提供完整的工作代码。
  • typealias T 是返回枚举的类型。这里 allValues 不返回 Options 枚举列表,而是返回 OptionsEnumProtocol。所以我不能再使用我的枚举了。实际上,在我的代码中,当我们点击集合视图的一个单元格时,它会调用一个块,其中 item from let item = self.items[indexPath.row] 作为参数。我需要测试枚举类型。
  • @Dragouf 我明白了,但是编译器并不真正知道这个T 到底是什么。 :) 它实际上可以是任何东西。您知道 Options 枚举在这种特殊情况下是您的 T,但编译器不知道它 - 它知道 T 是“某物”。此外,我不确定您是否可以在任意枚举上执行 switch-case,因为编译器需要事先知道 case-set。所以你需要检查你的item 是什么枚举,向下转换,然后切换大小写。更好的选择是通过协议获取所有需要的东西,从底层枚举中完全抽象出来。
  • Nevs12 这就是泛型存在的原因。这是为了避免铸造。当我们用 表示法实例化类时,应该指定 T 确切类型。并且 T 应该成为这个实例的选项
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-07-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-10-15
  • 1970-01-01
  • 2020-01-23
相关资源
最近更新 更多