diff options
| author | Fuwn <[email protected]> | 2025-02-22 07:07:57 -0800 |
|---|---|---|
| committer | Fuwn <[email protected]> | 2025-02-22 07:07:57 -0800 |
| commit | cafece91bae45194d64f4932bb04be018b82d21b (patch) | |
| tree | 02d727a13fe530550e3b29b28b7ee9980262eef2 /Sora/Views/InteractiveImageView.swift | |
| parent | feat: Development commit (diff) | |
| download | sora-testing-cafece91bae45194d64f4932bb04be018b82d21b.tar.xz sora-testing-cafece91bae45194d64f4932bb04be018b82d21b.zip | |
feat: Development commit
Diffstat (limited to 'Sora/Views/InteractiveImageView.swift')
| -rw-r--r-- | Sora/Views/InteractiveImageView.swift | 89 |
1 files changed, 89 insertions, 0 deletions
diff --git a/Sora/Views/InteractiveImageView.swift b/Sora/Views/InteractiveImageView.swift new file mode 100644 index 0000000..7afde7d --- /dev/null +++ b/Sora/Views/InteractiveImageView.swift @@ -0,0 +1,89 @@ +import SwiftUI + +struct InteractiveImageView: View { + let image: Image + @State private var screenWidth = 0.0 + @State private var screenHeight = 0.0 + @State private var currentScale = 1.0 + @State private var previousScale = 0.0 + @State private var currentOffset: CGSize = .zero + @State private var previousOffset: CGSize = .zero + + var body: some View { + GeometryReader { geometry in + VStack { + image + .resizable() + .scaledToFit() + .scaleEffect(currentScale) + .offset(currentOffset) + .frame(width: screenWidth, height: screenHeight) + .clipped() + .gesture( + MagnifyGesture() + .onChanged { gesture in + withAnimation(.interactiveSpring()) { + currentScale = + previousScale + gesture.magnification - (previousScale == 0 ? 0 : 1) + } + } + .onEnded { _ in + previousScale = currentScale + } + .simultaneously( + with: DragGesture(minimumDistance: 0) + .onChanged { gesture in + withAnimation(.interactiveSpring()) { + var newOffset: CGSize = .zero + let offset = gesture.translation + + newOffset.width = offset.width + previousOffset.width + newOffset.height = offset.height + previousOffset.height + + currentOffset = clampOffset(offset: newOffset) + } + } + .onEnded { _ in + previousOffset = currentOffset + } + ) + ) + .highPriorityGesture( + TapGesture(count: 2) + .onEnded { + withAnimation { + currentScale = 1.0 + previousScale = 0 + currentOffset = .zero + previousOffset = .zero + } + } + ) + } + .onAppear { + screenWidth = geometry.size.width + screenHeight = geometry.size.height + } + } + } + + private func clampOffset(offset: CGSize = .zero) -> CGSize { + var newOffset = offset + + if currentScale > 1 { + let maxX = ((screenWidth * currentScale) - screenWidth) / 2 + let maxY = ((screenHeight * currentScale) - screenHeight) / 2 + + newOffset.width = min(max(-maxX, newOffset.width), maxX) + newOffset.height = min(max(-maxY, newOffset.height), maxY) + } else { + newOffset = .zero + } + + return newOffset + } +} + +#Preview { + InteractiveImageView(image: Image(systemName: "photo")) +} |