IGListKit reorder by drag and drop

  1. return true to canMoveItem in cell sectionController
    override func canMoveItem(at index: Int) -> Bool {
        return true
    }

2. detect longPressGesture on View Controller

    let collectionViewUpdateQueue = BlockableTaskQueue()

func bind() {
        collectionView.rx
            .longPressGesture(configuration: { _, delegate in
                delegate.simultaneousRecognitionPolicy = .never
                delegate.beginPolicy = .custom { [weak self] gesture -> Bool in
                    guard let collectionView = self?.collectionView else { return false }
                    let touchLocation = gesture.location(in: collectionView)
                    guard let selectedIndexPath = collectionView.indexPathForItem(at: touchLocation) else { return false }
                    guard let cell = collectionView.cellForItem(at: selectedIndexPath) as? PositionListPositionCell else { return false }
                    return cell.isMovable
                }
            })
            .subscribe(onNext: { [weak self] gesture in
                self?.handleGesture(gesture)
            })
            .disposed(by: disposeBag)
}

extension PositionListStockViewController {
    func handleGesture(_ gesture: UILongPressGestureRecognizer) {
        switch gesture.state {
        case .began:
            let touchLocation = gesture.location(in: collectionView)
            guard let selectedIndexPath = collectionView.indexPathForItem(at: touchLocation) else { return }
            collectionViewUpdateQueue.block()
            collectionView.beginInteractiveMovementForItem(at: selectedIndexPath)
        case .changed:
            let position = gesture.location(in: collectionView)
            collectionView.updateInteractiveMovementTargetPosition(position)
        case .ended:
            collectionView.endInteractiveMovement()
            collectionViewUpdateQueue.unblock()
        default:
            collectionView.cancelInteractiveMovement()
            collectionViewUpdateQueue.unblock()
        }
    }
}

3. limit removable index from start to end

init() {
         let (collectionView, layout) = ViewFactory.collectionViewAndLayout(isHorizontal: false)
        layout.reorderingDelegate = self
}

extension PositionListStockViewController: CollectionViewFlowLayoutWithReorderingDelegate {
    func collectionViewFlowLayoutWithReorderingOverrideTargetIndex(moveFrom originalIndexPath: IndexPath, to computedTargetIndexPath: IndexPath) -> IndexPath {
        guard let reactor = reactor else { return originalIndexPath }

        guard let minIndexPath = reactor.getStartIndexPathOfPositions(),
              let maxIndexPath = reactor.getEndIndexPathOfPositions() else { return originalIndexPath }
        return computedTargetIndexPath.clamped(min: minIndexPath, max: maxIndexPath)
    }
}
extension PositionListStockViewReactor {
    func getStartIndexPathOfPositions() -> IndexPath? {
        guard let positions = currentState.positions,
              positions.isNotEmpty,
              let sectionIndex = makeListSection().firstIndex(where: {
                  guard let model = $0 as? DiffableBox<PositionListPositionModel> else {
                      return false
                  }
                  return true
              })
        else {
            return nil
        }
        return IndexPath(row: 0, section: sectionIndex)
    }

    func getEndIndexPathOfPositions() -> IndexPath? {
        guard let startIndex = getStartIndexPathOfPositions() else { return nil }
        let numberOfPositions = currentState.positions?.count ?? 0
        return IndexPath(row: 0, section: startIndex.section + numberOfPositions)
    }
}

4. Change list object first and then reorder request to server

init() {
        adapter.moveDelegate = self
}

extension PositionListStockViewController: ListAdapterMoveDelegate {
    func listAdapter(_ listAdapter: ListAdapter, move object: Any, from previousObjects: [Any], to objects: [Any]) {
        guard let object = object as? DiffableBox<PositionListPositionModel> else {
            return
        }
        let previous = previousObjects
            .compactMap { object in
                return object as? DiffableBox<PositionListPositionModel>
            }
        let current = objects
            .compactMap { object in
                return object as? DiffableBox<PositionListPositionModel>
            }

        reactorNotNil?.action.onNext(.onlyChangePositionsTo(current.compactMap(\.value.userStock))) 
        reactorNotNil?.action.onNext(.reorder(newList: current.compactMap(\.value.userStock)))
    }
}

5. When updating the collection view, enque to the collectionViewUpdateQueue

reactor.state.distinctUntilChanged { $0.listVersion }
.observe(on: MainScheduler.instance)
.subscribe(onNext: { [weak self] _ in
self?.collectionViewUpdateQueue.enqueue(
BlockableTaskQueue.Mutation(
type: .wholeUpdate,
mutate: { [weak self] in self?.adapter.performUpdates(animated: false, completion: nil) }
)
)
})
.disposed(by: disposeBag)

How to add action to cell using IGListKit, ReactorKit

  1. Cell
final class ExampleCell: UICollectionViewCell, Reusable {

    enum Event {
        case tapButton(Bool)
    }

    private func bind() {
        button?.rx.tapGesture().when(.recognized)
            .asPublisherIgnoringError()
            .sink(receiveValue: { [weak self] _ in
                guard let isSomething = self?.isSomething else { return }
                self?.isSomething.toggle()
                self?.onEvent?(.tapButton(isSomething))
            })
            .store(in: &subscriptions)
    }
}

2. SectionController

final class ExampleSectionController: NeoListSectionController {
    typealias Model = ExampleModel
    typealias Cell = ExampleCell

    private var model: Model?
    private let eventSubject = PublishSubject<ExampleCell.Event>()
    var event: Observable<ExampleCell.Event> {
        return eventSubject
    }

3. Reactor+IGListKit

extension ExampleViewReactor: ListAdapterDataSource {
    func listAdapter(_ listAdapter: ListAdapter, sectionControllerFor object: Any) -> ListSectionController {
        switch object {
        case is DiffableBox<ExampleModel>:
            let controller = ExampleSectionController()
            controller.event
                .subscribe(onNext: { [weak self] event in
                  switch event {
                    case .tapButton(let isSomething):
                        self?.action.onNext(.tapButton(isSomething))
                    }
                })
                .disposed(by: disposeBag)

4. Reactor

final class ExampleViewReactor: NSObject, Reactor {
    enum Action {
        case tapButton(Bool)
    }

    enum Mutation {
        case setIsSomething(Bool)
    }

    struct State {
        var isSomething: Bool
    }

    // MARK: - Properties

    var initialState: State

    let disposeBag = DisposeBag()

    // MARK: - Dependencies

    override init() {
        self.initialState = State(listVersion: 0, isSomething: true)
    }

    func mutate(action: Action) -> Observable<Mutation> {
        switch action {
        case .tapButton(let isSomething):
            return .just(.setIsSomething(isSomething))
        }
    }

    func reduce(state: State, mutation: Mutation) -> State {
        let state = state
        return state
    }
}