|
struct ScreenshotDemo: View { |
|
|
|
@State var croppedOutput: UIImage? |
|
let squareBounds: CGFloat = 250 |
|
|
|
// MARK: - Panning / Cropping Overlay |
|
@State var reset = false |
|
@State var position: CGSize = .zero |
|
@State var scale: CGFloat = 1 |
|
@State var rotation: Angle = .degrees(0) |
|
@GestureState var positioning: CGSize = .zero |
|
@GestureState var scaling: CGFloat = 1 |
|
@GestureState var rotating: Angle = .degrees(0) |
|
|
|
var input: some View { |
|
let rotateAndScale = SimultaneousGesture(RotationGesture(minimumAngleDelta: .degrees(5)), MagnificationGesture()) |
|
.updating($rotating) { (values, state, _) in |
|
guard let value = values.first, |
|
(Double(-90)...Double(90)).contains(state.degrees + rotation.degrees) else { return } |
|
state = value |
|
} |
|
.updating($scaling) { (values, state, _) in |
|
guard let value = values.second else { return } |
|
state = value |
|
} |
|
.onEnded { values in |
|
if let angle = values.first { |
|
rotation += angle |
|
} |
|
if let magnitude = values.second { |
|
scale *= magnitude |
|
} |
|
} |
|
|
|
let drag = DragGesture() |
|
.updating($positioning) { (value, state, _) in |
|
state = value.translation |
|
} |
|
.onEnded { |
|
self.position.height += $0.translation.height |
|
self.position.width += $0.translation.width |
|
} |
|
|
|
return |
|
GeometryReader { geo in |
|
ZStack { |
|
Image("glacier") |
|
.resizable() |
|
.rotationEffect(rotating + rotation) |
|
.offset(x: position.width + positioning.width, |
|
y: position.height + positioning.height) |
|
.contentShape(Rectangle()) |
|
.scaleEffect(min(10, max(1, scale * scaling))) |
|
.gesture(drag) |
|
.gesture(rotateAndScale) |
|
.clipShape(Circle()) |
|
.clipped() |
|
.onTapGesture { |
|
let origin = CGPoint(x: geo.frame(in: .local).origin.x, |
|
y: geo.frame(in: .local).origin.y + 1) // Why +1? NO CLUE. |
|
let rect = CGRect(origin: origin, size: geo.size) |
|
croppedOutput = self.input.screenshot(of: rect) ?? UIImage() // Use self.input, not self |
|
} |
|
} |
|
}.frame(width: squareBounds, height: squareBounds) |
|
|
|
} |
|
|
|
var body: some View { |
|
|
|
VStack(alignment: .center, spacing: 0) { |
|
buttons |
|
Spacer() |
|
input |
|
Spacer() |
|
output |
|
} |
|
.background( |
|
Color(.secondarySystemBackground) |
|
.drawingGroup() // Necessary for transparency |
|
) |
|
|
|
} |
|
|
|
var output: some View { |
|
VStack(alignment: .center) { |
|
if let cropped = croppedOutput { |
|
Image(uiImage: cropped) |
|
.resizable() |
|
.scaledToFit() |
|
.transition(AnyTransition.scale.combined(with: .opacity)) |
|
.animation(.easeInOut(duration: 1)) |
|
} else { |
|
EmptyView() |
|
} |
|
} |
|
.frame(width: squareBounds, height: squareBounds) |
|
.padding() |
|
.background(Color.blue.opacity(0.5)) |
|
} |
|
|
|
var buttons: some View { |
|
HStack { |
|
Spacer() |
|
resetButton |
|
Spacer() |
|
} |
|
.padding() |
|
.font(.headline) |
|
} |
|
|
|
var resetButton: some View { |
|
Button { withAnimation { |
|
position = .zero |
|
scale = 1 |
|
rotation = .degrees(0) |
|
croppedOutput = nil |
|
}} label: { Text("Reset") } |
|
.padding() |
|
} |
|
} |
Thank you very much, I spend days trying to save view with Transparency Background .
Your sloution is the only sloution that is working !