Skip to content

Commit be21499

Browse files
author
Gabriel Hernández Ontiveros
authored
Add layout-aware drop targeting for custom layouts (#605)
_Describe your changes here. Please include screenshots if they're visual!_ This change allows custom ListLayout implementations to override drop target determination during interactive item reordering. - Add targetIndexPath(forInteractivelyMovingItem:withPosition:) protocol method to ListLayout with default nil implementation - Add CollectionViewLayout override that delegates to custom layout - Add isReorderable property on ListLayoutContent.ItemInfo to expose whether an item has reordering enabled ### Checklist Please do the following before merging: - [x] Ensure any public-facing changes are reflected in the [changelog](https://github.com/square/Listable/blob/main/CHANGELOG.md). Include them in the `Main` section.
2 parents 2ff75f8 + f8ce83f commit be21499

4 files changed

Lines changed: 41 additions & 3 deletions

File tree

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44

55
### Added
66

7+
- Added `targetIndexPath(forInteractivelyMovingItem:withPosition:)` protocol method to `ListLayout`, allowing custom layouts to override drop target determination during interactive reordering.
8+
- Added `isReorderable` property on `ListLayoutContent.ItemInfo` to check if an item has reordering enabled.
9+
710
### Removed
811

912
### Changed

ListableUI/Sources/Layout/CollectionViewLayout.swift

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -623,9 +623,15 @@ final class CollectionViewLayout : UICollectionViewLayout
623623
withPosition position: CGPoint
624624
) -> IndexPath {
625625

626-
/// TODO: The default implementation provided by `UICollectionView` does not work correctly
627-
/// when trying to move an item to the end of a section, or when trying to move an item into an
628-
/// empty section. We should add casing that allows moving into the section in these cases.
626+
// Allow custom layouts to provide layout-aware drop targeting.
627+
// This fixes issues with UICollectionView's default implementation which doesn't
628+
// work correctly for some layout types.
629+
if let customTarget = self.layout.targetIndexPath(
630+
forInteractivelyMovingItem: previousIndexPath,
631+
withPosition: position
632+
) {
633+
return customTarget
634+
}
629635

630636
return super.targetIndexPath(forInteractivelyMovingItem: previousIndexPath, withPosition: position)
631637
}

ListableUI/Sources/Layout/ListLayout/ListLayout.swift

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,21 @@ public protocol AnyListLayout : AnyObject
168168
at indexPath: IndexPath,
169169
withTargetPosition position: CGPoint
170170
)
171+
172+
/// Returns the target index path for an item being interactively moved.
173+
///
174+
/// Custom layouts can override this to provide layout-aware drop target
175+
/// determination. The default implementation returns `nil`, which causes
176+
/// `CollectionViewLayout` to fall back to UICollectionView's default behavior.
177+
///
178+
/// - Parameters:
179+
/// - previousIndexPath: The current index path of the item being moved.
180+
/// - position: The current position of the item in the collection view's coordinate space.
181+
/// - Returns: The target index path if the layout can determine it, or `nil` to use default behavior.
182+
func targetIndexPath(
183+
forInteractivelyMovingItem previousIndexPath: IndexPath,
184+
withPosition position: CGPoint
185+
) -> IndexPath?
171186
}
172187

173188

@@ -338,6 +353,15 @@ extension ListLayout
338353
) {
339354
// Nothing. Just a default implementation.
340355
}
356+
357+
public func targetIndexPath(
358+
forInteractivelyMovingItem previousIndexPath: IndexPath,
359+
withPosition position: CGPoint
360+
) -> IndexPath? {
361+
// Default: return nil to use UICollectionView's default behavior.
362+
// Custom layouts can override this for layout-aware drop targeting.
363+
nil
364+
}
341365

342366
private static func isHeaderSticky(
343367
list: Bool,

ListableUI/Sources/Layout/ListLayout/ListLayoutContent.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -510,6 +510,11 @@ extension ListLayoutContent
510510
public var layouts : ItemLayouts {
511511
self.state.anyModel.layouts
512512
}
513+
514+
/// Whether this item can be reordered (has reordering configuration).
515+
public var isReorderable: Bool {
516+
self.state.anyModel.reordering != nil
517+
}
513518

514519
public var frame : CGRect {
515520
CGRect(

0 commit comments

Comments
 (0)