How to make a swipeable view with SwiftUI - swift

I tried to make a SWIFTUI View that allows card Swipe like action by using gesture() method. But I can't figure out a way to make view swipe one by one. Currently when i swipe all the views are gone
import SwiftUI
struct EventView: View {
#State private var offset: CGSize = .zero
#ObservedObject var randomView: EventViewModel
var body: some View {
ZStack{
ForEach(randomView.randomViews,id:\.id){ view in
view
.background(Color.randomColor)
.cornerRadius(8)
.shadow(radius: 10)
.padding()
.offset(x: self.offset.width, y: self.offset.height)
.gesture(
DragGesture()
.onChanged { self.offset = $0.translation }
.onEnded {
if $0.translation.width < -100 {
self.offset = .init(width: -1000, height: 0)
} else if $0.translation.width > 100 {
self.offset = .init(width: 1000, height: 0)
} else {
self.offset = .zero
}
}
)
.animation(.spring())
}
}
}
}
struct EventView_Previews: PreviewProvider {
static var previews: some View {
EventView(randomView: EventViewModel())
}
}
struct PersonView: View {
var id:Int = Int.random(in: 1...1000)
var body: some View {
VStack(alignment: .center) {
Image("testBtn")
.clipShape(/*#START_MENU_TOKEN#*/Circle()/*#END_MENU_TOKEN#*/)
Text("Majid Jabrayilov")
.font(.title)
.accentColor(.white)
Text("iOS Developer")
.font(.body)
.accentColor(.white)
}.padding()
}
}
With this piece of code, when i swipe the whole thing is gone

Basically your code tells every view to follow offset, while actually you want only the top one move. So firstly I'd add a variable that'd hold current index of the card and a method to calculate it's offset:
#State private var currentCard = 0
func offset(for i: Int) -> CGSize {
return i == currentCard ? offset : .zero
}
Secondly, I found out that if we just leave it like that, on the next touch view would get offset of the last one (-1000, 0) and only then jump to the correct location, so it looks just like previous card decided to return instead of the new one. In order to fix this I added a flag marking that card has just gone, so when we touch it again it gets right location initially. Normally, we'd do that in gesture's .began state, but we don't have an analog for that in swiftUI, so the only place to do it is in .onChanged:
#State private var didJustSwipe = false
DragGesture()
.onChanged {
if self.didJustSwipe {
self.didJustSwipe = false
self.currentCard += 1
self.offset = .zero
} else {
self.offset = $0.translation
}
}
In .onEnded in the case of success we assign didJustSwipe = true
So now it works perfectly. Also I suggest you diving your code into smaller parts. It will not only improve readability, but also save some compile time. You didn't provide an implementation of EventViewModel and those randomViews so I used rectangles instead. Here's your code:
struct EventView: View {
#State private var offset: CGSize = .zero
#State private var currentCard = 0
#State private var didJustSwipe = false
var randomView: some View {
return Rectangle()
.foregroundColor(.green)
.cornerRadius(20)
.frame(width: 300, height: 400)
.shadow(radius: 10)
.padding()
.opacity(0.3)
}
func offset(for i: Int) -> CGSize {
return i == currentCard ? offset : .zero
}
var body: some View {
ZStack{
ForEach(currentCard..<5, id: \.self) { i in
self.randomView
.offset(self.offset(for: i))
.gesture(self.gesture)
.animation(.spring())
}
}
}
var gesture: some Gesture {
DragGesture()
.onChanged {
if self.didJustSwipe {
self.didJustSwipe = false
self.currentCard += 1
self.offset = .zero
} else {
self.offset = $0.translation
}
}
.onEnded {
let w = $0.translation.width
if abs(w) > 100 {
self.didJustSwipe = true
let x = w > 0 ? 1000 : -1000
self.offset = .init(width: x, height: 0)
} else {
self.offset = .zero
}
}
}
}

Related

How to dynamically change GridItems in LazyVGrid with MagnificationGesture [Zoom In, Out] in SwiftUI?

The idea is to recreate the same photo layout behaviour like in Apple Photo Library when I can zoom in and out with 1, 3 or 5 photos in a row. I'm stack in a half way. For that I use a MagnificationGesture() and based on gesture value I update number of GridItems() in LazyVGrid().
Please let me know how to achieve it. Thanks a lot ๐Ÿ™
Here's code:
import SwiftUI
struct ContentView: View {
let colors: [Color] = [.red, .purple, .yellow, .green, .blue, .mint, .orange]
#State private var colums = Array(repeating: GridItem(), count: 1)
// #GestureState var magnifyBy: CGFloat = 1.0
#State var magnifyBy: CGFloat = 1.0
#State var lastMagnifyBy: CGFloat = 1.0
let minMagnifyBy = 1.0
let maxMagnifyBy = 5.0
var magnification: some Gesture {
MagnificationGesture()
// .updating($magnifyBy) { (currentState, pastState, trans) in
// pastState = currentState.magnitude
// }
.onChanged { state in
adjustMagnification(from: state)
print("Current State \(state)")
}
.onEnded { state in
adjustMagnification(from: state)
// withAnimation(.spring()) {
// validateMagnificationLimits()
// }
lastMagnifyBy = 1.0
}
}
var body: some View {
NavigationView {
ScrollView {
LazyVGrid(columns: colums) {
ForEach(1..<101) { number in
colors[number % colors.count]
.overlay(Text("\(number)").font(.title2.bold()).foregroundColor(.white))
.frame(height: 100)
}
}
.scaleEffect(magnifyBy)
.gesture(magnification)
.navigationTitle("๐Ÿงจ Grid")
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Button {
withAnimation(.spring(response: 0.8)) {
colums = Array(repeating: .init(), count: colums.count == 5 ? 1 : colums.count % 5 + 2)
}
} label: {
Image(systemName: "square.grid.3x3")
.font(.title2)
.foregroundColor(.primary)
}
}
}
}
}
}
private func adjustMagnification(from state: MagnificationGesture.Value) {
let stepCount = Int(min(max(1, state), 5))
// let delta = state / lastMagnifyBy
// magnifyBy *= delta
withAnimation(.linear) {
colums = Array(repeating: GridItem(), count: stepCount)
}
lastMagnifyBy = state
}
private func getMinMagnificationAllowed() -> CGFloat {
max(magnifyBy, minMagnifyBy)
}
private func getMaxMagnificationAllowed() -> CGFloat {
min(magnifyBy, maxMagnifyBy)
}
private func validateMagnificationLimits() {
magnifyBy = getMinMagnificationAllowed()
magnifyBy = getMaxMagnificationAllowed()
}
}
Here you go. This uses a TrackableScrollView (git link in the code).
I implemented an array of possible zoomStages (cols per row), to make switching between them easier.
Next to dos would be scrolling back to the magnification center, so the same item stays in focus. And maybe an opacity transition in stead of rearranging the Grid. Have fun ;)
import SwiftUI
// https://github.com/maxnatchanon/trackable-scroll-view.git
import SwiftUITrackableScrollView
struct ContentView: View {
let colors: [Color] = [.red, .purple, .yellow, .green, .blue, .mint, .orange]
let zoomStages = [1, 3, 5, 9, 15]
#State private var zoomStageIndex = 0
var colums: [GridItem] { Array(repeating: GridItem(spacing: 0), count: zoomStages[zoomStageIndex]) }
#State var magnifyBy: CGFloat = 1.0
#State private var scrollViewOffset = CGFloat.zero // SwiftUITrackableScrollView: Content offset available to use
var body: some View {
NavigationView {
TrackableScrollView(.vertical, showIndicators: false, contentOffset: $scrollViewOffset) {
LazyVGrid(columns: colums, spacing: 0) {
ForEach(0..<500) { number in
colors[number % colors.count]
.overlay(
Text("\(number)").font(.title2.bold()).foregroundColor(.white)
.minimumScaleFactor(0.1)
)
.aspectRatio(1, contentMode: .fit) // always squares
.id(number)
}
}
.scaleEffect(magnifyBy, anchor: .top)
// offset to correct magnify "center" point
.offset(x: 0, y: (scrollViewOffset + UIScreen.main.bounds.midY) * (1 - magnifyBy) )
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Button {
withAnimation(.spring(response: 0.8)) {
if zoomStageIndex < zoomStages.count-1 {
zoomStageIndex += 1
} else {
zoomStageIndex = 0
}
}
} label: {
Image(systemName: "square.grid.3x3")
.font(.title2)
.foregroundColor(.primary)
}
}
}
.gesture(magnification)
}
.ignoresSafeArea()
}
}
var magnification: some Gesture {
MagnificationGesture()
.onChanged { state in
magnifyBy = state
}
.onEnded { state in
// find predefined zoom(index) that is closest to actual pinch zoom value
let newZoom = Double(zoomStages[zoomStageIndex]) * 1 / state
let newZoomIndex = findClosestZoomIndex(value: newZoom)
// print("***", zoomStages[zoomStageIndex], state, newZoom, newZoomIndex)
withAnimation(.spring(response: 0.8)) {
magnifyBy = 1 // reset scaleEffect
zoomStageIndex = newZoomIndex // set new zoom level
}
}
}
func findClosestZoomIndex(value: Double) -> Int {
let distanceArray = zoomStages.map { abs(Double($0) - value) } // absolute difference between zoom stages and actual pinch zoom
// print("dist:", distanceArray)
return distanceArray.indices.min(by: {distanceArray[$0] < distanceArray[$1]}) ?? 0 // return index of element that is "closest"
}
}

How to implement scrolling bottom down when we got to the beginning of the content, inside the scrollview using pure SwiftUI?

Here is my attempt to implement this functionality, I also tried to solve it through UIKit, it worked, but I ran into problems with dynamically changing the content of SwiftUI, which was inside UIScrollView. More precisely, the problem was in changing the height of the container
https://imgur.com/a/6du73pt
import SwiftUI
struct ScrollViewOffsetPreferenceKey: PreferenceKey {
static var defaultValue: CGFloat = .zero
static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
value = nextValue()
}
}
struct ContentView: View {
#State private var offset: CGFloat = 300
var body: some View {
ZStack {
Color.yellow.ignoresSafeArea()
ScrollView(.vertical) {
ForEach(0..<100, id: \.self) { _ in
Color.red
.frame(width: 250, height: 125, alignment: .center)
}
.overlay(
GeometryReader { proxy in
let offset = proxy.frame(in: .named("scroll")).minY
Color.clear.preference(key: ScrollViewOffsetPreferenceKey.self, value: offset)
.frame(width: 0, height: 0, alignment: .center)
})
}
.coordinateSpace(name: "scroll")
.onPreferenceChange(ScrollViewOffsetPreferenceKey.self) { value in
if value >= 0 {
offset = value + 300
}
}
.gesture(DragGesture()
.onChanged({ value in
print("scrooll")
print(value)
})
)
}
.offset(y: offset)
.gesture(DragGesture(minimumDistance: 25, coordinateSpace: .local)
.onChanged({ value in
offset = value.translation.height + 300
}))
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Below is an example of how you can lock the ScrollView when you are at the top, and then allow the DragGesture to operate instead of scroll. I removed your PreferenceKey as it was not necessary. I also used frame reader to determine where in the scroll view the top cell was. Code is extensively commented.
struct ScrollViewWithPulldown: View {
#State private var offset: CGFloat = 300
#State private var scrollEnabled = true
#State private var cellRect: CGRect = .zero
// if the top of the cell is in view, origin.y will be greater than or equal to zero
var topInView: Bool {
cellRect.origin.y >= 0
}
var body: some View {
ZStack {
Color.yellow.ignoresSafeArea()
ScrollView {
ForEach(0..<100, id: \.self) { id in
Color.red
.id(id)
.frame(width: 250, height: 125, alignment: .center)
// This is inspired by https://www.fivestars.blog/articles/swiftui-share-layout-information/
.copyFrame(in: .named("scroll"), to: $cellRect)
.onChange(of: cellRect) { _ in
if id == 0 { // insure the first view however you need to
if topInView {
scrollEnabled = false
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
scrollEnabled = true
}
} else {
scrollEnabled = true
}
}
}
}
}
.disabled(!scrollEnabled)
.coordinateSpace(name: "scroll")
}
.offset(y: offset)
.gesture(DragGesture()
.onChanged({ value in
// Scrolling down
if value.translation.height > 0 && topInView {
scrollEnabled = false
print("scroll locked")
print(value)
} else { // Scrolling up
scrollEnabled = true
print("scroll up")
print(value)
}
})
.onEnded({ _ in
scrollEnabled = true
})
)
}
}
A view extension inspired by FiveStar Blog:
extension View {
func readFrame(in space: CoordinateSpace, onChange: #escaping (CGRect) -> Void) -> some View {
background(
GeometryReader { geometryProxy in
Color.clear
.preference(key: FrameInPreferenceKey.self, value: geometryProxy.frame(in: space))
}
)
.onPreferenceChange(FrameInPreferenceKey.self, perform: onChange)
}
func copyFrame(in space: CoordinateSpace, to binding: Binding<CGRect>) -> some View {
self.readFrame(in: space) { frame in
binding.wrappedValue = frame
}
}
}

How can I set this code up to zoom in from anywhere on the image?

I'm working on some code in which users should be allowed to use various gestures on the app's image. What I'd like is to understand how I can allow users to pinch, or double tap to a specific location in the image. Right now, they can only zoom from the center, or else I'm getting unexpected results with the zoom. I chose this path since this uses both SwiftUI, as well as a drag to dismiss feature. Thanks!
import SwiftUI
public struct SampleZoom {
#ObservedObject
private var viewModel: ViewModel
#State private var currentZoom: CGFloat = 0
#State private var endingZoom: CGFloat = 1
#State private var isZoomed = false
#State private var pointTapped: CGPoint = .zero
#GestureState var swipeOffset: CGSize = .zero
public init(viewModel: ViewModel) {
self.viewModel = viewModel
}
}
extension SampleZoom: View {
public var body: some View {
if viewModel.isVisible {
ZStack {
Color.black
.opacity(viewModel.bgOpacity)
.edgesIgnoringSafeArea(.all)
image(imageData: viewModel.image)
}
.onAppear {
endingZoom = 1
isZoomed = false
}
.overlay(
/// Close Button
Button(action: {
viewModel.isVisible.toggle()
}, label: {
Image(systemName: "xmark")
.foregroundColor(.white)
.padding()
.background(Color.white.opacity(0.33))
.clipShape(Circle())
})
.padding(Spacing.standard), alignment: .topLeading
)
} else {
Spacer()
}
}
}
private extension SampleZoom {
/// Creates card Image
func image(imageData: Data) -> some View {
GeometryReader { reader in
Image("Your Image Here!")
.resizable()
.aspectRatio(contentMode: .fit)
.offset(y: viewModel.imageOffset.height)
.animation(.default)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.scaleEffect(endingZoom + currentZoom > 1 ? endingZoom + currentZoom : 1, anchor: UnitPoint(
x: pointTapped.x / reader.frame(in: .global).maxX,
y: pointTapped.y / reader.frame(in: .global).maxY
))
/// Double tap to zoom
.gesture(
TapGesture(count: 2).onEnded {
withAnimation {
isZoomed.toggle()
endingZoom = endingZoom > 1 ? 1 : 2
}
}
.simultaneously(
with: DragGesture(minimumDistance: 0, coordinateSpace: .global).onChanged { value in
if !isZoomed {
pointTapped = value.startLocation
}
}
/// Swipe & close interactions
.updating($swipeOffset) { value, outValue, _ in
outValue = value.translation
viewModel.onChange(imagePosition: swipeOffset)
}
.onEnded(viewModel.onEnd(swipeDistance:))
)
)
/// Pinch & zoom interactions
.gesture(
MagnificationGesture()
.onChanged { amount in
currentZoom = amount - 1
}
.onEnded { _ in
endingZoom += currentZoom
currentZoom = 0
isZoomed = endingZoom + currentZoom > 1 ? true : false
}
)
}
}
}
public extension SampleZoom {
final class ViewModel: ObservableObject {
let image: Data
#Published
var isVisible: Bool
#Published
var imageOffset: CGSize = .zero
#Published
var bgOpacity: Double = 1
func onChange(imagePosition: CGSize) {
imageOffset = imagePosition
let halfScreenHeight = UIScreen.main.bounds.height / 2
let progress = imagePosition.height / halfScreenHeight
withAnimation(.default) {
bgOpacity = Double(1 - (progress < 0 ? -progress : progress))
}
}
func onEnd(swipeDistance: DragGesture.Value) {
withAnimation(.easeOut) {
var translation = swipeDistance.translation.height
if translation < Spacing.none {
translation = -translation
}
if translation < Spacing.doubleExtraLarge * 3 {
imageOffset.height = Spacing.none
bgOpacity = 1
} else {
isVisible.toggle()
imageOffset.height = Spacing.none
bgOpacity = 1
}
}
}
init(image: Data, isVisible: Bool) {
self.image = image
self.isVisible = isVisible
}
}
}
You can pinch to zoom to your imageView very easily with the help of a UIScrollView. Add UIScrollView and inside add the imageView. Then implement the UIScrollViewDelegate and the function viewForZooming
override func viewDidLoad() {
super.viewDidLoad()
scrollView.minimumZoomScale = 1.0
scrollView.maximumZoomScale = 10.0
scrollView.delegate = self
}
extension YourVC: UIScrollViewDelegate {
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
return image
}
}

Slide a Scrollview over another view

Here is what i am trying to achieve :
Here is what i currently have :
I cannot seem to get the my ScrollView to slide over the green header view (schedulerHeaderView) like in the first example. Here is my code
struct CreateShiftView: View {
#EnvironmentObject private var createShiftViewModel: CreateShiftViewModel
var body: some View {
GeometryReader { geo in
VStack {
schedulerHeaderView
Spacer()
}
.background(
Color(createShiftViewModel.isNavbarTitleHidden ? ThemeManager.current().greenery : .white)
.ignoresSafeArea(.all, edges: .top)
)
.overlay(
ScrollView {
VStack(spacing: 42) {
addEmployeeSection
shiftInfoSection
addBreakSection
addResourceSection
addJustificationSection
savePublishSection
}
.background(Color.white)
.cornerRadius(20, corners: [.topLeft, .topRight])
.padding(.horizontal, 24)
}
.frame(minWidth: UIScreen.main.bounds.width)
.offset(y: geo.size.height * 0.155)
)
}
}
}
I'm guessing i need to dynamically change the offset or position of the scrollView with a DragGesture but i haven't been able to get a working example so far. Any help would be appreciated. Thanks in advance.
I have found the solution to my question. In summary, i need to keep track of the last offset (using state) and use DragGesture's .onChanged and .onEnded to set a new offset depending on the value of the drag translation. Here is my updated code which now works
struct CreateShiftView: View {
#EnvironmentObject private var createShiftViewModel: CreateShiftViewModel
#State private var offsets = (top: CGFloat.zero, bottom: CGFloat.zero)
#State private var offset: CGFloat = .zero
#State private var lastOffset: CGFloat = .zero
#State private var isAtTopOffset = false
#State private var dragging = false
var body: some View {
GeometryReader { geo in
VStack {
schedulerHeaderView
Spacer()
}
.background(
Color(createShiftViewModel.isNavbarTitleHidden ? ThemeManager.current().greenery : .white)
.ignoresSafeArea(.all, edges: .top)
)
.overlay(
SlideOverView(cardviewInitialPosition: geo.size.height * 0.30, viewModel: createShiftViewModel) {
VStack(spacing: 42) {
informationSection
shiftInfoSection
addEmployeeSection
addBreakSection
addResourceSection
addJustificationSection
savePublishSection
}
.background(Color.white)
.cornerRadius(20, corners: [.topLeft, .topRight])
.padding(.horizontal, 24)
}
.onAppear {
self.offsets = (
top: .zero,
bottom: geo.size.height * 0.155
)
self.offset = self.offsets.bottom
self.lastOffset = self.offset
}
.offset(y: self.offset)
.animation(dragging ? nil : {
Animation.interpolatingSpring(stiffness: 250.0, damping: 40.0, initialVelocity: 5.0)
}())
.simultaneousGesture(
DragGesture(minimumDistance: createShiftViewModel.isNavbarTitleHidden ? 5 : 100, coordinateSpace: .local)
.onChanged { v in
dragging = true
let newOffset = self.lastOffset + v.translation.height
if (newOffset > self.offsets.top && newOffset < self.offsets.bottom) {
self.offset = newOffset
}
}
.onEnded{ v in
dragging = false
if (self.lastOffset == self.offsets.top && v.translation.height > 0) {
self.offset = self.offsets.bottom
createShiftViewModel.cardAtBottom()
} else if (self.lastOffset == self.offsets.bottom && v.translation.height < 0) {
self.offset = self.offsets.top
createShiftViewModel.cardAtTop()
}
self.lastOffset = self.offset
}
)
)
}
}
}

SwiftUI Table Custom Swipe?

Is there a way to swipe table rows to the left and to the right? I haven't found something for the new Framework SwiftUI so maybe there is no chance to use SwiftUI for this? I need to delete rows and use custom Swipes
It is possible to implement a delete action and the ability to reorder list items quite simply.
struct SwipeActionView: View {
#State var items: [String] = ["One", "two", "three", "four"]
var body: some View {
NavigationView {
List {
ForEach(items.identified(by: \.self)) { item in
Text(item)
}
.onMove(perform: move)
.onDelete(perform: delete)
}
.navigationBarItems(trailing: EditButton())
}
}
func delete(at offsets: IndexSet) {
if let first = offsets.first {
items.remove(at: first)
}
}
func move(from source: IndexSet, to destination: Int) {
// sort the indexes low to high
let reversedSource = source.sorted()
// then loop from the back to avoid reordering problems
for index in reversedSource.reversed() {
// for each item, remove it and insert it at the destination
items.insert(items.remove(at: index), at: destination)
}
}
}
Edit: There is this article by apple that I cannot believe I didn't find previously. Composing SwiftUI Gestures. I haven't experimented with it yet, but the article seems to do a great job!
I wanted the same and have now the following implementation.
The SwipeController checks when to execute a swipe action and performs the SwipeAction, for now you can add your swipe actions under the print lines in the executeAction function. But it is better make an abstract class from this.
Then in the SwipeLeftRightContainer struct we have most of the logic in the DragGesture. What it does is while your dragging its gonna change the offset and then make calls to the SwipeController to see if the threshold for swipe left or right are reached. Then when you finish the dragging it will come into the onEnded callback of the DragGesture. Here we will reset the offset and let the SwipeController decide to execute an action.
Keep in mind lot of the variables in the view are static for an iPhone X so you should change them to what fits best.
import SwiftUI
/** executeRight: checks if it should execute the swipeRight action
execute Left: checks if it should execute the swipeLeft action
submitThreshold: the threshold of the x offset when it should start executing the action
*/
class SwipeController {
var executeRight = false
var executeLeft = false
let submitThreshold: CGFloat = 200
func checkExecutionRight(offsetX: CGFloat) {
if offsetX > submitThreshold && self.executeRight == false {
Utils.HapticSuccess()
self.executeRight = true
} else if offsetX < submitThreshold {
self.executeRight = false
}
}
func checkExecutionLeft(offsetX: CGFloat) {
if offsetX < -submitThreshold && self.executeLeft == false {
Utils.HapticSuccess()
self.executeLeft = true
} else if offsetX > -submitThreshold {
self.executeLeft = false
}
}
func excuteAction() {
if executeRight {
print("executed right")
} else if executeLeft {
print("executed left")
}
self.executeLeft = false
self.executeRight = false
}
}
struct SwipeLeftRightContainer: View {
var swipeController: SwipeController = SwipeController()
#State var offsetX: CGFloat = 0
let maxWidth: CGFloat = 335
let maxHeight: CGFloat = 125
let swipeObjectsOffset: CGFloat = 350
let swipeObjectsWidth: CGFloat = 400
#State var rowAnimationOpacity: Double = 0
var body: some View {
ZStack {
Group {
HStack {
Text("Sample row")
Spacer()
}
}.padding(10)
.zIndex(1.0)
.frame(width: maxWidth, height: maxHeight)
.cornerRadius(5)
.background(RoundedRectangle(cornerRadius: 10).fill(Color.gray))
.padding(10)
.offset(x: offsetX)
.gesture(DragGesture(minimumDistance: 5).onChanged { gesture in
withAnimation(Animation.linear(duration: 0.1)) {
offsetX = gesture.translation.width
}
swipeController.checkExecutionLeft(offsetX: offsetX)
swipeController.checkExecutionRight(offsetX: offsetX)
}.onEnded { _ in
withAnimation(Animation.linear(duration: 0.1)) {
offsetX = 0
swipeController.prevLocX = 0
swipeController.prevLocXDiff = 0
self.swipeController.excuteAction()
}
})
Group {
ZStack {
Rectangle().fill(Color.red).frame(width: swipeObjectsWidth, height: maxHeight).opacity(opacityDelete)
Image(systemName: "multiply").font(Font.system(size: 34)).foregroundColor(Color.white).padding(.trailing, 150)
}
}.zIndex(0.9).offset(x: swipeObjectsOffset + offsetX)
Group {
ZStack {
Rectangle().fill(Color.green).frame(width: swipeObjectsWidth, height: maxHeight).opacity(opacityLike)
Image(systemName: "heart").font(Font.system(size: 34)).foregroundColor(Color.white).padding(.leading, 150)
}
}.zIndex(0.9).offset(x: -swipeObjectsOffset + offsetX)
}
}
var opacityDelete: Double {
if offsetX < 0 {
return Double(abs(offsetX) / 50)
}
return 0
}
var opacityLike: Double {
if offsetX > 0 {
return Double(offsetX / 50)
}
return 0
}
}
struct SwipeListView: View {
var body: some View {
ScrollView {
ForEach(0..<10) { index in
SwipeLeftRightContainer().listRowInsets(EdgeInsets(top: 0, leading: 10, bottom: 0, trailing: 10))
}
}
}
}
struct SwipeLeftRight_Previews: PreviewProvider {
static var previews: some View {
SwipeListView()
}
}