RxSwiftCommunity / RxDataSources

UITableView and UICollectionView Data Sources for RxSwift (sections, animated updates, editing ...)
MIT License
3.1k stars 496 forks source link

UITableviewCell displays different types of data. #79

Open seekcx opened 8 years ago

seekcx commented 8 years ago

Hi, I am experiencing the need to:

A tableview needs to display the content section of an article and a list of comments for the article.

So, the content and the comments of the cell is different types, so they come from different data model, this situation should be how to use RxDatasources to achieve?

seekcx commented 8 years ago

I'm sorry I did not look at example

whisper-bye commented 7 years ago

@Abel94 I have same issue, but I still don't figure it out... can you please share your experience with me? thx!

seekcx commented 7 years ago

@whisper-bye

Set the item to enum type, and then each type is a enumeration of this one.

Here's a little code example.

1.Define the Section Model

import RxSwift
import RxDataSources
enum PuzzleItemSectionItem
{
    case Content(Puzzle)
    case Author(User)
    case Action(id: Int, commentCount: Int)
    case Probe(Probe)
}
enum PuzzleItemSectionModel
{
    case ContentSection(content: [PuzzleItemSectionItem])
    case ProbeSection(count: Int, content: [PuzzleItemSectionItem])
}
extension PuzzleItemSectionModel: SectionModelType
{
    typealias Item = PuzzleItemSectionItem

    var items: [Item] {
        switch self {
        case let .ContentSection(content):
            return content
        case let .ProbeSection(_, content):
            return content
        }
    }

    var titles: String {
        switch self {
        case let .ProbeSection(count, _):
            return "探讨(\(count))"
        default:
            return ""
        }
    }

    init(original: PuzzleItemSectionModel, items: [Item]) {
        switch original {
        case let .ContentSection(content):
            self = .ContentSection(content: content)
        case let .ProbeSection(count, content):
            self = .ProbeSection(count: count, content: content)
        }
    }
}

2.Bind the data source

        dataSource = RxTableViewSectionedReloadDataSource<PuzzleItemSectionModel>().then { source in
            source.configureCell = { dataSource, tableView, indexPath, puzzle in
                switch dataSource[indexPath] {
                case .Content(let puzzle ):
                    let cell = tableView.dequeueReusableCell(withIdentifier: "content") as! PuzzleItemContentCell
                    cell.setup(puzzle: puzzle)
                    return cell
                case .Author(let author):
                    let cell = tableView.dequeueReusableCell(withIdentifier: "author") as! PuzzleItemAuthorCell
                    cell.setup(author: author)
                    self.registerForPreviewing(with: self, sourceView: cell)
                    return cell
                case let .Action(id, commentCount):
                    let cell = tableView.dequeueReusableCell(withIdentifier: "action") as! PuzzleItemActionCell
                    cell.setup(id: id, commentCount: commentCount)
                    return cell
                case let .Probe(probe):
                    let cell = tableView.dequeueReusableCell(withIdentifier: "probe") as! PuzzleItemProbeCell
                    cell.setup(probe: probe)
                    self.registerForPreviewing(with: self, sourceView: cell.authorContentView!)
                    return cell
                }
            }

            source.titleForHeaderInSection = { ds, section -> String? in
                return ds[section].titles
            }
        }

I may not be very good expression, I hope you can understand.

whisper-bye commented 7 years ago

@Abel94 thx a lot! you are my life saver!

jasin755 commented 7 years ago

It's not working with RxCollectionViewSectionedAnimatedDataSource :(

sergdort commented 7 years ago

The difference it that you need to have unique ID, right?

jasin755 commented 7 years ago

Here is my code:

enum PurchaseItemSectionItem: Hashable, IdentifiableType {

    typealias Identity = Int

    case purchaseProduct(product: PurchasedCommodityEntity)
    case loader

    var identity: Int {

        switch self {
        case .loader:
            return "".hashValue
        case .purchaseProduct(product: let product):
            return product.name.hashValue
        }

    }

    var hashValue: Int {

        switch self {
        case .loader:
            return "".hashValue
        case .purchaseProduct(product: let product):
            return product.name.hashValue
        }

    }

    static func == (lhs: PurchaseItemSectionItem, rhs: PurchaseItemSectionItem) -> Bool {
        if case .loader = lhs, case .loader = rhs {
            return true
        } else {
            if case .purchaseProduct(let lhsProduct) = lhs, case .purchaseProduct(let rhsProduct) = rhs {
                return lhsProduct.name == rhsProduct.name
            }
        }
        return false
    }

}

enum PurchaseItemSections: AnimatableSectionModelType {

    typealias Item = PurchaseItemSectionItem
    typealias Identity = Int

    case purchaseProductsSection(products: [PurchaseItemSectionItem])
    case loaderSection

    var items: [Item] {
        switch self {
        case .purchaseProductsSection(products: let products):
            return products
        case .loaderSection:
            return [.loader]
        }
    }

    var identity: Int {
        switch self {
        case .purchaseProductsSection(products: _):
            return 1
        case .loaderSection:
            return -1
        }
    }

    init(original: PurchaseItemSections, items: [Item]) {

        switch original {
        case .purchaseProductsSection(products: let products):
            self = .purchaseProductsSection(products: products)
        case .loaderSection:
            self = .loaderSection
        }
    }
}

Section pipe:

        //sections: Driver<[PurchaseItemSections]>
        self.sections = itemsPaging
            .map { (tuple: (items: [PurchasedCommodityEntity], paging: PagingEntity<PurchasedCommodityCollection>)) -> [PurchaseItemSections] in

                let items = tuple.items.map({ (commodityEntity) -> PurchaseItemSectionItem in
                    .purchaseProduct(product: commodityEntity)
                })

                var sections: [PurchaseItemSections] = []
                sections.append(.purchaseProductsSection(products: items))

                if tuple.paging.hasNext {
                    sections.append(.loaderSection)
                }
                return sections
            }
            .startWith([.loaderSection])
            .asDriver(onErrorJustReturn: [])

ViewController:

let dataSource = RxCollectionViewSectionedAnimatedDataSource<PurchaseItemSections>()

purchasedItemsViewModel.sections
                .drive(productCollectionView.rx.items(dataSource: dataSource))
                .addDisposableTo(disposeBag)

When I start app in debug mode I obtain this error: Error while binding data animated: Wrong initializer implementation for:

Here is complete exception:

Printing description of e:
▿ Wrong initializer implementation for: purchaseProductsSection([PurchaseItemSectionItem.purchaseProduct(PurchasedCommodityEntity(imageUrl: https://i.company.cz/Foto/f3/BO/BOC123f.jpg, name: "BOSCH GMS 120", purchasedCommodityDetail: ProxyEntity<PurchasedCommodityDetailEntity>)), PurchaseItemSectionItem.purchaseProduct(PurchasedCommodityEntity(imageUrl: https://i.company.cz/Foto/f3/BO/BOC123e.jpg, name: "BOSCH GMS 100 M", purchasedCommodityDetail: ProxyEntity<PurchasedCommodityDetailEntity>)), .PurchaseItemSectionItem.purchaseProduct(PurchasedCommodityEntity(imageUrl: https://i.company.cz/Foto/f3/NA/NAR002n.jpg, name: "Narex Industrial-CrV 7-Bit Box", purchasedCommodityDetail: ProxyEntity<PurchasedCommodityDetailEntity>)), PurchaseItemSectionItem.purchaseProduct(PurchasedCommodityEntity(imageUrl: https://i.company.cz/Foto/f3/VD/VD500g2.jpg, name: "Devolo dLAN 1200+ Starter Kit", purchasedCommodityDetail: ProxyEntity<PurchasedCommodityDetailEntity>)), PurchaseItemSectionItem.purchaseProduct(PurchasedCommodityEntity(imageUrl: https://i.company.cz/Foto/f3/WG/WG513.jpg, name: "Tom Clancy\'s Ghost Recon: Wildlands", purchasedCommodityDetail: ProxyEntity<PurchasedCommodityDetailEntity>))])
Expected it should return items: [PurchaseItemSectionItem.purchaseProduct(PurchasedCommodityEntity(imageUrl: https://i.company.cz/Foto/f3/NA/NAR002n.jpg, name: "Narex Industrial-CrV 7-Bit Box", purchasedCommodityDetail: ProxyEntity<PurchasedCommodityDetailEntity>)), PurchaseItemSectionItem.purchaseProduct(PurchasedCommodityEntity(imageUrl: https://i.company.cz/Foto/f3/BO/BODK050c.jpg, name: "Stanley FatMax STA56045-QZ", purchasedCommodityDetail: ProxyEntity<PurchasedCommodityDetailEntity>))]
Expected it should have id: 1
  ▿ invalidInitializerImplementation : 3 elements
    ▿ section : PurchaseItemSections
      ▿ purchaseProductsSection : 4 elements
        ▿ 0 : PurchaseItemSectionItem
          ▿ purchaseProduct : PurchasedCommodityEntity
            ▿ imageUrl : https://i.company.cz/Foto/f3/BO/BOC123f.jpg
            - name : "BOSCH GMS 120"
            ▿ purchasedCommodityDetail : <ProxyEntity<PurchasedCommodityDetailEntity>: 0x11ba43670>
        ▿ 1 : PurchaseItemSectionItem
          ▿ purchaseProduct : PurchasedCommodityEntity
            ▿ imageUrl : https://i.company.cz/Foto/f3/BO/BOC123e.jpg
            - name : "BOSCH GMS 100 M"
            ▿ purchasedCommodityDetail : <ProxyEntity<PurchasedCommodityDetailEntity>: 0x11ba44a50>
        ▿ 2 : PurchaseItemSectionItem
          ▿ purchaseProduct : PurchasedCommodityEntity
            ▿ imageUrl : https://i.company.cz/Foto/f3/NA/NAR002n.jpg
            - name : "Narex Industrial-CrV 7-Bit Box"
            ▿ purchasedCommodityDetail : <ProxyEntity<PurchasedCommodityDetailEntity>: 0x11ba44bd0>
        ▿ 3 : PurchaseItemSectionItem
          ▿ purchaseProduct : PurchasedCommodityEntity
            ▿ imageUrl : https://i.company.cz/Foto/f3/VD/VD500g2.jpg
            - name : "Devolo dLAN 1200+ Starter Kit"
            ▿ purchasedCommodityDetail : <ProxyEntity<PurchasedCommodityDetailEntity>: 0x11ba44d50>
        ▿ 4 : PurchaseItemSectionItem
          ▿ purchaseProduct : PurchasedCommodityEntity
            ▿ imageUrl : https://i.company.cz/Foto/f3/WG/WG513.jpg
            - name : "Tom Clancy\'s Ghost Recon: Wildlands"
            ▿ purchasedCommodityDetail : <ProxyEntity<PurchasedCommodityDetailEntity>: 0x11ba44ed0>
    ▿ expectedItems : 2 elements
      ▿ 0 : PurchaseItemSectionItem
        ▿ purchaseProduct : PurchasedCommodityEntity
          ▿ imageUrl : https://i.company.cz/Foto/f3/NA/NAR002n.jpg
          - name : "Narex Industrial-CrV 7-Bit Box"
          ▿ purchasedCommodityDetail : <ProxyEntity<PurchasedCommodityDetailEntity>: 0x11ba7bbc0>
      ▿ 1 : PurchaseItemSectionItem
        ▿ purchaseProduct : PurchasedCommodityEntity
          ▿ imageUrl : https://i.company.cz/Foto/f3/BO/BODK050c.jpg
          - name : "Stanley FatMax STA56045-QZ"
          ▿ purchasedCommodityDetail : <ProxyEntity<PurchasedCommodityDetailEntity>: 0x11ba61eb0>
    - expectedIdentifier : 1
jasin755 commented 7 years ago

ID is unique

jasin755 commented 7 years ago

Can me somebody help?

tsomaev commented 4 years ago

@jasin755 @seekcx has anyone solved this problem ?

tsomaev commented 4 years ago

This my section model

enum EditorFileSectionModel: AnimatableSectionModelType, IdentifiableType, Equatable {
    typealias Item = EditorCells
    typealias Identity = String

    case editorFile(items: [EditorCells])

    var items: [Item] {
        switch self {
        case .editorFile(let items): return items.map { $0 }
        }
    }

    var identity: String {
        switch self {
        case .editorFile: return "editorFile"
        }
    }

    init(original: EditorFileSectionModel, items: [Item]) {
        switch original {
        case .editorFile(let items): self = .editorFile(items: items)
        }
    }

    static func ==(lhs: EditorFileSectionModel, rhs: EditorFileSectionModel) -> Bool {
        return lhs.identity == rhs.identity
    }
}

And Items model

enum EditorCells: IdentifiableType, Equatable {
    case text(data: String)
    case .....

    typealias Identity = String
    var identity: String {
        switch self {
        case .text(let data): return data 
        case .....
        }
    }

    static func ==(lhs: EditorCells, rhs: EditorCells) -> Bool {
        return lhs.identity == rhs.identity
    }
}

When added a new cell in model, I get error Wrong initializer implementation for:

seekcx commented 4 years ago

@Tawfikk Sorry, I may not be able to help you. I haven't written Swift for a long time.

goldmoment commented 4 years ago

This issue can be resolved by change

    init(original: PuzzleItemSectionModel, items: [Item]) {
        switch original {
        case let .ContentSection(content):
            self = .ContentSection(content: content)
        case let .ProbeSection(count, content):
            self = .ProbeSection(count: count, content: content)
        }
    }

to

    init(original: PuzzleItemSectionModel, items: [Item]) {
        switch original {
        case let .ContentSection:
            self = .ContentSection(content: items)
        case let .ProbeSection:
            self = .ProbeSection(count: items.count, content: items)
        }
    }