Created
April 27, 2021 11:16
-
-
Save prafullakumar/0b576c5e4d9f664c51c732010fb8f404 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import SwiftUI | |
let buttonWidth: CGFloat = 60 | |
enum CellButtons: Identifiable { | |
case edit | |
case delete | |
case save | |
case info | |
var id: String { | |
return "\(self)" | |
} | |
} | |
struct CellButtonView: View { | |
let data: CellButtons | |
let cellHeight: CGFloat | |
func getView(for image: String, title: String) -> some View { | |
VStack { | |
Image(systemName: image) | |
Text(title) | |
}.padding(5) | |
.foregroundColor(.primary) | |
.font(.subheadline) | |
.frame(width: buttonWidth, height: cellHeight) | |
} | |
var body: some View { | |
switch data { | |
case .edit: | |
getView(for: "pencil.circle", title: "Edit") | |
.background(Color.pink) | |
case .delete: | |
getView(for: "delete.right", title: "Delete") | |
.background(Color.red) | |
case .save: | |
getView(for: "square.and.arrow.down", title: "Save") | |
.background(Color.blue) | |
case .info: | |
getView(for: "info.circle", title: "Info") | |
.background(Color.green) | |
} | |
} | |
} | |
struct ContentView: View { | |
var body: some View { | |
NavigationView { | |
ScrollView { | |
LazyVStack.init(spacing: 0, pinnedViews: [.sectionHeaders], content: { | |
Section.init(header: | |
HStack { | |
Text("Section 1") | |
Spacer() | |
}.padding() | |
.background(Color.blue)) | |
{ | |
ForEach(1...10, id: \.self) { count in | |
ContentCell(data: "cell \(count)") | |
.addButtonActions(leadingButtons: [.save,.edit, .info], | |
trailingButton: [.delete], onClick: { button in | |
print("clicked: \(button)") | |
}) | |
} | |
} | |
}) | |
}.navigationTitle("Demo") | |
} | |
} | |
} | |
struct ContentCell: View { | |
let data: String | |
var body: some View { | |
VStack { | |
HStack { | |
Text(data) | |
Spacer() | |
}.padding() | |
Divider() | |
.padding(.leading) | |
} | |
} | |
} | |
extension View { | |
func addButtonActions(leadingButtons: [CellButtons], trailingButton: [CellButtons], onClick: @escaping (CellButtons) -> Void) -> some View { | |
self.modifier(SwipeContainerCell(leadingButtons: leadingButtons, trailingButton: trailingButton, onClick: onClick)) | |
} | |
} | |
struct SwipeContainerCell: ViewModifier { | |
enum VisibleButton { | |
case none | |
case left | |
case right | |
} | |
@State private var offset: CGFloat = 0 | |
@State private var oldOffset: CGFloat = 0 | |
@State private var visibleButton: VisibleButton = .none | |
let leadingButtons: [CellButtons] | |
let trailingButton: [CellButtons] | |
let maxLeadingOffset: CGFloat | |
let minTrailingOffset: CGFloat | |
let onClick: (CellButtons) -> Void | |
init(leadingButtons: [CellButtons], trailingButton: [CellButtons], onClick: @escaping (CellButtons) -> Void) { | |
self.leadingButtons = leadingButtons | |
self.trailingButton = trailingButton | |
maxLeadingOffset = CGFloat(leadingButtons.count) * buttonWidth | |
minTrailingOffset = CGFloat(trailingButton.count) * buttonWidth * -1 | |
self.onClick = onClick | |
} | |
func reset() { | |
visibleButton = .none | |
offset = 0 | |
oldOffset = 0 | |
} | |
func body(content: Content) -> some View { | |
ZStack { | |
content | |
.contentShape(Rectangle()) ///otherwise swipe won't work in vacant area | |
.offset(x: offset) | |
.gesture(DragGesture(minimumDistance: 15, coordinateSpace: .local) | |
.onChanged({ (value) in | |
let totalSlide = value.translation.width + oldOffset | |
if (0...Int(maxLeadingOffset) ~= Int(totalSlide)) || (Int(minTrailingOffset)...0 ~= Int(totalSlide)) { //left to right slide | |
withAnimation{ | |
offset = totalSlide | |
} | |
} | |
///can update this logic to set single button action with filled single button background if scrolled more then buttons width | |
}) | |
.onEnded({ value in | |
withAnimation { | |
if visibleButton == .left && value.translation.width < -20 { ///user dismisses left buttons | |
reset() | |
} else if visibleButton == .right && value.translation.width > 20 { ///user dismisses right buttons | |
reset() | |
} else if offset > 25 || offset < -25 { ///scroller more then 50% show button | |
if offset > 0 { | |
visibleButton = .left | |
offset = maxLeadingOffset | |
} else { | |
visibleButton = .right | |
offset = minTrailingOffset | |
} | |
oldOffset = offset | |
///Bonus Handling -> set action if user swipe more then x px | |
} else { | |
reset() | |
} | |
} | |
})) | |
GeometryReader { proxy in | |
HStack(spacing: 0) { | |
HStack(spacing: 0) { | |
ForEach(leadingButtons) { buttonsData in | |
Button(action: { | |
withAnimation { | |
reset() | |
} | |
DispatchQueue.main.asyncAfter(deadline: .now() + 0.20) { ///call once hide animation done | |
onClick(buttonsData) | |
} | |
}, label: { | |
CellButtonView.init(data: buttonsData, cellHeight: proxy.size.height) | |
}) | |
} | |
}.offset(x: (-1 * maxLeadingOffset) + offset) | |
Spacer() | |
HStack(spacing: 0) { | |
ForEach(trailingButton) { buttonsData in | |
Button(action: { | |
withAnimation { | |
reset() | |
} | |
DispatchQueue.main.asyncAfter(deadline: .now() + 0.20) { ///call once hide animation done | |
onClick(buttonsData) | |
} | |
}, label: { | |
CellButtonView.init(data: buttonsData, cellHeight: proxy.size.height) | |
}) | |
} | |
}.offset(x: (-1 * minTrailingOffset) + offset) | |
} | |
} | |
} | |
} | |
} | |
struct ContentView_Previews: PreviewProvider { | |
static var previews: some View { | |
ContentView() | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment