3

We have implemented a protocol, Reusable, to ease UITableView register/dequeue implementations, for our UITableViewCells.

protocol Reusable: class {
    static var defaultIdentifier: String { get }
}

extension Reusable where Self: UITableViewCell {
    static var defaultIdentifier: String {
        return String(describing: self)
    }
}

class TestTableViewCell: UITableViewCell, Reusable { }
class AnotherTestTableViewCell: UITableViewCell, Reusable { }

Then, there's an extension to UITableView like:

extension UITableView {
    func register<T: UITableViewCell & Reusable>(_: T.Type) {
        register(UINib(nibName: T.defaultIdentifier, bundle: nil), forCellReuseIdentifier: T.defaultIdentifier)
    }
}

and as its usage:

let tableView: UITableView = UITableView()
tableView.register(TestTableViewCell.self)
tableView.register(AnotherTableViewCell.self)

Everything works well, but we'd like to move these types to an array, for order. That's where we're stuck, this doesn't work:

let viewCells = [TestTableViewCell.self, AnotherTestTableViewCell.self]
// Without type annotation, it's [UITableViewCell.Type]
// And the error is: Instance method 'register' requires that 'UITableViewCell' conform to 'Reusable'

for viewCell in viewCells {
    tableView.register(viewCell)
}

We've also tried:

let viewCells: [Reusable.Type] = ...
// Error: Cannot invoke 'register' with an argument list of type '(Reusable.Type)'

Also this:

let viewCells: [(UITableViewCell & Reusable).Type] = ...
// Error: Instance method 'register' requires that 'UITableViewCell' conform to 'Reusable'

Is there a way to store the class type info with protocol conformance in an array to make this work?

EDUsta
  • 1,932
  • 2
  • 21
  • 30
  • What's version of Swift are you using? – Vyacheslav Apr 18 '19 at 13:05
  • @Vyacheslav 4.2 – EDUsta Apr 18 '19 at 13:08
  • What's the problem? It doesn't compile? I tried in playground and it compiles – Rico Crescenzio Apr 18 '19 at 13:25
  • @RicoCrescenzio yes, I've edited the question, it's because there's only 1 type of class type in the array. It should not compile. – EDUsta Apr 18 '19 at 13:30
  • 1
    "but we'd like to move these types to an array” You’re going to have trouble doing that. Swift doesn’t like the notion of an array of metatypes; a metatype is not really a type in Swift. You might be happier using some other programming language, or some other approach to the overall goal. – matt Apr 18 '19 at 15:40
  • @matt My thanks for the info, it's not a showstopper but it'd be nice to have, though. – EDUsta Apr 19 '19 at 07:36

4 Answers4

5

Just write an extension for UITableViewCell so it conforms to Reusable protocol and do not cast cells to any type:

extension UITableViewCell: Reusable {}


P.S. you can also check the most popular Reusable protocol implementation on iOS here.

Good luck :)

Tomas Jablonskis
  • 4,246
  • 4
  • 22
  • 40
  • It doesn't work unfortunately: `Value of type ‘UITableViewCell.Type’ does not conform to ‘UITableViewCell & Reusable’ in coercion` – EDUsta Apr 18 '19 at 13:12
  • Instead of discussing in comments lets have a [chat](https://chat.stackoverflow.com/rooms/192054/room-for-tomas-jablonskis-and-edusta). – Tomas Jablonskis Apr 18 '19 at 13:14
  • Summary of chat: It works, but we'd really like not to extend the protocol to every `UITableViewCell`. – EDUsta Apr 18 '19 at 13:44
  • @EDUsta Why don't you want to extend every `UITableViewCell` to conform to the protocol? – JeremyP Apr 18 '19 at 14:35
  • @JeremyP Fair question, there are some `UITableViewCell` classes that doesn't have a separate nib, or doesn't match with their names, so we're not ready to make that transition yet, instead we're doing one-by-one and get to know the process (which cells are ready/not ready). – EDUsta Apr 18 '19 at 14:52
  • @EDUsta In that case, why not just extend the subclasses of UITableViewCell that you are OK with? – JeremyP Apr 18 '19 at 14:56
  • @JeremyP Do you mean as a superclass? If so, I wouldn't prefer that. Anyways, the main point in the question is that how can we hold on to the class type in an array. Do you have an idea over that, or is it not possible? – EDUsta Apr 18 '19 at 15:02
  • @EDUsta No, I mean add `Reusable` conformance to each specific subclass. If you have an extension on `Reusable` to add a default implementation of `defaultIdentifier`, the work is minimal. – JeremyP Apr 18 '19 at 15:10
  • @JeremyP Indeed, we did that exactly as per the question. – EDUsta Apr 19 '19 at 07:31
2
protocol Reusable: class {
    static var defaultIdentifier: String { get }
}

extension Reusable where Self: UITableViewCell {
    static var defaultIdentifier: String {
        return String(describing: self)
    }
}

class TestTableViewCell: UITableViewCell, Reusable { }
extension UITableView {
    func register<T: UITableViewCell & Reusable>(_: T.Type) {
        register(UINib(nibName: T.defaultIdentifier, bundle: nil), forCellReuseIdentifier: T.defaultIdentifier)
    }
}
let tableView: UITableView = UITableView()
tableView.register(TestTableViewCell.self)
let viewCells = [TestTableViewCell.self]
// Without type annotation, it's [UITableViewCell.Type]
// And the error is: Instance method 'register' requires that 'UITableViewCell' conform to 'Reusable'

for viewCell in viewCells {
    tableView.register(viewCell)
}

This code successfully executes.

Vyacheslav
  • 26,359
  • 19
  • 112
  • 194
1

Hope you are happy with this. The key is Self keyword in protocol.

protocol Reusable: class {
    static var defaultIdentifier: String { get }
    static func register(tableView: UITableView)
}

extension Reusable where Self: UITableViewCell {
    static var defaultIdentifier: String {
        return String(describing: self)
    }

    static func register(tableView: UITableView) {
        tableView.register(UINib(nibName: Self.defaultIdentifier, bundle: nil), forCellReuseIdentifier: Self.defaultIdentifier)
    }
}

extension UITableView {
    func register(_ reusable: Reusable.Type) {
        reusable.register(tableView: self)
    }
}

let viewCells: [Reusable.Type] = [TestTableViewCell.self, AnotherTestTableViewCell.self]

for viewCell in viewCells {
    tableView.register(viewCell)
}

I recommend you to read the following links:

Yonguk Jeong
  • 258
  • 1
  • 6
0

Simply use this table view extension method to register the cell.

 extension UITableView {
     func registerCell<T: UITableViewCell>(cellType: T.Type) {
        let nib = UINib(nibName: String(describing: cellType.self), bundle: nil)
        let reuseIdentifier = String(describing: cellType.self)
        self.register(nib, forCellReuseIdentifier: reuseIdentifier)
     }
} 

And use it like so

 let tableView: UITableView = UITableView()
    let cellsArray = [Test.self, Test1.self, Test2.self]
    for cell in cellsArray {
        tableView.registerCell(cellType: cell)
    }
Aman Pathak
  • 219
  • 2
  • 6
  • Thanks for your response, but it just ignores the protocol and that's the whole reason we're doing this. – EDUsta Apr 18 '19 at 14:24