从swift中的UICollectionViewDataSource继承时的泛型类

时间:2022-09-24 23:29:23

when I try to create a generic class which implement UICollectionViewDataSource in swift it say that my class does not conform to protocol (and sometime Xcode crash).

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

Does it mean that we can't create generic data provider for UICollectionView and that we have to duplicate code ?

这是否意味着我们无法为UICollectionView创建通用数据提供程序,我们必须重复代码?

Here is the generic code :

这是通用代码:

// 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
    }
}

But because it failed I have to use this non generic form instead :

但是因为它失败了我不得不使用这个非泛型形式:

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
    }
}

which obligate me to duplicate the class for each enum type I have.

这使我不得不为我拥有的每个枚举类型复制类。

Exact error :

确切的错误:

从swift中的UICollectionViewDataSource继承时的泛型类

3 个解决方案

#1


5  

You are right, it is not possible to write a generic class. However, I have found a workaround. It doesn't use enums and so maybe you don't find it very useful. However, it achieves what you want - you are getting a collection view data source which can be used with different classes providing necessary data. Here is the code:

你是对的,不可能写一个泛型类。但是,我找到了一个解决方法。它不使用枚举,所以你可能没有发现它非常有用。但是,它实现了您想要的 - 您将获得一个集合视图数据源,可以与提供必要数据的不同类一起使用。这是代码:

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
    }
}

If you have any questions please let me know.

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

#2


2  

When you inherit from a protocol you must implement all required methods. Swift 2 will change this somewhat. Perhaps you really want to inherit from a class.

从协议继承时,必须实现所有必需的方法。 Swift 2会稍微改变一下。也许你真的想从一个类继承。

#3


1  

I had the similar problem/question when I was trying to inherit Generic class from NSOperation class. xCode didn't give me a compile error because there were no protocols involved, instead my override func main() was simply never called :)

当我试图从NSOperation类继承Generic类时,我遇到了类似的问题/问题。 xCode没有给我一个编译错误,因为没有涉及协议,而是我的覆盖func main()根本就没有被调用:)

Anyway... If you follow workaround that mr. Topal Sergey advised, you can achieve exactly what you want relatively easily.

无论如何......如果你按照先生的方式进行解决。 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?
}

The key here is to create OptionsDataProviderWrapper that is not a generic and implements all your protocols. The only thing that it does - it redirects calls to another functions like func wrapperCollectionView...

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

Now you can inherit your Generic class from this OptionsDataProviderWrapper and override that wrapper functions.

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

Note: you have to override exactly wrapper functions because native func collectionView... functions will not be called in your generic subclass similarly to my issue with NSOperation. That's why I marked native functions with final.

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

#1


5  

You are right, it is not possible to write a generic class. However, I have found a workaround. It doesn't use enums and so maybe you don't find it very useful. However, it achieves what you want - you are getting a collection view data source which can be used with different classes providing necessary data. Here is the code:

你是对的,不可能写一个泛型类。但是,我找到了一个解决方法。它不使用枚举,所以你可能没有发现它非常有用。但是,它实现了您想要的 - 您将获得一个集合视图数据源,可以与提供必要数据的不同类一起使用。这是代码:

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
    }
}

If you have any questions please let me know.

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

#2


2  

When you inherit from a protocol you must implement all required methods. Swift 2 will change this somewhat. Perhaps you really want to inherit from a class.

从协议继承时,必须实现所有必需的方法。 Swift 2会稍微改变一下。也许你真的想从一个类继承。

#3


1  

I had the similar problem/question when I was trying to inherit Generic class from NSOperation class. xCode didn't give me a compile error because there were no protocols involved, instead my override func main() was simply never called :)

当我试图从NSOperation类继承Generic类时,我遇到了类似的问题/问题。 xCode没有给我一个编译错误,因为没有涉及协议,而是我的覆盖func main()根本就没有被调用:)

Anyway... If you follow workaround that mr. Topal Sergey advised, you can achieve exactly what you want relatively easily.

无论如何......如果你按照先生的方式进行解决。 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?
}

The key here is to create OptionsDataProviderWrapper that is not a generic and implements all your protocols. The only thing that it does - it redirects calls to another functions like func wrapperCollectionView...

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

Now you can inherit your Generic class from this OptionsDataProviderWrapper and override that wrapper functions.

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

Note: you have to override exactly wrapper functions because native func collectionView... functions will not be called in your generic subclass similarly to my issue with NSOperation. That's why I marked native functions with final.

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