Skip to content

Instantly share code, notes, and snippets.

@ryanlintott
Created January 12, 2023 19:11
Show Gist options
  • Save ryanlintott/361d2535a45d2537c4f9fe0bfaf68756 to your computer and use it in GitHub Desktop.
Save ryanlintott/361d2535a45d2537c4f9fe0bfaf68756 to your computer and use it in GitHub Desktop.
Draggable paged views where clicking the focused view triggers an action and clicking any other view scrolls to that view.
//
// PagedSelectableView.swift
// FrameUpExample
//
// Created by Ryan Lintott on 2023-01-12.
//
import SwiftUI
struct PagedSelectableView: View {
let items = ["1","2","3","4","5"]
@State private var currentItemIndex: Int = 0
@State private var predictedDragOffset: CGFloat = .zero
@State private var dragOffset: CGFloat = .zero
@GestureState private var isDragging: Bool = false
let itemWidth: CGFloat = 80
@State private var lastTappedItem: String? = nil
var totalOffset: CGFloat {
(itemWidth * CGFloat(items.count) / 2)
- ((CGFloat(currentItemIndex) + 0.5) * itemWidth)
+ dragOffset
}
var drag: some Gesture {
DragGesture(minimumDistance: 0.001)
.updating($isDragging) { value, gestureState, transaction in
gestureState = true
}
.onChanged { value in
predictedDragOffset = value.predictedEndTranslation.width
dragOffset = value.translation.width
}
}
func onDragEnded() {
let itemDelta = Int(-predictedDragOffset / itemWidth)
currentItemIndex = max(items.startIndex, min(currentItemIndex + itemDelta, items.count - 1))
dragOffset = .zero
predictedDragOffset = .zero
}
var enumeratedItems: Array<(String,Int)> {
Array(zip(items, items.indices))
}
var body: some View {
VStack {
HStack(spacing: 0) {
ForEach(enumeratedItems, id: \.0) { (item, index) in
Circle()
.fill(Color.blue)
.overlay(Text("\(item)"))
.foregroundColor(.white)
.aspectRatio(1, contentMode: .fit)
.padding(3)
.frame(width: itemWidth)
.onTapGesture {
if isDragging { return }
if index == currentItemIndex {
lastTappedItem = item
} else {
currentItemIndex = index
}
}
}
}
.animation(.spring(), value: totalOffset)
.fixedSize(horizontal: true, vertical: false)
.offset(x: totalOffset)
.simultaneousGesture(drag)
.background(
Circle()
.stroke(lineWidth: 3)
.aspectRatio(1, contentMode: .fit)
.frame(width: itemWidth + 20)
.foregroundColor(.red)
)
.onChange(of: isDragging) { isDragging in
if !isDragging { onDragEnded() }
}
Text("Last Red Item Tapped: \(lastTappedItem ?? "nil")")
// Text("currentItemIndex: \(currentItemIndex)")
// Text("dragOffset: \(dragOffset)")
// Text("predictedDragOffset: \(predictedDragOffset)")
}
}
}
struct PagedSelectableView_Previews: PreviewProvider {
static var previews: some View {
PagedSelectableView()
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment