gmoraleda
gmoraleda

Reputation: 1953

Swift (RxSwift): Using generics to link ViewItem and Cell classes

I am refactoring an UITableView implementation in RxSwift. I have different view items and I want to switch them so that I am able to configure my dataSource accordingly. I need to know which cell class belongs to which view item.

To do so, I wrote a method that take an item which implement my ViewItemProtocol and returns AnyClass?.

func getAssociatedtCellType<T>(for item: T) -> AnyClass? where T: ViewItemProtocol {
    switch item  {
    case is TextFieldViewItem: return TextFieldCell.self
    case is FaqDetailViewItem: return FaqCell.self
    case is HeaderViewItem: return HeaderCell.self
    case is SubmitButtonViewItem: return SubmitButtonCell.self
    case is BankDetailViewItem: return BankDetailCell.self
    case is EmailViewItem: return EmailCell.self
    default: return nil
    }
}

The function method configured my cell:

private func configCell<T>(for item: T, with identifier: String, at indexPath: IndexPath) -> UITableViewCell where T: ViewItemProtocol {
    guard let cellType = getAssociatedtCellType(for: item.self) else { return UITableViewCell() }
    guard let cell = tableView.dequeueReusableCell(withIdentifier: identifier, for: indexPath) as? cellType else { fatalError() }
    cell.configureBindings(itemSource: item)
    return cell
}

The problem arises with cellType:

Use of undeclared type 'cellType'

The objective behind is to refactor this method:

private func dataSource() -> RxTableViewSectionedReloadDataSource<SectionedViewItem> {

    let dataSource = RxTableViewSectionedReloadDataSource<SectionedViewItem>(configureCell: { [unowned self] _, tableView, indexPath, item in

        switch item {
        case let item as TextFieldViewItem:
            guard let cell = tableView.dequeueReusableCell(withIdentifier: TextFieldCell.Key, for: indexPath) as? TextFieldCell else { fatalError() }
            cell.configureBindings(itemSource: item)
            cell.changeButton.isHidden = item.editable
            cell.changeButton.addTarget(self, action: #selector(self.showFieldNotEditableAlert), for: .touchUpInside)
            cell.textField.addTarget(self, action: #selector(self.textFieldDidChange), for: .editingChanged)
            return cell

        case let item as FaqDetailViewItem:
            guard let cell = tableView.dequeueReusableCell(withIdentifier: FaqDetailCell.Key, for: indexPath) as? FaqDetailCell else { fatalError() }
            cell.configureBindings(itemSource: item)
            return cell

        case let item as PasswordTextFieldViewItem:
            guard let cell = tableView.dequeueReusableCell(withIdentifier: PasswordTextFieldCell.Key, for: indexPath) as? PasswordTextFieldCell else { fatalError() }
            cell.configureBindings(itemSource: item)
            return cell

        case let item as HeaderViewItem:
            guard let cell = tableView.dequeueReusableCell(withIdentifier: HeaderCell.Key, for: indexPath) as? HeaderCell else { fatalError() }
            cell.configureBindings(itemSource: item)
            return cell

// ... My other cell types... :-(

    })

    dataSource.titleForHeaderInSection = { dataSource, index in
        let section = dataSource[index]
        return section.header
    }

    return dataSource
}

... into something like this:

private func dataSource() -> RxTableViewSectionedReloadDataSource<SectionedViewItem> {

    let dataSource = RxTableViewSectionedReloadDataSource<SectionedViewItem>(configureCell: { [unowned self] _, tableView, indexPath, item in

        switch item {
        case let item as TextFieldViewItem:
            guard let cell = tableView.dequeueReusableCell(withIdentifier: TextFieldCell.Key, for: indexPath) as? TextFieldCell else { fatalError() }
            cell.configureBindings(itemSource: item)
            cell.changeButton.isHidden = item.editable
            cell.changeButton.addTarget(self, action: #selector(self.showFieldNotEditableAlert), for: .touchUpInside)
            cell.textField.addTarget(self, action: #selector(self.textFieldDidChange), for: .editingChanged)
            return cell

        case let item as FaqDetailViewItem, let item as PasswordTextFieldViewItem, let item as HeaderViewItem:
            return configCell(for: item, with: "Cell.Key... still to implement)", at: indexPath)


        default:
            return UITableViewCell()
        }

    })

    dataSource.titleForHeaderInSection = { dataSource, index in
        let section = dataSource[index]
        return section.header
    }

    return dataSource
}

Upvotes: 0

Views: 165

Answers (1)

Galo Torres Sevilla
Galo Torres Sevilla

Reputation: 1575

Unfortunately that cannot be done in Swift. In swift, the compiler needs to know the types of the variables at compile time but in your case you are trying to downcast at runtime. So no, you won't be able to do that.

You will probably be able to get it done with some sort of conversion with generics at runtime but the casting won't let you access the properties from that class, which is probably what you need to do all the bindings. I think you will be better off moving the code that you have in your switch statement to the tableViewCell setup.

Upvotes: 1

Related Questions