Skip to content

Instantly share code, notes, and snippets.

@abesmon
Created November 27, 2024 14:27
Show Gist options
  • Save abesmon/7a75432582ee20bcb0e48a0607f71162 to your computer and use it in GitHub Desktop.
Save abesmon/7a75432582ee20bcb0e48a0607f71162 to your computer and use it in GitHub Desktop.
Some simple slider with scaling effect and auto paging
//: [Previous](@previous)
import SwiftUI
import PlaygroundSupport
struct SliderView: View {
@State private var containerWidth: CGFloat = 0
private var leadingOffset: CGFloat {
(containerWidth - (containerWidth * normalScale)) / 2
}
@State var xOffset: CGFloat = 0
@State var startOffset: CGFloat?
let items: [Int]
let itemsPadding: CGFloat = 8
let normalScale = 0.9
var dragGesture: some Gesture {
DragGesture()
.onChanged { value in
if startOffset == nil {
startOffset = xOffset
}
xOffset = startOffset! + value.translation.width
}
.onEnded { _ in
startOffset = nil
scrollToNearest()
}
}
var body: some View {
GeometryReader { proxy in
HStack(spacing: itemsPadding) {
ForEach(0..<items.count) { idx in
RoundedRectangle(cornerSize: .init(width: 6, height: 6))
.fill(Color.gray)
.frame(width: containerWidth * normalScale, height: 300)
.overlay {
Text("\(idx)")
}
.scaleEffect(calculateScale(for: idx), anchor: .center)
}
}
.offset(x: xOffset, y: 0)
.gesture(dragGesture)
.onAppear {
containerWidth = proxy.size.width
xOffset = leadingOffset
}
}
}
private var slideWidth: CGFloat { containerWidth * 0.9 }
private var pageWidth: CGFloat { slideWidth + itemsPadding }
private func calculateScale(for slideIndex: Int) -> CGSize {
let slidePosition = (CGFloat(slideIndex + 1) * pageWidth) - (slideWidth / 2) + xOffset - itemsPadding
let distanceFromCenter = abs((containerWidth / 2) - slidePosition)
let maxScalingDistance = CGFloat(slideWidth)
let scalingPercent = max(min((distanceFromCenter / maxScalingDistance), 1.0), 0.0)
let minScale = 0.95
let maxScale = 1.0
let scale = minScale + (maxScale - minScale) * (1 - scalingPercent)
return CGSize(width: scale, height: scale)
}
private func scrollToNearest() {
let currentCentral = abs(xOffset) + (containerWidth / 2)
let pageSize = itemsPadding + containerWidth * 0.9
let nearestIndex = Int((currentCentral / pageSize).rounded(.down))
let maxItems = items.count
var targetIndex = min(max(0, nearestIndex), maxItems - 1)
withAnimation(.interactiveSpring) {
xOffset = -(CGFloat(targetIndex) * pageSize) + leadingOffset
}
}
}
struct _Preview: View {
var body: some View {
SliderView(items: [1,2,3,4,5])
.padding(.vertical)
}
}
PlaygroundPage.current.setLiveView(
_Preview().frame(width: 400, height: 600)
)
//: [Next](@next)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment