Last active
October 10, 2020 12:49
-
-
Save TheNoim/6ca6f35eb830a2d53d6adf04d68e05ba to your computer and use it in GitHub Desktop.
This file contains 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 Foundation | |
import Combine | |
enum LoadDirection { | |
case BACK | |
case FORTH | |
} | |
protocol SwipeableList: ObservableObject, RandomAccessCollection where Index == Int, Element: IdentifiableElement { | |
func updateCurrentIndex(to index: Index); | |
} | |
protocol IdentifiableElement: Identifiable where ID == Int {} |
This file contains 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
// | |
// SwipeView.swift | |
// SwiftyMCGFoodplan | |
// | |
// Created by Nils Bergmann on 30.06.20. | |
// Copyright © 2020 Nils Bergmann. All rights reserved. | |
// | |
import SwiftUI | |
import Combine | |
struct SwipeView<Content: View, L: SwipeableList>: View { | |
@ObservedObject var swipeableList: L | |
public var startIndex = 0; | |
public var spacing: CGFloat = 0; | |
public var content: (Int, L.Element) -> Content | |
var body: some View { | |
GeometryReader { geometry in | |
_SwipeView(swipeableList: self.swipeableList, width: geometry.size.width, height: geometry.size.height, spacing: self.spacing, content: self.content, realIndex: self.startIndex) | |
} | |
} | |
} | |
struct _SwipeView<Content: View, L: SwipeableList>: View { | |
@State private var offset: CGFloat = 0; | |
@State private var view1: AnyView = AnyView(EmptyView()); | |
@State private var view2: AnyView = AnyView(EmptyView()); | |
@State private var view3: AnyView = AnyView(EmptyView()); | |
@ObservedObject var swipeableList: L | |
public var width: CGFloat = 0; | |
public var height: CGFloat = 0; | |
public var spacing: CGFloat = 0 | |
public var content: (Int, L.Element) -> Content | |
@State public var realIndex = 0 { | |
didSet { | |
self.swipeableList.updateCurrentIndex(to: self.realIndex) | |
} | |
}; | |
let semaphore = DispatchSemaphore(value: 1) | |
@State var timer: Timer?; | |
var body: some View { | |
return ScrollView(.horizontal, showsIndicators: true) { | |
HStack(spacing: self.spacing) { | |
self.view1 | |
.frame(width: self.width, alignment: .leading) | |
.background(Color(UIColor.systemBackground)); | |
self.view2 | |
.frame(width: self.width, alignment: .leading) | |
.background(Color(UIColor.systemBackground)); | |
self.view3 | |
.frame(width: self.width, alignment: .leading) | |
.background(Color(UIColor.systemBackground)); | |
} | |
} | |
.content.offset(x: offset) | |
.frame(width: self.width, alignment: .leading) | |
.gesture( | |
DragGesture(minimumDistance: 25.0, coordinateSpace: .local) | |
.onChanged({ value in | |
let targetTranslation = value.translation.width - self.width * 1; | |
var setOffset = true; | |
if self.swipeableList.startIndex == self.realIndex { | |
if targetTranslation < self.width { | |
setOffset = false; | |
} | |
} | |
if self.swipeableList.endIndex == self.realIndex { | |
if targetTranslation > self.width { | |
setOffset = false; | |
} | |
} | |
if setOffset { | |
self.offset = value.translation.width - self.width * 1 | |
} | |
}) | |
.onEnded({ value in | |
var nextIndex = 1; | |
if -value.predictedEndTranslation.width > self.width / 2 { | |
nextIndex = 2; | |
} | |
if value.predictedEndTranslation.width > self.width / 2 { | |
nextIndex = 0; | |
} | |
let duration = 0.5; | |
if let timer = self.timer { | |
timer.invalidate(); | |
} | |
withAnimation(.easeInOut(duration: duration)) { | |
let targetOffset = -(self.width + self.spacing) * CGFloat(nextIndex); | |
self.offset = targetOffset; | |
} | |
self.timer = Timer.scheduledTimer(withTimeInterval: duration, repeats: false) {_ in | |
self.offsetUpdate(); | |
} | |
}) | |
) | |
.onAppear { | |
self.offsetUpdate() | |
self.forceUpdateArray(); | |
let _ = self.swipeableList.objectWillChange.sink { _ in | |
self.forceUpdateArray(); | |
} | |
} | |
} | |
func offsetUpdate() { | |
var reapplyViews = false; | |
if self.offset == 0 || self.offset == -0 { | |
// Direction: Back | |
self.view2 = self.view1; | |
reapplyViews = true; | |
self.realIndex -= 1; | |
} else if self.offset == (self.width * 2) || self.offset == -(self.width * 2) { | |
// Dirction: Forward | |
self.view2 = self.view3; | |
reapplyViews = true; | |
self.realIndex += 1; | |
} | |
if reapplyViews { | |
self.offset = -self.width; | |
self.applyView(for: 1, with: realIndex - 1); | |
self.applyView(for: 3, with: realIndex + 1); | |
} | |
} | |
func forceUpdateArray() { | |
self.semaphore.wait(); | |
self.applyView(for: 2, with: self.realIndex) | |
self.applyView(for: 3, with: self.realIndex + 1) | |
self.applyView(for: 1, with: self.realIndex - 1) | |
self.semaphore.signal(); | |
} | |
func applyView(for index: Int, with realIndex: L.Index) { | |
if realIndex >= self.swipeableList.startIndex && realIndex <= self.swipeableList.endIndex { | |
if index == 1 { | |
self.view1 = AnyView(self.content(realIndex, self.swipeableList[realIndex])); | |
} else if index == 2 { | |
self.view2 = AnyView(self.content(realIndex, self.swipeableList[realIndex])); | |
} else if index == 3 { | |
self.view3 = AnyView(self.content(realIndex, self.swipeableList[realIndex])); | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment