Reorderable
A SwiftUI structural component that allows for easy drag-and-drop reordering operations
Install / Use
/learn @visfitness/ReorderableREADME
SwiftUI Reorderable
A pure SwiftUI structural component that allows for easy drag-and-drop reordering operations. It enables fast, DragGesture based interactions with its elements instead of to the "long press" based one that comes with .onDrag/.draggable. Here it is in action in the Vis iOS app:

<img src="Documentation/appstore.svg" alt="Download Vis on the App Store"/>
This package contains a ReorderableVStack and a ReorderableHStack.
Features
- Specify your own drag handle with the
.dragHandle()modifier - Disable/Enable dragging via the
.dragDisabled(_ dragDisabled: Bool)modifier, which plays nicely with animations (as opposed to adding/removing a.onDrag()modifier) - Easily customize your drag state via a
isDraggedparameter passed to yourcontentview builder. - Haptic feedback on move.
Installation
This framework is distributed as a Swift Package. To use, add the following URL to your package list:
https://github.com/visfitness/reorderable
To add this package to your Xcode project, follow these instructions.
Documentation
The documentation for this package can be found here
Usage
[!NOTE] All the following sample use the following
structfor their dataprivate struct Sample: Identifiable { var color: UIColor var id: UUID = UUID() var height: CGFloat init(_ color: UIColor, _ height: CGFloat) { self.color = color self.height = height } }
Simple Example
struct SimpleExample: View {
@State var data = [
Sample(UIColor.systemBlue, 200),
Sample(UIColor.systemGreen, 100),
Sample(UIColor.systemGray, 300)
]
var body: some View {
ReorderableVStack($data) { $sample in
RoundedRectangle(cornerRadius: 32, style: .continuous)
.fill(Color(sample.color))
.frame(height: sample.height)
.padding()
}
.padding()
}
}
Using a collection and onMove.
struct SimpleExample: View {
@State var data = [
Sample(UIColor.systemBlue, 200),
Sample(UIColor.systemGreen, 100),
Sample(UIColor.systemGray, 300)
]
var body: some View {
ReorderableVStack(data, onMove: { from, to in
withAnimation {
data.move(fromOffsets: IndexSet(integer: from),
toOffset: (to > from) ? to + 1 : to)
}
}) { sample in
RoundedRectangle(cornerRadius: 32, style: .continuous)
.fill(Color(sample.color))
.frame(height: sample.height)
.padding()
}
.padding()
}
}
With Custom Drag Handle and Dragging Effect
struct SimpleExample: View {
@State var data = [
Sample(UIColor.systemBlue, 200),
Sample(UIColor.systemGreen, 100),
Sample(UIColor.systemGray, 300)
]
var body: some View {
ReorderableVStack($data) { $sample, isDragged in // <------ Notice the additional `isDragged` parameter
ZStack(alignment: .leading) {
RoundedRectangle(cornerRadius: 32, style: .continuous)
.fill(Color(sample.color))
.frame(height: sample.height)
Image(systemName: "line.3.horizontal")
.foregroundStyle(.secondary)
.padding()
.offset(x: 16)
// This will now be the only place users can drag the view from
.dragHandle() // <------------
}
.scaleEffect(isDragged ? 1.1: 1)
.animation(.easeOut, value: isDragged)
.padding()
}.padding()
}
}
When Part of a ScrollView
[!WARNING] Because this package doesn't rely on SwiftUI's native
onDrag, it also doesn't automatically trigger auto-scrolling when users drag the element to the edge of the parent/ancestorScrollView. To enable this behavior, theautoScrollOnEdges()modifier needs to be applied to theScrollView.
struct SimpleExample: View {
@State var data = [
Sample(UIColor.systemBlue, 200),
Sample(UIColor.systemGreen, 200),
Sample(UIColor.systemGray, 300),
Sample(UIColor.systemMint, 200),
Sample(UIColor.systemPurple, 300),
Sample(UIColor.orange, 200)
]
var body: some View {
ScrollView {
ReorderableVStack($data) { $sample in
RoundedRectangle(cornerRadius: 32, style: .continuous)
.fill(Color(sample.color))
.frame(height: sample.height)
.padding()
}.padding()
}.autoScrollOnEdges() // <------- This modifier enables the autoscrolling
}
}
Nested ReorderableHStack in ReorderableVStack
private struct Sample2D: Identifiable {
var id: UUID = UUID()
var row: [Sample]
}
struct SimpleExample: View {
@State var data: [Sample2D] = [
.init(row: [.init(UIColor.systemBlue, 200), .init(UIColor.systemGreen, 100), .init(UIColor.systemGray, 200)]),
.init(row: [.init(UIColor.systemRed, 200), .init(UIColor.systemMint, 100), .init(UIColor.systemPurple, 200)]),
.init(row: [.init(UIColor.systemIndigo, 200), .init(UIColor.systemTeal, 100), .init(UIColor.systemYellow, 200)]),
]
var body: some View {
ReorderableVStack($data) { $sample in
HStack {
ZStack {
RoundedRectangle(cornerRadius: 24, style: .continuous)
.fill(Color(UIColor.systemOrange))
.frame(width: 64, height: 64)
.padding()
Image(systemName: "line.3.horizontal")
.foregroundStyle(.secondary)
.padding()
}
.dragHandle()
ReorderableHStack($sample.row) { $sample in
RoundedRectangle(cornerRadius: 24, style: .continuous)
.fill(Color(sample.color))
.frame(width: 64, height: 64)
.padding()
}
}
}
}
}
Copyright and License
Copyright Vis Fitness Inc. Licensed under the MIT License
