- 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)