Skip to content

Instantly share code, notes, and snippets.

@TheNoim
Last active October 10, 2020 12:49
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save TheNoim/6ca6f35eb830a2d53d6adf04d68e05ba to your computer and use it in GitHub Desktop.
Save TheNoim/6ca6f35eb830a2d53d6adf04d68e05ba to your computer and use it in GitHub Desktop.
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 {}
//
// 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