Created
November 27, 2024 14:27
-
-
Save abesmon/7a75432582ee20bcb0e48a0607f71162 to your computer and use it in GitHub Desktop.
Some simple slider with scaling effect and auto paging
This file contains hidden or 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
//: [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