Instagram / IGListKit

A data-driven UICollectionView framework for building fast and flexible lists.
https://instagram.github.io/IGListKit/
MIT License
12.87k stars 1.54k forks source link

[QUESTION] there is a way to use IGListBindingSectionController with embedded section controller for horizontal view? #829

Closed kleiberjp closed 7 years ago

kleiberjp commented 7 years ago

New issue checklist

General information

Debug information

# Please include debug logs using the following lldb command:
po [IGListDebugger dump]

I see the awesome for the protocol IGListBindingSectionController, but i have tried hard to used in a cell that use embedded section controller for an horizontal scroll view, and the cell inside this one be the clickable for select and binding, but to make update it reload everything and doesn't work like the example of calendar

this is what i'm trying to do

captura de pantalla 2017-06-23 a la s 10 44 20

instead the original that was vertical alignment, so in context this is what i've tried

func sectionController(_ sectionController: ListBindingSectionController<ListDiffable>, cellForViewModel viewModel: Any, at index: Int) -> UICollectionViewCell {
        let cellClass: AnyClass
        if viewModel is MonthItems {
            let cell = collectionContext!.dequeueReusableCell(of: EmbeddedCollectionViewCell.self, for: self, at: index) as! EmbeddedCollectionViewCell
            cell.configure(on: adapter, delegate: self)
            return cell    
        } else if viewModel is MonthTitleViewModel {
            cellClass = MonthTitleCell.self
        } else {
            cellClass = LabelCell.self
        }
        return collectionContext?.dequeueReusableCell(of: cellClass, for: self, at: index) ?? UICollectionViewCell()
    }

the EmbeddedCollectionViewCell.self will work for horizontal items, and i pass the objects because doesn't find another way. So the cell will looks, something like

import IGListKit

protocol MonthDaySelectDelegate: class {

    func didSelect(object: Any?)
}

final class EmbeddedCollectionViewCell: UICollectionViewCell {

    lazy var collectionView: UICollectionView = {
        let layout = UICollectionViewFlowLayout()
        layout.scrollDirection = .horizontal
        let view = UICollectionView(frame: .zero, collectionViewLayout: layout)
        view.backgroundColor = .clear
        view.alwaysBounceVertical = false
        view.alwaysBounceHorizontal = true
        self.contentView.addSubview(view)
        return view
    }()

    fileprivate weak var delegate: MonthDaySelectDelegate?
    fileprivate weak var listAdapter: ListAdapter?

    fileprivate var monthItem: MonthItems?

    override func layoutSubviews() {
        super.layoutSubviews()
        collectionView.frame = contentView.frame
    }

    func configure(on adapter: ListAdapter, delegate: MonthDaySelectDelegate?) {
        adapter.collectionView = collectionView
        adapter.dataSource = self

        self.listAdapter = adapter
        self.delegate = delegate
    }
}

extension EmbeddedCollectionViewCell: ListBindable {

    func bindViewModel(_ viewModel: Any) {
        guard let data = viewModel as? MonthItems else { return }
        self.monthItem = data
        self.listAdapter?.performUpdates(animated: false, completion: nil)
    }
}

//MARK: ListAdapterDataSource
extension EmbeddedCollectionViewCell: ListAdapterDataSource {

    // MARK: ListAdapterDataSource

    func objects(for listAdapter: ListAdapter) -> [ListDiffable] {
        guard let month = monthItem else { return [] }
        return [month]
    }

    func listAdapter(_ listAdapter: ListAdapter, sectionControllerFor object: Any) -> ListSectionController {
        let controller = DayCalendarSectionController()
        controller.delegate = delegate
        return controller
    }

    func emptyView(for listAdapter: ListAdapter) -> UIView? { return nil }
}

//MARK: Day Section Controller
final class DayCalendarSectionController: ListBindingSectionController<ListDiffable> {

    fileprivate var delegate: MonthDaySelectDelegate?

    override init() {
        super.init()
        dataSource = self
        selectionDelegate = self
    }
}

extension DayCalendarSectionController: ListBindingSectionControllerDataSource {

    func sectionController(_ sectionController: ListBindingSectionController<ListDiffable>, viewModelsFor object: Any) -> [ListDiffable] {
        guard let monthItem = object as? MonthItems else { return [] }
        return monthItem.days
    }

    func sectionController(_ sectionController: ListBindingSectionController<ListDiffable>, cellForViewModel viewModel: Any, at index: Int) -> UICollectionViewCell {
        return collectionContext!.dequeueReusableCell(of: CalendarDayCell.self, for: self, at: index) as! CalendarDayCell
    }

    func sectionController(_ sectionController: ListBindingSectionController<ListDiffable>, sizeForViewModel viewModel: Any, at index: Int) -> CGSize {
        guard let container = collectionContext?.containerSize else { return .zero }
        let square = container.width/7

        return CGSize(width: square, height: container.height)
    }

}

extension DayCalendarSectionController: ListBindingSectionControllerSelectionDelegate {

    func sectionController(_ sectionController: ListBindingSectionController<ListDiffable>, didSelectItemAt index: Int, viewModel: Any) {
        delegate?.didSelect(object: viewModel)
    }
}

and on section controller where create the cell implement delegate for update data

extension MonthSectionController: MonthDaySelectDelegate {

    func didSelect(object: Any?) {
        guard let dayViewModel = object as? Potato else { return }
        if dayViewModel.day == selectedDay {
            selectedDay = -1
        } else {
            selectedDay = dayViewModel.day
        }
        update(animated: true)
    }
}

but doesn't have the same behavior because the embedded section cell, before update always put on initial index, and don't stay on the selected index

if there are a way for make it, i appreciate it?

heumn commented 7 years ago

I am not really sure what you mean. Maybe you could sketch out what you want? 🤔

kleiberjp commented 7 years ago

@heumn i've update the issue description with more info, hope it help.

rnystrom commented 7 years ago

@kleiberjp I'm not totally sure what the problem is here. What are you expecting to happen?

To be fair, I haven't tried a setup like this. I'm sure its possible, but there might be some weird behavior.

One thing to note: the EmbeddedCollectionViewCell shouldn't be the IGListAdapterDataSource here. It should be the section controller that contains the horizontal list. Remember that cells will be reused, so that might mess something up.

kleiberjp commented 7 years ago

@rnystrom ok what i expect is, that using the binding protocol on Embedded Cell , and to make update to al the Binding Section, the cell horizontal doesn't make a reload again.

I mean, at example, if it's pressed the date number 17 (that is visible because i did scroll until that cell), when select the cell, bind the data for the another cells, and the horizontal cell keeps on same position, and updated the view for selected.

But it is not what happens here.

rnystrom commented 7 years ago

@kleiberjp any chance you could provide an example? That'd help me debug what's going on and better understand the intended behavior. This might just be a limitation of the framework, or we need to provide some guidance on setup.

heumn commented 7 years ago

@kleiberjp and updates on this? I am using IGListBindingSectionController is large projects and have zero issues most also contain setups with sideways scrolling embedded sections... Would love to help if you could provide me with an example project

heumn commented 7 years ago

I looked at the design you are trying to archieve and to me it looks a bit you are just using the Calendar example as a starting point and trying to cram the sideways scrolling cell into that one 🤔

I am not sure if ListBindingSectionController is really suitable for this particular usecase, because you are really operating at several different UICollectionViews with different scrollDirections.

What about splitting them up and skip the ListBindingSectionController?

skjermbilde 2017-08-26 kl 22 27 09

Where the HoriztontalScrollingSectionController is the one who has a horizontal scrolling UICollectionView. It would need to have a delegate / block interface that tells someone (architeture depends on too many things... ) about the selection of a date.

rnystrom commented 7 years ago

Gonna close due to inactivity, but @heumn this is really awesome looking. Thank you for helping out here!!

drinkius commented 7 years ago

For those wondering: I've managed to use ListBindingSectionController with embeddedSections, using the following viewModel:

final class SwipeItemsModel {
    weak var adapter: ListAdapter?
    weak var resource: Resource?

    init(adapter: ListAdapter?, resource: Resource?) {
        self.adapter = adapter
        self.resource = resource
    }
}

extension SwipeItemsModel: ListDiffable {

    func diffIdentifier() -> NSObjectProtocol {
        let updated = (resource?.updated ?? 0)
        return updated as NSObjectProtocol
    }

    func isEqual(toDiffableObject object: ListDiffable?) -> Bool {
        if self === object { return true }
        guard let object = object as? SwipeItemsModel else { return false }
        return resource == object.resource
    }
}

extension HorizontalStickySwipeCollectionViewCell: ListBindable {
    func bindViewModel(_ viewModel: Any) {
        guard let adapter = (viewModel as? SwipeItemsModel)?.adapter else { return }
        adapter.collectionView = collectionView
    }
}

All shows up as expected, got problems with updates for now, adapter.sectionController(for:) returns nil, but in general setup works well and shows all the data