Reputation: 992
I am making a custom list using a ForEach in SwiftUI. My goal is to make a swipe to delete gesture and not embed the ForEach into a List.
This is my code so far:
import SwiftUI
struct ContentView: View {
let list = ["item1", "item2", "item3", "item4", "item5", "item6"]
var body: some View {
VStack {
List{
ForEach(list, id: \.self) { item in
Text(item)
.foregroundColor(.white)
.frame(maxWidth: .infinity)
.padding()
.background(Color.red)
.cornerRadius(20)
.padding()
}
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
I can't seem to find a gesture that will allow me to make a swipe to delete without using the list view.
I would also like to make a custom delete button what is shown when the user swipes an item to the left (like the below picture).
Upvotes: 14
Views: 10150
Reputation: 496
Based on the existing solution:
You can update all constants at the top to your values and it will work well. And also can update .padding(.trailing, -40)
to your values (to hide the delete button by default).
struct SomeContentView: View {
@State var list = ["item1", "item2", "item3", "item4", "item5", "item6"]
@State var offsets = [CGSize](repeating: CGSize.zero, count: 6)
private let swipeLeftLimit: CGFloat = -50
private let swipeRightLimit: CGFloat = 50
private let swipeLeftLimitToShow: CGFloat = -30
private let swipeRightLimitToHide: CGFloat = 30
var body: some View {
VStack {
ForEach(list.indices, id: \.self) { index in
HStack {
Text(list[index])
.foregroundColor(.white)
.frame(maxWidth: .infinity)
.padding()
.background(Color.red)
.cornerRadius(20)
.padding()
Button(action: {
self.list.remove(at: index)
self.offsets.remove(at: index)
}) {
Image(systemName: "xmark")
}
}
.padding(.trailing, -40)
.offset(x: offsets[index].width)
.gesture(
DragGesture()
.onChanged { gesture in
// Prevent swipe to the right in default position
if offsets[index].width == 0 && gesture.translation.width > 0 {
return
}
// Prevent drag more than swipeLeftLimit points
if gesture.translation.width < swipeLeftLimit {
return
}
// Prevent swipe againt to the left if it's already swiped
if offsets[index].width == swipeLeftLimit && gesture.translation.width < 0 {
return
}
// If view already swiped to the left and we start dragging to the right
// Firstly will check if it's swiped left
if offsets[index].width >= swipeLeftLimit {
// And here checking if swiped to the right more than swipeRightLimit points
// If more - need to set the view to zero position
if gesture.translation.width > swipeRightLimit {
self.offsets[index] = .zero
return
}
// Check if only swiping to the right - update distance by minus swipeLeftLimit points
if offsets[index].width != 0 && gesture.translation.width > 0 {
self.offsets[index] = .init(width: swipeLeftLimit + gesture.translation.width,
height: gesture.translation.height)
return
}
}
self.offsets[index] = gesture.translation
}
.onEnded { gesture in
withAnimation {
// Left swipe handle:
if self.offsets[index].width < swipeLeftLimitToShow {
self.offsets[index].width = swipeLeftLimit
return
}
if self.offsets[index].width < swipeLeftLimit {
self.offsets[index].width = swipeLeftLimit
return
}
// Right swipe handle:
if gesture.translation.width > swipeRightLimitToHide {
self.offsets[index] = .zero
return
}
if gesture.translation.width < swipeRightLimitToHide {
self.offsets[index].width = swipeLeftLimit
return
}
self.offsets[index] = .zero
}
}
)
}
}
}
Also you can easily move 'onChange' to the separate method private func handleOnChangeGesture(_ gesture: DragGesture.Value, index: Int) {...paste code here...}
And the same separate method for 'onEnd'.
Upvotes: 4
Reputation: 540
This is my solution for your problem. You should add DragGesture
and create offset
for each row.
Keep in mind that your variable declare by var
can't be mutated. You have to add @State
before.
struct ContentView: View {
@State var list = ["item1", "item2", "item3", "item4", "item5", "item6"]
@State private var offsets = [CGSize](repeating: CGSize.zero, count: 6)
var body: some View {
VStack {
ForEach(list.indices, id: \.self) { index in
HStack {
Text(list[index])
.foregroundColor(.white)
.frame(maxWidth: .infinity)
.padding()
.background(Color.red)
.cornerRadius(20)
.padding()
Button(action: {
self.list.remove(at: index)
self.offsets.remove(at: index)
}) {
Image(systemName: "xmark")
}
}
.padding(.trailing, -40)
.offset(x: offsets[index].width)
.gesture(
DragGesture()
.onChanged { gesture in
self.offsets[index] = gesture.translation
if offsets[index].width > 50 {
self.offsets[index] = .zero
}
}
.onEnded { _ in
if self.offsets[index].width < -100 {
self.list.remove(at: index)
self.offsets.remove(at: index)
}
else if self.offsets[index].width < -50 {
self.offsets[index].width = -50
}
}
)
}
}
}
}
Upvotes: 4