blob: 052e9cddcb95286079c369e80230fcbd1c66d46e (
plain) (
blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
|
import SwiftUI
struct InteractiveImageView<MenuItems: View>: View {
let image: Image
let contextMenu: MenuItems
@State private var screenWidth = 0.0
@State private var screenHeight = 0.0
@State private var currentScale = 1.0
@State private var previousScale = 1.0
@State private var currentOffset: CGSize = .zero
@State private var previousOffset: CGSize = .zero
@State private var zoomAnchor: UnitPoint = .center
var body: some View {
Group {
image
.resizable()
.scaledToFit()
.contextMenu { if currentScale == 1 { contextMenu } }
}
.scaleEffect(currentScale, anchor: zoomAnchor)
.offset(currentOffset)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.clipped()
.background(
GeometryReader { geometry in
Color.clear
.onAppear {
screenWidth = geometry.size.width
screenHeight = geometry.size.height
}
}
)
.gesture(
MagnifyGesture()
.onChanged { gesture in
withAnimation(.interactiveSpring()) {
if previousScale == 1 {
zoomAnchor = gesture.startAnchor
}
currentScale = max(previousScale * gesture.magnification, 1)
}
}
.onEnded { _ in
previousScale = currentScale
}
.simultaneously(
with: (currentScale > 1 ? DragGesture(minimumDistance: 0) : nil)
.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 = currentScale == 1 ? 2 : 1
previousScale = currentScale
currentOffset = .zero
previousOffset = .zero
zoomAnchor = .center
}
}
)
}
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"),
contextMenu: EmptyView()
)
}
|