Created
May 16, 2022 14:46
-
-
Save maiyama18/2d43b03eee0e035ae0528adf07b0acbe to your computer and use it in GitHub Desktop.
This file contains 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
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