Skip to content

Instantly share code, notes, and snippets.

@maiyama18
Created May 16, 2022 14:46
Show Gist options
  • Save maiyama18/2d43b03eee0e035ae0528adf07b0acbe to your computer and use it in GitHub Desktop.
Save maiyama18/2d43b03eee0e035ae0528adf07b0acbe to your computer and use it in GitHub Desktop.
import SwiftUI
struct ContentView: View {
@State private var isImageViewerPresented: Bool = false
var body: some View {
VStack {
Button(action: {
withAnimation {
isImageViewerPresented = true
}
}) {
Text("Show Image")
}
}
.imageViewer(isPresented: $isImageViewerPresented)
}
}
extension View {
func imageViewer(isPresented: Binding<Bool>) -> some View {
modifier(ImageViewerModifier(isPresented: isPresented))
}
}
struct ImageViewerModifier: ViewModifier {
@Binding var isPresented: Bool
func body(content: Content) -> some View {
ZStack {
content
ImageViewer(isPresented: $isPresented)
}
}
}
struct ImageViewer: View {
@Binding var isPresented: Bool
@State private var imageScale: CGFloat = 1
@State private var imagePreviousScale: CGFloat = 1
@State private var offsetY: CGFloat = 0
@State private var initialOffsetY: CGFloat?
private let offsetYThreshold: CGFloat = 120
private var backgroundOpacity: CGFloat {
guard imageScale == 1 else { return 1 }
return abs(offsetY) < offsetYThreshold
? 1 - 0.5 * (abs(offsetY) / offsetYThreshold)
: 0.25
}
private var image: some View {
Image(systemName: "person.circle")
.resizable()
.aspectRatio(contentMode: .fit)
.foregroundColor(.white)
.background(Color.cyan)
.frame(width: UIScreen.main.bounds.size.width * imageScale)
}
var body: some View {
Group {
if isPresented {
ZStack {
Color.black
.opacity(backgroundOpacity)
.ignoresSafeArea()
ScrollView([.horizontal, .vertical], showsIndicators: false) {
image
.gesture(
MagnificationGesture()
.onChanged { value in
imageScale = imagePreviousScale * value
}
.onEnded { _ in
if imageScale < 1 {
withAnimation(.easeOut(duration: 0.3)) {
imageScale = 1
}
}
imagePreviousScale = imageScale
}
)
.background(
GeometryReader { proxy in
let offsetY = proxy.frame(in: .named("scrollview")).origin.y
Color.clear
.onAppear {
initialOffsetY = offsetY
}
.preference(key: ViewOffsetYKey.self, value: offsetY)
}
)
.onPreferenceChange(ViewOffsetYKey.self) {
guard let initialOffsetY = initialOffsetY else { return }
self.offsetY = $0 - initialOffsetY
}
}
.coordinateSpace(name: "scrollview")
}
}
}
}
}
struct ViewOffsetYKey: PreferenceKey {
typealias Value = CGFloat
static var defaultValue = CGFloat.zero
static func reduce(value: inout Value, nextValue: () -> Value) {
value = nextValue()
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment